-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add max depth option to ExecutableNormalizedOperationFactory #3268
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |
| import graphql.GraphQLContext; | ||
| import graphql.PublicApi; | ||
| import graphql.collect.ImmutableKit; | ||
| import graphql.execution.AbortExecutionException; | ||
| import graphql.execution.CoercedVariables; | ||
| import graphql.execution.MergedField; | ||
| import graphql.execution.RawVariables; | ||
|
|
@@ -64,6 +65,85 @@ | |
| */ | ||
| @PublicApi | ||
| public class ExecutableNormalizedOperationFactory { | ||
| public static class Options { | ||
| private final GraphQLContext graphQLContext; | ||
| private final Locale locale; | ||
| private final int maxChildrenDepth; | ||
|
|
||
| private Options(GraphQLContext graphQLContext, | ||
| Locale locale, | ||
| int maxChildrenDepth) { | ||
| this.graphQLContext = graphQLContext; | ||
| this.locale = locale; | ||
| this.maxChildrenDepth = maxChildrenDepth; | ||
| } | ||
|
|
||
| public static Options defaultOptions() { | ||
| return new Options( | ||
| GraphQLContext.getDefault(), | ||
| Locale.getDefault(), | ||
| Integer.MAX_VALUE); | ||
| } | ||
|
|
||
| /** | ||
| * Locale to use when parsing the query. | ||
| * <p> | ||
| * 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); | ||
| } | ||
|
|
||
| /** | ||
| * Context object to use when parsing the operation. | ||
| * <p> | ||
| * 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); | ||
| } | ||
|
|
||
| /** | ||
| * Controls the maximum depth of the operation. Can be used to prevent | ||
| * 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 context to use during operation parsing | ||
| * @see #graphQLContext(GraphQLContext) | ||
| */ | ||
| public GraphQLContext getGraphQLContext() { | ||
| return graphQLContext; | ||
| } | ||
|
|
||
| /** | ||
| * @return locale to use during operation parsing | ||
| * @see #locale(Locale) | ||
| */ | ||
| public Locale getLocale() { | ||
| return locale; | ||
| } | ||
|
|
||
| /** | ||
| * @return maximum children depth before aborting parsing | ||
| * @see #maxChildrenDepth(int) | ||
| */ | ||
| public int getMaxChildrenDepth() { | ||
| return maxChildrenDepth; | ||
| } | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice one! but can we have some javadoc on what they do please since its API
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done 😄 |
||
|
|
||
| private final ConditionalNodes conditionalNodes = new ConditionalNodes(); | ||
|
|
||
|
|
@@ -90,8 +170,7 @@ public static ExecutableNormalizedOperation createExecutableNormalizedOperation( | |
| getOperationResult.fragmentsByName, | ||
| coercedVariableValues, | ||
| null, | ||
| GraphQLContext.getDefault(), | ||
| Locale.getDefault()); | ||
| Options.defaultOptions()); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -114,8 +193,7 @@ public static ExecutableNormalizedOperation createExecutableNormalizedOperation( | |
| fragments, | ||
| coercedVariableValues, | ||
| null, | ||
| GraphQLContext.getDefault(), | ||
| Locale.getDefault()); | ||
| Options.defaultOptions()); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -137,8 +215,7 @@ public static ExecutableNormalizedOperation createExecutableNormalizedOperationW | |
| document, | ||
| operationName, | ||
| rawVariables, | ||
| GraphQLContext.getDefault(), | ||
| Locale.getDefault()); | ||
| Options.defaultOptions()); | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -163,40 +240,64 @@ public static ExecutableNormalizedOperation createExecutableNormalizedOperationW | |
| GraphQLContext graphQLContext, | ||
| Locale locale | ||
| ) { | ||
| return createExecutableNormalizedOperationWithRawVariables( | ||
| graphQLSchema, | ||
| document, | ||
| operationName, | ||
| rawVariables, | ||
| Options.defaultOptions().graphQLContext(graphQLContext).locale(locale)); | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * 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 rawVariables the raw variables that have not yet been coerced | ||
| * @param options the {@link Options} to use for parsing | ||
| * | ||
| * @return a runtime representation of the graphql operation. | ||
| */ | ||
| public static ExecutableNormalizedOperation createExecutableNormalizedOperationWithRawVariables(GraphQLSchema graphQLSchema, | ||
| Document document, | ||
| String operationName, | ||
| RawVariables rawVariables, | ||
| Options options) { | ||
| NodeUtil.GetOperationResult getOperationResult = NodeUtil.getOperation(document, operationName); | ||
|
|
||
| return new ExecutableNormalizedOperationFactory().createExecutableNormalizedOperationImplWithRawVariables(graphQLSchema, | ||
| getOperationResult.operationDefinition, | ||
| getOperationResult.fragmentsByName, | ||
| rawVariables, | ||
| graphQLContext, | ||
| locale); | ||
| options | ||
| ); | ||
| } | ||
|
|
||
| private ExecutableNormalizedOperation createExecutableNormalizedOperationImplWithRawVariables(GraphQLSchema graphQLSchema, | ||
| OperationDefinition operationDefinition, | ||
| Map<String, FragmentDefinition> fragments, | ||
| RawVariables rawVariables, | ||
| GraphQLContext graphQLContext, | ||
| Locale locale) { | ||
|
|
||
| Options options) { | ||
| List<VariableDefinition> variableDefinitions = operationDefinition.getVariableDefinitions(); | ||
| CoercedVariables coercedVariableValues = ValuesResolver.coerceVariableValues(graphQLSchema, | ||
| variableDefinitions, | ||
| rawVariables, | ||
| graphQLContext, | ||
| locale); | ||
| options.getGraphQLContext(), | ||
| options.getLocale()); | ||
| Map<String, NormalizedInputValue> normalizedVariableValues = ValuesResolver.getNormalizedVariableValues(graphQLSchema, | ||
| variableDefinitions, | ||
| rawVariables, | ||
| graphQLContext, | ||
| locale); | ||
| options.getGraphQLContext(), | ||
| options.getLocale()); | ||
| return createNormalizedQueryImpl(graphQLSchema, | ||
| operationDefinition, | ||
| fragments, | ||
| coercedVariableValues, | ||
| normalizedVariableValues, | ||
| graphQLContext, | ||
| locale); | ||
| options); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -207,7 +308,7 @@ private ExecutableNormalizedOperation createNormalizedQueryImpl(GraphQLSchema gr | |
| Map<String, FragmentDefinition> fragments, | ||
| CoercedVariables coercedVariableValues, | ||
| @Nullable Map<String, NormalizedInputValue> normalizedVariableValues, | ||
| GraphQLContext graphQLContext, Locale locale) { | ||
| Options options) { | ||
| FieldCollectorNormalizedQueryParams parameters = FieldCollectorNormalizedQueryParams | ||
| .newParameters() | ||
| .fragments(fragments) | ||
|
|
@@ -226,8 +327,8 @@ private ExecutableNormalizedOperation createNormalizedQueryImpl(GraphQLSchema gr | |
| ImmutableListMultimap.Builder<FieldCoordinates, ExecutableNormalizedField> coordinatesToNormalizedFields = ImmutableListMultimap.builder(); | ||
|
|
||
| BiConsumer<ExecutableNormalizedField, MergedField> captureMergedField = (enf, mergedFld) -> { | ||
| //QueryDirectivesImpl is a lazy object and only computes itself when asked for | ||
| QueryDirectives queryDirectives = new QueryDirectivesImpl(mergedFld, graphQLSchema, coercedVariableValues.toMap(), graphQLContext, locale); | ||
| // 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); | ||
| }; | ||
|
|
@@ -241,16 +342,17 @@ private ExecutableNormalizedOperation createNormalizedQueryImpl(GraphQLSchema gr | |
| updateFieldToNFMap(topLevel, fieldAndAstParents, fieldToNormalizedField); | ||
| updateCoordinatedToNFMap(coordinatesToNormalizedFields, topLevel); | ||
|
|
||
| buildFieldWithChildren(topLevel, | ||
| buildFieldWithChildren( | ||
| topLevel, | ||
| fieldAndAstParents, | ||
| parameters, | ||
| fieldToNormalizedField, | ||
| captureMergedField, | ||
| coordinatesToNormalizedFields, | ||
| 1); | ||
|
|
||
| 1, | ||
| options.getMaxChildrenDepth()); | ||
| } | ||
| for (FieldCollectorNormalizedQueryParams.PossibleMerger possibleMerger : parameters.possibleMergerList) { | ||
| for (FieldCollectorNormalizedQueryParams.PossibleMerger possibleMerger : parameters.getPossibleMergerList()) { | ||
| List<ExecutableNormalizedField> childrenWithSameResultKey = possibleMerger.parent.getChildrenWithSameResultKey(possibleMerger.resultKey); | ||
| ENFMerger.merge(possibleMerger.parent, childrenWithSameResultKey, graphQLSchema); | ||
| } | ||
|
|
@@ -272,7 +374,12 @@ private void buildFieldWithChildren(ExecutableNormalizedField executableNormaliz | |
| ImmutableListMultimap.Builder<Field, ExecutableNormalizedField> fieldNormalizedField, | ||
| BiConsumer<ExecutableNormalizedField, MergedField> captureMergedField, | ||
| ImmutableListMultimap.Builder<FieldCoordinates, ExecutableNormalizedField> coordinatesToNormalizedFields, | ||
| int curLevel) { | ||
| int curLevel, | ||
| int maxLevel) { | ||
| if (curLevel > maxLevel) { | ||
| throw new AbortExecutionException("Maximum query depth exceeded " + curLevel + " > " + maxLevel); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Copied this from
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This makes sense - this is our |
||
| } | ||
|
|
||
| CollectNFResult nextLevel = collectFromMergedField(fieldCollectorNormalizedQueryParams, executableNormalizedField, fieldAndAstParents, curLevel + 1); | ||
|
|
||
| for (ExecutableNormalizedField childENF : nextLevel.children) { | ||
|
|
@@ -291,7 +398,8 @@ private void buildFieldWithChildren(ExecutableNormalizedField executableNormaliz | |
| fieldNormalizedField, | ||
| captureMergedField, | ||
| coordinatesToNormalizedFields, | ||
| curLevel + 1); | ||
| curLevel + 1, | ||
| maxLevel); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,7 +23,7 @@ public class FieldCollectorNormalizedQueryParams { | |
| private final GraphQLContext graphQLContext; | ||
| private final Locale locale; | ||
|
|
||
| public List<PossibleMerger> possibleMergerList = new ArrayList<>(); | ||
| private final List<PossibleMerger> possibleMergerList = new ArrayList<>(); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No reason for it to be public or not-final. The class is marked as |
||
|
|
||
| public static class PossibleMerger { | ||
| ExecutableNormalizedField parent; | ||
|
|
@@ -39,6 +39,10 @@ public void addPossibleMergers(ExecutableNormalizedField parent, String resultKe | |
| possibleMergerList.add(new PossibleMerger(parent, resultKey)); | ||
| } | ||
|
|
||
| public List<PossibleMerger> getPossibleMergerList() { | ||
| return possibleMergerList; | ||
| } | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice one |
||
| public GraphQLSchema getGraphQLSchema() { | ||
| return graphQLSchema; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added this new
Optionsclass which mimicks theOptionspattern seen in the repo.This stores things not directly related to the operation info.