Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

graphql: Do graphql query/mutation validation in the mock server. #5362

Merged
merged 24 commits into from
May 8, 2020

Conversation

pawanrawal
Copy link
Contributor

@pawanrawal pawanrawal commented May 4, 2020

@abhimanyusinghgaur and I realized that GraphQL variables were not being defined in the query/mutation that were being sent to the remote server because of which those requests would have failed. I am modifying the mock server to validate the incoming request and populate the arguments from the variables so that the end to end tests are more real. We'll have to fix the code so that the generated queries/mutation define the variables that they actually use.

TODO

  • Modify graphql mutations and graphql field batch operation endpoints to also do the validation.
  • Fix the code so that all tests pass.

This change is Reviewable

Docs Preview: Dgraph Preview

@pawanrawal pawanrawal marked this pull request as ready for review May 6, 2020 13:18
@pawanrawal pawanrawal requested review from manishrjain and a team as code owners May 6, 2020 13:18
Copy link
Contributor

@abhimanyusinghgaur abhimanyusinghgaur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still need to look at e2e tests for custom logic. Will do that by midnight.

Reviewed 45 of 48 files at r1.
Reviewable status: 42 of 48 files reviewed, 15 unresolved discussions (waiting on @abhimanyusinghgaur, @manishrjain, @MichaelJCompton, and @pawanrawal)


graphql/e2e/custom_logic/schemas/mixed-modes.graphql, line 14 at r1 (raw file):

        method: "POST"
        operation: "batch"
        graphql: "query($input: [UserInput]) { userNames(users: $input) }"

there is no input UserInput {...} here in this schema. We need that to validate body for given graphql.
Same for other types too.


graphql/resolve/resolver.go, line 738 at r1 (raw file):

			body := make(map[string]interface{})
			body["query"] = fconf.RemoteGqlQuery
			// For batch mode, the argument name is input which is a list of maps.

This is the comment that led to the discovery of this bug. 😄
I guess we should change it now to not have input.


graphql/schema/custom_http_config_test.yaml, line 11 at r1 (raw file):

    type Query {
      getCountry(id: ID!): Country! @custom(http: {

now we have a validation that query name can't be same as one of the queries that we may generate for given types (irrespective of @remote). So, can we change it to getCountry1 or something else...


graphql/schema/custom_http_config_test.yaml, line 33 at r1 (raw file):

    type Query {
      country(code: ID!): Country! @custom(http: {

I guess we are requiring @custom in remote schema only because schema parsing would fail if we don't have it.


graphql/schema/custom_http_config_test.yaml, line 77 at r1 (raw file):

    type Mutation {
      addCountry(input: CountryInput!): Country! @custom(http: {

addCountry1


graphql/schema/gqlschema_test.yml, line 1154 at r1 (raw file):

          method: "POST",
          operation: "batch"
          graphql: "query { getAuthor(postId: {id: $id}) }",

shouldn't we add the variable definitions for query? And it should fail if the variable definitions are not there, right?


graphql/schema/gqlschema_test.yml, line 1164 at r1 (raw file):

  # -
  #   name: "@custom directive with batch mode on field and repeating arguments for query in graphql"

I have removed this test in my validations PR.


graphql/schema/rules.go, line 1137 at r1 (raw file):

					"name `%s`, it can't have a name.", typ.Name, field.Name, graphqlOpDef.Name)
		}
		// TODO - Ensure that the variables used should be already defined.

Later, we may not need to do this explicitly, if we can construct remote schema from introspection response and validate the query against that using gqlparser.


graphql/schema/rules.go, line 1167 at r1 (raw file):

		if len(query.Arguments) > 0 {
			argCountMap := make(map[string]int)
			requiredFields = make(map[string]bool)

I have also changed some bits here related to required fields in my validations PR, we will have to figure out conflicts.


graphql/schema/rules.go, line 1182 at r1 (raw file):

				for _, arg := range query.Arguments[0].Value.Children[0].Value.Children {
					argCountMap[arg.Name] = argCountMap[arg.Name] + 1
					requiredFields[arg.Value.String()[1:]] = true

Just a note:
I was thinking that as for batch operations, we now have required fields parsed from body itself, so currently they would get validated correctly. But later when we are adding scalar support for both body and graphql, then there are things like """block string""" and enum which are scalars for graphql but can't be present for json body. Then we will need to change how required fields are calculated for batch from body.


graphql/schema/rules.go, line 1302 at r1 (raw file):

	var skip bool
	if si != nil {
		skip, _ = strconv.ParseBool(si.Raw)

Should we also verify that si.Raw is one of true/false only, and not ignore the error?


graphql/schema/wrappers.go, line 71 at r1 (raw file):

	// For the following request
	// graphql: "query($sinput: [SchoolInput]) { schoolNames(schools: $sinput) }"
	// the GraphqlBatchModeArgument would be schools, we use it to know the GraphQL variable that

When we are sending variables won't we need sinput instead of schools?
I guess this should be GraphqlBatchModeVariable instead...


graphql/schema/wrappers.go, line 777 at r1 (raw file):

	if !isQueryOrMutation && graphqlArg != nil && fconf.Operation == "single" {
		// For batch mode, required args would have been parsed from the body above.
		fconf.RequiredArgs, _ = parseRequiredArgsFromGQLRequest(graphqlArg.Raw)

should we ignore this error?


graphql/schema/wrappers.go, line 797 at r1 (raw file):

		qfield := queryDoc.Operations[0].SelectionSet[0].(*ast.Field)
		if fconf.Operation == "batch" {
			fconf.GraphqlBatchModeArgument = qfield.Arguments[0].Value.String()[1:]

it is equivalent to: fconf.GraphqlBatchModeArgument = queryDoc.Operations[0].VariableDefinitions[0].Variable


graphql/schema/wrappers_test.go, line 672 at r1 (raw file):

		{
			"parse required args for single request",
			"query($id: ID!) { userNames(id: $id, age: $age) }",

don't we need $age in query variable definitions?


graphql/schema/wrappers_test.go, line 773 at r1 (raw file):

			require.True(t, ok)

			require.Equal(t, tcase.RemoteQuery, c.RemoteGqlQuery)

can we also add a check that tmpl["query"] is same as tcase.RemoteQuery along with the existing check. It would just make things more clear I guess...

Copy link
Contributor Author

@pawanrawal pawanrawal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 42 of 48 files reviewed, 13 unresolved discussions (waiting on @abhimanyusinghgaur, @manishrjain, @MichaelJCompton, and @pawanrawal)


graphql/resolve/resolver.go, line 738 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…

This is the comment that led to the discovery of this bug. 😄
I guess we should change it now to not have input.

Done. Removed


graphql/schema/custom_http_config_test.yaml, line 11 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…

now we have a validation that query name can't be same as one of the queries that we may generate for given types (irrespective of @remote). So, can we change it to getCountry1 or something else...

Done.


graphql/schema/custom_http_config_test.yaml, line 33 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…

I guess we are requiring @custom in remote schema only because schema parsing would fail if we don't have it.

yes that is right and I wanted to use our code for validating the query against a schema (remote schema in this case)


graphql/schema/custom_http_config_test.yaml, line 77 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…

addCountry1

Done.


graphql/schema/rules.go, line 1182 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…

Just a note:
I was thinking that as for batch operations, we now have required fields parsed from body itself, so currently they would get validated correctly. But later when we are adding scalar support for both body and graphql, then there are things like """block string""" and enum which are scalars for graphql but can't be present for json body. Then we will need to change how required fields are calculated for batch from body.

Good point. May want to put that in code somewhere.


graphql/schema/wrappers.go, line 71 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…

When we are sending variables won't we need sinput instead of schools?
I guess this should be GraphqlBatchModeVariable instead...

yeah you are right, the comment is wrong.

Copy link
Contributor

@abhimanyusinghgaur abhimanyusinghgaur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 2 of 48 files at r1, 6 of 6 files at r2.
Reviewable status: all files reviewed, 11 unresolved discussions (waiting on @abhimanyusinghgaur, @manishrjain, @MichaelJCompton, and @pawanrawal)


graphql/e2e/custom_logic/schemas/mixed-modes.graphql, line 14 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…

there is no input UserInput {...} here in this schema. We need that to validate body for given graphql.
Same for other types too.

Actually, we won't need the variable type to be present in our schema for batched fields as we can gather the type's structure from given body. But we will need it for all other cases, as we need to do deep type checking.


graphql/schema/rules.go, line 1137 at r2 (raw file):

					"name `%s`, it can't have a name.", typ.Name, field.Name, graphqlOpDef.Name)
		}
		if graphqlOpDef.VariableDefinitions != nil {

There are a few checks I think we need to do as per the new format:

  • I guess ATM as we are not handling scalars, so not defining any variables itself should be an error, right? i.e., having variable definitions is mandatory as of now unless the query has no arguments.
  • Then, the query arguments can use only those variables that have been defined in variable definitions.
    Later, when we add query validation using schema generated from introspection, such checks would no longer be required though.

graphql/schema/rules.go, line 1144 at r2 (raw file):

						return gqlerror.ErrorPosf(graphql.Position,
							"Type %s; Field %s; @custom directive, graphql variables must use "+
								"fields defined within the type, found `%s`.", typ.Name,

This check was earlier happening in remote.go, but now it is better to do it here as we have variable definitions now.
I guess we will need to refactor remote.go to fit according to new needs.


graphql/schema/rules.go, line 1151 at r2 (raw file):

				// For batch operation we already verify that body should use fields defined inside the
				// parent type.
				if !isBatchOperation {

There were a couple more checks for this case, which were happening by the same logic as for batched fields and had tests too. So, if we just create the requiredFields map from these variable definitions, then those checks should automatically happen, and we won't need these checks here. Also, we should remove the creation of requiredFields using query arguments now. But before that we need to merge master in it, as there were some changes in this file from my PR, and I doubt that they may lead to conflicts.

@github-actions github-actions bot added the area/graphql Issues related to GraphQL support on Dgraph. label May 7, 2020
Copy link
Contributor Author

@pawanrawal pawanrawal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 5 of 49 files reviewed, 8 unresolved discussions (waiting on @abhimanyusinghgaur, @manishrjain, and @MichaelJCompton)


graphql/e2e/custom_logic/schemas/mixed-modes.graphql, line 14 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…

Actually, we won't need the variable type to be present in our schema for batched fields as we can gather the type's structure from given body. But we will need it for all other cases, as we need to do deep type checking.

Not needing this right now, will add a card for this.


graphql/schema/gqlschema_test.yml, line 1154 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…

shouldn't we add the variable definitions for query? And it should fail if the variable definitions are not there, right?

Done.


graphql/schema/rules.go, line 1302 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…

Should we also verify that si.Raw is one of true/false only, and not ignore the error?

Done.


graphql/schema/rules.go, line 1137 at r2 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…

There are a few checks I think we need to do as per the new format:

  • I guess ATM as we are not handling scalars, so not defining any variables itself should be an error, right? i.e., having variable definitions is mandatory as of now unless the query has no arguments.
  • Then, the query arguments can use only those variables that have been defined in variable definitions.
    Later, when we add query validation using schema generated from introspection, such checks would no longer be required though.

So I now check that required fields should have been variable definitions which should cover query/mutation/single mode. I have a separate check for batch mode. Hope that handles all the cases.


graphql/schema/rules.go, line 1151 at r2 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…

There were a couple more checks for this case, which were happening by the same logic as for batched fields and had tests too. So, if we just create the requiredFields map from these variable definitions, then those checks should automatically happen, and we won't need these checks here. Also, we should remove the creation of requiredFields using query arguments now. But before that we need to merge master in it, as there were some changes in this file from my PR, and I doubt that they may lead to conflicts.

Merged master, can you have a look again?


graphql/schema/wrappers.go, line 777 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…

should we ignore this error?

Still ignoring but added a comment on why.


graphql/schema/wrappers_test.go, line 672 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…

don't we need $age in query variable definitions?

We do fixed


graphql/schema/wrappers_test.go, line 773 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…

can we also add a check that tmpl["query"] is same as tcase.RemoteQuery along with the existing check. It would just make things more clear I guess...

That is only true in the case of custom query/mutation when this function is called.

Copy link
Contributor

@abhimanyusinghgaur abhimanyusinghgaur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got some minor comments and then :lgtm:
Later we should add a task to refactor remote.go to validate the new format correctly for all cases. For now it will work for everything other that batch mode, but those can be improved, given we have the new format.

Reviewed 44 of 44 files at r3.
Reviewable status: all files reviewed, 3 unresolved discussions (waiting on @manishrjain, @MichaelJCompton, and @pawanrawal)


graphql/schema/rules.go, line 1151 at r2 (raw file):

Previously, pawanrawal (Pawan Rawal) wrote…

Merged master, can you have a look again?

So, here now we just need to do this:

requiredFields = make(map[string]bool)
for _, vd := range graphqlOpDef.VariableDefinitions {
  requiredFields[vd.Variable] = true
}

It will take care of applying these checks along with some other checks in validation no. 11.
And then we need to remove some unnecessary code as mentioned in next comment.


graphql/schema/rules.go, line 1331 at r3 (raw file):

Quoted 22 lines of code…
			} else {
				var bodyBuilder strings.Builder
				comma := ","
				bodyBuilder.WriteString("{")
				for i, arg := range query.Arguments {
					if i == len(query.Arguments)-1 {
						comma = ""
					}
					bodyBuilder.WriteString(arg.Name)
					bodyBuilder.WriteString(":")
					bodyBuilder.WriteString(arg.Value.String())
					bodyBuilder.WriteString(comma)
				}
				bodyBuilder.WriteString("}")
				_, requiredFields, err = parseBodyTemplate(bodyBuilder.String())
				if err != nil {
					return gqlerror.ErrorPosf(graphql.Position,
						"Type %s; Field %s: inside graphql in @custom directive, "+
							"error in parsing arguments for %s `%s`: %s.", typ.Name, field.Name,
						graphqlOpDef.Operation, query.Name, err.Error())
				}
			}

This else block can be removed now, as it no longer makes sense. It is using argument names to construct requiredFields, instead we should be using names from variable definitions to construct requiredFields as mentioned above.


graphql/schema/rules.go, line 1352 at r3 (raw file):

			query := graphqlOpDef.SelectionSet[0].(*ast.Field)
			argVal := query.Arguments[0].Value.String()
			vd := graphqlOpDef.VariableDefinitions.ForName(argVal[1:])

I guess we can just do:

argVal := query.Arguments[0].Value.Raw
vd := graphqlOpDef.VariableDefinitions.ForName(argVal)

It directly gives variable name instead of attaching $ to it.

@pawanrawal
Copy link
Contributor Author

Seems like some tests are failing because schema isn't getting updated fast enough. Will add a sleep for them as they pass locally.

Copy link
Contributor Author

@pawanrawal pawanrawal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 46 of 49 files reviewed, 3 unresolved discussions (waiting on @abhimanyusinghgaur, @manishrjain, and @MichaelJCompton)


graphql/schema/rules.go, line 1151 at r2 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…

So, here now we just need to do this:

requiredFields = make(map[string]bool)
for _, vd := range graphqlOpDef.VariableDefinitions {
  requiredFields[vd.Variable] = true
}

It will take care of applying these checks along with some other checks in validation no. 11.
And then we need to remove some unnecessary code as mentioned in next comment.

Done.


graphql/schema/rules.go, line 1331 at r3 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…
			} else {
				var bodyBuilder strings.Builder
				comma := ","
				bodyBuilder.WriteString("{")
				for i, arg := range query.Arguments {
					if i == len(query.Arguments)-1 {
						comma = ""
					}
					bodyBuilder.WriteString(arg.Name)
					bodyBuilder.WriteString(":")
					bodyBuilder.WriteString(arg.Value.String())
					bodyBuilder.WriteString(comma)
				}
				bodyBuilder.WriteString("}")
				_, requiredFields, err = parseBodyTemplate(bodyBuilder.String())
				if err != nil {
					return gqlerror.ErrorPosf(graphql.Position,
						"Type %s; Field %s: inside graphql in @custom directive, "+
							"error in parsing arguments for %s `%s`: %s.", typ.Name, field.Name,
						graphqlOpDef.Operation, query.Name, err.Error())
				}
			}

This else block can be removed now, as it no longer makes sense. It is using argument names to construct requiredFields, instead we should be using names from variable definitions to construct requiredFields as mentioned above.

Doesn't work without this because arguments are not a subset of variable definitions. There might be arguments which are not defined in variable definitions.


graphql/schema/rules.go, line 1352 at r3 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…

I guess we can just do:

argVal := query.Arguments[0].Value.Raw
vd := graphqlOpDef.VariableDefinitions.ForName(argVal)

It directly gives variable name instead of attaching $ to it.

Done.

Copy link
Contributor

@abhimanyusinghgaur abhimanyusinghgaur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm:

Reviewed 2 of 3 files at r4, 1 of 1 files at r5.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on @manishrjain and @MichaelJCompton)

@pawanrawal pawanrawal merged commit 3203fd0 into master May 8, 2020
@pawanrawal pawanrawal deleted the pawanrawal/validate-graphql-call-in-tests branch May 8, 2020 10:00
dna2github pushed a commit to dna2fork/dgraph that referenced this pull request Jul 18, 2020
…raph-io#5362)

GraphQL variables were not being defined in the query/mutation that were being sent to the remote server because of which those requests would have failed. In this PR the mock server has been modified to validate the incoming request and populate the arguments from the variables so that the end to end tests are more real.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/graphql Issues related to GraphQL support on Dgraph.
Development

Successfully merging this pull request may close these issues.

2 participants