diff --git a/src/main/java/graphql/GraphQL.java b/src/main/java/graphql/GraphQL.java index eb8ae87ee9..03f05a844f 100644 --- a/src/main/java/graphql/GraphQL.java +++ b/src/main/java/graphql/GraphQL.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.function.UnaryOperator; import static graphql.Assert.assertNotNull; @@ -50,6 +51,7 @@ public class GraphQL { * A GraphQL object ready to execute queries * * @param graphQLSchema the schema to use + * * @deprecated use the {@link #newGraphQL(GraphQLSchema)} builder instead. This will be removed in a future version. */ @Internal @@ -63,6 +65,7 @@ public GraphQL(GraphQLSchema graphQLSchema) { * * @param graphQLSchema the schema to use * @param queryStrategy the query execution strategy to use + * * @deprecated use the {@link #newGraphQL(GraphQLSchema)} builder instead. This will be removed in a future version. */ @Internal @@ -77,6 +80,7 @@ public GraphQL(GraphQLSchema graphQLSchema, ExecutionStrategy queryStrategy) { * @param graphQLSchema the schema to use * @param queryStrategy the query execution strategy to use * @param mutationStrategy the mutation execution strategy to use + * * @deprecated use the {@link #newGraphQL(GraphQLSchema)} builder instead. This will be removed in a future version. */ @Internal @@ -91,6 +95,7 @@ public GraphQL(GraphQLSchema graphQLSchema, ExecutionStrategy queryStrategy, Exe * @param queryStrategy the query execution strategy to use * @param mutationStrategy the mutation execution strategy to use * @param subscriptionStrategy the subscription execution strategy to use + * * @deprecated use the {@link #newGraphQL(GraphQLSchema)} builder instead. This will be removed in a future version. */ @Internal @@ -112,6 +117,7 @@ private GraphQL(GraphQLSchema graphQLSchema, ExecutionStrategy queryStrategy, Ex * 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) { @@ -178,11 +184,12 @@ public GraphQL build() { } /** + * Executes the specified graphql query/mutation/subscription + * * @param query the query/mutation/subscription - * @return result including errors - * @deprecated Use {@link #execute(ExecutionInput)} + * + * @return an {@link ExecutionResult} which can include errors */ - @Deprecated public ExecutionResult execute(String query) { ExecutionInput executionInput = ExecutionInput.newExecutionInput() .query(query) @@ -195,7 +202,9 @@ public ExecutionResult execute(String query) { * * @param query the query/mutation/subscription * @param context custom object provided to each {@link graphql.schema.DataFetcher} - * @return result including errors + * + * @return an {@link ExecutionResult} which can include errors + * * @deprecated Use {@link #execute(ExecutionInput)} */ @Deprecated @@ -214,7 +223,9 @@ public ExecutionResult execute(String query, Object context) { * @param query the query/mutation/subscription * @param operationName the name of the operation to execute * @param context custom object provided to each {@link graphql.schema.DataFetcher} - * @return result including errors + * + * @return an {@link ExecutionResult} which can include errors + * * @deprecated Use {@link #execute(ExecutionInput)} */ @Deprecated @@ -234,7 +245,9 @@ public ExecutionResult execute(String query, String operationName, Object contex * @param query the query/mutation/subscription * @param context custom object provided to each {@link graphql.schema.DataFetcher} * @param variables variable values uses as argument - * @return result including errors + * + * @return an {@link ExecutionResult} which can include errors + * * @deprecated Use {@link #execute(ExecutionInput)} */ @Deprecated @@ -255,7 +268,9 @@ public ExecutionResult execute(String query, Object context, Map * @param operationName name of the operation to execute * @param context custom object provided to each {@link graphql.schema.DataFetcher} * @param variables variable values uses as argument - * @return result including errors + * + * @return an {@link ExecutionResult} which can include errors + * * @deprecated Use {@link #execute(ExecutionInput)} */ @Deprecated @@ -271,11 +286,91 @@ public ExecutionResult execute(String query, String operationName, Object contex } /** + * Executes the graphql query using the provided input object builder + * + * @param executionInputBuilder {@link ExecutionInput.Builder} + * + * @return an {@link ExecutionResult} which can include errors + */ + public ExecutionResult execute(ExecutionInput.Builder executionInputBuilder) { + return execute(executionInputBuilder.build()); + } + + /** + * Executes the graphql query using calling the builder function and giving it a new builder. + *

+ * 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 an {@link ExecutionResult} which can include errors + */ + public ExecutionResult execute(UnaryOperator builderFunction) { + return execute(builderFunction.apply(ExecutionInput.newExecutionInput()).build()); + } + + /** + * Executes the graphql query using the provided input object + * + * @param executionInput {@link ExecutionInput} + * + * @return 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.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 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 execution result + * + * @return a promise to an {@link ExecutionResult} which can include errors */ public CompletableFuture executeAsync(ExecutionInput executionInput) { log.debug("Executing request. operation name: {}. query: {}. variables {} ", executionInput.getOperationName(), executionInput.getQuery(), executionInput.getVariables()); @@ -286,13 +381,6 @@ public CompletableFuture executeAsync(ExecutionInput executionI return executionResult; } - /** - * @param executionInput {@link ExecutionInput} - * @return result including errors - */ - public ExecutionResult execute(ExecutionInput executionInput) { - return executeAsync(executionInput).join(); - } private CompletableFuture parseValidateAndExecute(ExecutionInput executionInput) { PreparsedDocumentEntry preparsedDoc = preparsedDocumentProvider.get(executionInput.getQuery(), query -> parseAndValidate(executionInput)); diff --git a/src/test/groovy/graphql/GraphQLTest.groovy b/src/test/groovy/graphql/GraphQLTest.groovy index 99e7a50b16..06889f7c33 100644 --- a/src/test/groovy/graphql/GraphQLTest.groovy +++ b/src/test/groovy/graphql/GraphQLTest.groovy @@ -13,6 +13,10 @@ import graphql.schema.StaticDataFetcher import graphql.validation.ValidationErrorType import spock.lang.Specification +import java.util.function.UnaryOperator + +import static graphql.ExecutionInput.Builder +import static graphql.ExecutionInput.newExecutionInput import static graphql.Scalars.GraphQLInt import static graphql.Scalars.GraphQLString import static graphql.schema.GraphQLArgument.newArgument @@ -24,9 +28,7 @@ import static graphql.schema.GraphQLSchema.newSchema class GraphQLTest extends Specification { - - def "simple query"() { - given: + GraphQLSchema simpleSchema() { GraphQLFieldDefinition.Builder fieldDefinition = newFieldDefinition() .name("hello") .type(GraphQLString) @@ -37,6 +39,12 @@ class GraphQLTest extends Specification { .field(fieldDefinition) .build() ).build() + schema + } + + def "simple query"() { + given: + GraphQLSchema schema = simpleSchema() when: def result = GraphQL.newGraphQL(schema).build().execute('{ hello }').data @@ -199,7 +207,8 @@ class GraphQLTest extends Specification { def expected = [field2: 'value2'] when: - def result = GraphQL.newGraphQL(schema).build().execute(query, 'Query2', null, [:]) + def executionInput = newExecutionInput().query(query).operationName('Query2').context(null).variables([:]) + def result = GraphQL.newGraphQL(schema).build().execute(executionInput) then: result.data == expected @@ -246,7 +255,6 @@ class GraphQLTest extends Specification { } - def "query with int literal too large"() { given: GraphQLSchema schema = newSchema().query( @@ -269,6 +277,7 @@ class GraphQLTest extends Specification { result.errors[0].description.contains("has wrong type") } + @SuppressWarnings("GroovyAssignabilityCheck") def "query with missing argument results in arguments map with value null"() { given: def dataFetcher = Mock(DataFetcher) @@ -295,6 +304,7 @@ class GraphQLTest extends Specification { } } + @SuppressWarnings("GroovyAssignabilityCheck") def "query with missing key in an input object result in a empty map"() { given: def dataFetcher = Mock(DataFetcher) @@ -336,6 +346,7 @@ class GraphQLTest extends Specification { result.errors[0].errorType == ErrorType.InvalidSyntax } + def "wrong argument type: array of enum instead of enum"() { given: GraphQLEnumType enumType = GraphQLEnumType.newEnum().name("EnumType").value("Val1").value("Val2").build() @@ -345,19 +356,70 @@ class GraphQLTest extends Specification { .field(newFieldDefinition() .name("query") .argument(newArgument().name("fooParam").type(enumType)) - .type(Scalars.GraphQLInt)) + .type(GraphQLInt)) .build() GraphQLSchema schema = newSchema() .query(queryType) .build() when: - final GraphQL graphQL = GraphQL.newGraphQL(schema).build(); - final ExecutionResult result = graphQL.execute("{query (fooParam: [Val1,Val2])}"); + final GraphQL graphQL = GraphQL.newGraphQL(schema).build() + final ExecutionResult result = graphQL.execute("{query (fooParam: [Val1,Val2])}") then: result.errors.size() == 1 result.errors[0].errorType == ErrorType.ValidationError } + + def "execution input passing builder"() { + given: + GraphQLSchema schema = simpleSchema() + + when: + def builder = newExecutionInput().query('{ hello }') + def result = GraphQL.newGraphQL(schema).build().execute(builder).data + + then: + result == [hello: 'world'] + } + + def "execution input using builder function"() { + given: + GraphQLSchema schema = simpleSchema() + + when: + + def builderFunction = { it.query('{hello}') } as UnaryOperator + def result = GraphQL.newGraphQL(schema).build().execute(builderFunction).data + + then: + result == [hello: 'world'] + } + + def "execution input passing builder to async"() { + given: + GraphQLSchema schema = simpleSchema() + + when: + def builder = newExecutionInput().query('{ hello }') + def result = GraphQL.newGraphQL(schema).build().executeAsync(builder).join().data + + then: + result == [hello: 'world'] + } + + def "execution input using builder function to async"() { + given: + GraphQLSchema schema = simpleSchema() + + when: + + def builderFunction = { it.query('{hello}') } as UnaryOperator + def result = GraphQL.newGraphQL(schema).build().executeAsync(builderFunction).join().data + + then: + result == [hello: 'world'] + } + }