Skip to content

Commit 98250a2

Browse files
committed
This introduces a Traversal Options to allow skipping the coercing of field arguments
1 parent 99ec596 commit 98250a2

File tree

6 files changed

+247
-21
lines changed

6 files changed

+247
-21
lines changed

src/main/java/graphql/analysis/NodeVisitorWithTypeTracking.java

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import graphql.util.TraversalControl;
3131
import graphql.util.TraverserContext;
3232

33+
import java.util.Collections;
3334
import java.util.Locale;
3435
import java.util.Map;
3536

@@ -50,13 +51,20 @@ public class NodeVisitorWithTypeTracking extends NodeVisitorStub {
5051
private final GraphQLSchema schema;
5152
private final Map<String, FragmentDefinition> fragmentsByName;
5253
private final ConditionalNodes conditionalNodes = new ConditionalNodes();
53-
54-
public NodeVisitorWithTypeTracking(QueryVisitor preOrderCallback, QueryVisitor postOrderCallback, Map<String, Object> variables, GraphQLSchema schema, Map<String, FragmentDefinition> fragmentsByName) {
54+
private final QueryTraversalOptions options;
55+
56+
public NodeVisitorWithTypeTracking(QueryVisitor preOrderCallback,
57+
QueryVisitor postOrderCallback,
58+
Map<String, Object> variables,
59+
GraphQLSchema schema,
60+
Map<String, FragmentDefinition> fragmentsByName,
61+
QueryTraversalOptions options) {
5562
this.preOrderCallback = preOrderCallback;
5663
this.postOrderCallback = postOrderCallback;
5764
this.variables = variables;
5865
this.schema = schema;
5966
this.fragmentsByName = fragmentsByName;
67+
this.options = options;
6068
}
6169

6270
@Override
@@ -156,12 +164,17 @@ public TraversalControl visitField(Field field, TraverserContext<Node> context)
156164
boolean isTypeNameIntrospectionField = fieldDefinition == schema.getIntrospectionTypenameFieldDefinition();
157165
GraphQLFieldsContainer fieldsContainer = !isTypeNameIntrospectionField ? (GraphQLFieldsContainer) unwrapAll(parentEnv.getOutputType()) : null;
158166
GraphQLCodeRegistry codeRegistry = schema.getCodeRegistry();
159-
Map<String, Object> argumentValues = ValuesResolver.getArgumentValues(codeRegistry,
160-
fieldDefinition.getArguments(),
161-
field.getArguments(),
162-
CoercedVariables.of(variables),
163-
GraphQLContext.getDefault(),
164-
Locale.getDefault());
167+
Map<String, Object> argumentValues;
168+
if (options.isCoerceFieldArguments()) {
169+
argumentValues = ValuesResolver.getArgumentValues(codeRegistry,
170+
fieldDefinition.getArguments(),
171+
field.getArguments(),
172+
CoercedVariables.of(variables),
173+
GraphQLContext.getDefault(),
174+
Locale.getDefault());
175+
} else {
176+
argumentValues = Collections.emptyMap();
177+
}
165178
QueryVisitorFieldEnvironment environment = new QueryVisitorFieldEnvironmentImpl(isTypeNameIntrospectionField,
166179
field,
167180
fieldDefinition,

src/main/java/graphql/analysis/QueryTransformer.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,22 @@ public class QueryTransformer {
3636
private final GraphQLSchema schema;
3737
private final Map<String, FragmentDefinition> fragmentsByName;
3838
private final Map<String, Object> variables;
39-
4039
private final GraphQLCompositeType rootParentType;
40+
private final QueryTraversalOptions options;
4141

4242

4343
private QueryTransformer(GraphQLSchema schema,
4444
Node root,
4545
GraphQLCompositeType rootParentType,
4646
Map<String, FragmentDefinition> fragmentsByName,
47-
Map<String, Object> variables) {
47+
Map<String, Object> variables,
48+
QueryTraversalOptions options) {
4849
this.schema = assertNotNull(schema, () -> "schema can't be null");
4950
this.variables = assertNotNull(variables, () -> "variables can't be null");
5051
this.root = assertNotNull(root, () -> "root can't be null");
5152
this.rootParentType = assertNotNull(rootParentType);
5253
this.fragmentsByName = assertNotNull(fragmentsByName, () -> "fragmentsByName can't be null");
54+
this.options = assertNotNull(options, () -> "options can't be null");
5355
}
5456

5557
/**
@@ -65,12 +67,17 @@ private QueryTransformer(GraphQLSchema schema,
6567
*/
6668
public Node transform(QueryVisitor queryVisitor) {
6769
QueryVisitor noOp = new QueryVisitorStub();
68-
NodeVisitorWithTypeTracking nodeVisitor = new NodeVisitorWithTypeTracking(queryVisitor, noOp, variables, schema, fragmentsByName);
70+
NodeVisitorWithTypeTracking nodeVisitor = new NodeVisitorWithTypeTracking(queryVisitor,
71+
noOp,
72+
variables,
73+
schema,
74+
fragmentsByName,
75+
options);
6976

7077
Map<Class<?>, Object> rootVars = new LinkedHashMap<>();
7178
rootVars.put(QueryTraversalContext.class, new QueryTraversalContext(rootParentType, null, null, GraphQLContext.getDefault()));
7279

73-
TraverserVisitor<Node> nodeTraverserVisitor = new TraverserVisitor<Node>() {
80+
TraverserVisitor<Node> nodeTraverserVisitor = new TraverserVisitor<>() {
7481

7582
@Override
7683
public TraversalControl enter(TraverserContext<Node> context) {
@@ -98,6 +105,7 @@ public static class Builder {
98105
private Node root;
99106
private GraphQLCompositeType rootParentType;
100107
private Map<String, FragmentDefinition> fragmentsByName;
108+
private QueryTraversalOptions options = QueryTraversalOptions.defaultOptions();
101109

102110

103111
/**
@@ -160,8 +168,25 @@ public Builder fragmentsByName(Map<String, FragmentDefinition> fragmentsByName)
160168
return this;
161169
}
162170

171+
/**
172+
* Sets the options to use while traversing
173+
*
174+
* @param options the options to use
175+
* @return this builder
176+
*/
177+
public Builder options(QueryTraversalOptions options) {
178+
this.options = assertNotNull(options, () -> "options can't be null");
179+
return this;
180+
}
181+
163182
public QueryTransformer build() {
164-
return new QueryTransformer(schema, root, rootParentType, fragmentsByName, variables);
183+
return new QueryTransformer(
184+
schema,
185+
root,
186+
rootParentType,
187+
fragmentsByName,
188+
variables,
189+
options);
165190
}
166191
}
167192
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package graphql.analysis;
2+
3+
import graphql.PublicApi;
4+
5+
/**
6+
* This options object controls how {@link QueryTraverser} works
7+
*/
8+
@PublicApi
9+
public class QueryTraversalOptions {
10+
11+
private final boolean coerceFieldArguments;
12+
13+
private QueryTraversalOptions(boolean coerceFieldArguments) {
14+
this.coerceFieldArguments = coerceFieldArguments;
15+
}
16+
17+
/**
18+
* @return true if field arguments should be coerced. This is true by default.
19+
*/
20+
public boolean isCoerceFieldArguments() {
21+
return coerceFieldArguments;
22+
}
23+
24+
public static QueryTraversalOptions defaultOptions() {
25+
return new QueryTraversalOptions(true);
26+
}
27+
28+
public QueryTraversalOptions coerceFieldArguments(boolean coerceFieldArguments) {
29+
return new QueryTraversalOptions(coerceFieldArguments);
30+
}
31+
}

src/main/java/graphql/analysis/QueryTraverser.java

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,42 +49,52 @@ public class QueryTraverser {
4949
private CoercedVariables coercedVariables;
5050

5151
private final GraphQLCompositeType rootParentType;
52+
private final QueryTraversalOptions options;
5253

5354
private QueryTraverser(GraphQLSchema schema,
5455
Document document,
5556
String operation,
56-
CoercedVariables coercedVariables) {
57+
CoercedVariables coercedVariables,
58+
QueryTraversalOptions options
59+
) {
5760
this.schema = schema;
5861
NodeUtil.GetOperationResult getOperationResult = NodeUtil.getOperation(document, operation);
5962
this.fragmentsByName = getOperationResult.fragmentsByName;
6063
this.roots = singletonList(getOperationResult.operationDefinition);
6164
this.rootParentType = getRootTypeFromOperation(getOperationResult.operationDefinition);
6265
this.coercedVariables = coercedVariables;
66+
this.options = options;
6367
}
6468

6569
private QueryTraverser(GraphQLSchema schema,
6670
Document document,
6771
String operation,
68-
RawVariables rawVariables) {
72+
RawVariables rawVariables,
73+
QueryTraversalOptions options
74+
) {
6975
this.schema = schema;
7076
NodeUtil.GetOperationResult getOperationResult = NodeUtil.getOperation(document, operation);
7177
List<VariableDefinition> variableDefinitions = getOperationResult.operationDefinition.getVariableDefinitions();
7278
this.fragmentsByName = getOperationResult.fragmentsByName;
7379
this.roots = singletonList(getOperationResult.operationDefinition);
7480
this.rootParentType = getRootTypeFromOperation(getOperationResult.operationDefinition);
7581
this.coercedVariables = ValuesResolver.coerceVariableValues(schema, variableDefinitions, rawVariables, GraphQLContext.getDefault(), Locale.getDefault());
82+
this.options = options;
7683
}
7784

7885
private QueryTraverser(GraphQLSchema schema,
7986
Node root,
8087
GraphQLCompositeType rootParentType,
8188
Map<String, FragmentDefinition> fragmentsByName,
82-
CoercedVariables coercedVariables) {
89+
CoercedVariables coercedVariables,
90+
QueryTraversalOptions options
91+
) {
8392
this.schema = schema;
8493
this.roots = Collections.singleton(root);
8594
this.rootParentType = rootParentType;
8695
this.fragmentsByName = fragmentsByName;
8796
this.coercedVariables = coercedVariables;
97+
this.options = options;
8898
}
8999

90100
public Object visitDepthFirst(QueryVisitor queryVisitor) {
@@ -191,7 +201,12 @@ private Object visitImpl(QueryVisitor visitFieldCallback, Boolean preOrder) {
191201
}
192202

193203
NodeTraverser nodeTraverser = new NodeTraverser(rootVars, this::childrenOf);
194-
NodeVisitorWithTypeTracking nodeVisitorWithTypeTracking = new NodeVisitorWithTypeTracking(preOrderCallback, postOrderCallback, coercedVariables.toMap(), schema, fragmentsByName);
204+
NodeVisitorWithTypeTracking nodeVisitorWithTypeTracking = new NodeVisitorWithTypeTracking(preOrderCallback,
205+
postOrderCallback,
206+
coercedVariables.toMap(),
207+
schema,
208+
fragmentsByName,
209+
options);
195210
return nodeTraverser.depthFirst(nodeVisitorWithTypeTracking, roots);
196211
}
197212

@@ -210,6 +225,7 @@ public static class Builder {
210225
private Node root;
211226
private GraphQLCompositeType rootParentType;
212227
private Map<String, FragmentDefinition> fragmentsByName;
228+
private QueryTraversalOptions options = QueryTraversalOptions.defaultOptions();
213229

214230

215231
/**
@@ -313,24 +329,53 @@ public Builder fragmentsByName(Map<String, FragmentDefinition> fragmentsByName)
313329
return this;
314330
}
315331

332+
/**
333+
* Sets the options to use while traversing
334+
*
335+
* @param options the options to use
336+
* @return this builder
337+
*/
338+
public Builder options(QueryTraversalOptions options) {
339+
this.options = assertNotNull(options, () -> "options can't be null");
340+
return this;
341+
}
342+
316343
/**
317344
* @return a built {@link QueryTraverser} object
318345
*/
319346
public QueryTraverser build() {
320347
checkState();
321348
if (document != null) {
322349
if (rawVariables != null) {
323-
return new QueryTraverser(schema, document, operation, rawVariables);
350+
return new QueryTraverser(schema,
351+
document,
352+
operation,
353+
rawVariables,
354+
options);
324355
}
325-
return new QueryTraverser(schema, document, operation, coercedVariables);
356+
return new QueryTraverser(schema,
357+
document,
358+
operation,
359+
coercedVariables,
360+
options);
326361
} else {
327362
if (rawVariables != null) {
328363
// When traversing with an arbitrary root, there is no variable definition context available
329364
// Thus, the variables must have already been coerced
330365
// Retaining this builder for backwards compatibility
331-
return new QueryTraverser(schema, root, rootParentType, fragmentsByName, CoercedVariables.of(rawVariables.toMap()));
366+
return new QueryTraverser(schema,
367+
root,
368+
rootParentType,
369+
fragmentsByName,
370+
CoercedVariables.of(rawVariables.toMap()),
371+
options);
332372
}
333-
return new QueryTraverser(schema, root, rootParentType, fragmentsByName, coercedVariables);
373+
return new QueryTraverser(schema,
374+
root,
375+
rootParentType,
376+
fragmentsByName,
377+
coercedVariables,
378+
options);
334379
}
335380
}
336381

src/test/groovy/graphql/analysis/QueryTransformerTest.groovy

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package graphql.analysis
22

33
import graphql.TestUtil
4+
import graphql.execution.CoercedVariables
45
import graphql.language.Document
56
import graphql.language.Field
67
import graphql.language.NodeUtil
@@ -448,4 +449,60 @@ class QueryTransformerTest extends Specification {
448449
printAstCompact(newNode) == "{__typename ...on A{aX}...on B{b}}"
449450

450451
}
452+
453+
def "can coerce field arguments or not"() {
454+
def sdl = """
455+
input Test{ x: String!}
456+
type Query{ testInput(input: Test!): String}
457+
type Mutation{ testInput(input: Test!): String}
458+
"""
459+
460+
def schema = TestUtil.schema(sdl)
461+
462+
def query = createQuery('''
463+
mutation a($test: Test!) {
464+
testInput(input: $test)
465+
}''')
466+
467+
468+
def fieldArgMap = [:]
469+
def queryVisitorStub = new QueryVisitorStub() {
470+
@Override
471+
void visitField(QueryVisitorFieldEnvironment queryVisitorFieldEnvironment) {
472+
super.visitField(queryVisitorFieldEnvironment)
473+
fieldArgMap = queryVisitorFieldEnvironment.getArguments()
474+
}
475+
}
476+
477+
when:
478+
QueryTraverser.newQueryTraverser()
479+
.schema(schema)
480+
.document(query)
481+
.coercedVariables(CoercedVariables.of([test: [x: "X"]]))
482+
.build()
483+
.visitPreOrder(queryVisitorStub)
484+
485+
then:
486+
487+
fieldArgMap == [input: [x:"X"]]
488+
489+
when:
490+
fieldArgMap = null
491+
492+
493+
def options = QueryTraversalOptions.defaultOptions()
494+
.coerceFieldArguments(false)
495+
QueryTraverser.newQueryTraverser()
496+
.schema(schema)
497+
.document(query)
498+
.coercedVariables(CoercedVariables.of([test: [x: "X"]]))
499+
.options(options)
500+
.build()
501+
.visitPreOrder(queryVisitorStub)
502+
503+
504+
then:
505+
fieldArgMap == [:] // empty map
506+
}
507+
451508
}

0 commit comments

Comments
 (0)