diff --git a/src/main/java/graphql/ExecutionInput.java b/src/main/java/graphql/ExecutionInput.java index 6c945d9404..917d4a519a 100644 --- a/src/main/java/graphql/ExecutionInput.java +++ b/src/main/java/graphql/ExecutionInput.java @@ -120,6 +120,17 @@ public static Builder newExecutionInput() { return new Builder(); } + /** + * Creates a new builder of ExecutionInput objects with the given query + * + * @param query the query to execute + * + * @return a new builder of ExecutionInput objects + */ + public static Builder newExecutionInput(String query) { + return new Builder().query(query); + } + public static class Builder { private String query; diff --git a/src/main/java/graphql/GraphQL.java b/src/main/java/graphql/GraphQL.java index c9ee072244..c3b84a6dd9 100644 --- a/src/main/java/graphql/GraphQL.java +++ b/src/main/java/graphql/GraphQL.java @@ -40,6 +40,7 @@ import java.util.function.UnaryOperator; import static graphql.Assert.assertNotNull; +import static graphql.execution.ExecutionIdProvider.DEFAULT_EXECUTION_ID_PROVIDER; import static graphql.execution.instrumentation.DocumentAndVariables.newDocumentAndVariables; /** @@ -79,6 +80,7 @@ * * */ +@SuppressWarnings("Duplicates") @PublicApi public class GraphQL { @@ -90,7 +92,6 @@ public class GraphQL { private static final Logger log = LoggerFactory.getLogger(GraphQL.class); - private static final ExecutionIdProvider DEFAULT_EXECUTION_ID_PROVIDER = (query, operationName, context) -> ExecutionId.generate(); private final GraphQLSchema graphQLSchema; private final ExecutionStrategy queryStrategy; @@ -603,37 +604,4 @@ private CompletableFuture execute(ExecutionInput executionInput return future; } - private static class ParseResult { - private final DocumentAndVariables documentAndVariables; - private final InvalidSyntaxException exception; - - private ParseResult(DocumentAndVariables documentAndVariables, InvalidSyntaxException exception) { - this.documentAndVariables = documentAndVariables; - this.exception = exception; - } - - private boolean isFailure() { - return documentAndVariables == null; - } - - private Document getDocument() { - return documentAndVariables.getDocument(); - } - - private Map getVariables() { - return documentAndVariables.getVariables(); - } - - private InvalidSyntaxException getException() { - return exception; - } - - private static ParseResult of(DocumentAndVariables document) { - return new ParseResult(document, null); - } - - private static ParseResult ofError(InvalidSyntaxException e) { - return new ParseResult(null, e); - } - } } diff --git a/src/main/java/graphql/ParseResult.java b/src/main/java/graphql/ParseResult.java new file mode 100644 index 0000000000..eeec599a7c --- /dev/null +++ b/src/main/java/graphql/ParseResult.java @@ -0,0 +1,42 @@ +package graphql; + +import graphql.execution.instrumentation.DocumentAndVariables; +import graphql.language.Document; +import graphql.parser.InvalidSyntaxException; + +import java.util.Map; + +@Internal +public class ParseResult { + private final DocumentAndVariables documentAndVariables; + private final InvalidSyntaxException exception; + + public ParseResult(DocumentAndVariables documentAndVariables, InvalidSyntaxException exception) { + this.documentAndVariables = documentAndVariables; + this.exception = exception; + } + + public boolean isFailure() { + return documentAndVariables == null; + } + + public Document getDocument() { + return documentAndVariables.getDocument(); + } + + public Map getVariables() { + return documentAndVariables.getVariables(); + } + + public InvalidSyntaxException getException() { + return exception; + } + + public static ParseResult of(DocumentAndVariables document) { + return new ParseResult(document, null); + } + + public static ParseResult ofError(InvalidSyntaxException e) { + return new ParseResult(null, e); + } +} diff --git a/src/main/java/graphql/execution/ExecutionIdProvider.java b/src/main/java/graphql/execution/ExecutionIdProvider.java index a9d8702166..7f4848c249 100644 --- a/src/main/java/graphql/execution/ExecutionIdProvider.java +++ b/src/main/java/graphql/execution/ExecutionIdProvider.java @@ -5,6 +5,9 @@ */ public interface ExecutionIdProvider { + ExecutionIdProvider DEFAULT_EXECUTION_ID_PROVIDER = (query, operationName, context) -> ExecutionId.generate(); + + /** * Allows provision of a unique identifier per query execution. * diff --git a/src/main/java/graphql/execution/instrumentation/Instrumentation.java b/src/main/java/graphql/execution/instrumentation/Instrumentation.java index f933a3edc0..b01d3d71cf 100644 --- a/src/main/java/graphql/execution/instrumentation/Instrumentation.java +++ b/src/main/java/graphql/execution/instrumentation/Instrumentation.java @@ -20,6 +20,8 @@ import java.util.List; import java.util.concurrent.CompletableFuture; +import static graphql.execution.instrumentation.SimpleInstrumentationContext.noOp; + /** * Provides the capability to instrument the execution steps of a GraphQL query. * @@ -139,7 +141,7 @@ default InstrumentationState createState(InstrumentationCreateStateParameters pa * @return a non null {@link InstrumentationContext} object that will be called back when the step ends */ default InstrumentationContext beginFieldComplete(InstrumentationFieldCompleteParameters parameters) { - return new SimpleInstrumentationContext<>(); + return noOp(); } /** @@ -150,7 +152,7 @@ default InstrumentationContext beginFieldComplete(Instrumentati * @return a non null {@link InstrumentationContext} object that will be called back when the step ends */ default InstrumentationContext beginFieldListComplete(InstrumentationFieldCompleteParameters parameters) { - return new SimpleInstrumentationContext<>(); + return noOp(); } /** diff --git a/src/main/java/graphql/execution/instrumentation/SimpleInstrumentationContext.java b/src/main/java/graphql/execution/instrumentation/SimpleInstrumentationContext.java index a7d796f416..7ded4f674b 100644 --- a/src/main/java/graphql/execution/instrumentation/SimpleInstrumentationContext.java +++ b/src/main/java/graphql/execution/instrumentation/SimpleInstrumentationContext.java @@ -12,6 +12,17 @@ @PublicApi public class SimpleInstrumentationContext implements InstrumentationContext { + /** + * A context that does nothing + * + * @param the type needed + * + * @return a context that does nothing + */ + public static InstrumentationContext noOp() { + return new SimpleInstrumentationContext<>(); + } + private final BiConsumer codeToRunOnComplete; private final Consumer> codeToRunOnDispatch; diff --git a/src/main/java/graphql/execution/instrumentation/nextgen/Instrumentation.java b/src/main/java/graphql/execution/instrumentation/nextgen/Instrumentation.java new file mode 100644 index 0000000000..3e59616284 --- /dev/null +++ b/src/main/java/graphql/execution/instrumentation/nextgen/Instrumentation.java @@ -0,0 +1,52 @@ +package graphql.execution.instrumentation.nextgen; + +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.Internal; +import graphql.execution.instrumentation.DocumentAndVariables; +import graphql.execution.instrumentation.InstrumentationContext; +import graphql.execution.instrumentation.InstrumentationState; +import graphql.language.Document; +import graphql.schema.GraphQLSchema; +import graphql.validation.ValidationError; + +import java.util.List; + +import static graphql.execution.instrumentation.SimpleInstrumentationContext.noOp; + +@Internal +public interface Instrumentation { + + default InstrumentationState createState(InstrumentationCreateStateParameters parameters) { + return new InstrumentationState() { + }; + } + + default ExecutionInput instrumentExecutionInput(ExecutionInput executionInput, InstrumentationExecutionParameters parameters) { + return executionInput; + } + + default DocumentAndVariables instrumentDocumentAndVariables(DocumentAndVariables documentAndVariables, InstrumentationExecutionParameters parameters) { + return documentAndVariables; + } + + default GraphQLSchema instrumentSchema(GraphQLSchema graphQLSchema, InstrumentationExecutionParameters parameters) { + return graphQLSchema; + } + + default ExecutionResult instrumentExecutionResult(ExecutionResult result, InstrumentationExecutionParameters parameters) { + return result; + } + + default InstrumentationContext beginExecution(InstrumentationExecutionParameters parameters) { + return noOp(); + } + + default InstrumentationContext beginParse(InstrumentationExecutionParameters parameters) { + return noOp(); + } + + default InstrumentationContext> beginValidation(InstrumentationValidationParameters parameters) { + return noOp(); + } +} diff --git a/src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationCreateStateParameters.java b/src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationCreateStateParameters.java new file mode 100644 index 0000000000..ff4ad52cdc --- /dev/null +++ b/src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationCreateStateParameters.java @@ -0,0 +1,27 @@ +package graphql.execution.instrumentation.nextgen; + +import graphql.ExecutionInput; +import graphql.Internal; +import graphql.schema.GraphQLSchema; + +/** + * Parameters sent to {@link graphql.execution.instrumentation.nextgen.Instrumentation} methods + */ +@Internal +public class InstrumentationCreateStateParameters { + private final GraphQLSchema schema; + private final ExecutionInput executionInput; + + public InstrumentationCreateStateParameters(GraphQLSchema schema, ExecutionInput executionInput) { + this.schema = schema; + this.executionInput = executionInput; + } + + public GraphQLSchema getSchema() { + return schema; + } + + public ExecutionInput getExecutionInput() { + return executionInput; + } +} diff --git a/src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationExecutionParameters.java b/src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationExecutionParameters.java new file mode 100644 index 0000000000..89c368c761 --- /dev/null +++ b/src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationExecutionParameters.java @@ -0,0 +1,75 @@ +package graphql.execution.instrumentation.nextgen; + +import graphql.ExecutionInput; +import graphql.Internal; +import graphql.execution.instrumentation.InstrumentationState; +import graphql.schema.GraphQLSchema; + +import java.util.Collections; +import java.util.Map; + +/** + * Parameters sent to {@link graphql.execution.instrumentation.nextgen.Instrumentation} methods + */ +@Internal +public class InstrumentationExecutionParameters { + private final ExecutionInput executionInput; + private final String query; + private final String operation; + private final Object context; + private final Map variables; + private final InstrumentationState instrumentationState; + private final GraphQLSchema schema; + + public InstrumentationExecutionParameters(ExecutionInput executionInput, GraphQLSchema schema, InstrumentationState instrumentationState) { + this.executionInput = executionInput; + this.query = executionInput.getQuery(); + this.operation = executionInput.getOperationName(); + this.context = executionInput.getContext(); + this.variables = executionInput.getVariables() != null ? executionInput.getVariables() : Collections.emptyMap(); + this.instrumentationState = instrumentationState; + this.schema = schema; + } + + /** + * Returns a cloned parameters object with the new state + * + * @param instrumentationState the new state for this parameters object + * + * @return a new parameters object with the new state + */ + public InstrumentationExecutionParameters withNewState(InstrumentationState instrumentationState) { + return new InstrumentationExecutionParameters(this.getExecutionInput(), this.schema, instrumentationState); + } + + public ExecutionInput getExecutionInput() { + return executionInput; + } + + public String getQuery() { + return query; + } + + public String getOperation() { + return operation; + } + + @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) + public T getContext() { + return (T) context; + } + + public Map getVariables() { + return variables; + } + + @SuppressWarnings("TypeParameterUnusedInFormals") + public T getInstrumentationState() { + //noinspection unchecked + return (T) instrumentationState; + } + + public GraphQLSchema getSchema() { + return this.schema; + } +} diff --git a/src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationValidationParameters.java b/src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationValidationParameters.java new file mode 100644 index 0000000000..ebaa26d3b2 --- /dev/null +++ b/src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationValidationParameters.java @@ -0,0 +1,38 @@ +package graphql.execution.instrumentation.nextgen; + +import graphql.ExecutionInput; +import graphql.Internal; +import graphql.execution.instrumentation.InstrumentationState; +import graphql.language.Document; +import graphql.schema.GraphQLSchema; + +/** + * Parameters sent to {@link graphql.execution.instrumentation.nextgen.Instrumentation} methods + */ +@Internal +public class InstrumentationValidationParameters extends InstrumentationExecutionParameters { + private final Document document; + + public InstrumentationValidationParameters(ExecutionInput executionInput, Document document, GraphQLSchema schema, InstrumentationState instrumentationState) { + super(executionInput, schema, instrumentationState); + this.document = document; + } + + /** + * Returns a cloned parameters object with the new state + * + * @param instrumentationState the new state for this parameters object + * + * @return a new parameters object with the new state + */ + @Override + public InstrumentationValidationParameters withNewState(InstrumentationState instrumentationState) { + return new InstrumentationValidationParameters( + this.getExecutionInput(), document, getSchema(), instrumentationState); + } + + + public Document getDocument() { + return document; + } +} diff --git a/src/main/java/graphql/execution/instrumentation/nextgen/package-info.java b/src/main/java/graphql/execution/instrumentation/nextgen/package-info.java new file mode 100644 index 0000000000..2f08b060ba --- /dev/null +++ b/src/main/java/graphql/execution/instrumentation/nextgen/package-info.java @@ -0,0 +1,4 @@ +/** + * WARNING: All code in this package is a work in progress for a new execution engine. + */ +package graphql.execution.instrumentation.nextgen; diff --git a/src/main/java/graphql/execution/nextgen/Execution.java b/src/main/java/graphql/execution/nextgen/Execution.java index bfe460fa77..38a23a41ed 100644 --- a/src/main/java/graphql/execution/nextgen/Execution.java +++ b/src/main/java/graphql/execution/nextgen/Execution.java @@ -7,6 +7,7 @@ import graphql.Internal; import graphql.execution.Async; import graphql.execution.ExecutionId; +import graphql.execution.instrumentation.InstrumentationState; import graphql.execution.nextgen.result.ResultNodesUtil; import graphql.language.Document; import graphql.schema.GraphQLSchema; @@ -18,14 +19,15 @@ public class Execution { ExecutionHelper executionHelper = new ExecutionHelper(); - public CompletableFuture execute(Class executionStrategy, + public CompletableFuture execute(ExecutionStrategy executionStrategy, Document document, GraphQLSchema graphQLSchema, ExecutionId executionId, - ExecutionInput executionInput) { + ExecutionInput executionInput, + InstrumentationState instrumentationState) { ExecutionHelper.ExecutionData executionData; try { - executionData = executionHelper.createExecutionData(document, graphQLSchema, executionId, executionInput); + executionData = executionHelper.createExecutionData(document, graphQLSchema, executionId, executionInput, instrumentationState); } catch (RuntimeException rte) { if (rte instanceof GraphQLError) { return CompletableFuture.completedFuture(new ExecutionResultImpl((GraphQLError) rte)); @@ -35,14 +37,10 @@ public CompletableFuture execute(Class fragmentsByName = getOperationResult.fragmentsByName; @@ -52,6 +54,7 @@ public ExecutionData createExecutionData(Document document, ExecutionContext executionContext = newExecutionContextBuilder() .executionId(executionId) + .instrumentationState(instrumentationState) .graphQLSchema(graphQLSchema) .context(executionInput.getContext()) .root(executionInput.getRoot()) @@ -59,7 +62,6 @@ public ExecutionData createExecutionData(Document document, .variables(coercedVariables) .document(document) .operationDefinition(operationDefinition) - .dataLoaderRegistry(executionInput.getDataLoaderRegistry()) .build(); GraphQLObjectType operationRootType; diff --git a/src/main/java/graphql/nextgen/GraphQL.java b/src/main/java/graphql/nextgen/GraphQL.java new file mode 100644 index 0000000000..b834d3f5af --- /dev/null +++ b/src/main/java/graphql/nextgen/GraphQL.java @@ -0,0 +1,349 @@ +package graphql.nextgen; + +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.ExecutionResultImpl; +import graphql.Internal; +import graphql.ParseResult; +import graphql.execution.AbortExecutionException; +import graphql.execution.ExecutionId; +import graphql.execution.ExecutionIdProvider; +import graphql.execution.instrumentation.DocumentAndVariables; +import graphql.execution.instrumentation.InstrumentationContext; +import graphql.execution.instrumentation.InstrumentationState; +import graphql.execution.instrumentation.nextgen.Instrumentation; +import graphql.execution.instrumentation.nextgen.InstrumentationCreateStateParameters; +import graphql.execution.instrumentation.nextgen.InstrumentationExecutionParameters; +import graphql.execution.instrumentation.nextgen.InstrumentationValidationParameters; +import graphql.execution.nextgen.DefaultExecutionStrategy; +import graphql.execution.nextgen.Execution; +import graphql.execution.nextgen.ExecutionStrategy; +import graphql.execution.preparsed.NoOpPreparsedDocumentProvider; +import graphql.execution.preparsed.PreparsedDocumentEntry; +import graphql.execution.preparsed.PreparsedDocumentProvider; +import graphql.language.Document; +import graphql.parser.InvalidSyntaxException; +import graphql.parser.Parser; +import graphql.schema.GraphQLSchema; +import graphql.validation.ValidationError; +import graphql.validation.Validator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.UnaryOperator; + +import static graphql.Assert.assertNotNull; +import static graphql.execution.instrumentation.DocumentAndVariables.newDocumentAndVariables; + +@SuppressWarnings("Duplicates") +@Internal +public class GraphQL { + private static final Logger log = LoggerFactory.getLogger(graphql.GraphQL.class); + + private final GraphQLSchema graphQLSchema; + private final ExecutionStrategy executionStrategy; + private final ExecutionIdProvider idProvider; + private final Instrumentation instrumentation; + private final PreparsedDocumentProvider preparsedDocumentProvider; + + public GraphQL(Builder builder) { + this.graphQLSchema = builder.graphQLSchema; + this.executionStrategy = builder.executionStrategy; + this.idProvider = builder.idProvider; + this.preparsedDocumentProvider = builder.preparsedDocumentProvider; + this.instrumentation = builder.instrumentation; + } + + /** + * Executes the graphql query using the provided input object builder + *

+ * This will return a completed {@link ExecutionResult} + * which is the result of executing the provided query. + * + * @param executionInputBuilder {@link ExecutionInput.Builder} + * + * @return an {@link ExecutionResult} which can include errors + */ + public ExecutionResult execute(ExecutionInput.Builder executionInputBuilder) { + return executeAsync(executionInputBuilder.build()).join(); + } + + /** + * Executes the graphql query using the provided input object builder + *

+ * This will return a completed {@link ExecutionResult} + * which is the result of executing the provided query. + *

+ * This allows a lambda style like : + *

+     * {@code
+     *    ExecutionResult result = graphql.execute(input -> input.query("{hello}").root(startingObj).context(contextObj));
+     * }
+     * 
+ * + * @param builderFunction a function that is given a {@link ExecutionInput.Builder} + * + * @return a promise to an {@link ExecutionResult} which can include errors + */ + public CompletableFuture execute(UnaryOperator builderFunction) { + return executeAsync(builderFunction.apply(ExecutionInput.newExecutionInput()).build()); + } + + /** + * Executes the graphql query using the provided input object + *

+ * This will return a completed {@link ExecutionResult} + * which is the result of executing the provided query. + * + * @param executionInput {@link ExecutionInput} + * + * @return a promise to an {@link ExecutionResult} which can include errors + */ + public ExecutionResult execute(ExecutionInput executionInput) { + return executeAsync(executionInput).join(); + } + + /** + * Executes the graphql query using the provided input object builder + *

+ * This will return a promise (aka {@link CompletableFuture}) to provide a {@link ExecutionResult} + * which is the result of executing the provided query. + * + * @param executionInputBuilder {@link ExecutionInput.Builder} + * + * @return a promise to an {@link ExecutionResult} which can include errors + */ + public CompletableFuture executeAsync(ExecutionInput.Builder executionInputBuilder) { + return executeAsync(executionInputBuilder.build()); + } + + /** + * Executes the graphql query using the provided input object builder + *

+ * This will return a promise (aka {@link CompletableFuture}) to provide a {@link ExecutionResult} + * which is the result of executing the provided query. + *

+ * This allows a lambda style like : + *

+     * {@code
+     *    ExecutionResult result = graphql.executeAsync(input -> input.query("{hello}").root(startingObj).context(contextObj));
+     * }
+     * 
+ * + * @param builderFunction a function that is given a {@link ExecutionInput.Builder} + * + * @return a promise to an {@link ExecutionResult} which can include errors + */ + public CompletableFuture executeAsync(UnaryOperator builderFunction) { + return executeAsync(builderFunction.apply(ExecutionInput.newExecutionInput()).build()); + } + + /** + * Executes the graphql query using the provided input object + *

+ * This will return a promise (aka {@link CompletableFuture}) to provide a {@link ExecutionResult} + * which is the result of executing the provided query. + * + * @param executionInput {@link ExecutionInput} + * + * @return a promise to an {@link ExecutionResult} which can include errors + */ + public CompletableFuture executeAsync(ExecutionInput executionInput) { + try { + log.debug("Executing request. operation name: '{}'. query: '{}'. variables '{}'", executionInput.getOperationName(), executionInput.getQuery(), executionInput.getVariables()); + + InstrumentationState instrumentationState = instrumentation.createState(new InstrumentationCreateStateParameters(this.graphQLSchema, executionInput)); + + InstrumentationExecutionParameters inputInstrumentationParameters = new InstrumentationExecutionParameters(executionInput, this.graphQLSchema, instrumentationState); + executionInput = instrumentation.instrumentExecutionInput(executionInput, inputInstrumentationParameters); + + InstrumentationExecutionParameters instrumentationParameters = new InstrumentationExecutionParameters(executionInput, this.graphQLSchema, instrumentationState); + InstrumentationContext executionInstrumentation = instrumentation.beginExecution(instrumentationParameters); + + GraphQLSchema graphQLSchema = instrumentation.instrumentSchema(this.graphQLSchema, instrumentationParameters); + + CompletableFuture executionResult = parseValidateAndExecute(executionInput, graphQLSchema, instrumentationState); + // + // finish up instrumentation + executionResult = executionResult.whenComplete(executionInstrumentation::onCompleted); + // + // allow instrumentation to tweak the result + executionResult = executionResult.thenApply(result -> instrumentation.instrumentExecutionResult(result, instrumentationParameters)); + return executionResult; + } catch (AbortExecutionException abortException) { + return CompletableFuture.completedFuture(abortException.toExecutionResult()); + } + } + + private CompletableFuture parseValidateAndExecute(ExecutionInput executionInput, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) { + AtomicReference executionInputRef = new AtomicReference<>(executionInput); + PreparsedDocumentEntry preparsedDoc = preparsedDocumentProvider.get(executionInput.getQuery(), + transformedQuery -> { + // if they change the original query in the pre-parser, then we want to see it downstream from then on + executionInputRef.set(executionInput.transform(bldr -> bldr.query(transformedQuery))); + return parseAndValidate(executionInputRef.get(), graphQLSchema, instrumentationState); + }); + if (preparsedDoc.hasErrors()) { + return CompletableFuture.completedFuture(new ExecutionResultImpl(preparsedDoc.getErrors())); + } + + return execute(executionInputRef.get(), preparsedDoc.getDocument(), graphQLSchema, instrumentationState); + } + + private PreparsedDocumentEntry parseAndValidate(ExecutionInput executionInput, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) { + log.debug("Parsing query: '{}'...", executionInput.getQuery()); + ParseResult parseResult = parse(executionInput, graphQLSchema, instrumentationState); + if (parseResult.isFailure()) { + log.warn("Query failed to parse : '{}'", executionInput.getQuery()); + return new PreparsedDocumentEntry(parseResult.getException().toInvalidSyntaxError()); + } else { + final Document document = parseResult.getDocument(); + + log.debug("Validating query: '{}'", executionInput.getQuery()); + final List errors = validate(executionInput, document, graphQLSchema, instrumentationState); + if (!errors.isEmpty()) { + log.warn("Query failed to validate : '{}'", executionInput.getQuery()); + return new PreparsedDocumentEntry(errors); + } + + return new PreparsedDocumentEntry(document); + } + } + + private ParseResult parse(ExecutionInput executionInput, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) { + InstrumentationExecutionParameters parameters = new InstrumentationExecutionParameters(executionInput, graphQLSchema, instrumentationState); + InstrumentationContext parseInstrumentation = instrumentation.beginParse(parameters); + + Parser parser = new Parser(); + Document document; + DocumentAndVariables documentAndVariables; + try { + document = parser.parseDocument(executionInput.getQuery()); + documentAndVariables = newDocumentAndVariables() + .document(document).variables(executionInput.getVariables()).build(); + documentAndVariables = instrumentation.instrumentDocumentAndVariables(documentAndVariables, parameters); + } catch (InvalidSyntaxException e) { + parseInstrumentation.onCompleted(null, e); + return ParseResult.ofError(e); + } + + parseInstrumentation.onCompleted(documentAndVariables.getDocument(), null); + return ParseResult.of(documentAndVariables); + } + + private List validate(ExecutionInput executionInput, Document document, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) { + InstrumentationContext> validationCtx = instrumentation.beginValidation(new InstrumentationValidationParameters(executionInput, document, graphQLSchema, instrumentationState)); + + Validator validator = new Validator(); + List validationErrors = validator.validateDocument(graphQLSchema, document); + + validationCtx.onCompleted(validationErrors, null); + return validationErrors; + } + + private CompletableFuture execute(ExecutionInput executionInput, Document document, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) { + String query = executionInput.getQuery(); + String operationName = executionInput.getOperationName(); + Object context = executionInput.getContext(); + + Execution execution = new Execution(); + ExecutionId executionId = idProvider.provide(query, operationName, context); + + log.debug("Executing '{}'. operation name: '{}'. query: '{}'. variables '{}'", executionId, executionInput.getOperationName(), executionInput.getQuery(), executionInput.getVariables()); + CompletableFuture future = execution.execute(executionStrategy, document, graphQLSchema, executionId, executionInput, instrumentationState); + future = future.whenComplete((result, throwable) -> { + if (throwable != null) { + log.error(String.format("Execution '%s' threw exception when executing : query : '%s'. variables '%s'", executionId, executionInput.getQuery(), executionInput.getVariables()), throwable); + } else { + int errorCount = result.getErrors().size(); + if (errorCount > 0) { + log.debug("Execution '{}' completed with '{}' errors", executionId, errorCount); + } else { + log.debug("Execution '{}' completed with zero errors", executionId); + } + } + }); + return future; + } + + /** + * Helps you build a GraphQL object ready to execute queries + * + * @param graphQLSchema the schema to use + * + * @return a builder of GraphQL objects + */ + public static Builder newGraphQL(GraphQLSchema graphQLSchema) { + return new Builder(graphQLSchema); + } + + /** + * This helps you transform the current GraphQL object into another one by starting a builder with all + * the current values and allows you to transform it how you want. + * + * @param builderConsumer the consumer code that will be given a builder to transform + * + * @return a new GraphQL object based on calling build on that builder + */ + public GraphQL transform(Consumer builderConsumer) { + Builder builder = new Builder(this); + builderConsumer.accept(builder); + return builder.build(); + } + + + public static class Builder { + private GraphQLSchema graphQLSchema; + private ExecutionStrategy executionStrategy = new DefaultExecutionStrategy(); + private ExecutionIdProvider idProvider = ExecutionIdProvider.DEFAULT_EXECUTION_ID_PROVIDER; + private Instrumentation instrumentation = new Instrumentation() { + }; + private PreparsedDocumentProvider preparsedDocumentProvider = NoOpPreparsedDocumentProvider.INSTANCE; + + + public Builder(GraphQLSchema graphQLSchema) { + this.graphQLSchema = graphQLSchema; + } + + public Builder(GraphQL graphQL) { + this.graphQLSchema = graphQL.graphQLSchema; + this.executionStrategy = graphQL.executionStrategy; + this.idProvider = graphQL.idProvider; + this.instrumentation = graphQL.instrumentation; + } + + public Builder schema(GraphQLSchema graphQLSchema) { + this.graphQLSchema = assertNotNull(graphQLSchema, "GraphQLSchema must be non null"); + return this; + } + + public Builder executionStrategy(ExecutionStrategy executionStrategy) { + this.executionStrategy = assertNotNull(executionStrategy, "ExecutionStrategy must be non null"); + return this; + } + + public Builder instrumentation(Instrumentation instrumentation) { + this.instrumentation = assertNotNull(instrumentation, "Instrumentation must be non null"); + return this; + } + + public Builder preparsedDocumentProvider(PreparsedDocumentProvider preparsedDocumentProvider) { + this.preparsedDocumentProvider = assertNotNull(preparsedDocumentProvider, "PreparsedDocumentProvider must be non null"); + return this; + } + + public Builder executionIdProvider(ExecutionIdProvider executionIdProvider) { + this.idProvider = assertNotNull(executionIdProvider, "ExecutionIdProvider must be non null"); + return this; + } + + public GraphQL build() { + assertNotNull(graphQLSchema, "graphQLSchema must be non null"); + return new GraphQL(this); + } + } +} diff --git a/src/main/java/graphql/nextgen/package-info.java b/src/main/java/graphql/nextgen/package-info.java new file mode 100644 index 0000000000..8c994937fd --- /dev/null +++ b/src/main/java/graphql/nextgen/package-info.java @@ -0,0 +1,4 @@ +/** + * WARNING: All code in this package is a work in progress for a new execution engine. + */ +package graphql.nextgen; diff --git a/src/test/groovy/graphql/ExecutionInputTest.groovy b/src/test/groovy/graphql/ExecutionInputTest.groovy index 31c7f38718..f9340cf140 100644 --- a/src/test/groovy/graphql/ExecutionInputTest.groovy +++ b/src/test/groovy/graphql/ExecutionInputTest.groovy @@ -62,4 +62,11 @@ class ExecutionInputTest extends Specification { executionInput.dataLoaderRegistry == registry executionInput.query == "new query" } + + def "defaults query into builder as expected"() { + when: + def executionInput = ExecutionInput.newExecutionInput("{ q }").build() + then: + executionInput.query == "{ q }" + } } diff --git a/src/test/groovy/graphql/execution/nextgen/BatchedExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/nextgen/BatchedExecutionStrategyTest.groovy index 5fc59a806a..6ba86d013a 100644 --- a/src/test/groovy/graphql/execution/nextgen/BatchedExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/nextgen/BatchedExecutionStrategyTest.groovy @@ -1,20 +1,21 @@ package graphql.execution.nextgen -import graphql.ExecutionInput -import graphql.TestUtil -import graphql.execution.ExecutionId + +import graphql.nextgen.GraphQL import graphql.schema.DataFetcher import spock.lang.Specification -class BatchedExecutionStrategyTest extends Specification { +import static graphql.ExecutionInput.newExecutionInput +import static graphql.TestUtil.schema +class BatchedExecutionStrategyTest extends Specification { def "test simple execution"() { def fooData = [id: "fooId", bar: [id: "barId", name: "someBar"]] def dataFetchers = [ Query: [foo: { env -> fooData } as DataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: Foo } @@ -29,7 +30,7 @@ class BatchedExecutionStrategyTest extends Specification { """, dataFetchers) - def document = graphql.TestUtil.parseQuery(""" + def query = """ {foo { id bar { @@ -37,22 +38,14 @@ class BatchedExecutionStrategyTest extends Specification { name } }} - """) + """ - ExecutionInput executionInput = ExecutionInput.newExecutionInput() - .build() - - Execution execution = new Execution() when: - def monoResult = execution.execute(BatchedExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - - + def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() + def result = graphQL.execute(newExecutionInput(query)) then: result.getData() == [foo: fooData] - - } def "test execution with lists"() { @@ -61,7 +54,7 @@ class BatchedExecutionStrategyTest extends Specification { def dataFetchers = [ Query: [foo: { env -> fooData } as DataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: [Foo] } @@ -76,7 +69,7 @@ class BatchedExecutionStrategyTest extends Specification { """, dataFetchers) - def document = graphql.TestUtil.parseQuery(""" + def query = """ {foo { id bar { @@ -84,22 +77,13 @@ class BatchedExecutionStrategyTest extends Specification { name } }} - """) - - ExecutionInput executionInput = ExecutionInput.newExecutionInput() - .build() - - - Execution execution = new Execution(); + """ when: - def monoResult = execution.execute(BatchedExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - - + def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() + def result = graphQL.execute(newExecutionInput(query)) then: result.getData() == [foo: fooData] - } def "test execution with null element "() { @@ -108,7 +92,7 @@ class BatchedExecutionStrategyTest extends Specification { def dataFetchers = [ Query: [foo: { env -> fooData } as DataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: [Foo] } @@ -123,7 +107,7 @@ class BatchedExecutionStrategyTest extends Specification { """, dataFetchers) - def document = graphql.TestUtil.parseQuery(""" + def query = """ {foo { id bar { @@ -131,18 +115,10 @@ class BatchedExecutionStrategyTest extends Specification { name } }} - """) - - ExecutionInput executionInput = ExecutionInput.newExecutionInput() - .build() - - - Execution execution = new Execution() - + """ when: - def monoResult = execution.execute(BatchedExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - + def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() + def result = graphQL.execute(newExecutionInput(query)) then: result.getData() == [foo: fooData] @@ -155,7 +131,7 @@ class BatchedExecutionStrategyTest extends Specification { def dataFetchers = [ Query: [foo: { env -> fooData } as DataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: [Foo] } @@ -170,7 +146,7 @@ class BatchedExecutionStrategyTest extends Specification { """, dataFetchers) - def document = graphql.TestUtil.parseQuery(""" + def query = """ {foo { id bar { @@ -178,22 +154,12 @@ class BatchedExecutionStrategyTest extends Specification { name } }} - """) - - ExecutionInput executionInput = ExecutionInput.newExecutionInput() - .build() - - - Execution execution = new Execution() - + """ when: - def monoResult = execution.execute(BatchedExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - - + def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() + def result = graphQL.execute(newExecutionInput(query)) then: result.getData() == [foo: fooData] - } def "test execution with null element in non null list"() { @@ -202,7 +168,7 @@ class BatchedExecutionStrategyTest extends Specification { def dataFetchers = [ Query: [foo: { env -> fooData } as DataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: [Foo] } @@ -217,7 +183,7 @@ class BatchedExecutionStrategyTest extends Specification { """, dataFetchers) - def document = graphql.TestUtil.parseQuery(""" + def query = """ {foo { id bar { @@ -225,25 +191,15 @@ class BatchedExecutionStrategyTest extends Specification { name } }} - """) - + """ def expectedFooData = [[id: "fooId1", bar: null], [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - ExecutionInput executionInput = ExecutionInput.newExecutionInput() - .build() - - - Execution execution = new Execution() - when: - def monoResult = execution.execute(BatchedExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - - + def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() + def result = graphQL.execute(newExecutionInput(query)) then: result.getData() == [foo: expectedFooData] - } def "test execution with null element bubbling up because of non null "() { @@ -252,7 +208,7 @@ class BatchedExecutionStrategyTest extends Specification { def dataFetchers = [ Query: [foo: { env -> fooData } as DataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: [Foo] } @@ -267,7 +223,7 @@ class BatchedExecutionStrategyTest extends Specification { """, dataFetchers) - def document = graphql.TestUtil.parseQuery(""" + def query = """ {foo { id bar { @@ -275,25 +231,16 @@ class BatchedExecutionStrategyTest extends Specification { name } }} - """) + """ def expectedFooData = [null, [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - ExecutionInput executionInput = ExecutionInput.newExecutionInput() - .build() - - - Execution execution = new Execution() - when: - def monoResult = execution.execute(BatchedExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - - + def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() + def result = graphQL.execute(newExecutionInput(query)) then: result.getData() == [foo: expectedFooData] - } def "test execution with null element bubbling up to top "() { @@ -302,7 +249,7 @@ class BatchedExecutionStrategyTest extends Specification { def dataFetchers = [ Query: [foo: { env -> fooData } as DataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: [Foo!]! } @@ -317,7 +264,7 @@ class BatchedExecutionStrategyTest extends Specification { """, dataFetchers) - def document = graphql.TestUtil.parseQuery(""" + def query = """ {foo { id bar { @@ -325,23 +272,12 @@ class BatchedExecutionStrategyTest extends Specification { name } }} - """) - - - ExecutionInput executionInput = ExecutionInput.newExecutionInput() - .build() - - - Execution execution = new Execution() - + """ when: - def monoResult = execution.execute(BatchedExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - - + def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() + def result = graphQL.execute(newExecutionInput(query)) then: result.getData() == null - } def "test list"() { @@ -349,7 +285,7 @@ class BatchedExecutionStrategyTest extends Specification { def dataFetchers = [ Query: [foo: { env -> fooData } as DataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: [Foo] } @@ -359,33 +295,23 @@ class BatchedExecutionStrategyTest extends Specification { """, dataFetchers) - def document = graphql.TestUtil.parseQuery(""" + def query = """ {foo { id }} - """) - - ExecutionInput executionInput = ExecutionInput.newExecutionInput() - .build() - - - Execution execution = new Execution(); + """ when: - def monoResult = execution.execute(BatchedExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - - + def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() + def result = graphQL.execute(newExecutionInput(query)) then: result.getData() == [foo: fooData] - - } def "test list in lists "() { - def catsBatchSize + def catsBatchSize = 0 def catsCallCount = 0 - def idsBatchSize + def idsBatchSize = 0 def idsCallCount = 0 def catsDataFetcher = { env -> @@ -406,7 +332,7 @@ class BatchedExecutionStrategyTest extends Specification { Person: [cats: catsDataFetcher], Cat : [id: idDataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { friends: [Person] } @@ -419,33 +345,23 @@ class BatchedExecutionStrategyTest extends Specification { """, dataFetchers) - def document = graphql.TestUtil.parseQuery(""" + def query = """ {friends { cats { id } }} - """) - - ExecutionInput executionInput = ExecutionInput.newExecutionInput() - .build() - - - Execution execution = new Execution(); + """ when: - def monoResult = execution.execute(BatchedExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - - + def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() + def result = graphQL.execute(newExecutionInput(query)) then: result.getData() == [friends: [[cats: [[id: "catId1"], [id: "catId2"]]], [cats: null], [cats: [[id: "catId3"], [id: "catId4"], [id: "catId5"]]]]] catsCallCount == 1 idsCallCount == 1 catsBatchSize == 3 idsBatchSize == 5 - - } def "test simple batching with null value in list"() { @@ -453,7 +369,7 @@ class BatchedExecutionStrategyTest extends Specification { def dataFetchers = [ Query: [foo: { env -> fooData } as DataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: [Foo] } @@ -463,28 +379,18 @@ class BatchedExecutionStrategyTest extends Specification { """, dataFetchers) - def document = graphql.TestUtil.parseQuery(""" + def query = """ {foo { id }} - """) - - ExecutionInput executionInput = ExecutionInput.newExecutionInput() - .build() - - - Execution execution = new Execution(); + """ when: - def monoResult = execution.execute(BatchedExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - + def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() + def result = graphQL.execute(newExecutionInput(query)) then: result.getData() == [foo: fooData] - - } - } diff --git a/src/test/groovy/graphql/execution/nextgen/DefaultExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/nextgen/DefaultExecutionStrategyTest.groovy index f26b33a641..dea91a908b 100644 --- a/src/test/groovy/graphql/execution/nextgen/DefaultExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/nextgen/DefaultExecutionStrategyTest.groovy @@ -1,18 +1,15 @@ package graphql.execution.nextgen -import graphql.ContextPassingDataFetcher import graphql.ExceptionWhileDataFetching -import graphql.ExecutionInput -import graphql.TestUtil -import graphql.execution.ExecutionId +import graphql.nextgen.GraphQL import graphql.schema.DataFetcher import graphql.schema.DataFetchingEnvironment -import graphql.schema.idl.RuntimeWiring import spock.lang.Specification import java.util.concurrent.CompletableFuture import static graphql.ExecutionInput.newExecutionInput +import static graphql.TestUtil.schema import static graphql.execution.DataFetcherResult.newResult class DefaultExecutionStrategyTest extends Specification { @@ -23,7 +20,7 @@ class DefaultExecutionStrategyTest extends Specification { def dataFetchers = [ Query: [foo: { env -> fooData } as DataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: Foo } @@ -38,7 +35,7 @@ class DefaultExecutionStrategyTest extends Specification { """, dataFetchers) - def document = TestUtil.parseQuery(""" + def query = """ {foo { id bar { @@ -46,17 +43,12 @@ class DefaultExecutionStrategyTest extends Specification { name } }} - """) + """ - ExecutionInput executionInput = newExecutionInput() - .build() - - Execution execution = new Execution() when: - def monoResult = execution.execute(DefaultExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - + def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new DefaultExecutionStrategy()).build() + def result = graphQL.execute(newExecutionInput().query(query)) then: result.getData() == [foo: fooData] @@ -81,7 +73,7 @@ class DefaultExecutionStrategyTest extends Specification { Query: [foo: fooResolver], Foo : [id1: idResolver1, id2: idResolver2, id3: idResolver3] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: Foo } @@ -93,24 +85,24 @@ class DefaultExecutionStrategyTest extends Specification { """, dataFetchers) - def document = TestUtil.parseQuery(""" + def query = """ { f1: foo { id1 } f2: foo { id2 } f3: foo { id3 } } - """) - ExecutionInput executionInput = newExecutionInput() - .build() - - Execution execution = new Execution() + """ def cfId1 = new CompletableFuture() def cfId2 = new CompletableFuture() def cfId3 = new CompletableFuture() when: - execution.execute(DefaultExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) + def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new DefaultExecutionStrategy()).build() + // + // I think this is a dangerous test - It dispatches the query but never joins on the result + // so its expecting the DFs to be called by never resolved properly. ?? + graphQL.executeAsync(newExecutionInput().query(query)) then: cfs.size() == 3 @@ -148,7 +140,7 @@ class DefaultExecutionStrategyTest extends Specification { def dataFetchers = [ Query: [foo: { env -> fooData } as DataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: [Foo] } @@ -163,7 +155,7 @@ class DefaultExecutionStrategyTest extends Specification { """, dataFetchers) - def document = TestUtil.parseQuery(""" + def query = """ {foo { id bar { @@ -171,21 +163,14 @@ class DefaultExecutionStrategyTest extends Specification { name } }} - """) - - ExecutionInput executionInput = newExecutionInput() - .build() - - Execution execution = new Execution() + """ when: - def monoResult = execution.execute(DefaultExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - + def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new DefaultExecutionStrategy()).build() + def result = graphQL.execute(newExecutionInput().query(query)) then: result.getData() == [foo: fooData] - } def "test execution with null element "() { @@ -194,7 +179,7 @@ class DefaultExecutionStrategyTest extends Specification { def dataFetchers = [ Query: [foo: { env -> fooData } as DataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: [Foo] } @@ -209,7 +194,7 @@ class DefaultExecutionStrategyTest extends Specification { """, dataFetchers) - def document = TestUtil.parseQuery(""" + def query = """ {foo { id bar { @@ -217,18 +202,11 @@ class DefaultExecutionStrategyTest extends Specification { name } }} - """) - - ExecutionInput executionInput = newExecutionInput() - .build() - - - Execution execution = new Execution() + """ when: - def monoResult = execution.execute(DefaultExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - + def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new DefaultExecutionStrategy()).build() + def result = graphQL.execute(newExecutionInput().query(query)) then: result.getData() == [foo: fooData] @@ -241,7 +219,7 @@ class DefaultExecutionStrategyTest extends Specification { def dataFetchers = [ Query: [foo: { env -> fooData } as DataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: [Foo] } @@ -256,7 +234,7 @@ class DefaultExecutionStrategyTest extends Specification { """, dataFetchers) - def document = TestUtil.parseQuery(""" + def query = """ {foo { id bar { @@ -264,18 +242,11 @@ class DefaultExecutionStrategyTest extends Specification { name } }} - """) - - ExecutionInput executionInput = newExecutionInput() - .build() - - - Execution execution = new Execution() + """ when: - def monoResult = execution.execute(DefaultExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - + def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new DefaultExecutionStrategy()).build() + def result = graphQL.execute(newExecutionInput().query(query)) then: result.getData() == [foo: fooData] @@ -288,7 +259,7 @@ class DefaultExecutionStrategyTest extends Specification { def dataFetchers = [ Query: [foo: { env -> fooData } as DataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: [Foo] } @@ -303,7 +274,7 @@ class DefaultExecutionStrategyTest extends Specification { """, dataFetchers) - def document = TestUtil.parseQuery(""" + def query = """ {foo { id bar { @@ -311,21 +282,15 @@ class DefaultExecutionStrategyTest extends Specification { name } }} - """) + """ def expectedFooData = [[id: "fooId1", bar: null], [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - ExecutionInput executionInput = newExecutionInput() - .build() - - - Execution execution = new Execution() when: - def monoResult = execution.execute(DefaultExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - + def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new DefaultExecutionStrategy()).build() + def result = graphQL.execute(newExecutionInput().query(query)) then: result.getData() == [foo: expectedFooData] @@ -338,7 +303,7 @@ class DefaultExecutionStrategyTest extends Specification { def dataFetchers = [ Query: [foo: { env -> fooData } as DataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: [Foo] } @@ -353,7 +318,7 @@ class DefaultExecutionStrategyTest extends Specification { """, dataFetchers) - def document = TestUtil.parseQuery(""" + def query = """ {foo { id bar { @@ -361,25 +326,17 @@ class DefaultExecutionStrategyTest extends Specification { name } }} - """) + """ def expectedFooData = [null, [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - ExecutionInput executionInput = newExecutionInput() - .build() - - - Execution execution = new Execution() - when: - def monoResult = execution.execute(DefaultExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - + def graphQL = GraphQL.newGraphQL(schema).build() + def result = graphQL.execute(newExecutionInput().query(query)) then: result.getData() == [foo: expectedFooData] - } def "test execution with null element bubbling up to top "() { @@ -388,7 +345,7 @@ class DefaultExecutionStrategyTest extends Specification { def dataFetchers = [ Query: [foo: { env -> fooData } as DataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: [Foo!]! } @@ -403,7 +360,7 @@ class DefaultExecutionStrategyTest extends Specification { """, dataFetchers) - def document = TestUtil.parseQuery(""" + def query = """ {foo { id bar { @@ -411,23 +368,14 @@ class DefaultExecutionStrategyTest extends Specification { name } }} - """) - - - ExecutionInput executionInput = newExecutionInput() - .build() - - - Execution execution = new Execution() + """ when: - def monoResult = execution.execute(DefaultExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - + def graphQL = GraphQL.newGraphQL(schema).build() + def result = graphQL.execute(newExecutionInput().query(query)) then: result.getData() == null - } def "test list"() { @@ -435,7 +383,7 @@ class DefaultExecutionStrategyTest extends Specification { def dataFetchers = [ Query: [foo: { env -> fooData } as DataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: [Foo] } @@ -445,27 +393,18 @@ class DefaultExecutionStrategyTest extends Specification { """, dataFetchers) - def document = TestUtil.parseQuery(""" + def query = """ {foo { id }} - """) - - ExecutionInput executionInput = newExecutionInput() - .build() - - - Execution execution = new Execution() + """ when: - def monoResult = execution.execute(DefaultExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - + def graphQL = GraphQL.newGraphQL(schema).build() + def result = graphQL.execute(newExecutionInput().query(query)) then: result.getData() == [foo: fooData] - - } def "test list in lists "() { @@ -473,7 +412,7 @@ class DefaultExecutionStrategyTest extends Specification { def dataFetchers = [ Query: [foo: { env -> fooData } as DataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: [Foo] } @@ -486,29 +425,19 @@ class DefaultExecutionStrategyTest extends Specification { """, dataFetchers) - def document = TestUtil.parseQuery(""" + def query = """ {foo { bar { id } }} - """) - - ExecutionInput executionInput = newExecutionInput() - .build() - - - Execution execution = new Execution() - + """ when: - def monoResult = execution.execute(DefaultExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - + def graphQL = GraphQL.newGraphQL(schema).build() + def result = graphQL.execute(newExecutionInput().query(query)) then: result.getData() == [foo: fooData] - - } def "test simple batching with null value in list"() { @@ -516,7 +445,7 @@ class DefaultExecutionStrategyTest extends Specification { def dataFetchers = [ Query: [foo: { env -> fooData } as DataFetcher] ] - def schema = TestUtil.schema(""" + def schema = schema(""" type Query { foo: [Foo] } @@ -526,107 +455,20 @@ class DefaultExecutionStrategyTest extends Specification { """, dataFetchers) - def document = TestUtil.parseQuery(""" + def query = """ {foo { id }} - """) - - ExecutionInput executionInput = newExecutionInput() - .build() - - - Execution execution = new Execution() + """ when: - def monoResult = execution.execute(DefaultExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - + def graphQL = GraphQL.newGraphQL(schema).build() + def result = graphQL.execute(newExecutionInput().query(query)) then: result.getData() == [foo: fooData] } - def "data fetcher can return context down each level"() { - given: - - def spec = ''' - type Query { - first : Level1 - } - - type Level1 { - second : Level2 - } - - type Level2 { - third : Level3 - } - - type Level3 { - skip : Level4 - } - - type Level4 { - fourth : String - } - ''' - - - def runtimeWiring = RuntimeWiring.newRuntimeWiring() - .type("Query", - { type -> - type.dataFetcher("first", new ContextPassingDataFetcher()) - }) - .type("Level1", - { type -> - type.dataFetcher("second", new ContextPassingDataFetcher()) - }) - .type("Level2", - { type -> - type.dataFetcher("third", new ContextPassingDataFetcher()) - }) - .type("Level3", - { type -> - type.dataFetcher("skip", new ContextPassingDataFetcher(true)) - }) - .type("Level4", - { type -> - type.dataFetcher("fourth", new ContextPassingDataFetcher()) - }) - .build() - - def query = ''' - { - first { - second { - third { - skip { - fourth - } - } - } - } - } - ''' - - def schema = TestUtil.schema(spec, runtimeWiring) - def executionInput = newExecutionInput().query(query).root("").context(1).build() - def document = TestUtil.parseQuery(query) - - Execution execution = new Execution() - - when: - - def monoResult = execution.execute(DefaultExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - - - then: - - result.errors.isEmpty() - result.data == [first: [second: [third: [skip: [fourth: "1,2,3,4,4,"]]]]] - } def "DataFetcherResult is respected with errors"() { @@ -647,7 +489,7 @@ class DefaultExecutionStrategyTest extends Specification { } as DataFetcher ] ] - def schema = TestUtil.schema(''' + def schema = schema(''' type Query { foo: [Foo] } @@ -657,21 +499,17 @@ class DefaultExecutionStrategyTest extends Specification { ''', dataFetchers) - def document = TestUtil.parseQuery(''' + def query = ''' { foo { id } } - ''') - - ExecutionInput executionInput = newExecutionInput().build() - Execution execution = new Execution() + ''' when: - def monoResult = execution.execute(DefaultExecutionStrategy, document, schema, ExecutionId.generate(), executionInput) - def result = monoResult.get() - + def graphQL = GraphQL.newGraphQL(schema).build() + def result = graphQL.execute(newExecutionInput().query(query)) then: result.errors.size() == 3 diff --git a/src/test/groovy/graphql/nextgen/GraphqlNextGenTest.groovy b/src/test/groovy/graphql/nextgen/GraphqlNextGenTest.groovy new file mode 100644 index 0000000000..5b59e6ca9c --- /dev/null +++ b/src/test/groovy/graphql/nextgen/GraphqlNextGenTest.groovy @@ -0,0 +1,32 @@ +package graphql.nextgen + + +import graphql.schema.DataFetcher +import spock.lang.Specification + +import static graphql.ExecutionInput.newExecutionInput +import static graphql.TestUtil.schema + +class GraphqlNextGenTest extends Specification { + + def "simple query"() { + given: + def dataFetchers = [ + Query: [hello: { env -> "world" } as DataFetcher] + ] + + def schema = schema(''' + type Query { + hello : String! + } + ''', dataFetchers) + + def graphQL = GraphQL.newGraphQL(schema).build() + + when: + def result = graphQL.executeAsync(newExecutionInput('{ hello }')).get() + + then: + result.data == [hello: 'world'] + } +}