Repository: jensneuse/graphql-go-tools
Pull request #340 (2022-02-11 18:26:06)

Fix `addVariableDefinitionsRecursively` to handle arrays inside objects.
`resolveNestedArgumentType` wasn't returning the proper type when the
argument was an array inside an object.  There were two reasons for
this: first, the proper object field wasn't being carried through the
recursive addVariableDefinitionsRecursively() call that was made in
the "list" case, and second, nothing was looking through the
"array-of" to get the type of the underlying object.

The fix is just to get rid of that use of `resolveNestedArgumentType`
directly.  There's no need to try to figure out the type of the
variable from parsing the complex nesting; we already know the type of
the variable from the original document!  So now we just use that.


Patch:
diff --git a/pkg/engine/datasource/graphql_datasource/graphql_datasource.go b/pkg/engine/datasource/graphql_datasource/graphql_datasource.go
index 5e30e845..5c0909d1 100644
--- a/pkg/engine/datasource/graphql_datasource/graphql_datasource.go
+++ b/pkg/engine/datasource/graphql_datasource/graphql_datasource.go
@@ -812,17 +812,17 @@ func (p *Planner) addVariableDefinitionsRecursively(value ast.Value, sourcePath
 		return
 	}
 
-	variableDefinitionTypeName := p.visitor.Operation.ResolveTypeNameString(p.visitor.Operation.VariableDefinitions[variableDefinition].Type)
+	variableDefinitionTypeRef := p.visitor.Operation.VariableDefinitions[variableDefinition].Type
+	variableDefinitionTypeName := p.visitor.Operation.ResolveTypeNameString(variableDefinitionTypeRef)
 	variableDefinitionTypeName = p.visitor.Config.Types.RenameTypeNameOnMatchStr(variableDefinitionTypeName)
 
 	importedVariableDefinition := p.visitor.Importer.ImportVariableDefinitionWithRename(variableDefinition, p.visitor.Operation, p.upstreamOperation, variableDefinitionTypeName)
 	p.upstreamOperation.AddImportedVariableDefinitionToOperationDefinition(p.nodes[0].Ref, importedVariableDefinition)
 
-	fieldType := p.resolveNestedArgumentType(fieldName)
 	contextVariable := &resolve.ContextVariable{
 		Path: append(sourcePath, variableNameStr),
 	}
-	renderer, err := resolve.NewJSONVariableRendererWithValidationFromTypeRef(p.visitor.Definition, p.visitor.Definition, fieldType)
+	renderer, err := resolve.NewJSONVariableRendererWithValidationFromTypeRef(p.visitor.Operation, p.visitor.Definition, variableDefinitionTypeRef)
 	if err != nil {
 		return
 	}


Test patch:
diff --git a/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go b/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go
index 045e0652..ef8582f5 100644
--- a/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go
+++ b/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go
@@ -258,9 +258,9 @@ func TestGraphQLDataSource(t *testing.T) {
 		Response: &resolve.GraphQLResponse{
 			Data: &resolve.Object{
 				Fetch: &resolve.SingleFetch{
-					DataSource: &Source{},
-					BufferId:   0,
-					Input:      `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {id displayName}}"}}`,
+					DataSource:            &Source{},
+					BufferId:              0,
+					Input:                 `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {id displayName}}"}}`,
 					DataSourceIdentifier:  []byte("graphql_datasource.Source"),
 					ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true},
 				},
@@ -315,11 +315,11 @@ func TestGraphQLDataSource(t *testing.T) {
 				ChildNodes: []plan.TypeField{
 					{
 						TypeName:   "User",
-						FieldNames: []string{"id", "displayName","isLoggedIn"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn"},
 					},
 					{
 						TypeName:   "RegisteredUser",
-						FieldNames: []string{"id", "displayName","isLoggedIn"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn"},
 					},
 				},
 				Factory: &Factory{},
@@ -343,14 +343,14 @@ func TestGraphQLDataSource(t *testing.T) {
 		Response: &resolve.GraphQLResponse{
 			Data: &resolve.Object{
 				Fetch: &resolve.SingleFetch{
-					DataSource: &Source{},
-					BufferId:   0,
-					Input:      `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($skip: Boolean!){user {id displayName @skip(if: $skip)}}","variables":{"skip":$$0$$}}}`,
+					DataSource:            &Source{},
+					BufferId:              0,
+					Input:                 `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($skip: Boolean!){user {id displayName @skip(if: $skip)}}","variables":{"skip":$$0$$}}}`,
 					DataSourceIdentifier:  []byte("graphql_datasource.Source"),
 					ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true},
 					Variables: resolve.NewVariables(
 						&resolve.ContextVariable{
-							Path: []string{"skip"},
+							Path:     []string{"skip"},
 							Renderer: resolve.NewJSONVariableRendererWithValidation(`{"type":"boolean"}`),
 						},
 					),
@@ -408,11 +408,11 @@ func TestGraphQLDataSource(t *testing.T) {
 				ChildNodes: []plan.TypeField{
 					{
 						TypeName:   "User",
-						FieldNames: []string{"id", "displayName","isLoggedIn"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn"},
 					},
 					{
 						TypeName:   "RegisteredUser",
-						FieldNames: []string{"id", "displayName","isLoggedIn"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn"},
 					},
 				},
 				Factory: &Factory{},
@@ -438,14 +438,14 @@ func TestGraphQLDataSource(t *testing.T) {
 		Response: &resolve.GraphQLResponse{
 			Data: &resolve.Object{
 				Fetch: &resolve.SingleFetch{
-					DataSource: &Source{},
-					BufferId:   0,
-					Input:      `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($skip: Boolean!){user {... @skip(if: $skip){id displayName}}}","variables":{"skip":$$0$$}}}`,
+					DataSource:            &Source{},
+					BufferId:              0,
+					Input:                 `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($skip: Boolean!){user {... @skip(if: $skip){id displayName}}}","variables":{"skip":$$0$$}}}`,
 					DataSourceIdentifier:  []byte("graphql_datasource.Source"),
 					ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true},
 					Variables: resolve.NewVariables(
 						&resolve.ContextVariable{
-							Path: []string{"skip"},
+							Path:     []string{"skip"},
 							Renderer: resolve.NewJSONVariableRendererWithValidation(`{"type":"boolean"}`),
 						},
 					),
@@ -505,11 +505,11 @@ func TestGraphQLDataSource(t *testing.T) {
 				ChildNodes: []plan.TypeField{
 					{
 						TypeName:   "User",
-						FieldNames: []string{"id", "displayName","isLoggedIn"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn"},
 					},
 					{
 						TypeName:   "RegisteredUser",
-						FieldNames: []string{"id", "displayName","isLoggedIn"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn"},
 					},
 				},
 				Factory: &Factory{},
@@ -535,14 +535,14 @@ func TestGraphQLDataSource(t *testing.T) {
 		Response: &resolve.GraphQLResponse{
 			Data: &resolve.Object{
 				Fetch: &resolve.SingleFetch{
-					DataSource: &Source{},
-					BufferId:   0,
-					Input:      `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($include: Boolean!){user {... @include(if: $include){id displayName}}}","variables":{"include":$$0$$}}}`,
+					DataSource:            &Source{},
+					BufferId:              0,
+					Input:                 `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($include: Boolean!){user {... @include(if: $include){id displayName}}}","variables":{"include":$$0$$}}}`,
 					DataSourceIdentifier:  []byte("graphql_datasource.Source"),
 					ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true},
 					Variables: resolve.NewVariables(
 						&resolve.ContextVariable{
-							Path: []string{"include"},
+							Path:     []string{"include"},
 							Renderer: resolve.NewJSONVariableRendererWithValidation(`{"type":"boolean"}`),
 						},
 					),
@@ -602,11 +602,11 @@ func TestGraphQLDataSource(t *testing.T) {
 				ChildNodes: []plan.TypeField{
 					{
 						TypeName:   "User",
-						FieldNames: []string{"id", "displayName","isLoggedIn"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn"},
 					},
 					{
 						TypeName:   "RegisteredUser",
-						FieldNames: []string{"id", "displayName","isLoggedIn"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn"},
 					},
 				},
 				Factory: &Factory{},
@@ -630,9 +630,9 @@ func TestGraphQLDataSource(t *testing.T) {
 		Response: &resolve.GraphQLResponse{
 			Data: &resolve.Object{
 				Fetch: &resolve.SingleFetch{
-					DataSource: &Source{},
-					BufferId:   0,
-					Input:      `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {id}}"}}`,
+					DataSource:            &Source{},
+					BufferId:              0,
+					Input:                 `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {id}}"}}`,
 					DataSourceIdentifier:  []byte("graphql_datasource.Source"),
 					ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true},
 				},
@@ -677,11 +677,11 @@ func TestGraphQLDataSource(t *testing.T) {
 				ChildNodes: []plan.TypeField{
 					{
 						TypeName:   "User",
-						FieldNames: []string{"id", "displayName","isLoggedIn"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn"},
 					},
 					{
 						TypeName:   "RegisteredUser",
-						FieldNames: []string{"id", "displayName","isLoggedIn"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn"},
 					},
 				},
 				Factory: &Factory{},
@@ -705,9 +705,9 @@ func TestGraphQLDataSource(t *testing.T) {
 		Response: &resolve.GraphQLResponse{
 			Data: &resolve.Object{
 				Fetch: &resolve.SingleFetch{
-					DataSource: &Source{},
-					BufferId:   0,
-					Input:      `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {id displayName}}"}}`,
+					DataSource:            &Source{},
+					BufferId:              0,
+					Input:                 `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {id displayName}}"}}`,
 					DataSourceIdentifier:  []byte("graphql_datasource.Source"),
 					ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true},
 				},
@@ -762,11 +762,11 @@ func TestGraphQLDataSource(t *testing.T) {
 				ChildNodes: []plan.TypeField{
 					{
 						TypeName:   "User",
-						FieldNames: []string{"id", "displayName","isLoggedIn"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn"},
 					},
 					{
 						TypeName:   "RegisteredUser",
-						FieldNames: []string{"id", "displayName","isLoggedIn"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn"},
 					},
 				},
 				Factory: &Factory{},
@@ -790,14 +790,14 @@ func TestGraphQLDataSource(t *testing.T) {
 		Response: &resolve.GraphQLResponse{
 			Data: &resolve.Object{
 				Fetch: &resolve.SingleFetch{
-					DataSource: &Source{},
-					BufferId:   0,
-					Input:      `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($include: Boolean!){user {id displayName @include(if: $include)}}","variables":{"include":$$0$$}}}`,
+					DataSource:            &Source{},
+					BufferId:              0,
+					Input:                 `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($include: Boolean!){user {id displayName @include(if: $include)}}","variables":{"include":$$0$$}}}`,
 					DataSourceIdentifier:  []byte("graphql_datasource.Source"),
 					ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true},
 					Variables: resolve.NewVariables(
 						&resolve.ContextVariable{
-							Path: []string{"include"},
+							Path:     []string{"include"},
 							Renderer: resolve.NewJSONVariableRendererWithValidation(`{"type":"boolean"}`),
 						},
 					),
@@ -855,11 +855,11 @@ func TestGraphQLDataSource(t *testing.T) {
 				ChildNodes: []plan.TypeField{
 					{
 						TypeName:   "User",
-						FieldNames: []string{"id", "displayName","isLoggedIn"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn"},
 					},
 					{
 						TypeName:   "RegisteredUser",
-						FieldNames: []string{"id", "displayName","isLoggedIn"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn"},
 					},
 				},
 				Factory: &Factory{},
@@ -883,9 +883,9 @@ func TestGraphQLDataSource(t *testing.T) {
 		Response: &resolve.GraphQLResponse{
 			Data: &resolve.Object{
 				Fetch: &resolve.SingleFetch{
-					DataSource: &Source{},
-					BufferId:   0,
-					Input:      `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {id displayName}}"}}`,
+					DataSource:            &Source{},
+					BufferId:              0,
+					Input:                 `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {id displayName}}"}}`,
 					DataSourceIdentifier:  []byte("graphql_datasource.Source"),
 					ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true},
 				},
@@ -940,11 +940,11 @@ func TestGraphQLDataSource(t *testing.T) {
 				ChildNodes: []plan.TypeField{
 					{
 						TypeName:   "User",
-						FieldNames: []string{"id", "displayName","isLoggedIn"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn"},
 					},
 					{
 						TypeName:   "RegisteredUser",
-						FieldNames: []string{"id", "displayName","isLoggedIn"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn"},
 					},
 				},
 				Factory: &Factory{},
@@ -968,9 +968,9 @@ func TestGraphQLDataSource(t *testing.T) {
 		Response: &resolve.GraphQLResponse{
 			Data: &resolve.Object{
 				Fetch: &resolve.SingleFetch{
-					DataSource: &Source{},
-					BufferId:   0,
-					Input:      `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {id}}"}}`,
+					DataSource:            &Source{},
+					BufferId:              0,
+					Input:                 `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {id}}"}}`,
 					DataSourceIdentifier:  []byte("graphql_datasource.Source"),
 					ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true},
 				},
@@ -1015,11 +1015,11 @@ func TestGraphQLDataSource(t *testing.T) {
 				ChildNodes: []plan.TypeField{
 					{
 						TypeName:   "User",
-						FieldNames: []string{"id", "displayName","isLoggedIn"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn"},
 					},
 					{
 						TypeName:   "RegisteredUser",
-						FieldNames: []string{"id", "displayName","isLoggedIn"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn"},
 					},
 				},
 				Factory: &Factory{},
@@ -1046,9 +1046,9 @@ func TestGraphQLDataSource(t *testing.T) {
 		Response: &resolve.GraphQLResponse{
 			Data: &resolve.Object{
 				Fetch: &resolve.SingleFetch{
-					DataSource: &Source{},
-					BufferId:   0,
-					Input:      `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {id displayName __typename ... on RegisteredUser {hasVerifiedEmail}}}"}}`,
+					DataSource:            &Source{},
+					BufferId:              0,
+					Input:                 `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {id displayName __typename ... on RegisteredUser {hasVerifiedEmail}}}"}}`,
 					DataSourceIdentifier:  []byte("graphql_datasource.Source"),
 					ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true},
 				},
@@ -1114,11 +1114,11 @@ func TestGraphQLDataSource(t *testing.T) {
 				ChildNodes: []plan.TypeField{
 					{
 						TypeName:   "User",
-						FieldNames: []string{"id", "displayName","isLoggedIn"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn"},
 					},
 					{
 						TypeName:   "RegisteredUser",
-						FieldNames: []string{"id", "displayName","isLoggedIn","hasVerifiedEmail"},
+						FieldNames: []string{"id", "displayName", "isLoggedIn", "hasVerifiedEmail"},
 					},
 				},
 				Factory: &Factory{},
@@ -3138,7 +3138,7 @@ func TestGraphQLDataSource(t *testing.T) {
 	t.Run("mutation with variables in array object argument", RunTest(
 		todoSchema,
 		`mutation AddTask($title: String!, $completed: Boolean!, $name: String! @fromClaim(name: "sub")) {
-					  addTask(input: [{title: $title, completed: $completed, user: {name: $name}}]){
+					  addTask(input: [{titleSets: [[$title]], completed: $completed, user: {name: $name}}]){
 						task {
 						  id
 						  title
@@ -3152,7 +3152,7 @@ func TestGraphQLDataSource(t *testing.T) {
 				Data: &resolve.Object{
 					Fetch: &resolve.SingleFetch{
 						BufferId:   0,
-						Input:      `{"method":"POST","url":"https://graphql.service","body":{"query":"mutation($title: String!, $completed: Boolean!, $name: String!){addTask(input: [{title: $title,completed: $completed,user: {name: $name}}]){task {id title completed}}}","variables":{"name":$$2$$,"completed":$$1$$,"title":$$0$$}}}`,
+						Input:      `{"method":"POST","url":"https://graphql.service","body":{"query":"mutation($title: String!, $completed: Boolean!, $name: String!){addTask(input: [{titleSets: [[$title]],completed: $completed,user: {name: $name}}]){task {id title completed}}}","variables":{"name":$$2$$,"completed":$$1$$,"title":$$0$$}}}`,
 						DataSource: &Source{},
 						Variables: resolve.NewVariables(
 							&resolve.ContextVariable{
@@ -6185,7 +6185,7 @@ enum UserOrderable {
 """"""
 input AddTaskInput {
   """"""
-  title: String!
+  titleSets: [[String!]]
   """"""
   completed: Boolean!
   """"""