Repository: jensneuse/graphql-go-tools (go)

Issue:
make object variable arguments available to templating engine
Currently only ".arguments" and ".request.headers" are available to the templating engine. The current parent object during execution should also be available to the templating engine. That is, in order to fetch related object it's necessary to access fields from the parent object. E.g. you fetch all friends who contain an "ID". This id should be available to the templating engine to form requests to retrieve all friends pets.

Possible syntax:

```graphql
type JSONPlaceholderPost {
    userId: Int!
    id: Int!
    title: String!
    body: String!
    comments: [JSONPlaceholderComment]
    @HttpJsonDataSource(
        host: "jsonplaceholder.typicode.com"
        url: "/comments?postId={{ .object.id }}"
    )
    @mapping(mode: NONE)
}
```

Pull request:
add test and implement object variable templating
This PR implements object variable templating.
Closes #142 

Patch:
diff --git a/pkg/execution/execution.go b/pkg/execution/execution.go
index 5328c031..8f9b4d84 100644
--- a/pkg/execution/execution.go
+++ b/pkg/execution/execution.go
@@ -287,6 +287,14 @@ func (e *Executor) ResolveArgs(args []Argument, data []byte) ResolvedArgs {
 					}
 				}
 			}
+			if strings.HasPrefix(tag,".object."){
+				tag = strings.TrimPrefix(tag,".object.")
+				result := gjson.GetBytes(data,tag)
+				if result.Type == gjson.String {
+					return w.Write(unsafebytes.StringToBytes(result.Str))
+				}
+				return w.Write(unsafebytes.StringToBytes(result.Raw))
+			}
 			for j := range resolved {
 				key := string(resolved[j].Key)
 				if strings.HasPrefix(tag, ".") && !strings.HasPrefix(key, ".") {
@@ -300,9 +308,11 @@ func (e *Executor) ResolveArgs(args []Argument, data []byte) ResolvedArgs {
 					return w.Write(resolved[j].Value)
 				}
 				key = strings.TrimPrefix(key, ".")
-				keys := strings.Split(key, ".")
-				value, _, _, _ := jsonparser.Get(resolved[j].Value, keys...)
-				return w.Write(value)
+				result := gjson.GetBytes(resolved[j].Value,key)
+				if result.Type == gjson.String {
+					return w.Write(unsafebytes.StringToBytes(result.Str))
+				}
+				return w.Write(unsafebytes.StringToBytes(result.Raw))
 			}
 			return w.Write([]byte("{{ " + tag + " }}"))
 		})
diff --git a/pkg/lexer/literal/literal.go b/pkg/lexer/literal/literal.go
index cb40e35b..ad83d6ff 100644
--- a/pkg/lexer/literal/literal.go
+++ b/pkg/lexer/literal/literal.go
@@ -65,6 +65,7 @@ var (
 	ON                 = []byte("on")
 	FRAGMENT           = []byte("fragment")
 	NULL               = []byte("null")
+	OBJECT             = []byte("object")
 	DATA               = []byte("data")
 	URL                = []byte("url")
 	ADDR               = []byte("addr")


Test patch:
diff --git a/pkg/execution/execution_test.go b/pkg/execution/execution_test.go
index d679c763..f09d56b8 100644
--- a/pkg/execution/execution_test.go
+++ b/pkg/execution/execution_test.go
@@ -1168,6 +1168,155 @@ func TestExecutor_ListFilterFirstN(t *testing.T) {
 	}
 }
 
+func TestExecutor_ObjectVariables(t *testing.T) {
+
+	REST1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+		if r.Method != http.MethodPost {
+			t.Fatalf("want method POST, got: %s", r.Method)
+			return
+		}
+
+		got, err := ioutil.ReadAll(r.Body)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		want := []byte(`{"id":1}`)
+
+		if !bytes.Equal(want, got) {
+			t.Fatalf("want: '%s', got: '%s'", string(want), string(got))
+		}
+
+		response := []byte(`{"name":"Woof","age":3}`)
+
+		_, err = w.Write(response)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}))
+
+	plan := &Object{
+		operationType: ast.OperationTypeQuery,
+		Fields: []Field{
+			{
+				Name: []byte("data"),
+				Value: &Object{
+					Fetch: &SingleFetch{
+						Source: &DataSourceInvocation{
+							Args: []Argument{
+								&StaticVariableArgument{
+									Value: []byte(`{"name": "Jens","id":1}`),
+								},
+							},
+							DataSource: &StaticDataSource{},
+						},
+						BufferName: "user",
+					},
+					Fields: []Field{
+						{
+							Name:        []byte("user"),
+							HasResolver: true,
+							Value: &Object{
+								Fetch: &SingleFetch{
+									BufferName: "pet",
+									Source: &DataSourceInvocation{
+										Args: []Argument{
+											&StaticVariableArgument{
+												Name:  literal.HOST,
+												Value: []byte(REST1.URL),
+											},
+											&StaticVariableArgument{
+												Name:  literal.URL,
+												Value: []byte("/"),
+											},
+											&StaticVariableArgument{
+												Name:  literal.METHOD,
+												Value: []byte("POST"),
+											},
+											&StaticVariableArgument{
+												Name:  literal.BODY,
+												Value: []byte(`{"id":{{ .object.id }}}`),
+											},
+										},
+										DataSource: &HttpJsonDataSource{
+											log: zap.NewNop(),
+										},
+									},
+								},
+								Fields: []Field{
+									{
+										Name: []byte("name"),
+										Value: &Value{
+											PathSelector: PathSelector{
+												Path: "name",
+											},
+											QuoteValue: true,
+										},
+									},
+									{
+										Name: []byte("id"),
+										Value: &Value{
+											PathSelector: PathSelector{
+												Path: "id",
+											},
+											QuoteValue: false,
+										},
+									},
+									{
+										Name:        []byte("pet"),
+										HasResolver: true,
+										Value: &Object{
+											Fields: []Field{
+												{
+													Name: []byte("name"),
+													Value: &Value{
+														PathSelector: PathSelector{
+															Path: "name",
+														},
+														QuoteValue: true,
+													},
+												},
+												{
+													Name: []byte("age"),
+													Value: &Value{
+														PathSelector: PathSelector{
+															Path: "age",
+														},
+														QuoteValue: false,
+													},
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	out := &bytes.Buffer{}
+	ex := NewExecutor()
+	ctx := Context{
+		Context: context.Background(),
+	}
+
+	_, err := ex.Execute(ctx, plan, out)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	want := `{"data":{"user":{"name":"Jens","id":1,"pet":{"name":"Woof","age":3}}}}`
+	got := out.String()
+
+	if got != want {
+		t.Fatalf("want: %s\ngot: %s\n", want, got)
+	}
+}
+
 func TestExecutor_ListWithPath(t *testing.T) {
 
 	plan := &Object{
@@ -2150,7 +2299,7 @@ func TestExecutor_Introspection(t *testing.T) {
 
 	response := out.Bytes()
 
-	goldie.Assert(t,"introspection_execution",response)
+	goldie.Assert(t, "introspection_execution", response)
 	if t.Failed() {
 
 		fixture, err := ioutil.ReadFile("./fixtures/introspection_execution.golden")
@@ -2160,4 +2309,4 @@ func TestExecutor_Introspection(t *testing.T) {
 
 		diffview.NewGoland().DiffViewBytes("execution", fixture, response)
 	}
-}
\ No newline at end of file
+}