Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/main/java/graphql/GraphQL.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;

import static graphql.Assert.assertNotNull;
Expand Down Expand Up @@ -170,6 +171,7 @@ public ValueUnboxer getValueUnboxer() {
* 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) {
Expand All @@ -181,6 +183,7 @@ public static Builder newGraphQL(GraphQLSchema graphQLSchema) {
* 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<GraphQL.Builder> builderConsumer) {
Expand Down Expand Up @@ -241,6 +244,7 @@ public Builder subscriptionExecutionStrategy(ExecutionStrategy executionStrategy
* in {@link graphql.schema.DataFetcher} invocations.
*
* @param dataFetcherExceptionHandler the default handler for data fetching exception
*
* @return this builder
*/
public Builder defaultDataFetcherExceptionHandler(DataFetcherExceptionHandler dataFetcherExceptionHandler) {
Expand Down Expand Up @@ -306,6 +310,7 @@ public GraphQL build() {
* Executes the specified graphql query/mutation/subscription
*
* @param query the query/mutation/subscription
*
* @return an {@link ExecutionResult} which can include errors
*/
public ExecutionResult execute(String query) {
Expand All @@ -320,7 +325,9 @@ public ExecutionResult execute(String query) {
*
* @param query the query/mutation/subscription
* @param context custom object provided to each {@link graphql.schema.DataFetcher}
*
* @return an {@link ExecutionResult} which can include errors
*
* @deprecated Use {@link #execute(ExecutionInput)}
*/
@Deprecated
Expand All @@ -339,7 +346,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 an {@link ExecutionResult} which can include errors
*
* @deprecated Use {@link #execute(ExecutionInput)}
*/
@Deprecated
Expand All @@ -359,7 +368,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 an {@link ExecutionResult} which can include errors
*
* @deprecated Use {@link #execute(ExecutionInput)}
*/
@Deprecated
Expand All @@ -380,7 +391,9 @@ public ExecutionResult execute(String query, Object context, Map<String, Object>
* @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 an {@link ExecutionResult} which can include errors
*
* @deprecated Use {@link #execute(ExecutionInput)}
*/
@Deprecated
Expand All @@ -399,6 +412,7 @@ 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) {
Expand All @@ -416,6 +430,7 @@ public ExecutionResult execute(ExecutionInput.Builder executionInputBuilder) {
* </pre>
*
* @param builderFunction a function that is given a {@link ExecutionInput.Builder}
*
* @return an {@link ExecutionResult} which can include errors
*/
public ExecutionResult execute(UnaryOperator<ExecutionInput.Builder> builderFunction) {
Expand All @@ -426,6 +441,7 @@ public ExecutionResult execute(UnaryOperator<ExecutionInput.Builder> builderFunc
* 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) {
Expand All @@ -447,6 +463,7 @@ public ExecutionResult execute(ExecutionInput executionInput) {
* 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<ExecutionResult> executeAsync(ExecutionInput.Builder executionInputBuilder) {
Expand All @@ -467,6 +484,7 @@ public CompletableFuture<ExecutionResult> executeAsync(ExecutionInput.Builder ex
* </pre>
*
* @param builderFunction a function that is given a {@link ExecutionInput.Builder}
*
* @return a promise to an {@link ExecutionResult} which can include errors
*/
public CompletableFuture<ExecutionResult> executeAsync(UnaryOperator<ExecutionInput.Builder> builderFunction) {
Expand All @@ -480,6 +498,7 @@ public CompletableFuture<ExecutionResult> executeAsync(UnaryOperator<ExecutionIn
* 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<ExecutionResult> executeAsync(ExecutionInput executionInput) {
Expand Down Expand Up @@ -595,7 +614,8 @@ private List<ValidationError> validate(ExecutionInput executionInput, Document d
CompletableFuture<List<ValidationError>> cf = new CompletableFuture<>();
validationCtx.onDispatched(cf);

List<ValidationError> validationErrors = ParseAndValidate.validate(graphQLSchema, document);
Predicate<Class<?>> validationRulePredicate = executionInput.getGraphQLContext().getOrDefault(ParseAndValidate.INTERNAL_VALIDATION_PREDICATE_HINT, r -> true);
List<ValidationError> validationErrors = ParseAndValidate.validate(graphQLSchema, document, validationRulePredicate);

validationCtx.onCompleted(validationErrors, null);
cf.complete(validationErrors);
Expand Down
25 changes: 24 additions & 1 deletion src/main/java/graphql/ParseAndValidate.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import graphql.validation.Validator;

import java.util.List;
import java.util.function.Predicate;

/**
* This class allows you to parse and validate a graphql query without executing it. It will tell you
Expand All @@ -18,6 +19,15 @@
@PublicApi
public class ParseAndValidate {

/**
* This {@link GraphQLContext} hint can be used to supply a Predicate to the Validator so that certain rules can be skipped.
*
* This is an internal capability that you should use at your own risk. While we intend for this to be present for some time, the validation
* rule class names may change, as may this mechanism.
*/
@Internal
public static final String INTERNAL_VALIDATION_PREDICATE_HINT = "graphql.ParseAndValidate.Predicate";

/**
* This can be called to parse and validate a graphql query against a schema, which is useful if you want to know if it would be acceptable
* for execution.
Expand Down Expand Up @@ -65,7 +75,20 @@ public static ParseAndValidateResult parse(ExecutionInput executionInput) {
* @return a result object that indicates how this operation went
*/
public static List<ValidationError> validate(GraphQLSchema graphQLSchema, Document parsedDocument) {
return validate(graphQLSchema, parsedDocument, ruleClass -> true);
}

/**
* This can be called to validate a parsed graphql query.
*
* @param graphQLSchema the graphql schema to validate against
* @param parsedDocument the previously parsed document
* @param rulePredicate this predicate is used to decide what validation rules will be applied
*
* @return a result object that indicates how this operation went
*/
public static List<ValidationError> validate(GraphQLSchema graphQLSchema, Document parsedDocument, Predicate<Class<?>> rulePredicate) {
Validator validator = new Validator();
return validator.validateDocument(graphQLSchema, parsedDocument);
return validator.validateDocument(graphQLSchema, parsedDocument, rulePredicate);
}
}
12 changes: 11 additions & 1 deletion src/main/java/graphql/nextgen/GraphQL.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;

import static graphql.Assert.assertNotNull;
Expand Down Expand Up @@ -65,6 +66,7 @@ public GraphQL(Builder builder) {
* 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) {
Expand All @@ -85,6 +87,7 @@ public ExecutionResult execute(ExecutionInput.Builder executionInputBuilder) {
* </pre>
*
* @param builderFunction a function that is given a {@link ExecutionInput.Builder}
*
* @return a promise to an {@link ExecutionResult} which can include errors
*/
public CompletableFuture<ExecutionResult> execute(UnaryOperator<ExecutionInput.Builder> builderFunction) {
Expand All @@ -98,6 +101,7 @@ public CompletableFuture<ExecutionResult> execute(UnaryOperator<ExecutionInput.B
* 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) {
Expand All @@ -111,6 +115,7 @@ public ExecutionResult execute(ExecutionInput executionInput) {
* 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<ExecutionResult> executeAsync(ExecutionInput.Builder executionInputBuilder) {
Expand All @@ -131,6 +136,7 @@ public CompletableFuture<ExecutionResult> executeAsync(ExecutionInput.Builder ex
* </pre>
*
* @param builderFunction a function that is given a {@link ExecutionInput.Builder}
*
* @return a promise to an {@link ExecutionResult} which can include errors
*/
public CompletableFuture<ExecutionResult> executeAsync(UnaryOperator<ExecutionInput.Builder> builderFunction) {
Expand All @@ -144,6 +150,7 @@ public CompletableFuture<ExecutionResult> executeAsync(UnaryOperator<ExecutionIn
* 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<ExecutionResult> executeAsync(ExecutionInput executionInput) {
Expand Down Expand Up @@ -246,7 +253,8 @@ private List<ValidationError> validate(ExecutionInput executionInput, Document d
CompletableFuture<List<ValidationError>> cf = new CompletableFuture<>();
validationCtx.onDispatched(cf);

List<ValidationError> validationErrors = ParseAndValidate.validate(graphQLSchema, document);
Predicate<Class<?>> validationRulePredicate = executionInput.getGraphQLContext().getOrDefault(ParseAndValidate.INTERNAL_VALIDATION_PREDICATE_HINT, r -> true);
List<ValidationError> validationErrors = ParseAndValidate.validate(graphQLSchema, document, validationRulePredicate);

validationCtx.onCompleted(validationErrors, null);
cf.complete(validationErrors);
Expand Down Expand Up @@ -286,6 +294,7 @@ private CompletableFuture<ExecutionResult> execute(ExecutionInput executionInput
* 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) {
Expand All @@ -297,6 +306,7 @@ public static Builder newGraphQL(GraphQLSchema graphQLSchema) {
* 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<Builder> builderConsumer) {
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/graphql/validation/Validator.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

@Internal
public class Validator {
Expand All @@ -56,10 +58,16 @@ public static int getMaxValidationErrors() {
}

public List<ValidationError> validateDocument(GraphQLSchema schema, Document document) {
return validateDocument(schema, document, ruleClass -> true);
}

public List<ValidationError> validateDocument(GraphQLSchema schema, Document document, Predicate<Class<?>> applyRule) {
ValidationContext validationContext = new ValidationContext(schema, document);

ValidationErrorCollector validationErrorCollector = new ValidationErrorCollector(MAX_VALIDATION_ERRORS);
List<AbstractRule> rules = createRules(validationContext, validationErrorCollector);
// filter out any rules they don't want applied
rules = rules.stream().filter(r -> applyRule.test(r.getClass())).collect(Collectors.toList());
LanguageTraversal languageTraversal = new LanguageTraversal();
try {
languageTraversal.traverse(document, new RulesVisitor(validationContext, rules));
Expand Down
46 changes: 46 additions & 0 deletions src/test/groovy/graphql/ParseAndValidateTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package graphql
import graphql.parser.InvalidSyntaxException
import graphql.validation.ValidationError
import graphql.validation.ValidationErrorType
import graphql.validation.rules.NoUnusedFragments
import spock.lang.Specification

import java.util.function.Predicate

/**
* We trust that other unit tests of the parser and validation catch ALL of the combinations. These tests
* just show the combination of parsing and validation.
Expand Down Expand Up @@ -109,4 +112,47 @@ class ParseAndValidateTest extends Specification {

(result.errors[0] as InvalidSyntaxError).message.contains("Invalid Syntax")
}

def "can use the graphql context to stop certain validation rules"() {

def sdl = '''type Query { foo : ID } '''
def graphQL = TestUtil.graphQL(sdl).build()

Predicate<Class<?>> predicate = new Predicate<Class<?>>() {
@Override
boolean test(Class<?> aClass) {
if (aClass == NoUnusedFragments.class) {
return false
}
return true
}
}

def query = '''
query { foo }

fragment UnusedFrag on Query {
foo
}
'''

when:
def ei = ExecutionInput.newExecutionInput(query)
.graphQLContext(["graphql.ParseAndValidate.Predicate": predicate])
.build()
def rs = graphQL.execute(ei)

then:
rs.errors.isEmpty() // we skipped a rule

when:
predicate = { it -> true }
ei = ExecutionInput.newExecutionInput(query)
.graphQLContext(["graphql.ParseAndValidate.Predicate": predicate])
.build()
rs = graphQL.execute(ei)

then:
!rs.errors.isEmpty() // all rules apply - we have errors
}
}