Skip to content

Commit 7a0900e

Browse files
committed
add defer support for inline fragments and fragment spreads
1 parent 35c0a6b commit 7a0900e

File tree

9 files changed

+756
-88
lines changed

9 files changed

+756
-88
lines changed

src/main/java/com/intuit/graphql/orchestrator/authorization/DownstreamQueryRedactor.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
import graphql.language.Node;
99
import graphql.schema.DataFetchingEnvironment;
1010
import graphql.schema.GraphQLType;
11-
import java.util.Map;
1211
import lombok.Builder;
1312
import lombok.NonNull;
1413

14+
import java.util.Map;
15+
1516
@Builder
1617
public class DownstreamQueryRedactor {
1718

@@ -31,7 +32,9 @@ public DownstreamQueryRedactorResult redact() {
3132
return new DownstreamQueryRedactorResult(
3233
transformedRoot,
3334
downstreamQueryModifier.getDeclineFieldErrors(),
34-
downstreamQueryModifier.redactedQueryHasEmptySelectionSet());
35+
downstreamQueryModifier.redactedQueryHasEmptySelectionSet(),
36+
downstreamQueryModifier.getFragmentSpreadsRemoved()
37+
);
3538
}
3639

3740
private AuthDownstreamQueryModifier createQueryModifier() {

src/main/java/com/intuit/graphql/orchestrator/authorization/DownstreamQueryRedactorResult.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
import graphql.GraphqlErrorException;
44
import graphql.language.Node;
5-
import java.util.List;
65
import lombok.AllArgsConstructor;
76
import lombok.Getter;
87
import lombok.NonNull;
98

9+
import java.util.List;
10+
1011
@Getter
1112
@AllArgsConstructor
1213
public class DownstreamQueryRedactorResult {
@@ -17,4 +18,7 @@ public class DownstreamQueryRedactorResult {
1718
private List<GraphqlErrorException> errors;
1819

1920
boolean hasEmptySelectionSet;
21+
22+
@NonNull
23+
private List<String> fragmentSpreadsRemoved;
2024
}

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

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@
1111
import com.intuit.graphql.orchestrator.schema.transform.FieldResolverContext;
1212
import com.intuit.graphql.orchestrator.utils.SelectionCollector;
1313
import graphql.GraphQLContext;
14+
import graphql.GraphQLException;
1415
import graphql.GraphqlErrorException;
1516
import graphql.language.Argument;
1617
import graphql.language.BooleanValue;
1718
import graphql.language.Directive;
1819
import graphql.language.Field;
1920
import graphql.language.FragmentDefinition;
21+
import graphql.language.FragmentSpread;
2022
import graphql.language.InlineFragment;
2123
import graphql.language.Node;
2224
import graphql.language.NodeVisitorStub;
@@ -74,6 +76,7 @@ public class AuthDownstreamQueryModifier extends NodeVisitorStub {
7476
private static final ArgumentValueResolver ARGUMENT_VALUE_RESOLVER = new ArgumentValueResolver(); // thread-safe
7577
private final List<SelectionSetMetadata> processedSelectionSetMetadata = new ArrayList<>();
7678
private final List<GraphqlErrorException> declinedFieldsErrors = new ArrayList<>();
79+
private final List<String> fragmentSpreadsRemoved = new ArrayList<>();
7780

7881
private boolean hasEmptySelectionSet;
7982

@@ -105,20 +108,9 @@ public TraversalControl visitField(Field node, TraverserContext<Node> context) {
105108
return deleteNode(context);
106109
}
107110

108-
if(!node.getDirectives(DEFER_DIRECTIVE_NAME).isEmpty()) {
109-
Argument deferArg = node.getDirectives(DEFER_DIRECTIVE_NAME).get(0).getArgument(DEFER_IF_ARG);
110-
if(graphQLContext.getOrDefault(USE_DEFER, false) && (deferArg == null || ((BooleanValue)deferArg.getValue()).isValue())) {
111-
decreaseParentSelectionSetCount(context.getParentContext());
112-
return deleteNode(context);
113-
} else {
114-
//remove directive from query since directive is not built in and will fail downstream if added
115-
List<Directive> directives = node.getDirectives()
116-
.stream()
117-
.filter(directive -> !DEFER_DIRECTIVE_NAME.equals(directive.getName()))
118-
.collect(Collectors.toList());
119-
120-
return changeNode(context, node.transform(builder -> builder.directives(directives)));
121-
}
111+
List<Directive> directives = node.getDirectives();
112+
if(containsDeferDirective(directives)) {
113+
return pruneDeferInfo(node, context, directives);
122114
}
123115

124116
if(!serviceMetadata.getRenamedMetadata().getOriginalFieldNamesByRenamedName().isEmpty()) {
@@ -232,6 +224,24 @@ public TraversalControl visitFragmentDefinition(
232224
public TraversalControl visitInlineFragment(InlineFragment node, TraverserContext<Node> context) {
233225
String typeName = node.getTypeCondition().getName();
234226
context.setVar(GraphQLType.class, this.graphQLSchema.getType(typeName));
227+
228+
List<Directive> directives = node.getDirectives();
229+
if(containsDeferDirective(directives)) {
230+
return pruneDeferInfo(node, context, directives);
231+
}
232+
233+
return TraversalControl.CONTINUE;
234+
}
235+
236+
@Override
237+
public TraversalControl visitFragmentSpread(FragmentSpread node, TraverserContext<Node> context) {
238+
context.setVar(GraphQLType.class, this.graphQLSchema.getType(node.getName()));
239+
240+
List<Directive> directives = node.getDirectives();
241+
if(containsDeferDirective(directives)) {
242+
return pruneDeferInfo(node, context, directives);
243+
}
244+
235245
return TraversalControl.CONTINUE;
236246
}
237247

@@ -319,10 +329,52 @@ private GraphQLType getParentType(TraverserContext<Node> context) {
319329
return GraphQLTypeUtil.unwrapAll(parentType);
320330
}
321331

332+
private boolean containsDeferDirective(List<Directive> directives) {
333+
return directives != null && directives.stream()
334+
.anyMatch(directive -> DEFER_DIRECTIVE_NAME.equals(directive.getName()));
335+
}
336+
337+
private TraversalControl pruneDeferInfo(Node node, TraverserContext<Node> context, List<Directive> nodeDirectives) {
338+
Directive deferDirective = nodeDirectives
339+
.stream()
340+
.filter(directive -> DEFER_DIRECTIVE_NAME.equals(directive.getName()))
341+
.findFirst()
342+
.get();
343+
344+
Argument deferArg = deferDirective.getArgument(DEFER_IF_ARG);
345+
if(graphQLContext.getOrDefault(USE_DEFER, false) && (deferArg == null || ((BooleanValue)deferArg.getValue()).isValue())) {
346+
decreaseParentSelectionSetCount(context.getParentContext());
347+
if(node instanceof FragmentSpread) {
348+
this.fragmentSpreadsRemoved.add(((FragmentSpread)node).getName());
349+
}
350+
351+
return deleteNode(context);
352+
} else {
353+
final List<Directive> directives = nodeDirectives
354+
.stream()
355+
.filter(directive -> !DEFER_DIRECTIVE_NAME.equals(directive.getName()))
356+
.collect(Collectors.toList());
357+
//remove directive from query since directive is not built in and will fail downstream if added
358+
if(node instanceof Field) {
359+
return changeNode(context, ((Field)node).transform(builder -> builder.directives(directives)));
360+
} else if(node instanceof InlineFragment) {
361+
return changeNode(context, ((InlineFragment)node).transform(builder -> builder.directives(directives)));
362+
} else if(node instanceof FragmentSpread) {
363+
return changeNode(context, ((FragmentSpread)node).transform(builder -> builder.directives(directives)));
364+
} else {
365+
throw new GraphQLException("Not Supported Defer Location.");
366+
}
367+
}
368+
}
369+
322370
public List<GraphqlErrorException> getDeclineFieldErrors() {
323371
return declinedFieldsErrors;
324372
}
325373

374+
public List<String> getFragmentSpreadsRemoved() {
375+
return this.fragmentSpreadsRemoved;
376+
}
377+
326378
public List<SelectionSetMetadata> getEmptySelectionSets() {
327379
return this.processedSelectionSetMetadata.stream()
328380
.filter(selectionSetMetadata -> selectionSetMetadata.getRemainingSelectionsCount() == 0)

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import java.util.Collection;
4343
import java.util.Collections;
4444
import java.util.HashMap;
45+
import java.util.HashSet;
4546
import java.util.List;
4647
import java.util.Map;
4748
import java.util.Objects;
@@ -117,15 +118,17 @@ private CompletableFuture<List<DataFetcherResult<Object>>> load(List<DataFetchin
117118

118119
Map<String, FragmentDefinition> mergedFragmentDefinitions = new HashMap<>();
119120

121+
//Possibly add a complex object to hold these two
120122
MultiValuedMap<String, GraphqlErrorException> queryRedactErrorsByKey = new ArrayListValuedHashMap<>();
123+
Set<String> removedFragments = new HashSet<>();
121124

122125
for (final DataFetchingEnvironment key : keys) {
123126
MergedFieldModifierResult result = new MergedFieldModifier(key).getFilteredRootField();
124127
MergedField filteredRootField = result.getMergedField();
125128
if (filteredRootField != null) {
126129
filteredRootField.getFields().stream()
127130
.map(field -> removeFieldsWithExternalTypes(field,
128-
operationObjectType, key, authData, fieldAuthorization, queryRedactErrorsByKey, context))
131+
operationObjectType, key, authData, fieldAuthorization, queryRedactErrorsByKey, removedFragments, context))
129132
.filter(Objects::nonNull) // denied access or has an empty selectionSet
130133
.forEach(selectionSetBuilder::selection);
131134
}
@@ -195,6 +198,7 @@ private CompletableFuture<List<DataFetcherResult<Object>>> load(List<DataFetchin
195198
.collect(Collectors.toList());
196199

197200
List<Definition> fragmentsAsDefinitions = mergedFragmentDefinitions.values().stream()
201+
.filter(fragment -> !removedFragments.contains(fragment.getName()))
198202
.map(GraphQLObjects::<Definition>cast).collect(Collectors.toList());
199203

200204
OperationDefinition query = OperationDefinition.newOperationDefinition()
@@ -229,6 +233,7 @@ private CompletableFuture<List<DataFetcherResult<Object>>> load(List<DataFetchin
229233
query = queryOperationModifier.modifyQuery(graphQLSchema, query, mergedFragmentDefinitions, filteredVariables);
230234
}
231235

236+
232237
return execute(context, query, filteredVariables, fragmentsAsDefinitions)
233238
.thenApply(queryResponseModifier::modify)
234239
.thenApply(result -> batchResultTransformer.toBatchResult(result, keys))
@@ -370,7 +375,7 @@ private FragmentDefinition removeDomainTypeFromFragment(final FragmentDefinition
370375

371376
private Field removeFieldsWithExternalTypes(Field origField, GraphQLObjectType operationObjectType,
372377
DataFetchingEnvironment dfe, Object authData, FieldAuthorization fieldAuthorization,
373-
final MultiValuedMap<String, GraphqlErrorException> queryRedactErrorsByKey, GraphQLContext context) {
378+
final MultiValuedMap<String, GraphqlErrorException> queryRedactErrorsByKey, Set<String> removedFragments, GraphQLContext context) {
374379

375380
if (serviceMetadata.shouldModifyDownStreamQuery() ||
376381
!(fieldAuthorization instanceof DefaultFieldAuthorization) ||
@@ -391,6 +396,10 @@ private Field removeFieldsWithExternalTypes(Field origField, GraphQLObjectType o
391396
String keyPath = dfe.getExecutionStepInfo().getPath().toString();
392397
queryRedactErrorsByKey.putAll(keyPath, redactResult.getErrors());
393398
}
399+
if (CollectionUtils.isNotEmpty(redactResult.getFragmentSpreadsRemoved())) {
400+
removedFragments.addAll(redactResult.getFragmentSpreadsRemoved());
401+
}
402+
394403
if (redactResult.isHasEmptySelectionSet()) {
395404
return null; // if null, not added to selection
396405
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.intuit.graphql.orchestrator.deferDirective;
2+
3+
import graphql.language.OperationDefinition;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
import lombok.Singular;
7+
8+
import java.util.Set;
9+
10+
@Getter
11+
@Builder
12+
public class MultipartQueryRequest {
13+
private OperationDefinition multipartOperationDef;
14+
@Singular
15+
private Set<String> fragmentSpreadNames;
16+
}

src/main/java/com/intuit/graphql/orchestrator/utils/DirectivesUtil.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,18 @@ private static GraphQLDirective createDeferDirective() {
8787
.defaultValueLiteral(BooleanValue.newBooleanValue(true).build())
8888
.build();
8989

90+
GraphQLArgument labelArgument = GraphQLArgument.newArgument()
91+
.name(DEFER_IF_ARG)
92+
.type(Scalars.GraphQLString)
93+
.build();
94+
9095
return GraphQLDirective.newDirective()
9196
.name(DEFER_DIRECTIVE_NAME)
9297
.validLocation(DirectiveLocation.FIELD)
9398
.validLocation(DirectiveLocation.INLINE_FRAGMENT)
9499
.validLocation(DirectiveLocation.FRAGMENT_SPREAD)
95100
.argument(ifArgument)
101+
.argument(labelArgument)
96102
.build();
97103
}
98104

0 commit comments

Comments
 (0)