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
Raw and Coerced Variables refactor #2802
Raw and Coerced Variables refactor #2802
Conversation
@@ -39,7 +40,7 @@ private ExecutionInput(Builder builder) { | |||
this.context = builder.context; | |||
this.graphQLContext = assertNotNull(builder.graphQLContext); | |||
this.root = builder.root; | |||
this.variables = builder.variables; | |||
this.rawVariables = builder.rawVariables; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Variables in ExecutionInput
are always raw
NodeUtil.GetOperationResult getOperationResult = NodeUtil.getOperation(document, operation); | ||
List<VariableDefinition> variableDefinitions = getOperationResult.operationDefinition.getVariableDefinitions(); | ||
this.schema = assertNotNull(schema, () -> "schema can't be null"); | ||
this.fragmentsByName = getOperationResult.fragmentsByName; | ||
this.roots = singletonList(getOperationResult.operationDefinition); | ||
this.rootParentType = getRootTypeFromOperation(getOperationResult.operationDefinition); | ||
this.variables = coerceVariables(assertNotNull(variables, () -> "variables can't be null"), variableDefinitions); | ||
this.coercedVariables = coerceVariables(assertNotNull(variables, () -> "variables can't be null"), variableDefinitions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now, keep functionality similar to before
In the next PR (#2773), QueryTraverser
will change to accept already coerced variables
*/ | ||
@Internal | ||
public class CoercedVariables { | ||
private final ImmutableMapWithNullValues<String, Object> coercedVariables; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using this special map as variable values can be null
@@ -43,7 +42,7 @@ public class ExecutionContext { | |||
private final ImmutableMap<String, FragmentDefinition> fragmentsByName; | |||
private final OperationDefinition operationDefinition; | |||
private final Document document; | |||
private final ImmutableMapWithNullValues<String, Object> variables; | |||
private final CoercedVariables coercedVariables; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ExecutionContext
can only contain coerced variables. This change makes it clearer
*/ | ||
@Internal | ||
public class RawVariables { | ||
private final ImmutableMapWithNullValues<String, Object> rawVariables; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using this special map as values can be null
|
||
def argument = Argument.newArgument("if", new BooleanValue(true)).build() | ||
def directives = [Directive.newDirective().name("skip").arguments([argument]).build()] | ||
|
||
conditionalNodes.valuesResolver.getArgumentValues(Directives.SkipDirective.getArguments(), [argument], variables) >> ["if": true] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This mock wasn't necessary
then: | ||
resolvedValues['variable'] == outputValue | ||
resolvedValues.get('variable') == outputValue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most API changes in this test file
@@ -39,7 +39,7 @@ public class ExecutionContextBuilder { | |||
Object root; | |||
Document document; | |||
OperationDefinition operationDefinition; | |||
ImmutableMapWithNullValues<String, Object> variables = ImmutableMapWithNullValues.emptyMap(); | |||
CoercedVariables coercedVariables = new CoercedVariables(Collections.emptyMap()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about CoercedVariables.emptyVariables()
- reads nicer - not essential
this.coercedVariables = ImmutableMapWithNullValues.copyOf(coercedVariables); | ||
} | ||
|
||
public Map<String, Object> getMap() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
map name it toMap
that is you are converting it from a X -> Y - from a CoercedVariables to a map representtion
executionInput.locale == Locale.GERMAN | ||
executionInput.extensions == [some: "map"] | ||
executionInput.query == "new query" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if we set both
.rawVariables(x).variables(y)
Should we have a test for this. Do they go into the same place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add a test.
Yes, in ExecutionInput
both rawVariables
and variables
set the same field. If both get set, the last one would be used (in your example y
).
Is it worthwhile preventing both .rawVariables(x).variables(y)
to be used? I don't mind adding another check if it'll be clearer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I generally appove strongly of this
See my specific comments on naming.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we discussed in person - lets remove the RawVariables fron ExecutionInput signature but use it on the inside
# Conflicts: # src/main/java/graphql/ExecutionInput.java # src/main/java/graphql/execution/ValuesResolver.java # src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merge ! Merge ! Merge!!
Why refactor? Identifying double coercion
Variables were previously represented as a
Map<String, Object>
, which made it difficult to determine if variables were raw or had been already coerced.Example: double coercion in
FieldValidationInstrumentation
Let's start from this line in
Execution#executeOperation
. At this point, variables have already been coerced. TheExecutionContext
can only contain coerced variables.Then inside
FieldValidationInstrumentation#beginExecuteOperation
we haveWithin
validateFieldsAndArguments
, we create aQueryTraverser
, which coerces variables a second time.Example: double coercion in WIP PR to shift max query depth after validation
https://github.com/graphql-java/graphql-java/pull/2773/files#diff-2696e88b9e4d095eea4d0e47b1c98ea872bfce3642c5aaee7317b1c3c2c478a5R149
This is a WIP PR so I don't want to be harsh, nevertheless it's a good illustration of how confusing raw and coerced variables are. The
ExecutionContext
can only contain coerced variables. The linkedQueryTraverser
will coerce variables a second time.This PR adds
RawVariables
andCoercedVariables
This PR adds
RawVariables
andCoercedVariables
to make the distinction clearer and avoid accidental double coercion.This PR addresses #2764, and sets the foundation for #2773.
This is a large PR! To make it easier to see the changes, please read commit by commit (or alternatively, I can split this work into multiple PRs).
What did change
I refactored variables to
RawVariables
orCoercedVariables
in crucial classes related to Execution and classes required for the upcoming traverser changes in #2773.What didn't change
There are other classes containing a map of variables such as
FieldCollectionParameters
. These classes have been left unchanged primarily to keep the scope of this PR manageable. In these unchanged classes, there was far less ambiguity about coercion. I can extendRawVariables
andCoercedVariables
to all other classes in another PR.What is in the next PR
After this PR is merged in I can help finish off #2773 and fix both double coercion examples I outlined above.