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

Fix double variable coercion in QueryTraverser #2836

Merged

Conversation

dondonz
Copy link
Member

@dondonz dondonz commented May 25, 2022

This PR fixes double variable coercion in QueryTraverser and classes using traversal such as MaxQueryComplexityInstrumentation, MaxQueryDepthInstrumentation, and FieldValidationSupport. Fixes #2819.

The double coercion problem

Previously, raw and coerced variables were represented as plain maps Map<String, Object>. Using the same type made it hard to distinguish raw from coerced variables and in some places, such as MaxQueryDepthInstrumentation, we unintentionally coerced variables twice. The double coercion was there for some time (e.g. #2458), but became a problem in v18.1 (see #2819)

Caused by: graphql.schema.CoercingParseValueException: Variable 'memberDues' has an invalid value: Failed to parse input value 2022-05-16T19:52:37Z as ZonedDateTime

Caused by: java.lang.IllegalArgumentException: Unexpected input type: class java.time.ZonedDateTime at com.meetup.graphql.wiring.CustomScalars.parseDateTimeValue(CustomScalars.java:38) ...

The ZonedDateTime custom scalar parser in the #2819 example accepts String and StringValue, but not itself, causing a IllegalArgumentException during the second coercion.

The fix

This PR adds a QueryTraverser that handles already coerced variables, thus avoiding the problematic second coercion.

In most cases, when a new QueryTraverser is instantiated, the variables have already been coerced. Following #2773, traversal will usually take place after the ExecutionContext is instantiated.

@dondonz dondonz changed the title Fix double variable coercion in QueryTraverser (WIP) Fix double variable coercion in QueryTraverser May 29, 2022
@@ -147,7 +147,7 @@ QueryTraverser newQueryTraverser(ExecutionContext executionContext) {
.schema(executionContext.getGraphQLSchema())
.document(executionContext.getDocument())
.operationName(executionContext.getExecutionInput().getOperationName())
.variables(executionContext.getVariables())
.coercedVariables(executionContext.getCoercedVariables())
Copy link
Member

Choose a reason for hiding this comment

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

love it!

Copy link
Member

@bbakerman bbakerman left a comment

Choose a reason for hiding this comment

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

I am guessing that you have multiple entry points into the constructor and hence the asserts of nbull ness got moved up into the builder

@@ -84,7 +84,7 @@ QueryTraverser newQueryTraverser(ExecutionContext executionContext) {
.schema(executionContext.getGraphQLSchema())
.document(executionContext.getDocument())
.operationName(executionContext.getExecutionInput().getOperationName())
.variables(executionContext.getVariables())
.coercedVariables(executionContext.getCoercedVariables())
Copy link
Member

Choose a reason for hiding this comment

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

nice

Map<String, Object> variables) {
assertNotNull(document, () -> "document can't be null");
CoercedVariables coercedVariables) {
this.schema = schema;
Copy link
Member

Choose a reason for hiding this comment

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

did you get rid of assertNotNull(document, () -> "document can't be null"); accidentilly here??

Copy link
Member Author

Choose a reason for hiding this comment

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

You probably saw it later - the null assert is inside the builder now

this.fragmentsByName = getOperationResult.fragmentsByName;
this.roots = singletonList(getOperationResult.operationDefinition);
this.rootParentType = getRootTypeFromOperation(getOperationResult.operationDefinition);
this.coercedVariables = new ValuesResolver().coerceVariableValues(schema, variableDefinitions, rawVariables);
Copy link
Member

Choose a reason for hiding this comment

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

This is one for Andi omre yhan you - but since ValuesResolver is a stateless class - we should move to static entry points. Object allocation is cheap - but statics are even cheaper AND it says its a singleton

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a good point, doesn't make sense to have an instance here.

I'll have a chat to Andi and make this change in a separate PR

this.roots = Collections.singleton(root);
this.rootParentType = assertNotNull(rootParentType, () -> "rootParentType can't be null");
this.fragmentsByName = assertNotNull(fragmentsByName, () -> "fragmentsByName can't be null");
this.coercedVariables = new CoercedVariables(assertNotNull(variables, () -> "variables can't be null"));
Copy link
Member

Choose a reason for hiding this comment

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

are the asserts removed because these invariants are no longer true?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm adding 2 new constructors so to keep the code tidier I thought I would move all the null checks into the builder, rather than having to remember 4 lots of asserts

return input
}
})
.build()
Copy link
Member

Choose a reason for hiding this comment

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

Nice one - great repo!

@dondonz dondonz merged commit d133ec5 into graphql-java:master Jun 7, 2022
@dondonz dondonz deleted the 2819-remove-double-variable-coercion branch June 7, 2022 04:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

regression in GraphQLScalarType Coercing parseValue in 18.1
2 participants