Skip to content

Commit 806bb9a

Browse files
committed
add builder for QueryTraversal
1 parent 2b1f1c4 commit 806bb9a

5 files changed

Lines changed: 211 additions & 47 deletions

File tree

src/main/java/graphql/analysis/MaxQueryComplexityInstrumentation.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,12 @@ protected AbortExecutionException mkAbortException(int totalComplexity, int maxC
8888
}
8989

9090
QueryTraversal newQueryTraversal(InstrumentationValidationParameters parameters) {
91-
return new QueryTraversal(
92-
parameters.getSchema(),
93-
parameters.getDocument(),
94-
parameters.getOperation(),
95-
parameters.getVariables()
96-
);
91+
return QueryTraversal.newQueryTraversal()
92+
.schema(parameters.getSchema())
93+
.document(parameters.getDocument())
94+
.operationName(parameters.getOperation())
95+
.variables(parameters.getVariables())
96+
.build();
9797
}
9898

9999
private int calculateComplexity(QueryVisitorFieldEnvironment QueryVisitorFieldEnvironment, int childsComplexity) {

src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,12 @@ protected AbortExecutionException mkAbortException(int depth, int maxDepth) {
5050
}
5151

5252
QueryTraversal newQueryTraversal(InstrumentationValidationParameters parameters) {
53-
return new QueryTraversal(
54-
parameters.getSchema(),
55-
parameters.getDocument(),
56-
parameters.getOperation(),
57-
parameters.getVariables()
58-
);
53+
return QueryTraversal.newQueryTraversal()
54+
.schema(parameters.getSchema())
55+
.document(parameters.getDocument())
56+
.operationName(parameters.getOperation())
57+
.variables(parameters.getVariables())
58+
.build();
5959
}
6060

6161
private int getPathLength(QueryVisitorFieldEnvironment path) {

src/main/java/graphql/analysis/QueryTraversal.java

Lines changed: 139 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,29 +59,31 @@ public class QueryTraversal {
5959
private final ChildrenOfSelectionProvider childrenOfSelectionProvider;
6060
private final GraphQLObjectType rootParentType;
6161

62-
public QueryTraversal(GraphQLSchema schema,
63-
Document document,
64-
String operation,
65-
Map<String, Object> variables) {
62+
private QueryTraversal(GraphQLSchema schema,
63+
Document document,
64+
String operation,
65+
Map<String, Object> variables) {
66+
assertNotNull(document, "document can't be null");
6667
NodeUtil.GetOperationResult getOperationResult = NodeUtil.getOperation(document, operation);
67-
this.schema = schema;
68-
this.variables = variables;
68+
this.schema = assertNotNull(schema, "schema can't be null");
69+
this.variables = assertNotNull(variables, "variables can't be null");
6970
this.fragmentsByName = getOperationResult.fragmentsByName;
7071
this.roots = getOperationResult.operationDefinition.getSelectionSet().getChildren();
7172
this.rootParentType = getRootTypeFromOperation(getOperationResult.operationDefinition);
7273
this.childrenOfSelectionProvider = new ChildrenOfSelectionProvider(fragmentsByName);
7374
}
7475

75-
public QueryTraversal(GraphQLSchema schema,
76-
Node root,
77-
GraphQLObjectType rootParentType,
78-
Map<String, FragmentDefinition> fragmentsByName,
79-
Map<String, Object> variables) {
80-
this.schema = schema;
81-
this.variables = variables;
76+
private QueryTraversal(GraphQLSchema schema,
77+
Node root,
78+
GraphQLObjectType rootParentType,
79+
Map<String, FragmentDefinition> fragmentsByName,
80+
Map<String, Object> variables) {
81+
this.schema = assertNotNull(schema, "schema can't be null");
82+
this.variables = assertNotNull(variables, "variables can't be null");
83+
assertNotNull(root, "root can't be null");
8284
this.roots = Collections.singleton(root);
83-
this.rootParentType = rootParentType;
84-
this.fragmentsByName = fragmentsByName;
85+
this.rootParentType = assertNotNull(rootParentType, "rootParentType can't be null");
86+
this.fragmentsByName = assertNotNull(fragmentsByName, "fragmentsByName can't be null");
8587
this.childrenOfSelectionProvider = new ChildrenOfSelectionProvider(fragmentsByName);
8688
}
8789

@@ -277,4 +279,126 @@ public TraversalControl visitField(Field field, TraverserContext<Node> context)
277279
}
278280

279281
}
282+
283+
public static Builder newQueryTraversal() {
284+
return new Builder();
285+
}
286+
287+
@PublicApi
288+
public static class Builder {
289+
private GraphQLSchema schema;
290+
private Document document;
291+
private String operation;
292+
private Map<String, Object> variables;
293+
294+
private Node root;
295+
private GraphQLObjectType rootParentType;
296+
private Map<String, FragmentDefinition> fragmentsByName;
297+
298+
299+
/**
300+
* The schema used to identify the types of the query.
301+
*
302+
* @param schema
303+
*
304+
* @return
305+
*/
306+
public Builder schema(GraphQLSchema schema) {
307+
this.schema = schema;
308+
return this;
309+
}
310+
311+
/**
312+
* specify the operation if a document is traversed and there
313+
* are more than one operation.
314+
*
315+
* @param operationName
316+
*
317+
* @return
318+
*/
319+
public Builder operationName(String operationName) {
320+
this.operation = operationName;
321+
return this;
322+
}
323+
324+
/**
325+
* document to be used to traverse the whole query.
326+
* If set a {@link Builder#operationName(String)} might be required.
327+
*
328+
* @param document
329+
*
330+
* @return
331+
*/
332+
public Builder document(Document document) {
333+
this.document = document;
334+
return this;
335+
}
336+
337+
/**
338+
* Variables used in the query.
339+
*
340+
* @param variables
341+
*
342+
* @return
343+
*/
344+
public Builder variables(Map<String, Object> variables) {
345+
this.variables = variables;
346+
return this;
347+
}
348+
349+
/**
350+
* Specify the root node for the traversal. Needs to be provided if there is
351+
* no {@link Builder#document(Document)}.
352+
*
353+
* @param root
354+
*
355+
* @return
356+
*/
357+
public Builder root(Node root) {
358+
this.root = root;
359+
return this;
360+
}
361+
362+
/**
363+
* The type of the parent of the root node. (See {@link Builder#root(Node)}
364+
*
365+
* @param rootParentType
366+
*
367+
* @return
368+
*/
369+
public Builder rootParentType(GraphQLObjectType rootParentType) {
370+
this.rootParentType = rootParentType;
371+
return this;
372+
}
373+
374+
/**
375+
* Fragment by name map. Needs to be provided together with a {@link Builder#root(Node)} and {@link Builder#rootParentType(GraphQLObjectType)}
376+
*
377+
* @param fragmentsByName
378+
*
379+
* @return
380+
*/
381+
public Builder fragmentsByName(Map<String, FragmentDefinition> fragmentsByName) {
382+
this.fragmentsByName = fragmentsByName;
383+
return this;
384+
}
385+
386+
public QueryTraversal build() {
387+
checkState();
388+
if (document != null) {
389+
return new QueryTraversal(schema, document, operation, variables);
390+
} else {
391+
return new QueryTraversal(schema, root, rootParentType, fragmentsByName, variables);
392+
}
393+
}
394+
395+
private void checkState() {
396+
if (document != null || operation != null) {
397+
if (root != null || rootParentType != null || fragmentsByName != null) {
398+
throw new IllegalStateException("ambiguous builder");
399+
}
400+
}
401+
}
402+
403+
}
280404
}

src/main/java/graphql/execution/instrumentation/fieldvalidation/FieldValidationSupport.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ static List<GraphQLError> validateFieldsAndArguments(FieldValidation fieldValida
2828

2929
Map<ExecutionPath, List<FieldAndArguments>> fieldArgumentsMap = new LinkedHashMap<>();
3030

31-
QueryTraversal queryTraversal = new QueryTraversal(
32-
executionContext.getGraphQLSchema(),
33-
executionContext.getDocument(),
34-
executionContext.getOperationDefinition().getName(),
35-
executionContext.getVariables()
36-
);
31+
QueryTraversal queryTraversal = QueryTraversal.newQueryTraversal()
32+
.schema(executionContext.getGraphQLSchema())
33+
.document(executionContext.getDocument())
34+
.operationName(executionContext.getOperationDefinition().getName())
35+
.variables(executionContext.getVariables())
36+
.build();
37+
3738
queryTraversal.visitPreOrder(new QueryVisitorStub() {
3839
@Override
3940
public void visitField(QueryVisitorFieldEnvironment env) {

src/test/groovy/graphql/analysis/QueryTraversalTest.groovy

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import graphql.language.FragmentDefinition
77
import graphql.language.FragmentSpread
88
import graphql.language.InlineFragment
99
import graphql.parser.Parser
10+
import graphql.schema.GraphQLObjectType
1011
import graphql.schema.GraphQLSchema
1112
import spock.lang.Specification
1213
import spock.lang.Unroll
1314

15+
import static java.util.Collections.emptyMap
16+
1417
class QueryTraversalTest extends Specification {
1518

1619

@@ -20,12 +23,11 @@ class QueryTraversalTest extends Specification {
2023
}
2124

2225
QueryTraversal createQueryTraversal(Document document, GraphQLSchema schema, Map variables = [:]) {
23-
QueryTraversal queryTraversal = new QueryTraversal(
24-
schema,
25-
document,
26-
null,
27-
variables
28-
)
26+
QueryTraversal queryTraversal = QueryTraversal.newQueryTraversal()
27+
.schema(schema)
28+
.document(document)
29+
.variables(variables)
30+
.build()
2931
return queryTraversal
3032
}
3133

@@ -51,7 +53,8 @@ class QueryTraversalTest extends Specification {
5153
then:
5254
1 * visitor.visitField({ QueryVisitorFieldEnvironmentImpl it ->
5355
it.field.name == "foo" && it.fieldDefinition.type.name == "Foo" && it.parentType.name == "Query" &&
54-
it.selectionSetContainer == null})
56+
it.selectionSetContainer == null
57+
})
5558
then:
5659
1 * visitor.visitField({ QueryVisitorFieldEnvironmentImpl it ->
5760
it.field.name == "subFoo" && it.fieldDefinition.type.name == "String" &&
@@ -1113,13 +1116,13 @@ class QueryTraversalTest extends Specification {
11131116
assert root instanceof Field
11141117
((Field) root).name == "subFoo"
11151118
def rootParentType = schema.getType("Foo")
1116-
QueryTraversal queryTraversal = new QueryTraversal(
1117-
schema,
1118-
root,
1119-
rootParentType,
1120-
Collections.emptyMap(),
1121-
Collections.emptyMap()
1122-
)
1119+
QueryTraversal queryTraversal = QueryTraversal.newQueryTraversal()
1120+
.schema(schema)
1121+
.root(root)
1122+
.rootParentType(rootParentType)
1123+
.variables(emptyMap())
1124+
.fragmentsByName(emptyMap())
1125+
.build()
11231126
when:
11241127
queryTraversal.visitPreOrder(visitor)
11251128
@@ -1132,5 +1135,41 @@ class QueryTraversalTest extends Specification {
11321135
11331136
}
11341137
1138+
@Unroll
1139+
def "builder doesn't allow ambiguous arguments"() {
1140+
// def document = createQuery("""
1141+
// {foo}
1142+
// """)
1143+
// def root = new Field()
1144+
1145+
when:
1146+
def queryTraversal = QueryTraversal.newQueryTraversal()
1147+
.document(document)
1148+
.operationName(operationName)
1149+
.root(root)
1150+
.rootParentType(rootParentType)
1151+
.fragmentsByName(fragmentsByName)
1152+
.build()
1153+
1154+
then:
1155+
thrown(IllegalStateException)
1156+
1157+
where:
1158+
document | operationName | root | rootParentType | fragmentsByName
1159+
createQuery("{foo}") | null | new Field() | null | null
1160+
createQuery("{foo}") | "foo" | new Field() | null | null
1161+
createQuery("{foo}") | "foo" | new Field() | Mock(GraphQLObjectType) | null
1162+
createQuery("{foo}") | "foo" | new Field() | null | emptyMap()
1163+
null | "foo" | new Field() | Mock(GraphQLObjectType) | null
1164+
null | "foo" | new Field() | Mock(GraphQLObjectType) | emptyMap()
1165+
null | "foo" | new Field() | Mock(GraphQLObjectType) | emptyMap()
1166+
null | "foo" | new Field() | null | emptyMap()
1167+
null | "foo" | null | Mock(GraphQLObjectType) | emptyMap()
1168+
null | "foo" | null | Mock(GraphQLObjectType) | null
1169+
null | "foo" | null | null | emptyMap()
1170+
1171+
1172+
}
1173+
11351174

11361175
}

0 commit comments

Comments
 (0)