From fc6f304e66cab18b6d06a80c7009524938939a03 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Fri, 22 Mar 2024 17:02:47 +1100 Subject: [PATCH 1/4] Cherry pick ENF introspection and make ExecutableNormalizedOperationFactory closer to structure on master --- .../graphql/execution/ExecutionContext.java | 2 +- .../introspection/GoodFaithIntrospection.java | 35 +- .../graphql/introspection/Introspection.java | 24 +- .../ExecutableNormalizedOperation.java | 23 +- .../ExecutableNormalizedOperationFactory.java | 860 ++++++++++-------- ...nterfacesImplementingInterfacesTest.groovy | 44 +- src/test/groovy/graphql/UnionTest.groovy | 39 +- ...ithIntrospectionInstrumentationTest.groovy | 96 ++ ...tableNormalizedOperationFactoryTest.groovy | 261 ++++++ src/test/java/benchmark/BenchmarkUtils.java | 64 +- .../ENFBenchmarkDeepIntrospection.java | 122 +++ 11 files changed, 1144 insertions(+), 426 deletions(-) create mode 100644 src/test/java/benchmark/ENFBenchmarkDeepIntrospection.java diff --git a/src/main/java/graphql/execution/ExecutionContext.java b/src/main/java/graphql/execution/ExecutionContext.java index 141954205b..edcced1e59 100644 --- a/src/main/java/graphql/execution/ExecutionContext.java +++ b/src/main/java/graphql/execution/ExecutionContext.java @@ -80,7 +80,7 @@ public class ExecutionContext { this.errors.set(builder.errors); this.localContext = builder.localContext; this.executionInput = builder.executionInput; - queryTree = FpKit.interThreadMemoize(() -> ExecutableNormalizedOperationFactory.createExecutableNormalizedOperation(graphQLSchema, operationDefinition, fragmentsByName, coercedVariables)); + this.queryTree = FpKit.interThreadMemoize(() -> ExecutableNormalizedOperationFactory.createExecutableNormalizedOperation(graphQLSchema, operationDefinition, fragmentsByName, coercedVariables)); } diff --git a/src/main/java/graphql/introspection/GoodFaithIntrospection.java b/src/main/java/graphql/introspection/GoodFaithIntrospection.java index 7e853016ac..37a1406500 100644 --- a/src/main/java/graphql/introspection/GoodFaithIntrospection.java +++ b/src/main/java/graphql/introspection/GoodFaithIntrospection.java @@ -18,6 +18,8 @@ import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; +import static graphql.normalized.ExecutableNormalizedOperationFactory.Options; +import static graphql.normalized.ExecutableNormalizedOperationFactory.createExecutableNormalizedOperation; import static graphql.schema.FieldCoordinates.coordinates; /** @@ -44,6 +46,14 @@ public class GoodFaithIntrospection { public static final String GOOD_FAITH_INTROSPECTION_DISABLED = "GOOD_FAITH_INTROSPECTION_DISABLED"; private static final AtomicBoolean ENABLED_STATE = new AtomicBoolean(true); + /** + * This is the maximum number of executable fields that can be in a good faith introspection query + */ + public static final int GOOD_FAITH_MAX_FIELDS_COUNT = 500; + /** + * This is the maximum depth a good faith introspection query can be + */ + public static final int GOOD_FAITH_MAX_DEPTH_COUNT = 20; /** * @return true if good faith introspection is enabled @@ -75,7 +85,7 @@ public static boolean enabledJvmWide(boolean flag) { public static Optional checkIntrospection(ExecutionContext executionContext) { if (isIntrospectionEnabled(executionContext.getGraphQLContext())) { - ExecutableNormalizedOperation operation = executionContext.getNormalizedQueryTree().get(); + ExecutableNormalizedOperation operation = mkOperation(executionContext); ImmutableListMultimap coordinatesToENFs = operation.getCoordinatesToNormalizedFields(); for (Map.Entry entry : ALLOWED_FIELD_INSTANCES.entrySet()) { FieldCoordinates coordinates = entry.getKey(); @@ -90,6 +100,29 @@ public static Optional checkIntrospection(ExecutionContext exec return Optional.empty(); } + /** + * This makes an executable operation limited in size then which suits a good faith introspection query. This helps guard + * against malicious queries. + * + * @param executionContext the execution context + * + * @return an executable operation + */ + private static ExecutableNormalizedOperation mkOperation(ExecutionContext executionContext) { + Options options = Options.defaultOptions() + .maxFieldsCount(GOOD_FAITH_MAX_FIELDS_COUNT) + .maxChildrenDepth(GOOD_FAITH_MAX_DEPTH_COUNT) + .locale(executionContext.getLocale()) + .graphQLContext(executionContext.getGraphQLContext()); + + return createExecutableNormalizedOperation(executionContext.getGraphQLSchema(), + executionContext.getOperationDefinition(), + executionContext.getFragmentsByName(), + executionContext.getCoercedVariables(), + options); + + } + private static boolean isIntrospectionEnabled(GraphQLContext graphQlContext) { if (!isEnabledJvmWide()) { return false; diff --git a/src/main/java/graphql/introspection/Introspection.java b/src/main/java/graphql/introspection/Introspection.java index c228f1c39f..b934f46071 100644 --- a/src/main/java/graphql/introspection/Introspection.java +++ b/src/main/java/graphql/introspection/Introspection.java @@ -115,20 +115,20 @@ public static boolean isEnabledJvmWide() { */ public static Optional isIntrospectionSensible(MergedSelectionSet mergedSelectionSet, ExecutionContext executionContext) { GraphQLContext graphQLContext = executionContext.getGraphQLContext(); - MergedField schemaField = mergedSelectionSet.getSubField(SchemaMetaFieldDef.getName()); - if (schemaField != null) { - if (!isIntrospectionEnabled(graphQLContext)) { - return mkDisabledError(schemaField); - } - } - MergedField typeField = mergedSelectionSet.getSubField(TypeMetaFieldDef.getName()); - if (typeField != null) { - if (!isIntrospectionEnabled(graphQLContext)) { - return mkDisabledError(typeField); + + boolean isIntrospection = false; + for (String key : mergedSelectionSet.getKeys()) { + String fieldName = mergedSelectionSet.getSubField(key).getName(); + if (fieldName.equals(SchemaMetaFieldDef.getName()) + || fieldName.equals(TypeMetaFieldDef.getName())) { + if (!isIntrospectionEnabled(graphQLContext)) { + return mkDisabledError(mergedSelectionSet.getSubField(key)); + } + isIntrospection = true; + break; } } - if (schemaField != null || typeField != null) - { + if (isIntrospection) { return GoodFaithIntrospection.checkIntrospection(executionContext); } return Optional.empty(); diff --git a/src/main/java/graphql/normalized/ExecutableNormalizedOperation.java b/src/main/java/graphql/normalized/ExecutableNormalizedOperation.java index ce50c9931b..cfcda2746d 100644 --- a/src/main/java/graphql/normalized/ExecutableNormalizedOperation.java +++ b/src/main/java/graphql/normalized/ExecutableNormalizedOperation.java @@ -31,6 +31,8 @@ public class ExecutableNormalizedOperation { private final Map normalizedFieldToMergedField; private final Map normalizedFieldToQueryDirectives; private final ImmutableListMultimap coordinatesToNormalizedFields; + private final int operationFieldCount; + private final int operationDepth; public ExecutableNormalizedOperation( OperationDefinition.Operation operation, @@ -39,8 +41,9 @@ public ExecutableNormalizedOperation( ImmutableListMultimap fieldToNormalizedField, Map normalizedFieldToMergedField, Map normalizedFieldToQueryDirectives, - ImmutableListMultimap coordinatesToNormalizedFields - ) { + ImmutableListMultimap coordinatesToNormalizedFields, + int operationFieldCount, + int operationDepth) { this.operation = operation; this.operationName = operationName; this.topLevelFields = topLevelFields; @@ -48,6 +51,8 @@ public ExecutableNormalizedOperation( this.normalizedFieldToMergedField = normalizedFieldToMergedField; this.normalizedFieldToQueryDirectives = normalizedFieldToQueryDirectives; this.coordinatesToNormalizedFields = coordinatesToNormalizedFields; + this.operationFieldCount = operationFieldCount; + this.operationDepth = operationDepth; } /** @@ -64,6 +69,20 @@ public String getOperationName() { return operationName; } + /** + * @return This returns how many {@link ExecutableNormalizedField}s are in the operation. + */ + public int getOperationFieldCount() { + return operationFieldCount; + } + + /** + * @return This returns the depth of the operation + */ + public int getOperationDepth() { + return operationDepth; + } + /** * This multimap shows how a given {@link ExecutableNormalizedField} maps to a one or more field coordinate in the schema * diff --git a/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java b/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java index 1124c9b7ce..a64fd07153 100644 --- a/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java +++ b/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java @@ -34,7 +34,6 @@ import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; import graphql.schema.GraphQLType; -import graphql.schema.GraphQLTypeUtil; import graphql.schema.GraphQLUnionType; import graphql.schema.GraphQLUnmodifiedType; import graphql.schema.impl.SchemaUtil; @@ -47,7 +46,6 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.function.BiConsumer; import static graphql.Assert.assertNotNull; import static graphql.Assert.assertShouldNeverHappen; @@ -65,24 +63,34 @@ */ @PublicApi public class ExecutableNormalizedOperationFactory { + public static class Options { private final GraphQLContext graphQLContext; private final Locale locale; private final int maxChildrenDepth; + private final int maxFieldsCount; + + private final boolean deferSupport; private Options(GraphQLContext graphQLContext, Locale locale, - int maxChildrenDepth) { + int maxChildrenDepth, + int maxFieldsCount, + boolean deferSupport) { this.graphQLContext = graphQLContext; this.locale = locale; this.maxChildrenDepth = maxChildrenDepth; + this.deferSupport = deferSupport; + this.maxFieldsCount = maxFieldsCount; } public static Options defaultOptions() { return new Options( GraphQLContext.getDefault(), Locale.getDefault(), - Integer.MAX_VALUE); + Integer.MAX_VALUE, + Integer.MAX_VALUE, + false); } /** @@ -91,10 +99,11 @@ public static Options defaultOptions() { * e.g. can be passed to {@link graphql.schema.Coercing} for parsing. * * @param locale the locale to use + * * @return new options object to use */ public Options locale(Locale locale) { - return new Options(this.graphQLContext, locale, this.maxChildrenDepth); + return new Options(this.graphQLContext, locale, this.maxChildrenDepth, this.maxFieldsCount, this.deferSupport); } /** @@ -103,10 +112,11 @@ public Options locale(Locale locale) { * Can be used to intercept input values e.g. using {@link graphql.execution.values.InputInterceptor}. * * @param graphQLContext the context to use + * * @return new options object to use */ public Options graphQLContext(GraphQLContext graphQLContext) { - return new Options(graphQLContext, this.locale, this.maxChildrenDepth); + return new Options(graphQLContext, this.locale, this.maxChildrenDepth, this.maxFieldsCount, this.deferSupport); } /** @@ -114,14 +124,28 @@ public Options graphQLContext(GraphQLContext graphQLContext) { * against malicious operations. * * @param maxChildrenDepth the max depth + * * @return new options object to use */ public Options maxChildrenDepth(int maxChildrenDepth) { - return new Options(this.graphQLContext, this.locale, maxChildrenDepth); + return new Options(this.graphQLContext, this.locale, maxChildrenDepth, this.maxFieldsCount, this.deferSupport); + } + + /** + * Controls the maximum number of ENFs created. Can be used to prevent + * against malicious operations. + * + * @param maxFieldsCount the max number of ENFs created + * + * @return new options object to use + */ + public Options maxFieldsCount(int maxFieldsCount) { + return new Options(this.graphQLContext, this.locale, maxChildrenDepth, maxFieldsCount, this.deferSupport); } /** * @return context to use during operation parsing + * * @see #graphQLContext(GraphQLContext) */ public GraphQLContext getGraphQLContext() { @@ -130,6 +154,7 @@ public GraphQLContext getGraphQLContext() { /** * @return locale to use during operation parsing + * * @see #locale(Locale) */ public Locale getLocale() { @@ -138,14 +163,24 @@ public Locale getLocale() { /** * @return maximum children depth before aborting parsing + * * @see #maxChildrenDepth(int) */ public int getMaxChildrenDepth() { return maxChildrenDepth; } + + public int getMaxFieldsCount() { + return maxFieldsCount; + } + } - private final ConditionalNodes conditionalNodes = new ConditionalNodes(); + private static final ConditionalNodes conditionalNodes = new ConditionalNodes(); + + private ExecutableNormalizedOperationFactory() { + + } /** * This will create a runtime representation of the graphql operation that would be executed @@ -163,14 +198,44 @@ public static ExecutableNormalizedOperation createExecutableNormalizedOperation( Document document, String operationName, CoercedVariables coercedVariableValues + ) { + return createExecutableNormalizedOperation( + graphQLSchema, + document, + operationName, + coercedVariableValues, + Options.defaultOptions()); + } + + /** + * This will create a runtime representation of the graphql operation that would be executed + * in a runtime sense. + * + * @param graphQLSchema the schema to be used + * @param document the {@link Document} holding the operation text + * @param operationName the operation name to use + * @param coercedVariableValues the coerced variables to use + * @param options the {@link Options} to use for parsing + * + * @return a runtime representation of the graphql operation. + */ + public static ExecutableNormalizedOperation createExecutableNormalizedOperation( + GraphQLSchema graphQLSchema, + Document document, + String operationName, + CoercedVariables coercedVariableValues, + Options options ) { NodeUtil.GetOperationResult getOperationResult = NodeUtil.getOperation(document, operationName); - return new ExecutableNormalizedOperationFactory().createNormalizedQueryImpl(graphQLSchema, + + return new ExecutableNormalizedOperationFactoryImpl( + graphQLSchema, getOperationResult.operationDefinition, getOperationResult.fragmentsByName, coercedVariableValues, null, - Options.defaultOptions()); + options + ).createNormalizedQueryImpl(); } /** @@ -188,14 +253,39 @@ public static ExecutableNormalizedOperation createExecutableNormalizedOperation( OperationDefinition operationDefinition, Map fragments, CoercedVariables coercedVariableValues) { - return new ExecutableNormalizedOperationFactory().createNormalizedQueryImpl(graphQLSchema, + return createExecutableNormalizedOperation(graphQLSchema, operationDefinition, fragments, coercedVariableValues, - null, Options.defaultOptions()); } + /** + * This will create a runtime representation of the graphql operation that would be executed + * in a runtime sense. + * + * @param graphQLSchema the schema to be used + * @param operationDefinition the operation to be executed + * @param fragments a set of fragments associated with the operation + * @param coercedVariableValues the coerced variables to use + * + * @return a runtime representation of the graphql operation. + */ + public static ExecutableNormalizedOperation createExecutableNormalizedOperation(GraphQLSchema graphQLSchema, + OperationDefinition operationDefinition, + Map fragments, + CoercedVariables coercedVariableValues, + Options options) { + return new ExecutableNormalizedOperationFactoryImpl( + graphQLSchema, + operationDefinition, + fragments, + coercedVariableValues, + null, + options + ).createNormalizedQueryImpl(); + } + /** * This will create a runtime representation of the graphql operation that would be executed * in a runtime sense. @@ -267,20 +357,8 @@ public static ExecutableNormalizedOperation createExecutableNormalizedOperationW RawVariables rawVariables, Options options) { NodeUtil.GetOperationResult getOperationResult = NodeUtil.getOperation(document, operationName); + OperationDefinition operationDefinition = getOperationResult.operationDefinition; - return new ExecutableNormalizedOperationFactory().createExecutableNormalizedOperationImplWithRawVariables(graphQLSchema, - getOperationResult.operationDefinition, - getOperationResult.fragmentsByName, - rawVariables, - options - ); - } - - private ExecutableNormalizedOperation createExecutableNormalizedOperationImplWithRawVariables(GraphQLSchema graphQLSchema, - OperationDefinition operationDefinition, - Map fragments, - RawVariables rawVariables, - Options options) { List variableDefinitions = operationDefinition.getVariableDefinitions(); CoercedVariables coercedVariableValues = ValuesResolver.coerceVariableValues(graphQLSchema, variableDefinitions, @@ -292,437 +370,451 @@ private ExecutableNormalizedOperation createExecutableNormalizedOperationImplWit rawVariables, options.getGraphQLContext(), options.getLocale()); - return createNormalizedQueryImpl(graphQLSchema, + + return new ExecutableNormalizedOperationFactoryImpl( + graphQLSchema, operationDefinition, - fragments, + getOperationResult.fragmentsByName, coercedVariableValues, normalizedVariableValues, - options); - } - - /** - * Creates a new ExecutableNormalizedOperation for the provided query - */ - private ExecutableNormalizedOperation createNormalizedQueryImpl(GraphQLSchema graphQLSchema, - OperationDefinition operationDefinition, - Map fragments, - CoercedVariables coercedVariableValues, - @Nullable Map normalizedVariableValues, - Options options) { - FieldCollectorNormalizedQueryParams parameters = FieldCollectorNormalizedQueryParams - .newParameters() - .fragments(fragments) - .schema(graphQLSchema) - .coercedVariables(coercedVariableValues.toMap()) - .normalizedVariables(normalizedVariableValues) - .build(); - - GraphQLObjectType rootType = SchemaUtil.getOperationRootType(graphQLSchema, operationDefinition); - - CollectNFResult collectFromOperationResult = collectFromOperation(parameters, operationDefinition, rootType); - - ImmutableListMultimap.Builder fieldToNormalizedField = ImmutableListMultimap.builder(); - ImmutableMap.Builder normalizedFieldToMergedField = ImmutableMap.builder(); - ImmutableMap.Builder normalizedFieldToQueryDirectives = ImmutableMap.builder(); - ImmutableListMultimap.Builder coordinatesToNormalizedFields = ImmutableListMultimap.builder(); - - BiConsumer captureMergedField = (enf, mergedFld) -> { - // QueryDirectivesImpl is a lazy object and only computes itself when asked for - QueryDirectives queryDirectives = new QueryDirectivesImpl(mergedFld, graphQLSchema, coercedVariableValues.toMap(), options.getGraphQLContext(), options.getLocale()); - normalizedFieldToQueryDirectives.put(enf, queryDirectives); - normalizedFieldToMergedField.put(enf, mergedFld); - }; - - for (ExecutableNormalizedField topLevel : collectFromOperationResult.children) { - ImmutableList fieldAndAstParents = collectFromOperationResult.normalizedFieldToAstFields.get(topLevel); - MergedField mergedField = newMergedField(fieldAndAstParents); - - captureMergedField.accept(topLevel, mergedField); - - updateFieldToNFMap(topLevel, fieldAndAstParents, fieldToNormalizedField); - updateCoordinatedToNFMap(coordinatesToNormalizedFields, topLevel); - - buildFieldWithChildren( - topLevel, - fieldAndAstParents, - parameters, - fieldToNormalizedField, - captureMergedField, - coordinatesToNormalizedFields, - 1, - options.getMaxChildrenDepth()); - } - for (FieldCollectorNormalizedQueryParams.PossibleMerger possibleMerger : parameters.getPossibleMergerList()) { - List childrenWithSameResultKey = possibleMerger.parent.getChildrenWithSameResultKey(possibleMerger.resultKey); - ENFMerger.merge(possibleMerger.parent, childrenWithSameResultKey, graphQLSchema); - } - return new ExecutableNormalizedOperation( - operationDefinition.getOperation(), - operationDefinition.getName(), - new ArrayList<>(collectFromOperationResult.children), - fieldToNormalizedField.build(), - normalizedFieldToMergedField.build(), - normalizedFieldToQueryDirectives.build(), - coordinatesToNormalizedFields.build() - ); + options + ).createNormalizedQueryImpl(); } - private void buildFieldWithChildren(ExecutableNormalizedField executableNormalizedField, - ImmutableList fieldAndAstParents, - FieldCollectorNormalizedQueryParams fieldCollectorNormalizedQueryParams, - ImmutableListMultimap.Builder fieldNormalizedField, - BiConsumer captureMergedField, - ImmutableListMultimap.Builder coordinatesToNormalizedFields, - int curLevel, - int maxLevel) { - if (curLevel > maxLevel) { - throw new AbortExecutionException("Maximum query depth exceeded " + curLevel + " > " + maxLevel); + private static class ExecutableNormalizedOperationFactoryImpl { + private final GraphQLSchema graphQLSchema; + private final OperationDefinition operationDefinition; + private final Map fragments; + private final CoercedVariables coercedVariableValues; + private final @Nullable Map normalizedVariableValues; + private final Options options; + + private final List possibleMergerList = new ArrayList<>(); + + private final ImmutableListMultimap.Builder fieldToNormalizedField = ImmutableListMultimap.builder(); + private final ImmutableMap.Builder normalizedFieldToMergedField = ImmutableMap.builder(); + private final ImmutableMap.Builder normalizedFieldToQueryDirectives = ImmutableMap.builder(); + private final ImmutableListMultimap.Builder coordinatesToNormalizedFields = ImmutableListMultimap.builder(); + private int fieldCount = 0; + private int maxDepthSeen = 0; + + private ExecutableNormalizedOperationFactoryImpl( + GraphQLSchema graphQLSchema, + OperationDefinition operationDefinition, + Map fragments, + CoercedVariables coercedVariableValues, + @Nullable Map normalizedVariableValues, + Options options + ) { + this.graphQLSchema = graphQLSchema; + this.operationDefinition = operationDefinition; + this.fragments = fragments; + this.coercedVariableValues = coercedVariableValues; + this.normalizedVariableValues = normalizedVariableValues; + this.options = options; } - CollectNFResult nextLevel = collectFromMergedField(fieldCollectorNormalizedQueryParams, executableNormalizedField, fieldAndAstParents, curLevel + 1); + /** + * Creates a new ExecutableNormalizedOperation for the provided query + */ + private ExecutableNormalizedOperation createNormalizedQueryImpl() { + GraphQLObjectType rootType = SchemaUtil.getOperationRootType(graphQLSchema, operationDefinition); - for (ExecutableNormalizedField childENF : nextLevel.children) { - executableNormalizedField.addChild(childENF); - ImmutableList childFieldAndAstParents = nextLevel.normalizedFieldToAstFields.get(childENF); + CollectNFResult collectFromOperationResult = collectFromOperation(rootType); - MergedField mergedField = newMergedField(childFieldAndAstParents); - captureMergedField.accept(childENF, mergedField); + for (ExecutableNormalizedField topLevel : collectFromOperationResult.children) { + ImmutableList fieldAndAstParents = collectFromOperationResult.normalizedFieldToAstFields.get(topLevel); + MergedField mergedField = newMergedField(fieldAndAstParents); - updateFieldToNFMap(childENF, childFieldAndAstParents, fieldNormalizedField); - updateCoordinatedToNFMap(coordinatesToNormalizedFields, childENF); + captureMergedField(topLevel, mergedField); - buildFieldWithChildren(childENF, - childFieldAndAstParents, - fieldCollectorNormalizedQueryParams, - fieldNormalizedField, - captureMergedField, - coordinatesToNormalizedFields, - curLevel + 1, - maxLevel); - } - } + updateFieldToNFMap(topLevel, fieldAndAstParents); + updateCoordinatedToNFMap(topLevel); - private static MergedField newMergedField(ImmutableList fieldAndAstParents) { - return MergedField.newMergedField(map(fieldAndAstParents, fieldAndAstParent -> fieldAndAstParent.field)).build(); - } - - private void updateFieldToNFMap(ExecutableNormalizedField executableNormalizedField, - ImmutableList mergedField, - ImmutableListMultimap.Builder fieldToNormalizedField) { - for (FieldAndAstParent astField : mergedField) { - fieldToNormalizedField.put(astField.field, executableNormalizedField); + int depthSeen = buildFieldWithChildren( + topLevel, + fieldAndAstParents, + 1); + maxDepthSeen = Math.max(maxDepthSeen,depthSeen); + } + // getPossibleMergerList + for (PossibleMerger possibleMerger : possibleMergerList) { + List childrenWithSameResultKey = possibleMerger.parent.getChildrenWithSameResultKey(possibleMerger.resultKey); + ENFMerger.merge(possibleMerger.parent, childrenWithSameResultKey, graphQLSchema); + } + return new ExecutableNormalizedOperation( + operationDefinition.getOperation(), + operationDefinition.getName(), + new ArrayList<>(collectFromOperationResult.children), + fieldToNormalizedField.build(), + normalizedFieldToMergedField.build(), + normalizedFieldToQueryDirectives.build(), + coordinatesToNormalizedFields.build(), + fieldCount, + maxDepthSeen + ); } - } - private void updateCoordinatedToNFMap(ImmutableListMultimap.Builder coordinatesToNormalizedFields, ExecutableNormalizedField topLevel) { - for (String objectType : topLevel.getObjectTypeNames()) { - FieldCoordinates coordinates = FieldCoordinates.coordinates(objectType, topLevel.getFieldName()); - coordinatesToNormalizedFields.put(coordinates, topLevel); + private void captureMergedField(ExecutableNormalizedField enf, MergedField mergedFld) { + // QueryDirectivesImpl is a lazy object and only computes itself when asked for + QueryDirectives queryDirectives = new QueryDirectivesImpl(mergedFld, graphQLSchema, coercedVariableValues.toMap(), options.getGraphQLContext(), options.getLocale()); + normalizedFieldToQueryDirectives.put(enf, queryDirectives); + normalizedFieldToMergedField.put(enf, mergedFld); } - } - private static class FieldAndAstParent { - final Field field; - final GraphQLCompositeType astParentType; + private int buildFieldWithChildren(ExecutableNormalizedField executableNormalizedField, + ImmutableList fieldAndAstParents, + int curLevel) { + checkMaxDepthExceeded(curLevel); - private FieldAndAstParent(Field field, GraphQLCompositeType astParentType) { - this.field = field; - this.astParentType = astParentType; - } - } + CollectNFResult nextLevel = collectFromMergedField(executableNormalizedField, fieldAndAstParents, curLevel + 1); + int maxDepthSeen = curLevel; + for (ExecutableNormalizedField childENF : nextLevel.children) { + executableNormalizedField.addChild(childENF); + ImmutableList childFieldAndAstParents = nextLevel.normalizedFieldToAstFields.get(childENF); - public static class CollectNFResult { - private final Collection children; - private final ImmutableListMultimap normalizedFieldToAstFields; + MergedField mergedField = newMergedField(childFieldAndAstParents); + captureMergedField(childENF, mergedField); - public CollectNFResult(Collection children, ImmutableListMultimap normalizedFieldToAstFields) { - this.children = children; - this.normalizedFieldToAstFields = normalizedFieldToAstFields; - } - } + updateFieldToNFMap(childENF, childFieldAndAstParents); + updateCoordinatedToNFMap(childENF); + int depthSeen = buildFieldWithChildren(childENF, + childFieldAndAstParents, + curLevel + 1); + maxDepthSeen = Math.max(maxDepthSeen,depthSeen); - public CollectNFResult collectFromMergedField(FieldCollectorNormalizedQueryParams parameters, - ExecutableNormalizedField executableNormalizedField, - ImmutableList mergedField, - int level) { - List fieldDefs = executableNormalizedField.getFieldDefinitions(parameters.getGraphQLSchema()); - Set possibleObjects = resolvePossibleObjects(fieldDefs, parameters.getGraphQLSchema()); - if (possibleObjects.isEmpty()) { - return new CollectNFResult(ImmutableKit.emptyList(), ImmutableListMultimap.of()); + checkMaxDepthExceeded(maxDepthSeen); + } + return maxDepthSeen; } - List collectedFields = new ArrayList<>(); - for (FieldAndAstParent fieldAndAstParent : mergedField) { - if (fieldAndAstParent.field.getSelectionSet() == null) { - continue; + private void checkMaxDepthExceeded(int depthSeen) { + if (depthSeen > this.options.getMaxChildrenDepth()) { + throw new AbortExecutionException("Maximum query depth exceeded. " + depthSeen + " > " + this.options.getMaxChildrenDepth()); } - GraphQLFieldDefinition fieldDefinition = Introspection.getFieldDef(parameters.getGraphQLSchema(), fieldAndAstParent.astParentType, fieldAndAstParent.field.getName()); - GraphQLUnmodifiedType astParentType = unwrapAll(fieldDefinition.getType()); - this.collectFromSelectionSet(parameters, - fieldAndAstParent.field.getSelectionSet(), - collectedFields, - (GraphQLCompositeType) astParentType, - possibleObjects - ); } - Map> fieldsByName = fieldsByResultKey(collectedFields); - ImmutableList.Builder resultNFs = ImmutableList.builder(); - ImmutableListMultimap.Builder normalizedFieldToAstFields = ImmutableListMultimap.builder(); - - createNFs(resultNFs, parameters, fieldsByName, normalizedFieldToAstFields, level, executableNormalizedField); - return new CollectNFResult(resultNFs.build(), normalizedFieldToAstFields.build()); - } + private static MergedField newMergedField(ImmutableList fieldAndAstParents) { + return MergedField.newMergedField(map(fieldAndAstParents, fieldAndAstParent -> fieldAndAstParent.field)).build(); + } - private Map> fieldsByResultKey(List collectedFields) { - Map> fieldsByName = new LinkedHashMap<>(); - for (CollectedField collectedField : collectedFields) { - fieldsByName.computeIfAbsent(collectedField.field.getResultKey(), ignored -> new ArrayList<>()).add(collectedField); + private void updateFieldToNFMap(ExecutableNormalizedField executableNormalizedField, + ImmutableList mergedField) { + for (FieldAndAstParent astField : mergedField) { + fieldToNormalizedField.put(astField.field, executableNormalizedField); + } } - return fieldsByName; - } - public CollectNFResult collectFromOperation(FieldCollectorNormalizedQueryParams parameters, - OperationDefinition operationDefinition, - GraphQLObjectType rootType) { + private void updateCoordinatedToNFMap(ExecutableNormalizedField topLevel) { + for (String objectType : topLevel.getObjectTypeNames()) { + FieldCoordinates coordinates = FieldCoordinates.coordinates(objectType, topLevel.getFieldName()); + coordinatesToNormalizedFields.put(coordinates, topLevel); + } + } + public CollectNFResult collectFromMergedField(ExecutableNormalizedField executableNormalizedField, + ImmutableList mergedField, + int level) { + List fieldDefs = executableNormalizedField.getFieldDefinitions(graphQLSchema); + Set possibleObjects = resolvePossibleObjects(fieldDefs); + if (possibleObjects.isEmpty()) { + return new CollectNFResult(ImmutableKit.emptyList(), ImmutableListMultimap.of()); + } - Set possibleObjects = ImmutableSet.of(rootType); - List collectedFields = new ArrayList<>(); - collectFromSelectionSet(parameters, operationDefinition.getSelectionSet(), collectedFields, rootType, possibleObjects); - // group by result key - Map> fieldsByName = fieldsByResultKey(collectedFields); - ImmutableList.Builder resultNFs = ImmutableList.builder(); - ImmutableListMultimap.Builder normalizedFieldToAstFields = ImmutableListMultimap.builder(); + List collectedFields = new ArrayList<>(); + for (FieldAndAstParent fieldAndAstParent : mergedField) { + if (fieldAndAstParent.field.getSelectionSet() == null) { + continue; + } + GraphQLFieldDefinition fieldDefinition = Introspection.getFieldDef(graphQLSchema, fieldAndAstParent.astParentType, fieldAndAstParent.field.getName()); + GraphQLUnmodifiedType astParentType = unwrapAll(fieldDefinition.getType()); + this.collectFromSelectionSet(fieldAndAstParent.field.getSelectionSet(), + collectedFields, + (GraphQLCompositeType) astParentType, + possibleObjects + ); + } + Map> fieldsByName = fieldsByResultKey(collectedFields); + ImmutableList.Builder resultNFs = ImmutableList.builder(); + ImmutableListMultimap.Builder normalizedFieldToAstFields = ImmutableListMultimap.builder(); - createNFs(resultNFs, parameters, fieldsByName, normalizedFieldToAstFields, 1, null); + createNFs(resultNFs, fieldsByName, normalizedFieldToAstFields, level, executableNormalizedField); - return new CollectNFResult(resultNFs.build(), normalizedFieldToAstFields.build()); - } + return new CollectNFResult(resultNFs.build(), normalizedFieldToAstFields.build()); + } - private void createNFs(ImmutableList.Builder nfListBuilder, - FieldCollectorNormalizedQueryParams parameters, - Map> fieldsByName, - ImmutableListMultimap.Builder normalizedFieldToAstFields, - int level, - ExecutableNormalizedField parent) { - for (String resultKey : fieldsByName.keySet()) { - List fieldsWithSameResultKey = fieldsByName.get(resultKey); - List commonParentsGroups = groupByCommonParents(fieldsWithSameResultKey); - for (CollectedFieldGroup fieldGroup : commonParentsGroups) { - ExecutableNormalizedField nf = createNF(parameters, fieldGroup, level, parent); - if (nf == null) { - continue; + private Map> fieldsByResultKey(List collectedFields) { + Map> fieldsByName = new LinkedHashMap<>(); + for (CollectedField collectedField : collectedFields) { + fieldsByName.computeIfAbsent(collectedField.field.getResultKey(), ignored -> new ArrayList<>()).add(collectedField); + } + return fieldsByName; + } + + public CollectNFResult collectFromOperation(GraphQLObjectType rootType) { + + + Set possibleObjects = ImmutableSet.of(rootType); + List collectedFields = new ArrayList<>(); + collectFromSelectionSet(operationDefinition.getSelectionSet(), collectedFields, rootType, possibleObjects); + // group by result key + Map> fieldsByName = fieldsByResultKey(collectedFields); + ImmutableList.Builder resultNFs = ImmutableList.builder(); + ImmutableListMultimap.Builder normalizedFieldToAstFields = ImmutableListMultimap.builder(); + + createNFs(resultNFs, fieldsByName, normalizedFieldToAstFields, 1, null); + + return new CollectNFResult(resultNFs.build(), normalizedFieldToAstFields.build()); + } + + private void createNFs(ImmutableList.Builder nfListBuilder, + Map> fieldsByName, + ImmutableListMultimap.Builder normalizedFieldToAstFields, + int level, + ExecutableNormalizedField parent) { + for (String resultKey : fieldsByName.keySet()) { + List fieldsWithSameResultKey = fieldsByName.get(resultKey); + List commonParentsGroups = groupByCommonParents(fieldsWithSameResultKey); + for (CollectedFieldGroup fieldGroup : commonParentsGroups) { + ExecutableNormalizedField nf = createNF(fieldGroup, level, parent); + if (nf == null) { + continue; + } + for (CollectedField collectedField : fieldGroup.fields) { + normalizedFieldToAstFields.put(nf, new FieldAndAstParent(collectedField.field, collectedField.astTypeCondition)); + } + nfListBuilder.add(nf); } - for (CollectedField collectedField : fieldGroup.fields) { - normalizedFieldToAstFields.put(nf, new FieldAndAstParent(collectedField.field, collectedField.astTypeCondition)); + if (commonParentsGroups.size() > 1) { + possibleMergerList.add(new PossibleMerger(parent, resultKey)); } - nfListBuilder.add(nf); - } - if (commonParentsGroups.size() > 1) { - parameters.addPossibleMergers(parent, resultKey); } } - } - - private ExecutableNormalizedField createNF(FieldCollectorNormalizedQueryParams parameters, - CollectedFieldGroup collectedFieldGroup, - int level, - ExecutableNormalizedField parent) { - Field field; - Set objectTypes = collectedFieldGroup.objectTypes; - field = collectedFieldGroup.fields.iterator().next().field; - String fieldName = field.getName(); - GraphQLFieldDefinition fieldDefinition = Introspection.getFieldDef(parameters.getGraphQLSchema(), objectTypes.iterator().next(), fieldName); - - Map argumentValues = ValuesResolver.getArgumentValues(fieldDefinition.getArguments(), field.getArguments(), CoercedVariables.of(parameters.getCoercedVariableValues()), parameters.getGraphQLContext(), parameters.getLocale()); - Map normalizedArgumentValues = null; - if (parameters.getNormalizedVariableValues() != null) { - normalizedArgumentValues = ValuesResolver.getNormalizedArgumentValues(fieldDefinition.getArguments(), field.getArguments(), parameters.getNormalizedVariableValues()); - } - ImmutableList objectTypeNames = map(objectTypes, GraphQLObjectType::getName); - - return ExecutableNormalizedField.newNormalizedField() - .alias(field.getAlias()) - .resolvedArguments(argumentValues) - .normalizedArguments(normalizedArgumentValues) - .astArguments(field.getArguments()) - .objectTypeNames(objectTypeNames) - .fieldName(fieldName) - .level(level) - .parent(parent) - .build(); - } - private static class CollectedFieldGroup { - Set objectTypes; - Set fields; - - public CollectedFieldGroup(Set fields, Set objectTypes) { - this.fields = fields; - this.objectTypes = objectTypes; - } - } + private ExecutableNormalizedField createNF(CollectedFieldGroup collectedFieldGroup, + int level, + ExecutableNormalizedField parent) { - private List groupByCommonParents(Collection fields) { - ImmutableSet.Builder objectTypes = ImmutableSet.builder(); - for (CollectedField collectedField : fields) { - objectTypes.addAll(collectedField.objectTypes); - } - Set allRelevantObjects = objectTypes.build(); - Map> groupByAstParent = groupingBy(fields, fieldAndType -> fieldAndType.astTypeCondition); - if (groupByAstParent.size() == 1) { - return singletonList(new CollectedFieldGroup(ImmutableSet.copyOf(fields), allRelevantObjects)); - } - ImmutableList.Builder result = ImmutableList.builder(); - for (GraphQLObjectType objectType : allRelevantObjects) { - Set relevantFields = filterSet(fields, field -> field.objectTypes.contains(objectType)); - result.add(new CollectedFieldGroup(relevantFields, singleton(objectType))); + this.fieldCount++; + if (this.fieldCount > this.options.getMaxFieldsCount()) { + throw new AbortExecutionException("Maximum field count exceeded. " + this.fieldCount + " > " + this.options.getMaxFieldsCount()); + } + Field field; + Set objectTypes = collectedFieldGroup.objectTypes; + field = collectedFieldGroup.fields.iterator().next().field; + String fieldName = field.getName(); + GraphQLFieldDefinition fieldDefinition = Introspection.getFieldDef(graphQLSchema, objectTypes.iterator().next(), fieldName); + + Map argumentValues = ValuesResolver.getArgumentValues(fieldDefinition.getArguments(), field.getArguments(), CoercedVariables.of(this.coercedVariableValues.toMap()), this.options.graphQLContext, this.options.locale); + Map normalizedArgumentValues = null; + if (this.normalizedVariableValues != null) { + normalizedArgumentValues = ValuesResolver.getNormalizedArgumentValues(fieldDefinition.getArguments(), field.getArguments(), this.normalizedVariableValues); + } + ImmutableList objectTypeNames = map(objectTypes, GraphQLObjectType::getName); + return ExecutableNormalizedField.newNormalizedField() + .alias(field.getAlias()) + .resolvedArguments(argumentValues) + .normalizedArguments(normalizedArgumentValues) + .astArguments(field.getArguments()) + .objectTypeNames(objectTypeNames) + .fieldName(fieldName) + .level(level) + .parent(parent) + .build(); + } + + private List groupByCommonParents(Collection fields) { + return groupByCommonParentsNoDeferSupport(fields); + } + + private List groupByCommonParentsNoDeferSupport(Collection fields) { + ImmutableSet.Builder objectTypes = ImmutableSet.builder(); + for (CollectedField collectedField : fields) { + objectTypes.addAll(collectedField.objectTypes); + } + Set allRelevantObjects = objectTypes.build(); + Map> groupByAstParent = groupingBy(fields, fieldAndType -> fieldAndType.astTypeCondition); + if (groupByAstParent.size() == 1) { + return singletonList(new CollectedFieldGroup(ImmutableSet.copyOf(fields), allRelevantObjects)); + } + ImmutableList.Builder result = ImmutableList.builder(); + for (GraphQLObjectType objectType : allRelevantObjects) { + Set relevantFields = filterSet(fields, field -> field.objectTypes.contains(objectType)); + result.add(new CollectedFieldGroup(relevantFields, singleton(objectType))); + } + return result.build(); + } + + private void collectFromSelectionSet(SelectionSet selectionSet, + List result, + GraphQLCompositeType astTypeCondition, + Set possibleObjects + ) { + for (Selection selection : selectionSet.getSelections()) { + if (selection instanceof Field) { + collectField(result, (Field) selection, possibleObjects, astTypeCondition); + } else if (selection instanceof InlineFragment) { + collectInlineFragment(result, (InlineFragment) selection, possibleObjects, astTypeCondition); + } else if (selection instanceof FragmentSpread) { + collectFragmentSpread(result, (FragmentSpread) selection, possibleObjects); + } + } } - return result.build(); - } + private void collectFragmentSpread(List result, + FragmentSpread fragmentSpread, + Set possibleObjects + ) { + if (!conditionalNodes.shouldInclude(fragmentSpread, + this.coercedVariableValues.toMap(), + this.graphQLSchema, + this.options.graphQLContext)) { + return; + } + FragmentDefinition fragmentDefinition = assertNotNull(this.fragments.get(fragmentSpread.getName())); - private void collectFromSelectionSet(FieldCollectorNormalizedQueryParams parameters, - SelectionSet selectionSet, - List result, - GraphQLCompositeType astTypeCondition, - Set possibleObjects - ) { - for (Selection selection : selectionSet.getSelections()) { - if (selection instanceof Field) { - collectField(parameters, result, (Field) selection, possibleObjects, astTypeCondition); - } else if (selection instanceof InlineFragment) { - collectInlineFragment(parameters, result, (InlineFragment) selection, possibleObjects, astTypeCondition); - } else if (selection instanceof FragmentSpread) { - collectFragmentSpread(parameters, result, (FragmentSpread) selection, possibleObjects); + if (!conditionalNodes.shouldInclude(fragmentDefinition, + this.coercedVariableValues.toMap(), + this.graphQLSchema, + this.options.graphQLContext)) { + return; } + GraphQLCompositeType newAstTypeCondition = (GraphQLCompositeType) assertNotNull(this.graphQLSchema.getType(fragmentDefinition.getTypeCondition().getName())); + Set newPossibleObjects = narrowDownPossibleObjects(possibleObjects, newAstTypeCondition); + + collectFromSelectionSet(fragmentDefinition.getSelectionSet(), result, newAstTypeCondition, newPossibleObjects); } - } - private static class CollectedField { - Field field; - Set objectTypes; - GraphQLCompositeType astTypeCondition; + private void collectInlineFragment(List result, + InlineFragment inlineFragment, + Set possibleObjects, + GraphQLCompositeType astTypeCondition + ) { + if (!conditionalNodes.shouldInclude(inlineFragment, this.coercedVariableValues.toMap(), this.graphQLSchema, this.options.graphQLContext)) { + return; + } + Set newPossibleObjects = possibleObjects; + GraphQLCompositeType newAstTypeCondition = astTypeCondition; - public CollectedField(Field field, Set objectTypes, GraphQLCompositeType astTypeCondition) { - this.field = field; - this.objectTypes = objectTypes; - this.astTypeCondition = astTypeCondition; - } + if (inlineFragment.getTypeCondition() != null) { + newAstTypeCondition = (GraphQLCompositeType) this.graphQLSchema.getType(inlineFragment.getTypeCondition().getName()); + newPossibleObjects = narrowDownPossibleObjects(possibleObjects, newAstTypeCondition); - public boolean isAbstract() { - return GraphQLTypeUtil.isInterfaceOrUnion(astTypeCondition); + } + + collectFromSelectionSet(inlineFragment.getSelectionSet(), result, newAstTypeCondition, newPossibleObjects); } - public boolean isConcrete() { - return GraphQLTypeUtil.isObjectType(astTypeCondition); + private void collectField(List result, + Field field, + Set possibleObjectTypes, + GraphQLCompositeType astTypeCondition + ) { + if (!conditionalNodes.shouldInclude(field, + this.coercedVariableValues.toMap(), + this.graphQLSchema, + this.options.graphQLContext)) { + return; + } + // this means there is actually no possible type for this field, and we are done + if (possibleObjectTypes.isEmpty()) { + return; + } + result.add(new CollectedField(field, possibleObjectTypes, astTypeCondition)); } - } - private void collectFragmentSpread(FieldCollectorNormalizedQueryParams parameters, - List result, - FragmentSpread fragmentSpread, - Set possibleObjects - ) { - if (!conditionalNodes.shouldInclude(fragmentSpread, - parameters.getCoercedVariableValues(), - parameters.getGraphQLSchema(), - parameters.getGraphQLContext())) { - return; - } - FragmentDefinition fragmentDefinition = assertNotNull(parameters.getFragmentsByName().get(fragmentSpread.getName())); - - if (!conditionalNodes.shouldInclude(fragmentDefinition, - parameters.getCoercedVariableValues(), - parameters.getGraphQLSchema(), - parameters.getGraphQLContext())) { - return; - } - GraphQLCompositeType newAstTypeCondition = (GraphQLCompositeType) assertNotNull(parameters.getGraphQLSchema().getType(fragmentDefinition.getTypeCondition().getName())); - Set newPossibleObjects = narrowDownPossibleObjects(possibleObjects, newAstTypeCondition, parameters.getGraphQLSchema()); - collectFromSelectionSet(parameters, fragmentDefinition.getSelectionSet(), result, newAstTypeCondition, newPossibleObjects); - } + private Set narrowDownPossibleObjects(Set currentOnes, + GraphQLCompositeType typeCondition) { + ImmutableSet resolvedTypeCondition = resolvePossibleObjects(typeCondition); + if (currentOnes.isEmpty()) { + return resolvedTypeCondition; + } - private void collectInlineFragment(FieldCollectorNormalizedQueryParams parameters, - List result, - InlineFragment inlineFragment, - Set possibleObjects, - GraphQLCompositeType astTypeCondition - ) { - if (!conditionalNodes.shouldInclude(inlineFragment, parameters.getCoercedVariableValues(), parameters.getGraphQLSchema(), parameters.getGraphQLContext())) { - return; + // Faster intersection, as either set often has a size of 1. + return intersection(currentOnes, resolvedTypeCondition); } - Set newPossibleObjects = possibleObjects; - GraphQLCompositeType newAstTypeCondition = astTypeCondition; - if (inlineFragment.getTypeCondition() != null) { - newAstTypeCondition = (GraphQLCompositeType) parameters.getGraphQLSchema().getType(inlineFragment.getTypeCondition().getName()); - newPossibleObjects = narrowDownPossibleObjects(possibleObjects, newAstTypeCondition, parameters.getGraphQLSchema()); + private ImmutableSet resolvePossibleObjects(List defs) { + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (GraphQLFieldDefinition def : defs) { + GraphQLUnmodifiedType outputType = unwrapAll(def.getType()); + if (outputType instanceof GraphQLCompositeType) { + builder.addAll(resolvePossibleObjects((GraphQLCompositeType) outputType)); + } + } + + return builder.build(); } - collectFromSelectionSet(parameters, inlineFragment.getSelectionSet(), result, newAstTypeCondition, newPossibleObjects); - } - private void collectField(FieldCollectorNormalizedQueryParams parameters, - List result, - Field field, - Set possibleObjectTypes, - GraphQLCompositeType astTypeCondition - ) { - if (!conditionalNodes.shouldInclude(field, - parameters.getCoercedVariableValues(), - parameters.getGraphQLSchema(), - parameters.getGraphQLContext())) { - return; + private ImmutableSet resolvePossibleObjects(GraphQLCompositeType type) { + if (type instanceof GraphQLObjectType) { + return ImmutableSet.of((GraphQLObjectType) type); + } else if (type instanceof GraphQLInterfaceType) { + return ImmutableSet.copyOf(graphQLSchema.getImplementations((GraphQLInterfaceType) type)); + } else if (type instanceof GraphQLUnionType) { + List unionTypes = ((GraphQLUnionType) type).getTypes(); + return ImmutableSet.copyOf(ImmutableKit.map(unionTypes, GraphQLObjectType.class::cast)); + } else { + return assertShouldNeverHappen(); + } } - // this means there is actually no possible type for this field, and we are done - if (possibleObjectTypes.isEmpty()) { - return; + + private static class PossibleMerger { + ExecutableNormalizedField parent; + String resultKey; + + public PossibleMerger(ExecutableNormalizedField parent, String resultKey) { + this.parent = parent; + this.resultKey = resultKey; + } } - result.add(new CollectedField(field, possibleObjectTypes, astTypeCondition)); - } - private Set narrowDownPossibleObjects(Set currentOnes, - GraphQLCompositeType typeCondition, - GraphQLSchema graphQLSchema) { + private static class CollectedField { + Field field; + Set objectTypes; + GraphQLCompositeType astTypeCondition; - ImmutableSet resolvedTypeCondition = resolvePossibleObjects(typeCondition, graphQLSchema); - if (currentOnes.isEmpty()) { - return resolvedTypeCondition; + public CollectedField(Field field, Set objectTypes, GraphQLCompositeType astTypeCondition) { + this.field = field; + this.objectTypes = objectTypes; + this.astTypeCondition = astTypeCondition; + } } - // Faster intersection, as either set often has a size of 1. - return intersection(currentOnes, resolvedTypeCondition); - } + public static class CollectNFResult { + private final Collection children; + private final ImmutableListMultimap normalizedFieldToAstFields; - private ImmutableSet resolvePossibleObjects(List defs, GraphQLSchema graphQLSchema) { - ImmutableSet.Builder builder = ImmutableSet.builder(); + public CollectNFResult(Collection children, ImmutableListMultimap normalizedFieldToAstFields) { + this.children = children; + this.normalizedFieldToAstFields = normalizedFieldToAstFields; + } + } + + private static class FieldAndAstParent { + final Field field; + final GraphQLCompositeType astParentType; - for (GraphQLFieldDefinition def : defs) { - GraphQLUnmodifiedType outputType = unwrapAll(def.getType()); - if (outputType instanceof GraphQLCompositeType) { - builder.addAll(resolvePossibleObjects((GraphQLCompositeType) outputType, graphQLSchema)); + private FieldAndAstParent(Field field, GraphQLCompositeType astParentType) { + this.field = field; + this.astParentType = astParentType; } } - return builder.build(); - } + private static class CollectedFieldGroup { + Set objectTypes; + Set fields; - private ImmutableSet resolvePossibleObjects(GraphQLCompositeType type, GraphQLSchema graphQLSchema) { - if (type instanceof GraphQLObjectType) { - return ImmutableSet.of((GraphQLObjectType) type); - } else if (type instanceof GraphQLInterfaceType) { - return ImmutableSet.copyOf(graphQLSchema.getImplementations((GraphQLInterfaceType) type)); - } else if (type instanceof GraphQLUnionType) { - List unionTypes = ((GraphQLUnionType) type).getTypes(); - return ImmutableSet.copyOf(ImmutableKit.map(unionTypes, GraphQLObjectType.class::cast)); - } else { - return assertShouldNeverHappen(); + public CollectedFieldGroup(Set fields, Set objectTypes) { + this.fields = fields; + this.objectTypes = objectTypes; + } } } -} + +} \ No newline at end of file diff --git a/src/test/groovy/graphql/InterfacesImplementingInterfacesTest.groovy b/src/test/groovy/graphql/InterfacesImplementingInterfacesTest.groovy index b5813c4d0b..bb22d70461 100644 --- a/src/test/groovy/graphql/InterfacesImplementingInterfacesTest.groovy +++ b/src/test/groovy/graphql/InterfacesImplementingInterfacesTest.groovy @@ -893,8 +893,10 @@ class InterfacesImplementingInterfacesTest extends Specification { given: def graphQLSchema = createComplexSchema() + GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build() + when: - def result = GraphQL.newGraphQL(graphQLSchema).build().execute(""" + String query = """ { nodeType: __type(name: "Node") { possibleTypes { @@ -902,7 +904,20 @@ class InterfacesImplementingInterfacesTest extends Specification { name } } - resourceType: __type(name: "Resource") { + } + """ + def result = graphQL.execute(query) + + then: + !result.errors + result.data == [ + nodeType: [possibleTypes: [[kind: 'OBJECT', name: 'File'], [kind: 'OBJECT', name: 'Image']]], + ] + + when: + query = """ + { + resourceType: __type(name: "Resource") { possibleTypes { kind name @@ -911,22 +926,35 @@ class InterfacesImplementingInterfacesTest extends Specification { kind name } - } - imageType: __type(name: "Image") { + } + } + """ + result = graphQL.execute(query) + + then: + !result.errors + result.data == [ + resourceType: [possibleTypes: [[kind: 'OBJECT', name: 'File'], [kind: 'OBJECT', name: 'Image']], interfaces: [[kind: 'INTERFACE', name: 'Node']]] + ] + + when: + + query = """ + { + imageType: __type(name: "Image") { interfaces { kind name } } - } - """) + } + """ + result = graphQL.execute(query) then: !result.errors result.data == [ - nodeType : [possibleTypes: [[kind: 'OBJECT', name: 'File'], [kind: 'OBJECT', name: 'Image']]], imageType : [interfaces: [[kind: 'INTERFACE', name: 'Resource'], [kind: 'INTERFACE', name: 'Node']]], - resourceType: [possibleTypes: [[kind: 'OBJECT', name: 'File'], [kind: 'OBJECT', name: 'Image']], interfaces: [[kind: 'INTERFACE', name: 'Node']]] ] } diff --git a/src/test/groovy/graphql/UnionTest.groovy b/src/test/groovy/graphql/UnionTest.groovy index 403f31d3d8..8edd7b2600 100644 --- a/src/test/groovy/graphql/UnionTest.groovy +++ b/src/test/groovy/graphql/UnionTest.groovy @@ -4,7 +4,7 @@ import spock.lang.Specification class UnionTest extends Specification { - def "can introspect on union and intersection types"() { + def "can introspect on union types"() { def query = """ { Named: __type(name: "Named") { @@ -15,15 +15,6 @@ class UnionTest extends Specification { possibleTypes { name } enumValues { name } inputFields { name } - } - Pet: __type(name: "Pet") { - kind - name - fields { name } - interfaces { name } - possibleTypes { name } - enumValues { name } - inputFields { name } } } """ @@ -42,8 +33,32 @@ class UnionTest extends Specification { ], enumValues : null, inputFields : null - ], - Pet : [ + ]] + when: + def executionResult = GraphQL.newGraphQL(GarfieldSchema.GarfieldSchema).build().execute(query) + + then: + executionResult.data == expectedResult + + + } + + def "can introspect on intersection types"() { + def query = """ + { + Pet: __type(name: "Pet") { + kind + name + fields { name } + interfaces { name } + possibleTypes { name } + enumValues { name } + inputFields { name } + } + } + """ + + def expectedResult = [Pet : [ kind : 'UNION', name : 'Pet', fields : null, diff --git a/src/test/groovy/graphql/introspection/GoodFaithIntrospectionInstrumentationTest.groovy b/src/test/groovy/graphql/introspection/GoodFaithIntrospectionInstrumentationTest.groovy index f1ffc2c570..b77e1d76e8 100644 --- a/src/test/groovy/graphql/introspection/GoodFaithIntrospectionInstrumentationTest.groovy +++ b/src/test/groovy/graphql/introspection/GoodFaithIntrospectionInstrumentationTest.groovy @@ -3,6 +3,10 @@ package graphql.introspection import graphql.ExecutionInput import graphql.ExecutionResult import graphql.TestUtil +import graphql.execution.AbortExecutionException +import graphql.execution.CoercedVariables +import graphql.language.Document +import graphql.normalized.ExecutableNormalizedOperationFactory import spock.lang.Specification class GoodFaithIntrospectionInstrumentationTest extends Specification { @@ -12,10 +16,23 @@ class GoodFaithIntrospectionInstrumentationTest extends Specification { def setup() { GoodFaithIntrospection.enabledJvmWide(true) } + def cleanup() { GoodFaithIntrospection.enabledJvmWide(true) } + def "standard introspection query is inside limits just in general"() { + + when: + Document document = TestUtil.toDocument(IntrospectionQuery.INTROSPECTION_QUERY) + def eno = ExecutableNormalizedOperationFactory.createExecutableNormalizedOperation(graphql.getGraphQLSchema(), document, + "IntrospectionQuery", CoercedVariables.emptyVariables()) + + then: + eno.getOperationFieldCount() < GoodFaithIntrospection.GOOD_FAITH_MAX_FIELDS_COUNT // currently 189 + eno.getOperationDepth() < GoodFaithIntrospection.GOOD_FAITH_MAX_DEPTH_COUNT // currently 13 + } + def "test asking for introspection in good faith"() { when: @@ -69,12 +86,25 @@ class GoodFaithIntrospectionInstrumentationTest extends Specification { alias1 : __type(name : "t1") { name } } """ | _ + // a case for __type with aliases + """ query badActor { + a1: __type(name : "t") { name } + a2 : __type(name : "t1") { name } + } + """ | _ // a case for schema repeated - dont ask twice """ query badActor { __schema { types { name} } alias1 : __schema { types { name} } } """ | _ + // a case for used aliases + """ query badActor { + a1: __schema { types { name} } + a2 : __schema { types { name} } + } + """ | _ + } def "mixed general queries and introspections will be stopped anyway"() { @@ -133,4 +163,70 @@ class GoodFaithIntrospectionInstrumentationTest extends Specification { !er.errors.isEmpty() er.errors[0] instanceof GoodFaithIntrospection.BadFaithIntrospectionError } + + def "can stop deep queries"() { + + when: + def query = createDeepQuery(depth) + def then = System.currentTimeMillis() + ExecutionResult er = graphql.execute(query) + def ms = System.currentTimeMillis()-then + + then: + !er.errors.isEmpty() + er.errors[0].class == targetError + er.data == null // it stopped hard - it did not continue to normal business + println "Took " + ms + "ms" + + where: + depth | targetError + 2 | GoodFaithIntrospection.BadFaithIntrospectionError.class + 10 | AbortExecutionException.class + 15 | AbortExecutionException.class + 20 | AbortExecutionException.class + 25 | AbortExecutionException.class + 50 | AbortExecutionException.class + 100 | AbortExecutionException.class + } + + String createDeepQuery(int depth = 25) { + def result = """ +query test { + __schema { + types { + ...F1 + } + } +} +""" + for (int i = 1; i < depth; i++) { + result += """ + fragment F$i on __Type { + fields { + type { + ...F${i + 1} + } + } + + ofType { + ...F${i + 1} + } +} + + +""" + } + result += """ + fragment F$depth on __Type { + fields { + type { +name + } + } +} + + +""" + return result + } } diff --git a/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy b/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy index 89ea656b81..cf1f7b497a 100644 --- a/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy +++ b/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy @@ -3,10 +3,12 @@ package graphql.normalized import graphql.ExecutionInput import graphql.GraphQL import graphql.TestUtil +import graphql.execution.AbortExecutionException import graphql.execution.CoercedVariables import graphql.execution.MergedField import graphql.execution.RawVariables import graphql.execution.directives.QueryAppliedDirective +import graphql.introspection.IntrospectionQuery import graphql.language.Document import graphql.language.Field import graphql.language.FragmentDefinition @@ -2875,4 +2877,263 @@ fragment personName on Person { then: noExceptionThrown() } + + def "big query exceeding fields count"() { + String schema = """ + type Query { + animal: Animal + } + interface Animal { + name: String + friends: [Friend] + } + union Pet = Dog | Cat + type Friend { + name: String + isBirdOwner: Boolean + isCatOwner: Boolean + pets: [Pet] + } + type Bird implements Animal { + name: String + friends: [Friend] + } + type Cat implements Animal { + name: String + friends: [Friend] + breed: String + } + type Dog implements Animal { + name: String + breed: String + friends: [Friend] + } + """ + + def garbageFields = IntStream.range(0, 1000) + .mapToObj { + """test_$it: friends { name }""" + } + .collect(Collectors.joining("\n")) + + GraphQLSchema graphQLSchema = TestUtil.schema(schema) + + String query = """ + { + animal { + name + otherName: name + ... on Animal { + name + } + ... on Cat { + name + friends { + ... on Friend { + isCatOwner + pets { + ... on Dog { + name + } + } + } + } + } + ... on Bird { + friends { + isBirdOwner + } + friends { + name + pets { + ... on Cat { + breed + } + } + } + } + ... on Dog { + name + } + $garbageFields + } + } + """ + + assertValidQuery(graphQLSchema, query) + + Document document = TestUtil.parseQuery(query) + + when: + def result = ExecutableNormalizedOperationFactory.createExecutableNormalizedOperationWithRawVariables( + graphQLSchema, + document, + null, + RawVariables.emptyVariables(), + ExecutableNormalizedOperationFactory.Options.defaultOptions().maxFieldsCount(2013)) + + then: + def e = thrown(AbortExecutionException) + e.message == "Maximum field count exceeded. 2014 > 2013" + } + + def "small query exceeding fields count"() { + String schema = """ + type Query { + hello: String + } + """ + + GraphQLSchema graphQLSchema = TestUtil.schema(schema) + + String query = """ {hello a1: hello}""" + + assertValidQuery(graphQLSchema, query) + + Document document = TestUtil.parseQuery(query) + + when: + def result = ExecutableNormalizedOperationFactory.createExecutableNormalizedOperationWithRawVariables( + graphQLSchema, + document, + null, + RawVariables.emptyVariables(), + ExecutableNormalizedOperationFactory.Options.defaultOptions().maxFieldsCount(1)) + + then: + def e = thrown(AbortExecutionException) + e.message == "Maximum field count exceeded. 2 > 1" + + + } + + def "query not exceeding fields count"() { + String schema = """ + type Query { + dogs: [Dog] + } + type Dog { + name: String + breed: String + } + """ + + GraphQLSchema graphQLSchema = TestUtil.schema(schema) + + String query = """ {dogs{name breed }}""" + + assertValidQuery(graphQLSchema, query) + + Document document = TestUtil.parseQuery(query) + + when: + def result = ExecutableNormalizedOperationFactory.createExecutableNormalizedOperationWithRawVariables( + graphQLSchema, + document, + null, + RawVariables.emptyVariables(), + ExecutableNormalizedOperationFactory.Options.defaultOptions().maxFieldsCount(3)) + + then: + notThrown(AbortExecutionException) + + + } + + def "query with meta fields exceeding fields count"() { + String schema = """ + type Query { + hello: String + } + """ + + GraphQLSchema graphQLSchema = TestUtil.schema(schema) + + String query = IntrospectionQuery.INTROSPECTION_QUERY + + assertValidQuery(graphQLSchema, query) + + Document document = TestUtil.parseQuery(query) + + when: + def result = ExecutableNormalizedOperationFactory.createExecutableNormalizedOperationWithRawVariables( + graphQLSchema, + document, + null, + RawVariables.emptyVariables(), + ExecutableNormalizedOperationFactory.Options.defaultOptions().maxFieldsCount(188)) + println result.normalizedFieldToMergedField.size() + + then: + def e = thrown(AbortExecutionException) + e.message == "Maximum field count exceeded. 189 > 188" + } + + def "can capture depth and field count"() { + String schema = """ + type Query { + foo: Foo + } + + type Foo { + stop : String + bar : Bar + } + + type Bar { + stop : String + foo : Foo + } + """ + + GraphQLSchema graphQLSchema = TestUtil.schema(schema) + + String query = "{ foo { bar { foo { bar { foo { stop bar { stop }}}}}}}" + + assertValidQuery(graphQLSchema, query) + + Document document = TestUtil.parseQuery(query) + + when: + def result = ExecutableNormalizedOperationFactory.createExecutableNormalizedOperationWithRawVariables( + graphQLSchema, + document, + null, + RawVariables.emptyVariables() + ) + + then: + result.getOperationDepth() == 7 + result.getOperationFieldCount() == 8 + } + + private static ExecutableNormalizedOperation localCreateExecutableNormalizedOperation( + GraphQLSchema graphQLSchema, + Document document, + String operationName, + CoercedVariables coercedVariableValues + ) { + + def options = ExecutableNormalizedOperationFactory.Options.defaultOptions() + + return ExecutableNormalizedOperationFactory.createExecutableNormalizedOperation(graphQLSchema, document, operationName, coercedVariableValues, options) + } + + private static ExecutableNormalizedOperation localCreateExecutableNormalizedOperationWithRawVariables( + GraphQLSchema graphQLSchema, + Document document, + String operationName, + RawVariables rawVariables + ) { + + def options = ExecutableNormalizedOperationFactory.Options.defaultOptions() + + return ExecutableNormalizedOperationFactory.createExecutableNormalizedOperationWithRawVariables( + graphQLSchema, + document, + operationName, + rawVariables, + options + ) + } } diff --git a/src/test/java/benchmark/BenchmarkUtils.java b/src/test/java/benchmark/BenchmarkUtils.java index fd7897e125..3364b437e0 100644 --- a/src/test/java/benchmark/BenchmarkUtils.java +++ b/src/test/java/benchmark/BenchmarkUtils.java @@ -1,11 +1,13 @@ package benchmark; -import com.google.common.io.Files; -import graphql.Assert; - -import java.io.File; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.net.URL; import java.nio.charset.Charset; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.concurrent.Callable; public class BenchmarkUtils { @@ -17,7 +19,11 @@ static String loadResource(String name) { if (resource == null) { throw new IllegalArgumentException("missing resource: " + name); } - return String.join("\n", Files.readLines(new File(resource.toURI()), Charset.defaultCharset())); + byte[] bytes; + try (InputStream inputStream = resource.openStream()) { + bytes = inputStream.readAllBytes(); + } + return new String(bytes, Charset.defaultCharset()); }); } @@ -29,4 +35,50 @@ static T asRTE(Callable callable) { } } -} + public static void runInToolingForSomeTimeThenExit(Runnable setup, Runnable r, Runnable tearDown) { + int runForMillis = getRunForMillis(); + if (runForMillis <= 0) { + System.out.print("'runForMillis' environment var is not set - continuing \n"); + return; + } + System.out.printf("Running initial code in some tooling - runForMillis=%d \n", runForMillis); + System.out.print("Get your tooling in order and press enter..."); + readLine(); + System.out.print("Lets go...\n"); + setup.run(); + + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("HH:mm:ss"); + long now, then = System.currentTimeMillis(); + do { + now = System.currentTimeMillis(); + long msLeft = runForMillis - (now - then); + System.out.printf("\t%s Running in loop... %s ms left\n", dtf.format(LocalDateTime.now()), msLeft); + r.run(); + now = System.currentTimeMillis(); + } while ((now - then) < runForMillis); + + tearDown.run(); + + System.out.printf("This ran for %d millis. Exiting...\n", System.currentTimeMillis() - then); + System.exit(0); + } + + private static void readLine() { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + try { + br.readLine(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static int getRunForMillis() { + String runFor = System.getenv("runForMillis"); + try { + return Integer.parseInt(runFor); + } catch (NumberFormatException e) { + return -1; + } + } + +} \ No newline at end of file diff --git a/src/test/java/benchmark/ENFBenchmarkDeepIntrospection.java b/src/test/java/benchmark/ENFBenchmarkDeepIntrospection.java new file mode 100644 index 0000000000..0ed09d4675 --- /dev/null +++ b/src/test/java/benchmark/ENFBenchmarkDeepIntrospection.java @@ -0,0 +1,122 @@ +package benchmark; + +import graphql.execution.CoercedVariables; +import graphql.language.Document; +import graphql.normalized.ExecutableNormalizedOperation; +import graphql.normalized.ExecutableNormalizedOperationFactory; +import graphql.parser.Parser; +import graphql.schema.GraphQLSchema; +import graphql.schema.idl.SchemaGenerator; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.util.concurrent.TimeUnit; + +import static graphql.normalized.ExecutableNormalizedOperationFactory.*; + +@State(Scope.Benchmark) +@Warmup(iterations = 2, time = 5) +@Measurement(iterations = 3, time = 5) +@Fork(2) +public class ENFBenchmarkDeepIntrospection { + + @Param({"2", "10", "20"}) + int howDeep = 2; + + String query = ""; + + GraphQLSchema schema; + Document document; + + @Setup(Level.Trial) + public void setUp() { + String schemaString = BenchmarkUtils.loadResource("large-schema-2.graphqls"); + schema = SchemaGenerator.createdMockedSchema(schemaString); + + query = createDeepQuery(howDeep); + document = Parser.parse(query); + } + @Benchmark + @BenchmarkMode(Mode.AverageTime) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public ExecutableNormalizedOperation benchMarkAvgTime() { + ExecutableNormalizedOperationFactory.Options options = ExecutableNormalizedOperationFactory.Options.defaultOptions(); + ExecutableNormalizedOperation executableNormalizedOperation = createExecutableNormalizedOperation(schema, + document, + null, + CoercedVariables.emptyVariables(), + options); + return executableNormalizedOperation; + } + + public static void main(String[] args) throws RunnerException { + runAtStartup(); + + Options opt = new OptionsBuilder() + .include("benchmark.ENFBenchmarkDeepIntrospection") + .build(); + + new Runner(opt).run(); + } + + private static void runAtStartup() { + + ENFBenchmarkDeepIntrospection benchmarkIntrospection = new ENFBenchmarkDeepIntrospection(); + benchmarkIntrospection.howDeep = 2; + + BenchmarkUtils.runInToolingForSomeTimeThenExit( + benchmarkIntrospection::setUp, + () -> { while (true) { benchmarkIntrospection.benchMarkAvgTime(); }}, + () ->{} + ); + } + + + + private static String createDeepQuery(int depth) { + String result = "query test {\n" + + " __schema {\n" + + " types {\n" + + " ...F1\n" + + " }\n" + + " }\n" + + "}\n"; + + for (int i = 1; i < depth; i++) { + result += " fragment F" + i + " on __Type {\n" + + " fields {\n" + + " type {\n" + + " ...F" + (i + 1) +"\n" + + " }\n" + + " }\n" + + "\n" + + " ofType {\n" + + " ...F"+ (i + 1) + "\n" + + " }\n" + + " }\n"; + } + result += " fragment F" + depth + " on __Type {\n" + + " fields {\n" + + " type {\n" + + "name\n" + + " }\n" + + " }\n" + + "}\n"; + return result; + } + +} From 7efac415134ea7ccb35b7c73b463883eb608cd1b Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Fri, 22 Mar 2024 17:11:43 +1100 Subject: [PATCH 2/4] Remove references to defer --- .../ExecutableNormalizedOperationFactory.java | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java b/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java index a64fd07153..49756b8f0d 100644 --- a/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java +++ b/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java @@ -70,17 +70,13 @@ public static class Options { private final int maxChildrenDepth; private final int maxFieldsCount; - private final boolean deferSupport; - private Options(GraphQLContext graphQLContext, Locale locale, int maxChildrenDepth, - int maxFieldsCount, - boolean deferSupport) { + int maxFieldsCount) { this.graphQLContext = graphQLContext; this.locale = locale; this.maxChildrenDepth = maxChildrenDepth; - this.deferSupport = deferSupport; this.maxFieldsCount = maxFieldsCount; } @@ -89,8 +85,7 @@ public static Options defaultOptions() { GraphQLContext.getDefault(), Locale.getDefault(), Integer.MAX_VALUE, - Integer.MAX_VALUE, - false); + Integer.MAX_VALUE); } /** @@ -103,7 +98,7 @@ public static Options defaultOptions() { * @return new options object to use */ public Options locale(Locale locale) { - return new Options(this.graphQLContext, locale, this.maxChildrenDepth, this.maxFieldsCount, this.deferSupport); + return new Options(this.graphQLContext, locale, this.maxChildrenDepth, this.maxFieldsCount); } /** @@ -116,7 +111,7 @@ public Options locale(Locale locale) { * @return new options object to use */ public Options graphQLContext(GraphQLContext graphQLContext) { - return new Options(graphQLContext, this.locale, this.maxChildrenDepth, this.maxFieldsCount, this.deferSupport); + return new Options(graphQLContext, this.locale, this.maxChildrenDepth, this.maxFieldsCount); } /** @@ -128,7 +123,7 @@ public Options graphQLContext(GraphQLContext graphQLContext) { * @return new options object to use */ public Options maxChildrenDepth(int maxChildrenDepth) { - return new Options(this.graphQLContext, this.locale, maxChildrenDepth, this.maxFieldsCount, this.deferSupport); + return new Options(this.graphQLContext, this.locale, maxChildrenDepth, this.maxFieldsCount); } /** @@ -140,7 +135,7 @@ public Options maxChildrenDepth(int maxChildrenDepth) { * @return new options object to use */ public Options maxFieldsCount(int maxFieldsCount) { - return new Options(this.graphQLContext, this.locale, maxChildrenDepth, maxFieldsCount, this.deferSupport); + return new Options(this.graphQLContext, this.locale, maxChildrenDepth, maxFieldsCount); } /** From b4407299d73a10fd1efcfae6c363ff382aa43479 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 25 Mar 2024 18:27:19 +1100 Subject: [PATCH 3/4] Making tests consistent with approach in v19 and v20 backports --- ...tableNormalizedOperationFactoryTest.groovy | 191 +++++++++--------- 1 file changed, 94 insertions(+), 97 deletions(-) diff --git a/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy b/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy index cf1f7b497a..4d2435ad70 100644 --- a/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy +++ b/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy @@ -30,6 +30,7 @@ import static graphql.parser.Parser.parseValue import static graphql.schema.FieldCoordinates.coordinates class ExecutableNormalizedOperationFactoryTest extends Specification { + def "test"() { String schema = """ type Query{ @@ -114,8 +115,7 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, graphQLSchema) expect: @@ -200,8 +200,7 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, graphQLSchema) expect: @@ -280,8 +279,7 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, graphQLSchema) expect: @@ -331,8 +329,8 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTree(tree) expect: @@ -374,8 +372,8 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTree(tree) expect: @@ -424,8 +422,8 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTree(tree) expect: @@ -487,8 +485,8 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, graphQLSchema) expect: @@ -533,8 +531,8 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTree(tree) expect: @@ -577,8 +575,8 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTree(tree) expect: @@ -621,8 +619,8 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTree(tree) expect: @@ -653,8 +651,8 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTree(tree) expect: @@ -704,8 +702,8 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, graphQLSchema) expect: @@ -754,8 +752,8 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, graphQLSchema) expect: @@ -793,8 +791,8 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTree(tree) expect: @@ -837,8 +835,8 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - def dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, graphQLSchema) expect: @@ -877,8 +875,8 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - def dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, graphQLSchema) expect: @@ -925,8 +923,8 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - def dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, graphQLSchema) expect: @@ -1028,8 +1026,8 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) def subFooField = (document.getDefinitions()[1] as FragmentDefinition).getSelectionSet().getSelections()[0] as Field - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def fieldToNormalizedField = tree.getFieldToNormalizedField() expect: @@ -1071,8 +1069,8 @@ type Dog implements Animal{ def petsField = (document.getDefinitions()[0] as OperationDefinition).getSelectionSet().getSelections()[0] as Field def idField = petsField.getSelectionSet().getSelections()[0] as Field - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def fieldToNormalizedField = tree.getFieldToNormalizedField() @@ -1120,8 +1118,8 @@ type Dog implements Animal{ def schemaField = selections[2] as Field def typeField = selections[3] as Field - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def fieldToNormalizedField = tree.getFieldToNormalizedField() expect: @@ -1177,8 +1175,8 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, graphQLSchema) expect: @@ -1220,8 +1218,8 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTree(tree) expect: @@ -1248,8 +1246,8 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def normalizedFieldToMergedField = tree.getNormalizedFieldToMergedField() Traverser traverser = Traverser.depthFirst({ it.getChildren() }) List result = new ArrayList<>() @@ -1286,10 +1284,9 @@ type Dog implements Animal{ Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() when: - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def coordinatesToNormalizedFields = tree.coordinatesToNormalizedFields then: @@ -1387,8 +1384,8 @@ schema { Document document = TestUtil.parseQuery(mutation) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, graphQLSchema) expect: @@ -1437,7 +1434,7 @@ schema { assertValidQuery(graphQLSchema, query) Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() + def variables = [ var1: [bar: 123], var2: [foo: "foo", input2: [bar: 123]] @@ -1445,7 +1442,7 @@ schema { // the normalized arg value should be the same regardless of how the value was provided def expectedNormalizedArgValue = [foo: new NormalizedInputValue("String", parseValue('"foo"')), input2: new NormalizedInputValue("Input2", [bar: new NormalizedInputValue("Int", parseValue("123"))])] when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.of(variables)) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.of(variables)) def topLevelField = tree.getTopLevelFields().get(0) def secondField = topLevelField.getChildren().get(0) def arg1 = secondField.getNormalizedArgument("arg1") @@ -1484,9 +1481,9 @@ schema { assertValidQuery(graphQLSchema, query) def document = TestUtil.parseQuery(query) - def dependencyGraph = new ExecutableNormalizedOperationFactory() + when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.emptyVariables()) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.emptyVariables()) then: def topLevelField = tree.getTopLevelFields().get(0) @@ -1519,13 +1516,13 @@ schema { assertValidQuery(graphQLSchema, query) def document = TestUtil.parseQuery(query) - def dependencyGraph = new ExecutableNormalizedOperationFactory() + def variables = [ varIds : null, otherVar: null, ] when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.of(variables)) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.of(variables)) then: def topLevelField = tree.getTopLevelFields().get(0) @@ -1575,9 +1572,9 @@ schema { ] assertValidQuery(graphQLSchema, query, variables) Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() + when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.of(variables)) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.of(variables)) def topLevelField = tree.getTopLevelFields().get(0) def arg1 = topLevelField.getNormalizedArgument("arg1") def arg2 = topLevelField.getNormalizedArgument("arg2") @@ -1628,9 +1625,9 @@ schema { ] assertValidQuery(graphQLSchema, query, variables) Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() + when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.of(variables)) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.of(variables)) def topLevelField = tree.getTopLevelFields().get(0) def arg1 = topLevelField.getNormalizedArgument("arg1") def arg2 = topLevelField.getNormalizedArgument("arg2") @@ -1683,9 +1680,9 @@ schema { ''' assertValidQuery(graphQLSchema, query) Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() + when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.emptyVariables()) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.emptyVariables()) then: tree.normalizedFieldToMergedField.size() == 3 @@ -1741,9 +1738,9 @@ schema { ''' assertValidQuery(graphQLSchema, query) Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() + when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.emptyVariables()) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, graphQLSchema) then: @@ -1789,9 +1786,9 @@ schema { ''' assertValidQuery(graphQLSchema, query) Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() + when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.emptyVariables()) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, graphQLSchema) then: @@ -1865,9 +1862,9 @@ schema { ''' assertValidQuery(schema, query) Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() + when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, schema) then: @@ -1929,9 +1926,9 @@ schema { ''' assertValidQuery(schema, query) Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() + when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, schema) then: @@ -1986,9 +1983,9 @@ schema { ''' assertValidQuery(schema, query) Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() + when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, schema) then: @@ -2061,9 +2058,9 @@ schema { ''' assertValidQuery(schema, query) Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() + when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, schema) then: @@ -2123,9 +2120,9 @@ schema { ''' assertValidQuery(schema, query) Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() + when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, schema) then: @@ -2165,9 +2162,9 @@ schema { ''' assertValidQuery(schema, query) Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() + when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, schema) then: @@ -2208,9 +2205,9 @@ schema { ''' assertValidQuery(schema, query) Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() + when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, schema) then: @@ -2251,9 +2248,9 @@ schema { ''' assertValidQuery(schema, query) Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() + when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, schema) then: @@ -2326,9 +2323,9 @@ schema { ''' assertValidQuery(schema, query) Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() + when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, schema) then: @@ -2402,9 +2399,9 @@ schema { ''' assertValidQuery(schema, query) Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() + when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(schema, document, null, RawVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, schema) then: @@ -2464,9 +2461,9 @@ schema { def variables = ["true": Boolean.TRUE, "false": Boolean.FALSE] assertValidQuery(graphQLSchema, query, variables) Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() + when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.of(variables)) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.of(variables)) println String.join("\n", printTree(tree)) def printedTree = printTree(tree) @@ -2521,9 +2518,9 @@ schema { def variables = [:] assertValidQuery(graphQLSchema, query, variables) Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() + when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.of(variables)) + def tree = localCreateExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.of(variables)) def printedTree = printTreeAndDirectives(tree) then: @@ -2586,8 +2583,8 @@ fragment personName on Person { Document document = TestUtil.parseQuery(query) - ExecutableNormalizedOperationFactory dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, graphQLSchema) expect: @@ -2639,8 +2636,8 @@ fragment personName on Person { Document document = TestUtil.parseQuery(query) - def dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, graphQLSchema) expect: @@ -2686,8 +2683,8 @@ fragment personName on Person { Document document = TestUtil.parseQuery(query) - def dependencyGraph = new ExecutableNormalizedOperationFactory() - def tree = dependencyGraph.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def tree = localCreateExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) def printedTree = printTreeWithLevelInfo(tree, graphQLSchema) expect: @@ -3100,7 +3097,7 @@ fragment personName on Person { document, null, RawVariables.emptyVariables() - ) + ) then: result.getOperationDepth() == 7 From aadb3a4f7b7cc875417864a393c0679ff33607cc Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:00:18 +1100 Subject: [PATCH 4/4] Fix typo (backport PR #3544) --- .../normalized/ExecutableNormalizedOperationFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java b/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java index 49756b8f0d..cf22255d18 100644 --- a/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java +++ b/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java @@ -135,7 +135,7 @@ public Options maxChildrenDepth(int maxChildrenDepth) { * @return new options object to use */ public Options maxFieldsCount(int maxFieldsCount) { - return new Options(this.graphQLContext, this.locale, maxChildrenDepth, maxFieldsCount); + return new Options(this.graphQLContext, this.locale, this.maxChildrenDepth, maxFieldsCount); } /**