Skip to content

Commit b3184ad

Browse files
committed
Support for include and skip directive with references
1 parent 1409d7f commit b3184ad

File tree

4 files changed

+396
-18
lines changed

4 files changed

+396
-18
lines changed

src/main/java/com/intuit/graphql/orchestrator/batch/AuthDownstreamQueryModifier.java

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,5 @@
11
package com.intuit.graphql.orchestrator.batch;
22

3-
import static com.intuit.graphql.orchestrator.resolverdirective.FieldResolverDirectiveUtil.hasResolverDirective;
4-
import static com.intuit.graphql.orchestrator.utils.QueryPathUtils.getNodesAsPathList;
5-
import static com.intuit.graphql.orchestrator.utils.QueryPathUtils.pathListToFQN;
6-
import static com.intuit.graphql.orchestrator.utils.RenameDirectiveUtil.convertGraphqlFieldWithOriginalName;
7-
import static com.intuit.graphql.orchestrator.utils.RenameDirectiveUtil.getRenameKey;
8-
import static graphql.introspection.Introspection.TypeNameMetaFieldDef;
9-
import static graphql.schema.FieldCoordinates.coordinates;
10-
import static graphql.util.TreeTransformerUtil.changeNode;
11-
import static graphql.util.TreeTransformerUtil.deleteNode;
12-
import static java.util.Objects.nonNull;
13-
import static java.util.Objects.requireNonNull;
14-
153
import com.intuit.graphql.orchestrator.authorization.FieldAuthorization;
164
import com.intuit.graphql.orchestrator.authorization.FieldAuthorizationEnvironment;
175
import com.intuit.graphql.orchestrator.authorization.FieldAuthorizationResult;
@@ -40,17 +28,31 @@
4028
import graphql.schema.GraphQLUnionType;
4129
import graphql.util.TraversalControl;
4230
import graphql.util.TraverserContext;
31+
import lombok.Builder;
32+
import lombok.NonNull;
33+
import org.apache.commons.collections4.CollectionUtils;
34+
import org.apache.commons.collections4.MapUtils;
35+
4336
import java.util.ArrayList;
4437
import java.util.Collections;
4538
import java.util.List;
4639
import java.util.Map;
4740
import java.util.Objects;
4841
import java.util.Set;
4942
import java.util.stream.Collectors;
50-
import lombok.Builder;
51-
import lombok.NonNull;
52-
import org.apache.commons.collections4.CollectionUtils;
53-
import org.apache.commons.collections4.MapUtils;
43+
44+
import static com.intuit.graphql.orchestrator.resolverdirective.FieldResolverDirectiveUtil.hasResolverDirective;
45+
import static com.intuit.graphql.orchestrator.utils.QueryDirectivesUtil.shouldIgnoreNode;
46+
import static com.intuit.graphql.orchestrator.utils.QueryPathUtils.getNodesAsPathList;
47+
import static com.intuit.graphql.orchestrator.utils.QueryPathUtils.pathListToFQN;
48+
import static com.intuit.graphql.orchestrator.utils.RenameDirectiveUtil.convertGraphqlFieldWithOriginalName;
49+
import static com.intuit.graphql.orchestrator.utils.RenameDirectiveUtil.getRenameKey;
50+
import static graphql.introspection.Introspection.TypeNameMetaFieldDef;
51+
import static graphql.schema.FieldCoordinates.coordinates;
52+
import static graphql.util.TreeTransformerUtil.changeNode;
53+
import static graphql.util.TreeTransformerUtil.deleteNode;
54+
import static java.util.Objects.nonNull;
55+
import static java.util.Objects.requireNonNull;
5456

5557
/**
5658
* This class modifies for query for a downstream provider.
@@ -91,6 +93,12 @@ public TraversalControl visitField(Field node, TraverserContext<Node> context) {
9193
requireNonNull(fieldDefinition, "Failed to get Field Definition for " + node.getName());
9294

9395
context.setVar(GraphQLType.class, fieldDefinition.getType());
96+
97+
if(shouldIgnoreNode(node, this.queryVariables)) {
98+
decreaseParentSelectionSetCount(context.getParentContext());
99+
return deleteNode(context);
100+
}
101+
94102
FieldAuthorizationResult fieldAuthorizationResult = authorize(node, fieldDefinition, parentType, context);
95103
if (!fieldAuthorizationResult.isAllowed()) {
96104
decreaseParentSelectionSetCount(context.getParentContext());
@@ -112,8 +120,10 @@ public TraversalControl visitField(Field node, TraverserContext<Node> context) {
112120
GraphQLFieldDefinition fieldDefinition = getFieldDefinition(node.getName(), parentType);
113121
requireNonNull(fieldDefinition, "Failed to get Field Definition for " + node.getName());
114122

115-
if (serviceMetadata.shouldModifyDownStreamQuery() && (hasResolverDirective(fieldDefinition)
116-
|| isExternalField(parentType.getName(), node.getName()))) {
123+
boolean shouldRemoveNode = (serviceMetadata.shouldModifyDownStreamQuery() && (hasResolverDirective(fieldDefinition)
124+
|| isExternalField(parentType.getName(), node.getName())))
125+
|| shouldIgnoreNode(node, this.queryVariables);
126+
if (shouldRemoveNode) {
117127
decreaseParentSelectionSetCount(context.getParentContext());
118128
return deleteNode(context);
119129
} else {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.intuit.graphql.orchestrator.utils;
2+
3+
import graphql.language.Argument;
4+
import graphql.language.BooleanValue;
5+
import graphql.language.Directive;
6+
import graphql.language.Field;
7+
import graphql.language.Value;
8+
import graphql.language.VariableReference;
9+
10+
import java.util.Map;
11+
import java.util.Optional;
12+
13+
public class QueryDirectivesUtil {
14+
15+
public static boolean shouldIgnoreNode(Field node, Map<String, Object> queryVariables) {
16+
Optional<Directive> optionalIncludesDir = node.getDirectives("include").stream().findFirst();
17+
Optional<Directive> optionalSkipDir = node.getDirectives("skip").stream().findFirst();
18+
if(optionalIncludesDir.isPresent() || optionalSkipDir.isPresent()) {
19+
if(optionalIncludesDir.isPresent() && (!getIfValue(optionalIncludesDir.get(), queryVariables))) {
20+
return true;
21+
}
22+
return optionalSkipDir.isPresent() && (getIfValue(optionalSkipDir.get(), queryVariables));
23+
}
24+
25+
return false;
26+
}
27+
28+
private static boolean getIfValue(Directive directive, Map<String, Object> queryVariables){
29+
Argument ifArg = directive.getArgument("if");
30+
Value ifValue = ifArg.getValue();
31+
32+
boolean defaultValue = directive.getName().equals("skip");
33+
34+
if(ifValue instanceof VariableReference) {
35+
String variableRefName = ((VariableReference) ifValue).getName();
36+
return (boolean) queryVariables.getOrDefault(variableRefName, defaultValue);
37+
} else if(ifValue instanceof BooleanValue) {
38+
return ((BooleanValue) ifValue).isValue();
39+
}
40+
return false;
41+
}
42+
}

src/test/groovy/com/intuit/graphql/orchestrator/authorization/AuthDownstreamQueryRedactorVisitorSpec.groovy

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,34 @@ class AuthDownstreamQueryRedactorVisitorSpec extends Specification {
5454
}
5555
"""
5656

57+
def skipQuery = """ query skipQuery(\$shouldSkip: Boolean) {
58+
a {
59+
b1 @skip(if: \$shouldSkip) {
60+
c1 {
61+
s1
62+
}
63+
}
64+
b2 {
65+
i1
66+
}
67+
}
68+
}
69+
"""
70+
71+
def includesQuery = """ query includesQuery(\$shouldInclude: Boolean) {
72+
a {
73+
b1 {
74+
c1 {
75+
s1
76+
}
77+
}
78+
b2 @include(if: \$shouldInclude) {
79+
i1
80+
}
81+
}
82+
}
83+
"""
84+
5785
static final Object TEST_AUTH_DATA = "TestAuthDataCanBeAnyObject"
5886

5987
Field mockField = Mock()
@@ -134,6 +162,171 @@ class AuthDownstreamQueryRedactorVisitorSpec extends Specification {
134162
argumentValueResolver.resolve(_, _, _) >> Collections.emptyMap()
135163
}
136164

165+
def "skip query with skip directive true removes selection set"() {
166+
given:
167+
Document document = new Parser().parseDocument(skipQuery)
168+
OperationDefinition operationDefinition = document.getDefinitionsOfType(OperationDefinition.class).get(0)
169+
Field rootField = SelectionSetUtil.getFieldByPath(Arrays.asList("a"), operationDefinition.getSelectionSet())
170+
GraphQLFieldsContainer rootFieldParentType = (GraphQLFieldsContainer) testGraphQLSchema.getType("Query")
171+
172+
Map<String, Object> queryVariables = new HashMap<>()
173+
queryVariables.put("shouldSkip", true)
174+
175+
AuthDownstreamQueryModifier specUnderTest = AuthDownstreamQueryModifier.builder()
176+
.rootParentType((GraphQLFieldsContainer) rootFieldParentType)
177+
.fieldAuthorization(mockFieldAuthorization)
178+
.graphQLContext(mockGraphQLContext)
179+
.queryVariables(queryVariables)
180+
.graphQLSchema(testGraphQLSchema)
181+
.selectionCollector(new SelectionCollector(fragmentsByName))
182+
.serviceMetadata(mockServiceMetadata)
183+
.authData(TEST_AUTH_DATA)
184+
.build()
185+
186+
when:
187+
Field transformedField = (Field) astTransformer.transform(rootField, specUnderTest)
188+
189+
then:
190+
transformedField.getName() == "a"
191+
Object[] selectionSet = transformedField.getSelectionSet()
192+
.getSelections()
193+
.asList()
194+
selectionSet.size() == 1
195+
((Field)selectionSet.first()).getName() == ("b2")
196+
197+
1 * mockFieldAuthorization.authorize(queryA) >> FieldAuthorizationResult.ALLOWED_FIELD_AUTH_RESULT
198+
1 * mockFieldAuthorization.authorize(aB2) >> FieldAuthorizationResult.ALLOWED_FIELD_AUTH_RESULT
199+
1 * mockFieldAuthorization.authorize(b2i1) >> FieldAuthorizationResult.ALLOWED_FIELD_AUTH_RESULT
200+
mockRenamedMetadata.getOriginalFieldNamesByRenamedName() >> Collections.emptyMap()
201+
mockServiceMetadata.getRenamedMetadata() >> mockRenamedMetadata
202+
}
203+
204+
def "skip query with skip directive false keeps selection set"() {
205+
given:
206+
Document document = new Parser().parseDocument(skipQuery)
207+
OperationDefinition operationDefinition = document.getDefinitionsOfType(OperationDefinition.class).get(0)
208+
Field rootField = SelectionSetUtil.getFieldByPath(Arrays.asList("a"), operationDefinition.getSelectionSet())
209+
GraphQLFieldsContainer rootFieldParentType = (GraphQLFieldsContainer) testGraphQLSchema.getType("Query")
210+
211+
Map<String, Object> queryVariables = new HashMap<>()
212+
queryVariables.put("shouldSkip", false)
213+
214+
AuthDownstreamQueryModifier specUnderTest = AuthDownstreamQueryModifier.builder()
215+
.rootParentType((GraphQLFieldsContainer) rootFieldParentType)
216+
.fieldAuthorization(mockFieldAuthorization)
217+
.graphQLContext(mockGraphQLContext)
218+
.queryVariables(queryVariables)
219+
.graphQLSchema(testGraphQLSchema)
220+
.selectionCollector(new SelectionCollector(fragmentsByName))
221+
.serviceMetadata(mockServiceMetadata)
222+
.authData(TEST_AUTH_DATA)
223+
.build()
224+
225+
when:
226+
Field transformedField = (Field) astTransformer.transform(rootField, specUnderTest)
227+
228+
then:
229+
transformedField.getName() == "a"
230+
Object[] selectionSet = transformedField.getSelectionSet()
231+
.getSelections()
232+
.asList()
233+
selectionSet.size() == 2
234+
((Field)selectionSet[0]).getName() == "b1"
235+
((Field)selectionSet[1]).getName() == "b2"
236+
237+
1 * mockFieldAuthorization.authorize(queryA) >> FieldAuthorizationResult.ALLOWED_FIELD_AUTH_RESULT
238+
1 * mockFieldAuthorization.authorize(aB1) >> FieldAuthorizationResult.ALLOWED_FIELD_AUTH_RESULT
239+
1 * mockFieldAuthorization.authorize(b1C1) >> FieldAuthorizationResult.ALLOWED_FIELD_AUTH_RESULT
240+
1 * mockFieldAuthorization.authorize(c1S1) >> FieldAuthorizationResult.ALLOWED_FIELD_AUTH_RESULT
241+
1 * mockFieldAuthorization.authorize(aB2) >> FieldAuthorizationResult.ALLOWED_FIELD_AUTH_RESULT
242+
1 * mockFieldAuthorization.authorize(b2i1) >> FieldAuthorizationResult.ALLOWED_FIELD_AUTH_RESULT
243+
mockRenamedMetadata.getOriginalFieldNamesByRenamedName() >> Collections.emptyMap()
244+
mockServiceMetadata.getRenamedMetadata() >> mockRenamedMetadata
245+
}
246+
247+
def "includes query with include directive true keeps selection set"() {
248+
given:
249+
Document document = new Parser().parseDocument(includesQuery)
250+
OperationDefinition operationDefinition = document.getDefinitionsOfType(OperationDefinition.class).get(0)
251+
Field rootField = SelectionSetUtil.getFieldByPath(Arrays.asList("a"), operationDefinition.getSelectionSet())
252+
GraphQLFieldsContainer rootFieldParentType = (GraphQLFieldsContainer) testGraphQLSchema.getType("Query")
253+
254+
Map<String, Object> queryVariables = new HashMap<>()
255+
queryVariables.put("shouldInclude", true)
256+
257+
AuthDownstreamQueryModifier specUnderTest = AuthDownstreamQueryModifier.builder()
258+
.rootParentType((GraphQLFieldsContainer) rootFieldParentType)
259+
.fieldAuthorization(mockFieldAuthorization)
260+
.graphQLContext(mockGraphQLContext)
261+
.queryVariables(queryVariables)
262+
.graphQLSchema(testGraphQLSchema)
263+
.selectionCollector(new SelectionCollector(fragmentsByName))
264+
.serviceMetadata(mockServiceMetadata)
265+
.authData(TEST_AUTH_DATA)
266+
.build()
267+
268+
when:
269+
Field transformedField = (Field) astTransformer.transform(rootField, specUnderTest)
270+
271+
then:
272+
transformedField.getName() == "a"
273+
Object[] selectionSet = transformedField.getSelectionSet()
274+
.getSelections()
275+
.asList()
276+
selectionSet.size() == 2
277+
((Field)selectionSet[0]).getName() == "b1"
278+
((Field)selectionSet[1]).getName() == "b2"
279+
280+
1 * mockFieldAuthorization.authorize(queryA) >> FieldAuthorizationResult.ALLOWED_FIELD_AUTH_RESULT
281+
1 * mockFieldAuthorization.authorize(aB1) >> FieldAuthorizationResult.ALLOWED_FIELD_AUTH_RESULT
282+
1 * mockFieldAuthorization.authorize(b1C1) >> FieldAuthorizationResult.ALLOWED_FIELD_AUTH_RESULT
283+
1 * mockFieldAuthorization.authorize(c1S1) >> FieldAuthorizationResult.ALLOWED_FIELD_AUTH_RESULT
284+
1 * mockFieldAuthorization.authorize(aB2) >> FieldAuthorizationResult.ALLOWED_FIELD_AUTH_RESULT
285+
1 * mockFieldAuthorization.authorize(b2i1) >> FieldAuthorizationResult.ALLOWED_FIELD_AUTH_RESULT
286+
mockRenamedMetadata.getOriginalFieldNamesByRenamedName() >> Collections.emptyMap()
287+
mockServiceMetadata.getRenamedMetadata() >> mockRenamedMetadata
288+
}
289+
290+
def "includes query with include directive false removes selection set"() {
291+
given:
292+
Document document = new Parser().parseDocument(includesQuery)
293+
OperationDefinition operationDefinition = document.getDefinitionsOfType(OperationDefinition.class).get(0)
294+
Field rootField = SelectionSetUtil.getFieldByPath(Arrays.asList("a"), operationDefinition.getSelectionSet())
295+
GraphQLFieldsContainer rootFieldParentType = (GraphQLFieldsContainer) testGraphQLSchema.getType("Query")
296+
297+
Map<String, Object> queryVariables = new HashMap<>()
298+
queryVariables.put("shouldInclude", false)
299+
300+
AuthDownstreamQueryModifier specUnderTest = AuthDownstreamQueryModifier.builder()
301+
.rootParentType((GraphQLFieldsContainer) rootFieldParentType)
302+
.fieldAuthorization(mockFieldAuthorization)
303+
.graphQLContext(mockGraphQLContext)
304+
.queryVariables(queryVariables)
305+
.graphQLSchema(testGraphQLSchema)
306+
.selectionCollector(new SelectionCollector(fragmentsByName))
307+
.serviceMetadata(mockServiceMetadata)
308+
.authData(TEST_AUTH_DATA)
309+
.build()
310+
311+
when:
312+
Field transformedField = (Field) astTransformer.transform(rootField, specUnderTest)
313+
314+
then:
315+
transformedField.getName() == "a"
316+
Object[] selectionSet = transformedField.getSelectionSet()
317+
.getSelections()
318+
.asList()
319+
selectionSet.size() == 1
320+
((Field)selectionSet[0]).getName() == "b1"
321+
322+
1 * mockFieldAuthorization.authorize(queryA) >> FieldAuthorizationResult.ALLOWED_FIELD_AUTH_RESULT
323+
1 * mockFieldAuthorization.authorize(aB1) >> FieldAuthorizationResult.ALLOWED_FIELD_AUTH_RESULT
324+
1 * mockFieldAuthorization.authorize(b1C1) >> FieldAuthorizationResult.ALLOWED_FIELD_AUTH_RESULT
325+
1 * mockFieldAuthorization.authorize(c1S1) >> FieldAuthorizationResult.ALLOWED_FIELD_AUTH_RESULT
326+
mockRenamedMetadata.getOriginalFieldNamesByRenamedName() >> Collections.emptyMap()
327+
mockServiceMetadata.getRenamedMetadata() >> mockRenamedMetadata
328+
}
329+
137330
def "redact query, results to empty selection set"() {
138331
given:
139332

0 commit comments

Comments
 (0)