99import graphql .language .FragmentDefinition ;
1010import graphql .language .FragmentSpread ;
1111import graphql .language .InlineFragment ;
12+ import graphql .language .Node ;
13+ import graphql .language .NodeTraverser ;
14+ import graphql .language .NodeTraverser .LeaveOrEnter ;
1215import graphql .language .NodeUtil ;
16+ import graphql .language .NodeVisitorStub ;
1317import graphql .language .OperationDefinition ;
1418import graphql .language .Selection ;
1519import graphql .language .SelectionSet ;
2024import graphql .schema .GraphQLSchema ;
2125import graphql .schema .GraphQLUnmodifiedType ;
2226import graphql .schema .SchemaUtil ;
27+ import graphql .util .TraversalControl ;
28+ import graphql .util .TraverserContext ;
2329
2430import java .util .LinkedHashMap ;
31+ import java .util .List ;
2532import java .util .Map ;
2633
2734import static graphql .Assert .assertNotNull ;
2835import static graphql .Assert .assertShouldNeverHappen ;
36+ import static graphql .language .NodeTraverser .LeaveOrEnter .LEAVE ;
2937
3038@ Internal
3139public class QueryTraversal {
3240
33-
3441 private final OperationDefinition operationDefinition ;
3542 private final GraphQLSchema schema ;
36- private Map <String , FragmentDefinition > fragmentsByName = new LinkedHashMap <>() ;
43+ private final Map <String , FragmentDefinition > fragmentsByName ;
3744 private final Map <String , Object > variables ;
3845
3946 private final ConditionalNodes conditionalNodes = new ConditionalNodes ();
40-
4147 private final ValuesResolver valuesResolver = new ValuesResolver ();
4248 private final SchemaUtil schemaUtil = new SchemaUtil ();
43-
49+ private final ChildrenOfSelectionProvider childrenOfSelectionProvider ;
4450
4551 public QueryTraversal (GraphQLSchema schema ,
4652 Document document ,
4753 String operation ,
4854 Map <String , Object > variables ) {
4955 NodeUtil .GetOperationResult getOperationResult = NodeUtil .getOperation (document , operation );
56+
5057 this .operationDefinition = getOperationResult .operationDefinition ;
5158 this .fragmentsByName = getOperationResult .fragmentsByName ;
59+ this .childrenOfSelectionProvider = new ChildrenOfSelectionProvider (fragmentsByName );
5260 this .schema = schema ;
5361 this .variables = variables ;
5462 }
5563
56- public void visitPostOrder (QueryVisitor visitor ) {
57- visitImpl (visitor , operationDefinition .getSelectionSet (), getRootType (), null , false );
64+ public void visitPostOrder (FieldVisitor visitor ) {
65+ visitImpl (visitor , operationDefinition .getSelectionSet (), getRootType (), false );
5866 }
5967
60- public void visitPreOrder (QueryVisitor visitor ) {
61- visitImpl (visitor , operationDefinition .getSelectionSet (), getRootType (), null , true );
68+ public void visitPreOrder (FieldVisitor visitor ) {
69+ visitImpl (visitor , operationDefinition .getSelectionSet (), getRootType (), true );
6270 }
6371
6472 private GraphQLObjectType getRootType () {
65- if (operationDefinition .getOperation () == OperationDefinition .Operation .MUTATION ) {
66- return assertNotNull (schema .getMutationType ());
67- } else if (operationDefinition .getOperation () == OperationDefinition .Operation .QUERY ) {
68- return assertNotNull (schema .getQueryType ());
69- } else if (operationDefinition .getOperation () == OperationDefinition .Operation .SUBSCRIPTION ) {
70- return assertNotNull (schema .getSubscriptionType ());
71- } else {
72- return assertShouldNeverHappen ();
73+ switch (operationDefinition .getOperation ()) {
74+ case MUTATION :
75+ return assertNotNull (schema .getMutationType ());
76+ case QUERY :
77+ return assertNotNull (schema .getQueryType ());
78+ case SUBSCRIPTION :
79+ return assertNotNull (schema .getSubscriptionType ());
80+ default :
81+ return assertShouldNeverHappen ();
7382 }
7483 }
7584
@@ -89,69 +98,111 @@ public <T> T reducePreOrder(QueryReducer<T> queryReducer, T initialValue) {
8998 return (T ) acc [0 ];
9099 }
91100
101+ private List <Node > childrenOf (Node selection ) {
102+ return childrenOfSelectionProvider .getSelections ((Selection ) selection );
103+ }
92104
93- private void visitImpl (QueryVisitor visitor , SelectionSet selectionSet , GraphQLCompositeType type , QueryVisitorEnvironment parent , boolean preOrder ) {
105+ private void visitImpl (FieldVisitor visitFieldCallback , SelectionSet selectionSet , GraphQLCompositeType type , boolean preOrder ) {
106+ Map <Class <?>, Object > rootVars = new LinkedHashMap <>();
107+ rootVars .put (QueryTraversalContext .class , new QueryTraversalContext (type , null ));
94108
95- for (Selection selection : selectionSet .getSelections ()) {
96- if (selection instanceof Field ) {
97- GraphQLFieldDefinition fieldDefinition = Introspection .getFieldDef (schema , type , ((Field ) selection ).getName ());
98- visitField (visitor , (Field ) selection , fieldDefinition , type , parent , preOrder );
99- } else if (selection instanceof InlineFragment ) {
100- visitInlineFragment (visitor , (InlineFragment ) selection , type , parent , preOrder );
101- } else if (selection instanceof FragmentSpread ) {
102- visitFragmentSpread (visitor , (FragmentSpread ) selection , parent , preOrder );
103- }
104- }
109+ FieldVisitor noOp = notUsed -> {
110+ };
111+ FieldVisitor preOrderCallback = preOrder ? visitFieldCallback : noOp ;
112+ FieldVisitor postOrderCallback = !preOrder ? visitFieldCallback : noOp ;
113+
114+ NodeTraverser nodeTraverser = new NodeTraverser (rootVars , this ::childrenOf );
115+ nodeTraverser .depthFirst (new NodeVisitorImpl (preOrderCallback , postOrderCallback ), selectionSet .getSelections ());
105116 }
106117
107- private void visitFragmentSpread (QueryVisitor visitor , FragmentSpread fragmentSpread , QueryVisitorEnvironment parent , boolean preOrder ) {
108- if (!conditionalNodes .shouldInclude (this .variables , fragmentSpread .getDirectives ())) {
109- return ;
118+ private class NodeVisitorImpl extends NodeVisitorStub {
119+
120+ final FieldVisitor preOrderCallback ;
121+ final FieldVisitor postOrderCallback ;
122+
123+ NodeVisitorImpl (FieldVisitor preOrderCallback , FieldVisitor postOrderCallback ) {
124+ this .preOrderCallback = preOrderCallback ;
125+ this .postOrderCallback = postOrderCallback ;
110126 }
111- FragmentDefinition fragmentDefinition = fragmentsByName .get (fragmentSpread .getName ());
112127
113- if (!conditionalNodes .shouldInclude (variables , fragmentDefinition .getDirectives ())) {
114- return ;
128+ @ Override
129+ public TraversalControl visitInlineFragment (InlineFragment inlineFragment , TraverserContext <Node > context ) {
130+ if (context .getVar (LeaveOrEnter .class ) == LEAVE ) {
131+ return TraversalControl .CONTINUE ;
132+ }
133+ if (!conditionalNodes .shouldInclude (variables , inlineFragment .getDirectives ()))
134+ return TraversalControl .ABORT ;
135+
136+ // inline fragments are allowed not have type conditions, if so the parent type counts
137+ QueryTraversalContext parentEnv = context
138+ .getParentContext ()
139+ .getVar (QueryTraversalContext .class );
140+
141+ GraphQLCompositeType fragmentCondition ;
142+ if (inlineFragment .getTypeCondition () != null ) {
143+ TypeName typeCondition = inlineFragment .getTypeCondition ();
144+ fragmentCondition = (GraphQLCompositeType ) schema .getType (typeCondition .getName ());
145+ } else {
146+ fragmentCondition = parentEnv .getType ();
147+ }
148+ // for unions we only have other fragments inside
149+ context .setVar (QueryTraversalContext .class , new QueryTraversalContext (fragmentCondition , parentEnv .getEnvironment ()));
150+ return TraversalControl .CONTINUE ;
115151 }
116- GraphQLCompositeType typeCondition = (GraphQLCompositeType ) schema .getType (fragmentDefinition .getTypeCondition ().getName ());
117- visitImpl (visitor , fragmentDefinition .getSelectionSet (), typeCondition , parent , preOrder );
118- }
119152
153+ @ Override
154+ public TraversalControl visitFragmentSpread (FragmentSpread fragmentSpread , TraverserContext <Node > context ) {
155+ if (context .getVar (LeaveOrEnter .class ) == LEAVE ) {
156+ return TraversalControl .CONTINUE ;
157+ }
158+ if (!conditionalNodes .shouldInclude (variables , fragmentSpread .getDirectives ()))
159+ return TraversalControl .ABORT ;
120160
121- private void visitInlineFragment (QueryVisitor visitor , InlineFragment inlineFragment , GraphQLCompositeType parentType , QueryVisitorEnvironment parent , boolean preOrder ) {
122- if (!conditionalNodes .shouldInclude (variables , inlineFragment .getDirectives ())) {
123- return ;
124- }
125- // inline fragments are allowed not have type conditions, if so the parent type counts
126- GraphQLCompositeType fragmentCondition ;
127- if (inlineFragment .getTypeCondition () != null ) {
128- TypeName typeCondition = inlineFragment .getTypeCondition ();
129- fragmentCondition = (GraphQLCompositeType ) schema .getType (typeCondition .getName ());
130- } else {
131- fragmentCondition = parentType ;
132- }
133- // for unions we only have other fragments inside
134- visitImpl (visitor , inlineFragment .getSelectionSet (), fragmentCondition , parent , preOrder );
135- }
161+ FragmentDefinition fragmentDefinition = fragmentsByName .get (fragmentSpread .getName ());
162+ if (!conditionalNodes .shouldInclude (variables , fragmentDefinition .getDirectives ()))
163+ return TraversalControl .ABORT ;
136164
137- private void visitField (QueryVisitor visitor , Field field , GraphQLFieldDefinition fieldDefinition , GraphQLCompositeType parentType , QueryVisitorEnvironment parentEnv , boolean preOrder ) {
138- if (!conditionalNodes .shouldInclude (variables , field .getDirectives ())) {
139- return ;
140- }
141- Map <String , Object > argumentValues = valuesResolver .getArgumentValues (schema .getFieldVisibility (), fieldDefinition .getArguments (), field .getArguments (), variables );
142- if (preOrder ) {
143- visitor .visitField (new QueryVisitorEnvironment (field , fieldDefinition , parentType , parentEnv , argumentValues ));
144- }
145- GraphQLUnmodifiedType unmodifiedType = schemaUtil .getUnmodifiedType (fieldDefinition .getType ());
146- if (unmodifiedType instanceof GraphQLCompositeType ) {
147- QueryVisitorEnvironment newParentEnvironment = new QueryVisitorEnvironment (field , fieldDefinition , parentType , parentEnv , argumentValues );
148- visitImpl (visitor , field .getSelectionSet (), (GraphQLCompositeType ) unmodifiedType , newParentEnvironment , preOrder );
149- }
150- if (!preOrder ) {
151- visitor .visitField (new QueryVisitorEnvironment (field , fieldDefinition , parentType , parentEnv , argumentValues ));
165+ QueryTraversalContext parentEnv = context
166+ .getParentContext ()
167+ .getVar (QueryTraversalContext .class );
168+
169+ GraphQLCompositeType typeCondition = (GraphQLCompositeType ) schema .getType (fragmentDefinition .getTypeCondition ().getName ());
170+
171+ context
172+ .setVar (QueryTraversalContext .class , new QueryTraversalContext (typeCondition , parentEnv .getEnvironment ()));
173+ return TraversalControl .CONTINUE ;
152174 }
153175
154- }
176+ @ Override
177+ public TraversalControl visitField (Field field , TraverserContext <Node > context ) {
178+ QueryTraversalContext parentEnv = context
179+ .getParentContext ()
180+ .getVar (QueryTraversalContext .class );
181+
182+ GraphQLFieldDefinition fieldDefinition = Introspection .getFieldDef (schema , parentEnv .getType (), field .getName ());
183+ Map <String , Object > argumentValues = valuesResolver .getArgumentValues (schema .getFieldVisibility (), fieldDefinition .getArguments (), field .getArguments (), variables );
184+ QueryVisitorEnvironment environment = new QueryVisitorEnvironment (field , fieldDefinition , parentEnv .getType (), parentEnv .getEnvironment (), argumentValues );
155185
186+ LeaveOrEnter leaveOrEnter = context .getVar (LeaveOrEnter .class );
187+ if (leaveOrEnter == LEAVE ) {
188+ postOrderCallback .visitField (environment );
189+ return TraversalControl .CONTINUE ;
190+ }
191+
192+ if (!conditionalNodes .shouldInclude (variables , field .getDirectives ()))
193+ return TraversalControl .ABORT ;
194+
195+ preOrderCallback .visitField (environment );
196+
197+ GraphQLUnmodifiedType unmodifiedType = schemaUtil .getUnmodifiedType (fieldDefinition .getType ());
198+ QueryTraversalContext fieldEnv = (unmodifiedType instanceof GraphQLCompositeType )
199+ ? new QueryTraversalContext ((GraphQLCompositeType ) unmodifiedType , environment )
200+ : new QueryTraversalContext (null , environment );// Terminal (scalar) node, EMPTY FRAME
156201
202+
203+ context .setVar (QueryTraversalContext .class , fieldEnv );
204+ return TraversalControl .CONTINUE ;
205+ }
206+
207+ }
157208}
0 commit comments