diff --git a/src/main/java/graphql/GraphQLError.java b/src/main/java/graphql/GraphQLError.java index 59706738f..69873b591 100644 --- a/src/main/java/graphql/GraphQLError.java +++ b/src/main/java/graphql/GraphQLError.java @@ -15,7 +15,7 @@ * with times frames that cross graphql-java versions. While we don't change things unnecessarily, we may inadvertently break * the serialised compatibility across versions. * - * @see GraphQL Spec - 7.2.2 Errors + * @see GraphQL Spec - 7.1.2 Errors */ @PublicApi public interface GraphQLError extends Serializable { @@ -51,7 +51,7 @@ default List getPath() { * should be present. Certain JSON serializers may or may interpret the error to spec, so this method * is provided to produce a map that strictly follows the specification. * - * See : http://facebook.github.io/graphql/#sec-Errors + * See : GraphQL Spec - 7.1.2 Errors * * @return a map of the error that strictly follows the specification */ diff --git a/src/main/java/graphql/GraphqlErrorBuilder.java b/src/main/java/graphql/GraphqlErrorBuilder.java index 814558966..ee974ea5e 100644 --- a/src/main/java/graphql/GraphqlErrorBuilder.java +++ b/src/main/java/graphql/GraphqlErrorBuilder.java @@ -5,7 +5,7 @@ import graphql.language.SourceLocation; import graphql.schema.DataFetchingEnvironment; -import javax.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Map; diff --git a/src/main/java/graphql/analysis/MaxQueryComplexityInstrumentation.java b/src/main/java/graphql/analysis/MaxQueryComplexityInstrumentation.java index 762d13aa6..66523d70e 100644 --- a/src/main/java/graphql/analysis/MaxQueryComplexityInstrumentation.java +++ b/src/main/java/graphql/analysis/MaxQueryComplexityInstrumentation.java @@ -1,9 +1,14 @@ package graphql.analysis; +import graphql.ExecutionResult; import graphql.PublicApi; import graphql.execution.AbortExecutionException; +import graphql.execution.ExecutionContext; import graphql.execution.instrumentation.InstrumentationContext; +import graphql.execution.instrumentation.InstrumentationState; import graphql.execution.instrumentation.SimpleInstrumentation; +import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters; +import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters; import graphql.validation.ValidationError; import org.slf4j.Logger; @@ -12,10 +17,11 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import static graphql.Assert.assertNotNull; -import static graphql.execution.instrumentation.SimpleInstrumentationContext.whenCompleted; +import static graphql.execution.instrumentation.SimpleInstrumentationContext.noOp; import static java.util.Optional.ofNullable; /** @@ -76,41 +82,52 @@ public MaxQueryComplexityInstrumentation(int maxComplexity, FieldComplexityCalcu this.maxQueryComplexityExceededFunction = maxQueryComplexityExceededFunction; } + @Override + public InstrumentationState createState(InstrumentationCreateStateParameters parameters) { + return new State(); + } + @Override public InstrumentationContext> beginValidation(InstrumentationValidationParameters parameters) { - return whenCompleted((errors, throwable) -> { - if ((errors != null && errors.size() > 0) || throwable != null) { - return; - } - QueryTraverser queryTraverser = newQueryTraverser(parameters); - - Map valuesByParent = new LinkedHashMap<>(); - queryTraverser.visitPostOrder(new QueryVisitorStub() { - @Override - public void visitField(QueryVisitorFieldEnvironment env) { - int childsComplexity = valuesByParent.getOrDefault(env, 0); - int value = calculateComplexity(env, childsComplexity); - - valuesByParent.compute(env.getParentEnvironment(), (key, oldValue) -> - ofNullable(oldValue).orElse(0) + value - ); - } - }); - int totalComplexity = valuesByParent.getOrDefault(null, 0); - if (log.isDebugEnabled()) { - log.debug("Query complexity: {}", totalComplexity); - } - if (totalComplexity > maxComplexity) { - QueryComplexityInfo queryComplexityInfo = QueryComplexityInfo.newQueryComplexityInfo() - .complexity(totalComplexity) - .instrumentationValidationParameters(parameters) - .build(); - boolean throwAbortException = maxQueryComplexityExceededFunction.apply(queryComplexityInfo); - if (throwAbortException) { - throw mkAbortException(totalComplexity, maxComplexity); - } + State state = parameters.getInstrumentationState(); + // for API backwards compatibility reasons we capture the validation parameters, so we can put them into QueryComplexityInfo + state.instrumentationValidationParameters.set(parameters); + return noOp(); + } + + @Override + public InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters instrumentationExecuteOperationParameters) { + State state = instrumentationExecuteOperationParameters.getInstrumentationState(); + QueryTraverser queryTraverser = newQueryTraverser(instrumentationExecuteOperationParameters.getExecutionContext()); + + Map valuesByParent = new LinkedHashMap<>(); + queryTraverser.visitPostOrder(new QueryVisitorStub() { + @Override + public void visitField(QueryVisitorFieldEnvironment env) { + int childsComplexity = valuesByParent.getOrDefault(env, 0); + int value = calculateComplexity(env, childsComplexity); + + valuesByParent.compute(env.getParentEnvironment(), (key, oldValue) -> + ofNullable(oldValue).orElse(0) + value + ); } }); + int totalComplexity = valuesByParent.getOrDefault(null, 0); + if (log.isDebugEnabled()) { + log.debug("Query complexity: {}", totalComplexity); + } + if (totalComplexity > maxComplexity) { + QueryComplexityInfo queryComplexityInfo = QueryComplexityInfo.newQueryComplexityInfo() + .complexity(totalComplexity) + .instrumentationValidationParameters(state.instrumentationValidationParameters.get()) + .instrumentationExecuteOperationParameters(instrumentationExecuteOperationParameters) + .build(); + boolean throwAbortException = maxQueryComplexityExceededFunction.apply(queryComplexityInfo); + if (throwAbortException) { + throw mkAbortException(totalComplexity, maxComplexity); + } + } + return noOp(); } /** @@ -125,12 +142,12 @@ protected AbortExecutionException mkAbortException(int totalComplexity, int maxC return new AbortExecutionException("maximum query complexity exceeded " + totalComplexity + " > " + maxComplexity); } - QueryTraverser newQueryTraverser(InstrumentationValidationParameters parameters) { + QueryTraverser newQueryTraverser(ExecutionContext executionContext) { return QueryTraverser.newQueryTraverser() - .schema(parameters.getSchema()) - .document(parameters.getDocument()) - .operationName(parameters.getOperation()) - .variables(parameters.getVariables()) + .schema(executionContext.getGraphQLSchema()) + .document(executionContext.getDocument()) + .operationName(executionContext.getExecutionInput().getOperationName()) + .variables(executionContext.getVariables()) .build(); } @@ -156,5 +173,8 @@ private FieldComplexityEnvironment convertEnv(QueryVisitorFieldEnvironment query ); } + private static class State implements InstrumentationState { + AtomicReference instrumentationValidationParameters = new AtomicReference<>(); + } } diff --git a/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java b/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java index 785fd5d14..991a830a6 100644 --- a/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java +++ b/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java @@ -1,18 +1,18 @@ package graphql.analysis; +import graphql.ExecutionResult; import graphql.PublicApi; import graphql.execution.AbortExecutionException; +import graphql.execution.ExecutionContext; import graphql.execution.instrumentation.InstrumentationContext; import graphql.execution.instrumentation.SimpleInstrumentation; -import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters; -import graphql.validation.ValidationError; +import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; import java.util.function.Function; -import static graphql.execution.instrumentation.SimpleInstrumentationContext.whenCompleted; +import static graphql.execution.instrumentation.SimpleInstrumentationContext.noOp; /** * Prevents execution if the query depth is greater than the specified maxDepth. @@ -49,26 +49,22 @@ public MaxQueryDepthInstrumentation(int maxDepth, Function> beginValidation(InstrumentationValidationParameters parameters) { - return whenCompleted((errors, throwable) -> { - if ((errors != null && errors.size() > 0) || throwable != null) { - return; - } - QueryTraverser queryTraverser = newQueryTraverser(parameters); - int depth = queryTraverser.reducePreOrder((env, acc) -> Math.max(getPathLength(env.getParentEnvironment()), acc), 0); - if (log.isDebugEnabled()) { - log.debug("Query depth info: {}", depth); - } - if (depth > maxDepth) { - QueryDepthInfo queryDepthInfo = QueryDepthInfo.newQueryDepthInfo() - .depth(depth) - .build(); - boolean throwAbortException = maxQueryDepthExceededFunction.apply(queryDepthInfo); - if (throwAbortException) { - throw mkAbortException(depth, maxDepth); - } + public InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters parameters) { + QueryTraverser queryTraverser = newQueryTraverser(parameters.getExecutionContext()); + int depth = queryTraverser.reducePreOrder((env, acc) -> Math.max(getPathLength(env.getParentEnvironment()), acc), 0); + if (log.isDebugEnabled()) { + log.debug("Query depth info: {}", depth); + } + if (depth > maxDepth) { + QueryDepthInfo queryDepthInfo = QueryDepthInfo.newQueryDepthInfo() + .depth(depth) + .build(); + boolean throwAbortException = maxQueryDepthExceededFunction.apply(queryDepthInfo); + if (throwAbortException) { + throw mkAbortException(depth, maxDepth); } - }); + } + return noOp(); } /** @@ -83,12 +79,12 @@ protected AbortExecutionException mkAbortException(int depth, int maxDepth) { return new AbortExecutionException("maximum query depth exceeded " + depth + " > " + maxDepth); } - QueryTraverser newQueryTraverser(InstrumentationValidationParameters parameters) { + QueryTraverser newQueryTraverser(ExecutionContext executionContext) { return QueryTraverser.newQueryTraverser() - .schema(parameters.getSchema()) - .document(parameters.getDocument()) - .operationName(parameters.getOperation()) - .variables(parameters.getVariables()) + .schema(executionContext.getGraphQLSchema()) + .document(executionContext.getDocument()) + .operationName(executionContext.getExecutionInput().getOperationName()) + .variables(executionContext.getVariables()) .build(); } diff --git a/src/main/java/graphql/analysis/QueryComplexityInfo.java b/src/main/java/graphql/analysis/QueryComplexityInfo.java index 0d4e213bd..f4e86d0be 100644 --- a/src/main/java/graphql/analysis/QueryComplexityInfo.java +++ b/src/main/java/graphql/analysis/QueryComplexityInfo.java @@ -1,6 +1,7 @@ package graphql.analysis; import graphql.PublicApi; +import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters; /** @@ -10,11 +11,13 @@ public class QueryComplexityInfo { private final int complexity; - private InstrumentationValidationParameters instrumentationValidationParameters; + private final InstrumentationValidationParameters instrumentationValidationParameters; + private final InstrumentationExecuteOperationParameters instrumentationExecuteOperationParameters; - private QueryComplexityInfo(int complexity, InstrumentationValidationParameters parameters) { - this.complexity = complexity; - this.instrumentationValidationParameters = parameters; + private QueryComplexityInfo(Builder builder) { + this.complexity = builder.complexity; + this.instrumentationValidationParameters = builder.instrumentationValidationParameters; + this.instrumentationExecuteOperationParameters = builder.instrumentationExecuteOperationParameters; } /** @@ -35,6 +38,15 @@ public InstrumentationValidationParameters getInstrumentationValidationParameter return instrumentationValidationParameters; } + /** + * This returns the instrumentation execute operation parameters. + * + * @return the instrumentation execute operation parameters. + */ + public InstrumentationExecuteOperationParameters getInstrumentationExecuteOperationParameters() { + return instrumentationExecuteOperationParameters; + } + @Override public String toString() { return "QueryComplexityInfo{" + @@ -54,6 +66,7 @@ public static class Builder { private int complexity; private InstrumentationValidationParameters instrumentationValidationParameters; + private InstrumentationExecuteOperationParameters instrumentationExecuteOperationParameters; private Builder() { } @@ -62,6 +75,7 @@ private Builder() { * The query complexity. * * @param complexity the query complexity + * * @return this builder */ public Builder complexity(int complexity) { @@ -73,6 +87,7 @@ public Builder complexity(int complexity) { * The instrumentation validation parameters. * * @param parameters the instrumentation validation parameters. + * * @return this builder */ public Builder instrumentationValidationParameters(InstrumentationValidationParameters parameters) { @@ -80,11 +95,16 @@ public Builder instrumentationValidationParameters(InstrumentationValidationPara return this; } + public Builder instrumentationExecuteOperationParameters(InstrumentationExecuteOperationParameters instrumentationExecuteOperationParameters) { + this.instrumentationExecuteOperationParameters = instrumentationExecuteOperationParameters; + return this; + } + /** * @return a built {@link QueryComplexityInfo} object */ public QueryComplexityInfo build() { - return new QueryComplexityInfo(complexity, instrumentationValidationParameters); + return new QueryComplexityInfo(this); } } } diff --git a/src/main/java/graphql/cachecontrol/CacheControl.java b/src/main/java/graphql/cachecontrol/CacheControl.java index 1f5059f11..aef4141c9 100644 --- a/src/main/java/graphql/cachecontrol/CacheControl.java +++ b/src/main/java/graphql/cachecontrol/CacheControl.java @@ -42,7 +42,7 @@ public enum Scope { PUBLIC, PRIVATE } - private class Hint { + private static final class Hint { private final List path; private final Integer maxAge; private final Scope scope; diff --git a/src/main/java/graphql/execution/NonNullableValueCoercedAsNullException.java b/src/main/java/graphql/execution/NonNullableValueCoercedAsNullException.java index 5a9a5ca3e..4dfb68081 100644 --- a/src/main/java/graphql/execution/NonNullableValueCoercedAsNullException.java +++ b/src/main/java/graphql/execution/NonNullableValueCoercedAsNullException.java @@ -43,6 +43,15 @@ public NonNullableValueCoercedAsNullException(VariableDefinition variableDefinit this.path = path; } + public NonNullableValueCoercedAsNullException(GraphQLType graphQLType) { + super(format("Coerced Null value for NonNull type '%s'", GraphQLTypeUtil.simplePrint(graphQLType))); + } + + public NonNullableValueCoercedAsNullException(VariableDefinition variableDefinition, String causeMessage) { + super(format("Variable '%s' has an invalid value: %s", variableDefinition.getName(), causeMessage)); + this.sourceLocations = Collections.singletonList(variableDefinition.getSourceLocation()); + } + public NonNullableValueCoercedAsNullException(String fieldName, List path, GraphQLType graphQLType) { super(format("Field '%s' has coerced Null value for NonNull type '%s'", fieldName, GraphQLTypeUtil.simplePrint(graphQLType))); diff --git a/src/main/java/graphql/execution/ValuesResolver.java b/src/main/java/graphql/execution/ValuesResolver.java index b38a1086f..23c51a552 100644 --- a/src/main/java/graphql/execution/ValuesResolver.java +++ b/src/main/java/graphql/execution/ValuesResolver.java @@ -418,6 +418,8 @@ private Map externalValueToInternalValueForVariables(GraphQLSche .cause(e.getCause()) .sourceLocation(variableDefinition.getSourceLocation()) .build(); + } catch (NonNullableValueCoercedAsNullException e) { + throw new NonNullableValueCoercedAsNullException(variableDefinition, e.getMessage()); } } @@ -495,7 +497,7 @@ private Object externalValueToInternalValue(GraphqlFieldVisibility fieldVisibili Object returnValue = externalValueToInternalValue(fieldVisibility, unwrapOne(graphQLType), value); if (returnValue == null) { - throw new NonNullableValueCoercedAsNullException("", emptyList(), graphQLType); + throw new NonNullableValueCoercedAsNullException(graphQLType); } return returnValue; } diff --git a/src/main/java/graphql/execution/directives/QueryAppliedDirective.java b/src/main/java/graphql/execution/directives/QueryAppliedDirective.java index 50ad663ac..638a89004 100644 --- a/src/main/java/graphql/execution/directives/QueryAppliedDirective.java +++ b/src/main/java/graphql/execution/directives/QueryAppliedDirective.java @@ -8,8 +8,8 @@ import graphql.schema.GraphQLDirective; import graphql.schema.GraphqlTypeBuilder; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; @@ -47,7 +47,7 @@ private QueryAppliedDirective(String name, Directive definition, Collection instrumentations, InstrumentationCreateStateParameters parameters) { - instrumentationStates = new LinkedHashMap<>(instrumentations.size()); + instrumentationStates = Maps.newLinkedHashMapWithExpectedSize(instrumentations.size()); instrumentations.forEach(i -> instrumentationStates.put(i, i.createState(parameters))); } diff --git a/src/main/java/graphql/introspection/IntrospectionResultToSchema.java b/src/main/java/graphql/introspection/IntrospectionResultToSchema.java index 17b1587b0..a20e75994 100644 --- a/src/main/java/graphql/introspection/IntrospectionResultToSchema.java +++ b/src/main/java/graphql/introspection/IntrospectionResultToSchema.java @@ -230,7 +230,7 @@ EnumTypeDefinition createEnum(Map input) { for (Map enumValue : enumValues) { EnumValueDefinition.Builder enumValueDefinition = EnumValueDefinition.newEnumValueDefinition().name((String) enumValue.get("name")); - enumTypeDefinition.description(toDescription(input)); + enumValueDefinition.description(toDescription(enumValue)); createDeprecatedDirective(enumValue, enumValueDefinition); diff --git a/src/main/java/graphql/language/AstPrinter.java b/src/main/java/graphql/language/AstPrinter.java index 156716258..8d1585463 100644 --- a/src/main/java/graphql/language/AstPrinter.java +++ b/src/main/java/graphql/language/AstPrinter.java @@ -163,7 +163,7 @@ private NodePrinter fieldDefinition() { final String argSep = compactMode ? "," : ", "; return (out, node) -> { String args; - if (hasDescription(node.getInputValueDefinitions()) && !compactMode) { + if (hasDescription(Collections.singletonList(node)) && !compactMode) { out.append(description(node)); args = join(node.getInputValueDefinitions(), "\n"); out.append(node.getName()) diff --git a/src/main/java/graphql/language/Comment.java b/src/main/java/graphql/language/Comment.java index 6ab6163ac..a7a546fac 100644 --- a/src/main/java/graphql/language/Comment.java +++ b/src/main/java/graphql/language/Comment.java @@ -4,6 +4,9 @@ import java.io.Serializable; +/** + * A single-line comment. These are comments that start with a {@code #} in source documents. + */ @PublicApi public class Comment implements Serializable { public final String content; diff --git a/src/main/java/graphql/language/EnumValue.java b/src/main/java/graphql/language/EnumValue.java index 08eea9672..999b58712 100644 --- a/src/main/java/graphql/language/EnumValue.java +++ b/src/main/java/graphql/language/EnumValue.java @@ -40,6 +40,10 @@ public EnumValue(String name) { this(name, null, emptyList(), IgnoredChars.EMPTY, emptyMap()); } + public static EnumValue of(String name) { + return newEnumValue().name(name).build(); + } + @Override public String getName() { return name; diff --git a/src/main/java/graphql/language/NullValue.java b/src/main/java/graphql/language/NullValue.java index e69fc4054..6808b3caa 100644 --- a/src/main/java/graphql/language/NullValue.java +++ b/src/main/java/graphql/language/NullValue.java @@ -26,6 +26,10 @@ protected NullValue(SourceLocation sourceLocation, List comments, Ignor super(sourceLocation, comments, ignoredChars, additionalData); } + public static NullValue of() { + return NullValue.newNullValue().build(); + } + @Override public List getChildren() { return Collections.emptyList(); diff --git a/src/main/java/graphql/normalized/ValueToVariableValueCompiler.java b/src/main/java/graphql/normalized/ValueToVariableValueCompiler.java index 1743f7871..14e975bba 100644 --- a/src/main/java/graphql/normalized/ValueToVariableValueCompiler.java +++ b/src/main/java/graphql/normalized/ValueToVariableValueCompiler.java @@ -4,6 +4,7 @@ import graphql.Internal; import graphql.language.ArrayValue; import graphql.language.BooleanValue; +import graphql.language.EnumValue; import graphql.language.FloatValue; import graphql.language.IntValue; import graphql.language.NullValue; @@ -41,7 +42,7 @@ static VariableValueWithDefinition normalizedInputValueToVariable(NormalizedInpu @SuppressWarnings("unchecked") @Nullable - private static Object normalisedValueToVariableValue(Object maybeValue) { + static Object normalisedValueToVariableValue(Object maybeValue) { Object variableValue; if (maybeValue instanceof NormalizedInputValue) { NormalizedInputValue normalizedInputValue = (NormalizedInputValue) maybeValue; @@ -52,8 +53,10 @@ private static Object normalisedValueToVariableValue(Object maybeValue) { variableValue = normalisedValueToVariableValues((List) inputValue); } else if (inputValue instanceof Map) { variableValue = normalisedValueToVariableValues((Map) inputValue); + } else if (inputValue == null) { + variableValue = null; } else { - throw new AssertException("Should never happen. Did not expect NormalizedInputValue.getValue() of type: " + inputValue.getClass()); + throw new AssertException("Should never happen. Did not expect NormalizedInputValue.getValue() of type: " + maybeClass(inputValue)); } } else if (maybeValue instanceof Value) { Value value = (Value) maybeValue; @@ -63,7 +66,7 @@ private static Object normalisedValueToVariableValue(Object maybeValue) { } else if (maybeValue instanceof Map) { variableValue = normalisedValueToVariableValues((Map) maybeValue); } else { - throw new AssertException("Should never happen. Did not expect type: " + maybeValue.getClass()); + throw new AssertException("Should never happen. Did not expect type: " + maybeClass(maybeValue)); } return variableValue; } @@ -115,10 +118,16 @@ private static Object toVariableValue(Value value) { return ((IntValue) value).getValue(); } else if (value instanceof BooleanValue) { return ((BooleanValue) value).isValue(); + } else if (value instanceof EnumValue) { + return ((EnumValue) value).getName(); } else if (value instanceof NullValue) { return null; } - throw new AssertException("Should never happen. Cannot handle node of type: " + value.getClass()); + throw new AssertException("Should never happen. Cannot handle node of type: " + maybeClass(value)); + } + + private static Object maybeClass(Object maybe) { + return maybe == null ? "null" : maybe.getClass(); } private static String getVarName(int variableOrdinal) { diff --git a/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java b/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java index b1518ec83..87f29e95a 100644 --- a/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java +++ b/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java @@ -82,6 +82,7 @@ @Internal public class GraphqlAntlrToLanguage { + private static final List NO_COMMENTS = ImmutableKit.emptyList(); private static final int CHANNEL_COMMENTS = 2; private static final int CHANNEL_IGNORED_CHARS = 3; private final CommonTokenStream tokens; @@ -866,6 +867,10 @@ protected SourceLocation getSourceLocation(Token token) { } protected List getComments(ParserRuleContext ctx) { + if (!parserOptions.isCaptureLineComments()) { + return NO_COMMENTS; + } + Token start = ctx.getStart(); if (start != null) { int tokPos = start.getTokenIndex(); @@ -874,7 +879,7 @@ protected List getComments(ParserRuleContext ctx) { return getCommentOnChannel(refChannel); } } - return ImmutableKit.emptyList(); + return NO_COMMENTS; } diff --git a/src/main/java/graphql/parser/ParserOptions.java b/src/main/java/graphql/parser/ParserOptions.java index e7d4cbdc5..234605bca 100644 --- a/src/main/java/graphql/parser/ParserOptions.java +++ b/src/main/java/graphql/parser/ParserOptions.java @@ -21,11 +21,12 @@ public class ParserOptions { * If you want to allow more, then {@link #setDefaultParserOptions(ParserOptions)} allows you to change this * JVM wide. */ - public static int MAX_QUERY_TOKENS = 15000; + public static final int MAX_QUERY_TOKENS = 15000; private static ParserOptions defaultJvmParserOptions = newParserOptions() .captureIgnoredChars(false) .captureSourceLocation(true) + .captureLineComments(true) .maxTokens(MAX_QUERY_TOKENS) // to prevent a billion laughs style attacks, we set a default for graphql-java .build(); @@ -66,12 +67,14 @@ public static void setDefaultParserOptions(ParserOptions options) { private final boolean captureIgnoredChars; private final boolean captureSourceLocation; + private final boolean captureLineComments; private final int maxTokens; private final ParsingListener parsingListener; private ParserOptions(Builder builder) { this.captureIgnoredChars = builder.captureIgnoredChars; this.captureSourceLocation = builder.captureSourceLocation; + this.captureLineComments = builder.captureLineComments; this.maxTokens = builder.maxTokens; this.parsingListener = builder.parsingListener; } @@ -80,7 +83,7 @@ private ParserOptions(Builder builder) { * Significant memory savings can be made if we do NOT capture ignored characters, * especially in SDL parsing. So we have set this to false by default. * - * @return true if ignored chars are captured in AST nodes + * @return true if ignored chars should be captured as AST nodes */ public boolean isCaptureIgnoredChars() { return captureIgnoredChars; @@ -91,7 +94,7 @@ public boolean isCaptureIgnoredChars() { * Memory savings can be made if we do NOT set {@link graphql.language.SourceLocation}s * on AST nodes, especially in SDL parsing. * - * @return true if {@link graphql.language.SourceLocation}s are captured in AST nodes + * @return true if {@link graphql.language.SourceLocation}s should be captured as AST nodes * * @see graphql.language.SourceLocation */ @@ -99,6 +102,20 @@ public boolean isCaptureSourceLocation() { return captureSourceLocation; } + /** + * Single-line {@link graphql.language.Comment}s do not have any semantic meaning in + * GraphQL source documents, as such you may wish to ignore them. + *

+ * This option does not ignore documentation {@link graphql.language.Description}s. + * + * @return true if {@link graphql.language.Comment}s should be captured as AST nodes + * + * @see graphql.language.SourceLocation + */ + public boolean isCaptureLineComments() { + return captureLineComments; + } + /** * A graphql hacking vector is to send nonsensical queries that burn lots of parsing CPU time and burn * memory representing a document that won't ever execute. To prevent this you can set a maximum number of parse @@ -128,6 +145,7 @@ public static class Builder { private boolean captureIgnoredChars = false; private boolean captureSourceLocation = true; + private boolean captureLineComments = true; private int maxTokens = MAX_QUERY_TOKENS; private ParsingListener parsingListener = ParsingListener.NOOP; @@ -137,6 +155,7 @@ public static class Builder { Builder(ParserOptions parserOptions) { this.captureIgnoredChars = parserOptions.captureIgnoredChars; this.captureSourceLocation = parserOptions.captureSourceLocation; + this.captureLineComments = parserOptions.captureLineComments; this.maxTokens = parserOptions.maxTokens; this.parsingListener = parserOptions.parsingListener; } @@ -151,6 +170,11 @@ public Builder captureSourceLocation(boolean captureSourceLocation) { return this; } + public Builder captureLineComments(boolean captureLineComments) { + this.captureLineComments = captureLineComments; + return this; + } + public Builder maxTokens(int maxTokens) { this.maxTokens = maxTokens; return this; diff --git a/src/main/java/graphql/parser/UnicodeUtil.java b/src/main/java/graphql/parser/UnicodeUtil.java index 53d1cefe0..cdf55ace4 100644 --- a/src/main/java/graphql/parser/UnicodeUtil.java +++ b/src/main/java/graphql/parser/UnicodeUtil.java @@ -13,11 +13,11 @@ */ @Internal public class UnicodeUtil { - public static int MAX_UNICODE_CODE_POINT = 0x10FFFF; - public static int LEADING_SURROGATE_LOWER_BOUND = 0xD800; - public static int LEADING_SURROGATE_UPPER_BOUND = 0xDBFF; - public static int TRAILING_SURROGATE_LOWER_BOUND = 0xDC00; - public static int TRAILING_SURROGATE_UPPER_BOUND = 0xDFFF; + public static final int MAX_UNICODE_CODE_POINT = 0x10FFFF; + public static final int LEADING_SURROGATE_LOWER_BOUND = 0xD800; + public static final int LEADING_SURROGATE_UPPER_BOUND = 0xDBFF; + public static final int TRAILING_SURROGATE_LOWER_BOUND = 0xDC00; + public static final int TRAILING_SURROGATE_UPPER_BOUND = 0xDFFF; public static int parseAndWriteUnicode(StringWriter writer, String string, int i, SourceLocation sourceLocation) { // Unicode code points can either be: @@ -31,7 +31,7 @@ public static int parseAndWriteUnicode(StringWriter writer, String string, int i int continueIndex = isBracedEscape(string, i) ? endIndexExclusive : endIndexExclusive - 1; String hexStr = string.substring(startIndex, endIndexExclusive); - Integer codePoint = Integer.parseInt(hexStr, 16); + int codePoint = Integer.parseInt(hexStr, 16); if (isTrailingSurrogateValue(codePoint)) { throw new InvalidSyntaxException(sourceLocation, "Invalid unicode - trailing surrogate must be preceded with a leading surrogate -", null, string.substring(i - 1, continueIndex + 1), null); @@ -45,7 +45,7 @@ public static int parseAndWriteUnicode(StringWriter writer, String string, int i int trailingStartIndex = isBracedEscape(string, i) ? i + 2 : i + 1; int trailingEndIndexExclusive = getEndIndexExclusive(string, i, sourceLocation); String trailingHexStr = string.substring(trailingStartIndex, trailingEndIndexExclusive); - Integer trailingCodePoint = Integer.parseInt(trailingHexStr, 16); + int trailingCodePoint = Integer.parseInt(trailingHexStr, 16); continueIndex = isBracedEscape(string, i) ? trailingEndIndexExclusive : trailingEndIndexExclusive - 1; if (isTrailingSurrogateValue(trailingCodePoint)) { diff --git a/src/main/java/graphql/schema/DelegatingDataFetchingEnvironment.java b/src/main/java/graphql/schema/DelegatingDataFetchingEnvironment.java index 3758d7318..e4f955242 100644 --- a/src/main/java/graphql/schema/DelegatingDataFetchingEnvironment.java +++ b/src/main/java/graphql/schema/DelegatingDataFetchingEnvironment.java @@ -39,26 +39,33 @@ public DelegatingDataFetchingEnvironment(DataFetchingEnvironment delegateEnviron this.delegateEnvironment = delegateEnvironment; } + @Override public T getSource() { return delegateEnvironment.getSource(); } + @Override public Map getArguments() { return delegateEnvironment.getArguments(); } + @Override public boolean containsArgument(String name) { return delegateEnvironment.containsArgument(name); } + @Override public T getArgument(String name) { return delegateEnvironment.getArgument(name); } + @Override public T getArgumentOrDefault(String name, T defaultValue) { return delegateEnvironment.getArgumentOrDefault(name, defaultValue); } + @Deprecated + @Override public T getContext() { return delegateEnvironment.getContext(); } @@ -68,63 +75,78 @@ public GraphQLContext getGraphQlContext() { return delegateEnvironment.getGraphQlContext(); } + @Override public T getLocalContext() { return delegateEnvironment.getLocalContext(); } + @Override public T getRoot() { return delegateEnvironment.getRoot(); } + @Override public GraphQLFieldDefinition getFieldDefinition() { return delegateEnvironment.getFieldDefinition(); } @Deprecated + @Override public List getFields() { return delegateEnvironment.getFields(); } + @Override public MergedField getMergedField() { return delegateEnvironment.getMergedField(); } + @Override public Field getField() { return delegateEnvironment.getField(); } + @Override public GraphQLOutputType getFieldType() { return delegateEnvironment.getFieldType(); } + @Override public ExecutionStepInfo getExecutionStepInfo() { return delegateEnvironment.getExecutionStepInfo(); } + @Override public GraphQLType getParentType() { return delegateEnvironment.getParentType(); } + @Override public GraphQLSchema getGraphQLSchema() { return delegateEnvironment.getGraphQLSchema(); } + @Override public Map getFragmentsByName() { return delegateEnvironment.getFragmentsByName(); } + @Override public ExecutionId getExecutionId() { return delegateEnvironment.getExecutionId(); } + @Override public DataFetchingFieldSelectionSet getSelectionSet() { return delegateEnvironment.getSelectionSet(); } + @Override public QueryDirectives getQueryDirectives() { return delegateEnvironment.getQueryDirectives(); } + @Override public DataLoader getDataLoader(String dataLoaderName) { return delegateEnvironment.getDataLoader(dataLoaderName); } @@ -139,18 +161,22 @@ public Locale getLocale() { return delegateEnvironment.getLocale(); } + @Override public CacheControl getCacheControl() { return delegateEnvironment.getCacheControl(); } + @Override public OperationDefinition getOperationDefinition() { return delegateEnvironment.getOperationDefinition(); } + @Override public Document getDocument() { return delegateEnvironment.getDocument(); } + @Override public Map getVariables() { return delegateEnvironment.getVariables(); } diff --git a/src/main/java/graphql/schema/GraphQLTypeResolvingVisitor.java b/src/main/java/graphql/schema/GraphQLTypeResolvingVisitor.java index d5faa84b8..0380ac5cd 100644 --- a/src/main/java/graphql/schema/GraphQLTypeResolvingVisitor.java +++ b/src/main/java/graphql/schema/GraphQLTypeResolvingVisitor.java @@ -60,8 +60,8 @@ public TraversalControl visitBackRef(TraverserContext cont return CONTINUE; } - private class TypeRefResolvingVisitor extends GraphQLTypeVisitorStub { - protected final GraphQLType resolvedType; + private static final class TypeRefResolvingVisitor extends GraphQLTypeVisitorStub { + private final GraphQLType resolvedType; TypeRefResolvingVisitor(GraphQLType resolvedType) { this.resolvedType = resolvedType; diff --git a/src/main/java/graphql/schema/idl/DirectiveInfo.java b/src/main/java/graphql/schema/idl/DirectiveInfo.java index 992074425..4b6159ee2 100644 --- a/src/main/java/graphql/schema/idl/DirectiveInfo.java +++ b/src/main/java/graphql/schema/idl/DirectiveInfo.java @@ -1,11 +1,11 @@ package graphql.schema.idl; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import graphql.Directives; import graphql.PublicApi; import graphql.schema.GraphQLDirective; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -18,27 +18,20 @@ public class DirectiveInfo { /** * A set of directives which provided by graphql specification */ - public static final Set GRAPHQL_SPECIFICATION_DIRECTIVES = new LinkedHashSet<>(); + public static final Set GRAPHQL_SPECIFICATION_DIRECTIVES = ImmutableSet.of( + Directives.IncludeDirective, + Directives.SkipDirective, + Directives.DeprecatedDirective, + Directives.SpecifiedByDirective); /** * A map from directive name to directive which provided by specification */ - public static final Map GRAPHQL_SPECIFICATION_DIRECTIVE_MAP = new LinkedHashMap<>(); - - static { - GRAPHQL_SPECIFICATION_DIRECTIVES.add(Directives.IncludeDirective); - GRAPHQL_SPECIFICATION_DIRECTIVES.add(Directives.SkipDirective); - GRAPHQL_SPECIFICATION_DIRECTIVES.add(Directives.DeprecatedDirective); - GRAPHQL_SPECIFICATION_DIRECTIVES.add(Directives.SpecifiedByDirective); - } - - static { - GRAPHQL_SPECIFICATION_DIRECTIVE_MAP.put(Directives.IncludeDirective.getName(), Directives.IncludeDirective); - GRAPHQL_SPECIFICATION_DIRECTIVE_MAP.put(Directives.SkipDirective.getName(), Directives.SkipDirective); - GRAPHQL_SPECIFICATION_DIRECTIVE_MAP.put(Directives.DeprecatedDirective.getName(), Directives.DeprecatedDirective); - GRAPHQL_SPECIFICATION_DIRECTIVE_MAP.put(Directives.SpecifiedByDirective.getName(), Directives.SpecifiedByDirective); - } - + public static final Map GRAPHQL_SPECIFICATION_DIRECTIVE_MAP = ImmutableMap.of( + Directives.IncludeDirective.getName(), Directives.IncludeDirective, + Directives.SkipDirective.getName(), Directives.SkipDirective, + Directives.DeprecatedDirective.getName(), Directives.DeprecatedDirective, + Directives.SpecifiedByDirective.getName(), Directives.SpecifiedByDirective); /** * Returns true if a directive with provided directiveName has been defined in graphql specification diff --git a/src/main/java/graphql/schema/idl/ScalarInfo.java b/src/main/java/graphql/schema/idl/ScalarInfo.java index 13e5a89f1..910b78f4b 100644 --- a/src/main/java/graphql/schema/idl/ScalarInfo.java +++ b/src/main/java/graphql/schema/idl/ScalarInfo.java @@ -1,12 +1,12 @@ package graphql.schema.idl; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import graphql.PublicApi; import graphql.Scalars; import graphql.language.ScalarTypeDefinition; import graphql.schema.GraphQLScalarType; -import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -19,30 +19,22 @@ public class ScalarInfo { /** * A list of the built-in scalar types as defined by the graphql specification */ - public static final List GRAPHQL_SPECIFICATION_SCALARS = new ArrayList<>(); + public static final List GRAPHQL_SPECIFICATION_SCALARS = ImmutableList.of( + Scalars.GraphQLInt, + Scalars.GraphQLFloat, + Scalars.GraphQLString, + Scalars.GraphQLBoolean, + Scalars.GraphQLID); /** * A map of scalar type definitions provided by graphql-java */ - public static final Map GRAPHQL_SPECIFICATION_SCALARS_DEFINITIONS = new LinkedHashMap<>(); - - static { - GRAPHQL_SPECIFICATION_SCALARS.add(Scalars.GraphQLInt); - GRAPHQL_SPECIFICATION_SCALARS.add(Scalars.GraphQLFloat); - GRAPHQL_SPECIFICATION_SCALARS.add(Scalars.GraphQLString); - GRAPHQL_SPECIFICATION_SCALARS.add(Scalars.GraphQLBoolean); - GRAPHQL_SPECIFICATION_SCALARS.add(Scalars.GraphQLID); - } - - static { - // graphql standard scalars - GRAPHQL_SPECIFICATION_SCALARS_DEFINITIONS.put("Int", ScalarTypeDefinition.newScalarTypeDefinition().name("Int").build()); - GRAPHQL_SPECIFICATION_SCALARS_DEFINITIONS.put("Float", ScalarTypeDefinition.newScalarTypeDefinition().name("Float").build()); - GRAPHQL_SPECIFICATION_SCALARS_DEFINITIONS.put("String", ScalarTypeDefinition.newScalarTypeDefinition().name("String").build()); - GRAPHQL_SPECIFICATION_SCALARS_DEFINITIONS.put("Boolean", ScalarTypeDefinition.newScalarTypeDefinition().name("Boolean").build()); - GRAPHQL_SPECIFICATION_SCALARS_DEFINITIONS.put("ID", ScalarTypeDefinition.newScalarTypeDefinition().name("ID").build()); - - } + public static final Map GRAPHQL_SPECIFICATION_SCALARS_DEFINITIONS = ImmutableMap.of( + "Int", ScalarTypeDefinition.newScalarTypeDefinition().name("Int").build(), + "Float", ScalarTypeDefinition.newScalarTypeDefinition().name("Float").build(), + "String", ScalarTypeDefinition.newScalarTypeDefinition().name("String").build(), + "Boolean", ScalarTypeDefinition.newScalarTypeDefinition().name("Boolean").build(), + "ID", ScalarTypeDefinition.newScalarTypeDefinition().name("ID").build()); /** * Returns true if the scalar type is a scalar that is specified by the graphql specification diff --git a/src/main/java/graphql/schema/idl/TypeInfo.java b/src/main/java/graphql/schema/idl/TypeInfo.java index fcc34b3fb..fe12d4988 100644 --- a/src/main/java/graphql/schema/idl/TypeInfo.java +++ b/src/main/java/graphql/schema/idl/TypeInfo.java @@ -8,8 +8,9 @@ import graphql.language.TypeName; import graphql.schema.GraphQLType; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.Objects; -import java.util.Stack; import static graphql.Assert.assertNotNull; import static graphql.schema.GraphQLList.list; @@ -27,7 +28,7 @@ public static TypeInfo typeInfo(Type type) { private final Type rawType; private final TypeName typeName; - private final Stack> decoration = new Stack<>(); + private final Deque> decoration = new ArrayDeque<>(); private TypeInfo(Type type) { this.rawType = assertNotNull(type, () -> "type must not be null"); @@ -79,8 +80,7 @@ public TypeInfo renameAs(String newName) { Type out = TypeName.newTypeName(newName).build(); - Stack> wrappingStack = new Stack<>(); - wrappingStack.addAll(this.decoration); + Deque> wrappingStack = new ArrayDeque<>(this.decoration); while (!wrappingStack.isEmpty()) { Class clazz = wrappingStack.pop(); if (clazz.equals(NonNullType.class)) { @@ -106,8 +106,7 @@ public TypeInfo renameAs(String newName) { public T decorate(GraphQLType objectType) { GraphQLType out = objectType; - Stack> wrappingStack = new Stack<>(); - wrappingStack.addAll(this.decoration); + Deque> wrappingStack = new ArrayDeque<>(this.decoration); while (!wrappingStack.isEmpty()) { Class clazz = wrappingStack.pop(); if (clazz.equals(NonNullType.class)) { diff --git a/src/main/java/graphql/schema/idl/TypeRuntimeWiring.java b/src/main/java/graphql/schema/idl/TypeRuntimeWiring.java index e0c4079ca..60bfe6f60 100644 --- a/src/main/java/graphql/schema/idl/TypeRuntimeWiring.java +++ b/src/main/java/graphql/schema/idl/TypeRuntimeWiring.java @@ -152,7 +152,7 @@ public Builder typeResolver(TypeResolver typeResolver) { } public Builder enumValues(EnumValuesProvider enumValuesProvider) { - assertNotNull(enumValuesProvider, () -> "you must provide a type resolver"); + assertNotNull(enumValuesProvider, () -> "you must provide an enum values provider"); this.enumValuesProvider = enumValuesProvider; return this; } diff --git a/src/main/java/graphql/schema/idl/errors/NonSDLDefinitionError.java b/src/main/java/graphql/schema/idl/errors/NonSDLDefinitionError.java index 1f770b701..fb41b7b7d 100644 --- a/src/main/java/graphql/schema/idl/errors/NonSDLDefinitionError.java +++ b/src/main/java/graphql/schema/idl/errors/NonSDLDefinitionError.java @@ -9,7 +9,7 @@ public class NonSDLDefinitionError extends BaseError { public NonSDLDefinitionError(Definition definition) { - super(definition, format("The schema definition text contains a non schema definition language (SDL) element '%s'", - definition.getClass().getSimpleName(), lineCol(definition), lineCol(definition))); + super(definition, format("%s The schema definition text contains a non schema definition language (SDL) element '%s'", + lineCol(definition), definition.getClass().getSimpleName())); } } diff --git a/src/main/java/graphql/util/Anonymizer.java b/src/main/java/graphql/util/Anonymizer.java index 6720e3bca..846c842f4 100644 --- a/src/main/java/graphql/util/Anonymizer.java +++ b/src/main/java/graphql/util/Anonymizer.java @@ -702,7 +702,7 @@ private static List getMatchingArgumentDefinitions( Set fieldDefinitions) { List result = new ArrayList<>(); for (GraphQLFieldDefinition fieldDefinition : fieldDefinitions) { - Optional.ofNullable(fieldDefinition.getArgument(name)).map(result::add); + Optional.ofNullable(fieldDefinition.getArgument(name)).ifPresent(result::add); } return result; } @@ -738,7 +738,7 @@ public void visitField(QueryVisitorFieldEnvironment env) { for (Argument argument : directive.getArguments()) { GraphQLArgument argumentDefinition = directiveDefinition.getArgument(argument.getName()); - String newArgumentName = assertNotNull(newNames.get(argumentDefinition), () -> format("%s no new name found for directive argument %s %s", directiveName, argument.getName())); + String newArgumentName = assertNotNull(newNames.get(argumentDefinition), () -> format("No new name found for directive %s argument %s", directiveName, argument.getName())); astNodeToNewName.put(argument, newArgumentName); visitDirectiveArgumentValues(directive, argument.getValue()); } diff --git a/src/main/java/graphql/validation/rules/UniqueArgumentNamesRule.java b/src/main/java/graphql/validation/rules/UniqueArgumentNamesRule.java index 5865ee2b1..b43f19e93 100644 --- a/src/main/java/graphql/validation/rules/UniqueArgumentNamesRule.java +++ b/src/main/java/graphql/validation/rules/UniqueArgumentNamesRule.java @@ -1,5 +1,6 @@ package graphql.validation.rules; +import com.google.common.collect.Sets; import graphql.Internal; import graphql.language.Argument; import graphql.language.Directive; @@ -10,7 +11,6 @@ import graphql.validation.ValidationErrorCollector; import graphql.validation.ValidationErrorType; -import java.util.HashSet; import java.util.List; import java.util.Set; @@ -31,7 +31,7 @@ public void checkField(Field field) { return; } - Set arguments = new HashSet<>(); + Set arguments = Sets.newHashSetWithExpectedSize(field.getArguments().size()); for (Argument argument : field.getArguments()) { if (arguments.contains(argument.getName())) { @@ -48,7 +48,7 @@ public void checkDirective(Directive directive, List ancestors) { return; } - Set arguments = new HashSet<>(directive.getArguments().size()); + Set arguments = Sets.newHashSetWithExpectedSize(directive.getArguments().size()); for (Argument argument : directive.getArguments()) { if (arguments.contains(argument.getName())) { diff --git a/src/test/groovy/graphql/Issue743.groovy b/src/test/groovy/graphql/Issue743.groovy index 183d63c80..b6466c65d 100644 --- a/src/test/groovy/graphql/Issue743.groovy +++ b/src/test/groovy/graphql/Issue743.groovy @@ -28,6 +28,6 @@ class Issue743 extends Specification { executionResult.data == null executionResult.errors.size() == 1 executionResult.errors[0].errorType == ErrorType.ValidationError - executionResult.errors[0].message == "Variable 'isTrue' has coerced Null value for NonNull type 'Boolean!'" + executionResult.errors[0].message == "Variable 'isTrue' has an invalid value: Variable 'isTrue' has coerced Null value for NonNull type 'Boolean!'" } } diff --git a/src/test/groovy/graphql/NullVariableCoercionTest.groovy b/src/test/groovy/graphql/NullVariableCoercionTest.groovy index 253b9a58c..ffd42ac36 100644 --- a/src/test/groovy/graphql/NullVariableCoercionTest.groovy +++ b/src/test/groovy/graphql/NullVariableCoercionTest.groovy @@ -1,6 +1,6 @@ package graphql - +import graphql.language.SourceLocation import graphql.schema.DataFetcher import graphql.schema.GraphQLObjectType import graphql.schema.idl.RuntimeWiring @@ -68,8 +68,8 @@ class NullVariableCoercionTest extends Specification { varResult.data == null varResult.errors.size() == 1 varResult.errors[0].errorType == ErrorType.ValidationError - varResult.errors[0].message == "Field 'baz' has coerced Null value for NonNull type 'String!'" -// varResult.errors[0].locations == [new SourceLocation(1, 11)] + varResult.errors[0].message == "Variable 'input' has an invalid value: Field 'baz' has coerced Null value for NonNull type 'String!'" + varResult.errors[0].locations == [new SourceLocation(1, 11)] } def "can handle defaulting on complex input objects"() { diff --git a/src/test/groovy/graphql/analysis/MaxQueryComplexityInstrumentationTest.groovy b/src/test/groovy/graphql/analysis/MaxQueryComplexityInstrumentationTest.groovy index 704a1c9c6..3670b4fe1 100644 --- a/src/test/groovy/graphql/analysis/MaxQueryComplexityInstrumentationTest.groovy +++ b/src/test/groovy/graphql/analysis/MaxQueryComplexityInstrumentationTest.groovy @@ -3,12 +3,14 @@ package graphql.analysis import graphql.ExecutionInput import graphql.TestUtil import graphql.execution.AbortExecutionException -import graphql.execution.instrumentation.InstrumentationContext +import graphql.execution.ExecutionContext +import graphql.execution.ExecutionContextBuilder +import graphql.execution.ExecutionId +import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters import graphql.language.Document import graphql.parser.Parser -import graphql.validation.ValidationError -import graphql.validation.ValidationErrorType +import graphql.schema.GraphQLSchema import spock.lang.Specification import java.util.function.Function @@ -20,61 +22,6 @@ class MaxQueryComplexityInstrumentationTest extends Specification { parser.parseDocument(query) } - def "doesn't do anything if validation errors occur"() { - given: - def schema = TestUtil.schema(""" - type Query{ - bar: String - } - """) - def query = createQuery(""" - { bar { thisIsWrong } } - """) - def queryTraversal = Mock(QueryTraverser) - MaxQueryComplexityInstrumentation maxQueryComplexityInstrumentation = new MaxQueryComplexityInstrumentation(6) { - - @Override - QueryTraverser newQueryTraverser(InstrumentationValidationParameters parameters) { - return queryTraversal - } - } - ExecutionInput executionInput = Mock(ExecutionInput) - InstrumentationValidationParameters validationParameters = new InstrumentationValidationParameters(executionInput, query, schema, null) - InstrumentationContext instrumentationContext = maxQueryComplexityInstrumentation.beginValidation(validationParameters) - when: - instrumentationContext.onCompleted([new ValidationError(ValidationErrorType.SubSelectionNotAllowed)], null) - then: - 0 * queryTraversal._(_) - - } - - def "doesn't do anything if exception was thrown"() { - given: - def schema = TestUtil.schema(""" - type Query{ - bar: String - } - """) - def query = createQuery(""" - { bar { thisIsWrong } } - """) - def queryTraversal = Mock(QueryTraverser) - MaxQueryComplexityInstrumentation maxQueryComplexityInstrumentation = new MaxQueryComplexityInstrumentation(6) { - - @Override - QueryTraverser newQueryTraverser(InstrumentationValidationParameters parameters) { - return queryTraversal - } - } - ExecutionInput executionInput = Mock(ExecutionInput) - InstrumentationValidationParameters validationParameters = new InstrumentationValidationParameters(executionInput, query, schema, null) - InstrumentationContext instrumentationContext = maxQueryComplexityInstrumentation.beginValidation(validationParameters) - when: - instrumentationContext.onCompleted(null, new RuntimeException()) - then: - 0 * queryTraversal._(_) - - } def "default complexity calculator"() { given: @@ -93,16 +40,16 @@ class MaxQueryComplexityInstrumentationTest extends Specification { """) MaxQueryComplexityInstrumentation queryComplexityInstrumentation = new MaxQueryComplexityInstrumentation(10) ExecutionInput executionInput = Mock(ExecutionInput) - InstrumentationValidationParameters validationParameters = new InstrumentationValidationParameters(executionInput, query, schema, null) - InstrumentationContext instrumentationContext = queryComplexityInstrumentation.beginValidation(validationParameters) + InstrumentationExecuteOperationParameters executeOperationParameters = createExecuteOperationParameters(queryComplexityInstrumentation, executionInput, query, schema) when: - instrumentationContext.onCompleted(null, null) + queryComplexityInstrumentation.beginExecuteOperation(executeOperationParameters) then: def e = thrown(AbortExecutionException) e.message == "maximum query complexity exceeded 11 > 10" } + def "complexity calculator works with __typename field with score 0"() { given: def schema = TestUtil.schema(""" @@ -115,10 +62,9 @@ class MaxQueryComplexityInstrumentationTest extends Specification { """) MaxQueryComplexityInstrumentation queryComplexityInstrumentation = new MaxQueryComplexityInstrumentation(1) ExecutionInput executionInput = Mock(ExecutionInput) - InstrumentationValidationParameters validationParameters = new InstrumentationValidationParameters(executionInput, query, schema, null) - InstrumentationContext instrumentationContext = queryComplexityInstrumentation.beginValidation(validationParameters) + InstrumentationExecuteOperationParameters executeOperationParameters = createExecuteOperationParameters(queryComplexityInstrumentation, executionInput, query, schema) when: - instrumentationContext.onCompleted(null, null) + queryComplexityInstrumentation.beginExecuteOperation(executeOperationParameters) then: def e = thrown(AbortExecutionException) e.message == "maximum query complexity exceeded 2 > 1" @@ -143,10 +89,9 @@ class MaxQueryComplexityInstrumentationTest extends Specification { def calculator = Mock(FieldComplexityCalculator) MaxQueryComplexityInstrumentation queryComplexityInstrumentation = new MaxQueryComplexityInstrumentation(5, calculator) ExecutionInput executionInput = Mock(ExecutionInput) - InstrumentationValidationParameters validationParameters = new InstrumentationValidationParameters(executionInput, query, schema, null) - InstrumentationContext instrumentationContext = queryComplexityInstrumentation.beginValidation(validationParameters) + InstrumentationExecuteOperationParameters executeOperationParameters = createExecuteOperationParameters(queryComplexityInstrumentation, executionInput, query, schema) when: - instrumentationContext.onCompleted(null, null) + queryComplexityInstrumentation.beginExecuteOperation(executeOperationParameters) then: 1 * calculator.calculate({ FieldComplexityEnvironment env -> env.field.name == "scalar" }, 0) >> 10 @@ -171,22 +116,23 @@ class MaxQueryComplexityInstrumentationTest extends Specification { def query = createQuery(""" {f2: foo {scalar foo{scalar}} f1: foo { foo {foo {foo {foo{foo{scalar}}}}}} } """) - Boolean test = false + Boolean customFunctionCalled = false Function maxQueryComplexityExceededFunction = new Function() { @Override Boolean apply(final QueryComplexityInfo queryComplexityInfo) { - test = true + assert queryComplexityInfo.instrumentationExecuteOperationParameters != null + assert queryComplexityInfo.instrumentationValidationParameters != null + customFunctionCalled = true return false } } MaxQueryComplexityInstrumentation queryComplexityInstrumentation = new MaxQueryComplexityInstrumentation(10, maxQueryComplexityExceededFunction) ExecutionInput executionInput = Mock(ExecutionInput) - InstrumentationValidationParameters validationParameters = new InstrumentationValidationParameters(executionInput, query, schema, null) - InstrumentationContext instrumentationContext = queryComplexityInstrumentation.beginValidation(validationParameters) + InstrumentationExecuteOperationParameters executeOperationParameters = createExecuteOperationParameters(queryComplexityInstrumentation, executionInput, query, schema) when: - instrumentationContext.onCompleted(null, null) + queryComplexityInstrumentation.beginExecuteOperation(executeOperationParameters) then: - test == true + customFunctionCalled notThrown(Exception) } @@ -205,14 +151,29 @@ class MaxQueryComplexityInstrumentationTest extends Specification { MaxQueryComplexityInstrumentation queryComplexityInstrumentation = new MaxQueryComplexityInstrumentation(0) ExecutionInput executionInput = Mock(ExecutionInput) - InstrumentationValidationParameters validationParameters = new InstrumentationValidationParameters(executionInput, query, schema, null) - InstrumentationContext instrumentationContext = queryComplexityInstrumentation.beginValidation(validationParameters) + InstrumentationExecuteOperationParameters executeOperationParameters = createExecuteOperationParameters(queryComplexityInstrumentation, executionInput, query, schema) when: - instrumentationContext.onCompleted(null, null) + queryComplexityInstrumentation.beginExecuteOperation(executeOperationParameters) then: def e = thrown(AbortExecutionException) e.message == "maximum query complexity exceeded 1 > 0" } + + private InstrumentationExecuteOperationParameters createExecuteOperationParameters(MaxQueryComplexityInstrumentation queryComplexityInstrumentation, ExecutionInput executionInput, Document query, GraphQLSchema schema) { + // we need to run N steps to create instrumentation state + def instrumentationState = queryComplexityInstrumentation.createState(null) + def validationParameters = new InstrumentationValidationParameters(executionInput, query, schema, instrumentationState) + queryComplexityInstrumentation.beginValidation(validationParameters) + def executionContext = executionCtx(executionInput, query, schema) + def executeOperationParameters = new InstrumentationExecuteOperationParameters(executionContext).withNewState(instrumentationState) + executeOperationParameters + } + + private ExecutionContext executionCtx(ExecutionInput executionInput, Document query, GraphQLSchema schema) { + ExecutionContextBuilder.newExecutionContextBuilder() + .executionInput(executionInput).document(query).graphQLSchema(schema).executionId(ExecutionId.generate()) + .build() + } } diff --git a/src/test/groovy/graphql/analysis/MaxQueryDepthInstrumentationTest.groovy b/src/test/groovy/graphql/analysis/MaxQueryDepthInstrumentationTest.groovy index 5a386786e..11ac79bc7 100644 --- a/src/test/groovy/graphql/analysis/MaxQueryDepthInstrumentationTest.groovy +++ b/src/test/groovy/graphql/analysis/MaxQueryDepthInstrumentationTest.groovy @@ -1,14 +1,16 @@ package graphql.analysis import graphql.ExecutionInput +import graphql.GraphQL import graphql.TestUtil import graphql.execution.AbortExecutionException -import graphql.execution.instrumentation.InstrumentationContext -import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters +import graphql.execution.ExecutionContext +import graphql.execution.ExecutionContextBuilder +import graphql.execution.ExecutionId +import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters import graphql.language.Document import graphql.parser.Parser -import graphql.validation.ValidationError -import graphql.validation.ValidationErrorType +import graphql.schema.GraphQLSchema import spock.lang.Specification import java.util.function.Function @@ -21,63 +23,7 @@ class MaxQueryDepthInstrumentationTest extends Specification { } - def "doesn't do anything if validation errors occur"() { - given: - def schema = TestUtil.schema(""" - type Query{ - bar: String - } - """) - def query = createQuery(""" - { bar { thisIsWrong } } - """) - def queryTraversal = Mock(QueryTraverser) - MaxQueryDepthInstrumentation maximumQueryDepthInstrumentation = new MaxQueryDepthInstrumentation(6) { - - @Override - QueryTraverser newQueryTraverser(InstrumentationValidationParameters parameters) { - return queryTraversal - } - } - ExecutionInput executionInput = Mock(ExecutionInput) - InstrumentationValidationParameters validationParameters = new InstrumentationValidationParameters(executionInput, query, schema, null) - InstrumentationContext instrumentationContext = maximumQueryDepthInstrumentation.beginValidation(validationParameters) - when: - instrumentationContext.onCompleted([new ValidationError(ValidationErrorType.SubSelectionNotAllowed)], null) - then: - 0 * queryTraversal._(_) - - } - - def "doesn't do anything if exception was thrown"() { - given: - def schema = TestUtil.schema(""" - type Query{ - bar: String - } - """) - def query = createQuery(""" - { bar { thisIsWrong } } - """) - def queryTraversal = Mock(QueryTraverser) - MaxQueryDepthInstrumentation maximumQueryDepthInstrumentation = new MaxQueryDepthInstrumentation(6) { - - @Override - QueryTraverser newQueryTraverser(InstrumentationValidationParameters parameters) { - return queryTraversal - } - } - ExecutionInput executionInput = Mock(ExecutionInput) - InstrumentationValidationParameters validationParameters = new InstrumentationValidationParameters(executionInput, query, schema, null) - InstrumentationContext instrumentationContext = maximumQueryDepthInstrumentation.beginValidation(validationParameters) - when: - instrumentationContext.onCompleted(null, new RuntimeException()) - then: - 0 * queryTraversal._(_) - - } - - def "throws exception"() { + def "throws exception if too deep"() { given: def schema = TestUtil.schema(""" type Query{ @@ -94,16 +40,16 @@ class MaxQueryDepthInstrumentationTest extends Specification { """) MaxQueryDepthInstrumentation maximumQueryDepthInstrumentation = new MaxQueryDepthInstrumentation(6) ExecutionInput executionInput = Mock(ExecutionInput) - InstrumentationValidationParameters validationParameters = new InstrumentationValidationParameters(executionInput, query, schema, null) - InstrumentationContext instrumentationContext = maximumQueryDepthInstrumentation.beginValidation(validationParameters) + def executionContext = executionCtx(executionInput, query, schema) + def executeOperationParameters = new InstrumentationExecuteOperationParameters(executionContext) when: - instrumentationContext.onCompleted(null, null) + maximumQueryDepthInstrumentation.beginExecuteOperation(executeOperationParameters) then: def e = thrown(AbortExecutionException) e.message.contains("maximum query depth exceeded 7 > 6") } - def "doesn't throw exception"() { + def "doesn't throw exception if not deep enough"() { given: def schema = TestUtil.schema(""" type Query{ @@ -120,10 +66,10 @@ class MaxQueryDepthInstrumentationTest extends Specification { """) MaxQueryDepthInstrumentation maximumQueryDepthInstrumentation = new MaxQueryDepthInstrumentation(7) ExecutionInput executionInput = Mock(ExecutionInput) - InstrumentationValidationParameters validationParameters = new InstrumentationValidationParameters(executionInput, query, schema, null) - InstrumentationContext instrumentationContext = maximumQueryDepthInstrumentation.beginValidation(validationParameters) + def executionContext = executionCtx(executionInput, query, schema) + def executeOperationParameters = new InstrumentationExecuteOperationParameters(executionContext) when: - instrumentationContext.onCompleted(null, null) + maximumQueryDepthInstrumentation.beginExecuteOperation(executeOperationParameters) then: notThrown(Exception) } @@ -143,22 +89,52 @@ class MaxQueryDepthInstrumentationTest extends Specification { def query = createQuery(""" {f1: foo {foo {foo {scalar}}} f2: foo { foo {foo {foo {foo{foo{scalar}}}}}} } """) - Boolean test = false + Boolean calledFunction = false Function maxQueryDepthExceededFunction = new Function() { @Override Boolean apply(final QueryDepthInfo queryDepthInfo) { - test = true + calledFunction = true return false } } MaxQueryDepthInstrumentation maximumQueryDepthInstrumentation = new MaxQueryDepthInstrumentation(6, maxQueryDepthExceededFunction) ExecutionInput executionInput = Mock(ExecutionInput) - InstrumentationValidationParameters validationParameters = new InstrumentationValidationParameters(executionInput, query, schema, null) - InstrumentationContext instrumentationContext = maximumQueryDepthInstrumentation.beginValidation(validationParameters) + def executionContext = executionCtx(executionInput, query, schema) + def executeOperationParameters = new InstrumentationExecuteOperationParameters(executionContext) when: - instrumentationContext.onCompleted(null, null) + maximumQueryDepthInstrumentation.beginExecuteOperation(executeOperationParameters) then: - test == true + calledFunction notThrown(Exception) } + + def "coercing null variables that are marked as non nullable wont blow up early"() { + + given: + def schema = TestUtil.schema(""" + type Query { + field(arg : String!) : String + } + """) + + MaxQueryDepthInstrumentation maximumQueryDepthInstrumentation = new MaxQueryDepthInstrumentation(6) + def graphQL = GraphQL.newGraphQL(schema).instrumentation(maximumQueryDepthInstrumentation).build() + + when: + def query = ''' + query x($var : String!) { + field(arg : $var) + } + ''' + def executionInput = ExecutionInput.newExecutionInput(query).variables(["var": null]).build() + def er = graphQL.execute(executionInput) + + then: + !er.errors.isEmpty() + } + + private ExecutionContext executionCtx(ExecutionInput executionInput, Document query, GraphQLSchema schema) { + ExecutionContextBuilder.newExecutionContextBuilder() + .executionInput(executionInput).document(query).graphQLSchema(schema).executionId(ExecutionId.generate()).build() + } } diff --git a/src/test/groovy/graphql/execution/ValuesResolverTest.groovy b/src/test/groovy/graphql/execution/ValuesResolverTest.groovy index 83a357725..783fb1cdc 100644 --- a/src/test/groovy/graphql/execution/ValuesResolverTest.groovy +++ b/src/test/groovy/graphql/execution/ValuesResolverTest.groovy @@ -495,7 +495,25 @@ class ValuesResolverTest extends Specification { then: def error = thrown(NonNullableValueCoercedAsNullException) - error.message == "Variable 'foo' has coerced Null value for NonNull type 'String!'" + error.message == "Variable 'foo' has an invalid value: Variable 'foo' has coerced Null value for NonNull type 'String!'" + } + + def "coerceVariableValues: if variableType is a list of Non-Nullable type, and element value is null, throw a query error"() { + given: + def schema = TestUtil.schemaWithInputType(list(nonNull(GraphQLString))) + + def defaultValueForFoo = new ArrayValue([new StringValue("defaultValueForFoo")]) + def type = new ListType(new NonNullType(new TypeName("String"))) + VariableDefinition fooVarDef = new VariableDefinition("foo", type, defaultValueForFoo) + + def variableValuesMap = ["foo": [null]] + + when: + resolver.coerceVariableValues(schema, [fooVarDef], variableValuesMap) + + then: + def error = thrown(NonNullableValueCoercedAsNullException) + error.message == "Variable 'foo' has an invalid value: Coerced Null value for NonNull type 'String!'" } // Note: use NullValue defined in Field when it exists, diff --git a/src/test/groovy/graphql/introspection/IntrospectionResultToSchemaTest.groovy b/src/test/groovy/graphql/introspection/IntrospectionResultToSchemaTest.groovy index 1bfedf779..82c38ce42 100644 --- a/src/test/groovy/graphql/introspection/IntrospectionResultToSchemaTest.groovy +++ b/src/test/groovy/graphql/introspection/IntrospectionResultToSchemaTest.groovy @@ -104,13 +104,11 @@ class IntrospectionResultToSchemaTest extends Specification { then: result == """type QueryType implements Query { - hero( - \"\"\" + hero(\"\"\" comment about episode on two lines \"\"\" - episode: Episode - foo: String = \"bar\"): Character @deprecated(reason: "killed off character") + episode: Episode, foo: String = \"bar\"): Character @deprecated(reason: "killed off character") }""" } @@ -212,9 +210,13 @@ class IntrospectionResultToSchemaTest extends Specification { then: result == """"A character in the Star Wars Trilogy" interface Character { + "The id of the character." id: String! + "The name of the character." name: String + "The friends of the character, or an empty list if they have none." friends: [Character] + "Which movies they appear in." appearsIn: [Episode] }""" @@ -260,8 +262,11 @@ interface Character { then: result == """"One of the films in the Star Wars Trilogy" enum Episode { + "Released in 1977." NEWHOPE + "Released in 1980." EMPIRE + "Released in 1983." JEDI @deprecated(reason: "killed by clones") }""" @@ -404,47 +409,61 @@ input CharacterInput { } type QueryType { - hero( - "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode." + hero("If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode." episode: Episode): Character - human( - "id of the human" + human("id of the human" id: String!): Human - droid( - "id of the droid" + droid("id of the droid" id: String!): Droid } "A character in the Star Wars Trilogy" interface Character { + "The id of the character." id: String! + "The name of the character." name: String + "The friends of the character, or an empty list if they have none." friends: [Character] + "Which movies they appear in." appearsIn: [Episode] } "One of the films in the Star Wars Trilogy" enum Episode { + "Released in 1977." NEWHOPE + "Released in 1980." EMPIRE + "Released in 1983." JEDI } "A humanoid creature in the Star Wars universe." type Human implements Character { + "The id of the human." id: String! + "The name of the human." name: String + "The friends of the human, or an empty list if they have none." friends: [Character] + "Which movies they appear in." appearsIn: [Episode] + "The home planet of the human, or null if unknown." homePlanet: String } "A mechanical creature in the Star Wars universe." type Droid implements Character { + "The id of the droid." id: String! + "The name of the droid." name: String + "The friends of the droid, or an empty list if they have none." friends: [Character] + "Which movies they appear in." appearsIn: [Episode] + "The primary function of the droid." primaryFunction: String } """ @@ -492,14 +511,17 @@ type Episode { " Simpson seasons" enum Season { + " the beginning" Season1 Season2 Season3 Season4 + " Another one" Season5 Season6 Season7 Season8 + " Not really the last one :-)" Season9 } @@ -966,4 +988,4 @@ scalar EmployeeRef scalar EmployeeRef ''' } -} \ No newline at end of file +} diff --git a/src/test/groovy/graphql/language/AstPrinterTest.groovy b/src/test/groovy/graphql/language/AstPrinterTest.groovy index bb7024e8b..54e5eb2d9 100644 --- a/src/test/groovy/graphql/language/AstPrinterTest.groovy +++ b/src/test/groovy/graphql/language/AstPrinterTest.groovy @@ -472,6 +472,26 @@ type Query { } + def "print field descriptions"() { + def query = '''type Query { + "description" + field( + "description" + a: String): String +} +''' + def document = parse(query) + String output = printAst(document) + expect: + output == '''type Query { + "description" + field( + "description" + a: String): String +} +''' + } + def "print empty description"() { def query = ''' "" diff --git a/src/test/groovy/graphql/parser/ParserTest.groovy b/src/test/groovy/graphql/parser/ParserTest.groovy index d2423c7fe..99b132ef2 100644 --- a/src/test/groovy/graphql/parser/ParserTest.groovy +++ b/src/test/groovy/graphql/parser/ParserTest.groovy @@ -42,6 +42,7 @@ import graphql.language.VariableDefinition import graphql.language.VariableReference import org.antlr.v4.runtime.CommonTokenStream import org.antlr.v4.runtime.ParserRuleContext +import spock.lang.Issue import spock.lang.Specification import spock.lang.Unroll @@ -373,6 +374,28 @@ class ParserTest extends Specification { helloField.comments.collect { c -> c.content } == [" this is some comment, which should be captured"] } + @Issue("https://github.com/graphql-java/graphql-java/issues/2767") + def "parser does not transform comments to AST nodes when ParserOptions.captureLineComments(false)"() { + given: + def input = """ + { # this is some comment, which should be captured + hello(arg: "hello, world" ) # test + } + """ + def parserOptionsWithoutCaptureLineComments = ParserOptions.newParserOptions() + .captureLineComments(false) + .build() + + when: + def document = new Parser().parseDocument(input, parserOptionsWithoutCaptureLineComments) + Field helloField = (document.definitions[0] as OperationDefinition).selectionSet.selections[0] as Field + + then: + isEqual(helloField, new Field("hello", [new Argument("arg", new StringValue("hello, world"))])) + assert helloField.comments.isEmpty() // No single-line comments on lone fields + assert document.comments.isEmpty() // No single-line comments in entire document + } + @Unroll def "parse floatValue #floatString"() { given: @@ -1118,7 +1141,7 @@ triple3 : """edge cases \\""" "" " \\"" \\" edge cases""" then: doc != null count == 9 - tokens == ["query" , "{", "f" , "(", "arg", ":", "1", ")", "}"] + tokens == ["query", "{", "f", "(", "arg", ":", "1", ")", "}"] when: "integration test to prove it be supplied via EI" @@ -1138,7 +1161,7 @@ triple3 : """edge cases \\""" "" " \\"" \\" edge cases""" then: er.errors.size() == 0 count == 9 - tokens == ["query" , "{", "f" , "(", "arg", ":", "1", ")", "}"] + tokens == ["query", "{", "f", "(", "arg", ":", "1", ")", "}"] } } diff --git a/src/test/java/graphql/normalized/ValueToVariableValueCompilerTest.groovy b/src/test/java/graphql/normalized/ValueToVariableValueCompilerTest.groovy new file mode 100644 index 000000000..58143c5b2 --- /dev/null +++ b/src/test/java/graphql/normalized/ValueToVariableValueCompilerTest.groovy @@ -0,0 +1,118 @@ +package graphql.normalized + +import graphql.language.ArrayValue +import graphql.language.BooleanValue +import graphql.language.EnumValue +import graphql.language.FloatValue +import graphql.language.IntValue +import graphql.language.NullValue +import graphql.language.ObjectField +import graphql.language.ObjectValue +import graphql.language.StringValue +import graphql.schema.idl.TypeUtil +import spock.lang.Specification + +class ValueToVariableValueCompilerTest extends Specification { + + def "cam handle different ast Value objects"() { + + expect: + def actual = ValueToVariableValueCompiler.normalisedValueToVariableValue(value) + actual == expected + + where: + value | expected + NullValue.of() | null + IntValue.of(666) | 666 + StringValue.of("str") | "str" + BooleanValue.of(true) | true + FloatValue.of(999d) | 999d + EnumValue.of("enumValue") | "enumValue" + ObjectValue.newObjectValue() + .objectField(ObjectField.newObjectField().name("a").value(IntValue.of(64)).build()) + .objectField(ObjectField.newObjectField().name("b").value(StringValue.of("65")).build()) + .build() | [a: 64, b: "65"] + ArrayValue.newArrayValue() + .value(IntValue.of(9)) + .value(StringValue.of("10")) + .value(EnumValue.of("enum")) + .build() | [9, "10", "enum"] + + } + + def "can handle NormalizedInputValue values that are literals"() { + expect: + def niv = new NormalizedInputValue("TypeName", value) + def actual = ValueToVariableValueCompiler.normalisedValueToVariableValue(niv) + actual == expected + + where: + value | expected + NullValue.of() | null + IntValue.of(666) | 666 + StringValue.of("str") | "str" + BooleanValue.of(true) | true + FloatValue.of(999d) | 999d + EnumValue.of("enumValue") | "enumValue" + ObjectValue.newObjectValue() + .objectField(ObjectField.newObjectField().name("a").value(IntValue.of(64)).build()) + .objectField(ObjectField.newObjectField().name("b").value(StringValue.of("65")).build()) + .build() | [a: 64, b: "65"] + ArrayValue.newArrayValue() + .value(IntValue.of(9)) + .value(StringValue.of("10")) + .value(EnumValue.of("enum")) + .build() | [9, "10", "enum"] + + + } + + def "can handle NormalizedInputValue values that are not literals"() { + expect: + def niv = new NormalizedInputValue("TypeName", value) + def actual = ValueToVariableValueCompiler.normalisedValueToVariableValue(niv) + actual == expected + + where: + value | expected + null | null + [IntValue.of(666), IntValue.of(664)] | [666, 664] + [a: IntValue.of(666), b: IntValue.of(664)] | [a: 666, b: 664] + + // at present we dont handle straight up java objects like 123 because + // the ValueResolver never produces them during + // ValueResolver.getNormalizedVariableValues say - this is debatable behavior + // but for now this is what we do + } + + + def "can print variables as expected"() { + expect: + def niv = new NormalizedInputValue(typeName, value) + def actual = ValueToVariableValueCompiler.normalizedInputValueToVariable(niv, varCount) + actual.value == expectedValue + actual.variableReference.name == expectedVarName + actual.definition.name == expectedVarName + TypeUtil.simplePrint(actual.definition.type) == typeName + + where: + value | varCount | typeName | expectedValue | expectedVarName + NullValue.newNullValue().build() | 1 | "ID" | null | "v1" + IntValue.of(666) | 2 | "Int!" | 666 | "v2" + StringValue.of("str") | 3 | "String" | "str" | "v3" + BooleanValue.of(true) | 4 | "Boolean!" | true | "v4" + FloatValue.of(999d) | 5 | "Float" | 999d | "v5" + EnumValue.of("enumValue") | 6 | "Foo!" | "enumValue" | "v6" + ObjectValue.newObjectValue() + .objectField(ObjectField.newObjectField().name("a").value(IntValue.of(64)).build()) + .objectField(ObjectField.newObjectField().name("b").value(StringValue.of("65")).build()) + .build() | 7 | "ObjectType" | [a: 64, b: "65"] | "v7" + ArrayValue.newArrayValue() + .value(IntValue.of(9)) + .value(StringValue.of("10")) + .value(EnumValue.of("enum")) + .build() | 8 | "ArrayType" | [9, "10", "enum"] | "v8" + + + } +}