diff --git a/src/main/java/graphql/validation/ValidationErrorType.java b/src/main/java/graphql/validation/ValidationErrorType.java index 5710a1b0b9..e701a5d778 100644 --- a/src/main/java/graphql/validation/ValidationErrorType.java +++ b/src/main/java/graphql/validation/ValidationErrorType.java @@ -43,5 +43,6 @@ public enum ValidationErrorType implements ValidationErrorClassification { NullValueForNonNullArgument, SubscriptionMultipleRootFields, SubscriptionIntrospectionRootField, - UniqueObjectFieldName + UniqueObjectFieldName, + UnknownOperation } diff --git a/src/main/java/graphql/validation/Validator.java b/src/main/java/graphql/validation/Validator.java index d7c3db2fdc..52709109d6 100644 --- a/src/main/java/graphql/validation/Validator.java +++ b/src/main/java/graphql/validation/Validator.java @@ -1,7 +1,6 @@ package graphql.validation; -import graphql.ExperimentalApi; import graphql.Internal; import graphql.i18n.I18n; import graphql.language.Document; @@ -10,6 +9,7 @@ import graphql.validation.rules.DeferDirectiveLabel; import graphql.validation.rules.DeferDirectiveOnRootLevel; import graphql.validation.rules.DeferDirectiveOnValidOperation; +import graphql.validation.rules.KnownOperationTypes; import graphql.validation.rules.UniqueObjectFieldName; import graphql.validation.rules.ExecutableDefinitions; import graphql.validation.rules.FieldsOnCorrectType; @@ -52,7 +52,7 @@ public class Validator { * `graphql-java` will stop validation after a maximum number of validation messages has been reached. Attackers * can send pathologically invalid queries to induce a Denial of Service attack and fill memory with 10000s of errors * and burn CPU in process. - * + *
* By default, this is set to 100 errors. You can set a new JVM wide value as the maximum allowed validation errors.
*
* @param maxValidationErrors the maximum validation errors allow JVM wide
@@ -169,6 +169,10 @@ public List
+ * A GraphQL operation is only valid if all its variables are uniquely named.
+ */
+@Internal
+public class KnownOperationTypes extends AbstractRule {
+
+ public KnownOperationTypes(ValidationContext validationContext, ValidationErrorCollector validationErrorCollector) {
+ super(validationContext, validationErrorCollector);
+ }
+
+ @Override
+ public void checkOperationDefinition(OperationDefinition operationDefinition) {
+ OperationDefinition.Operation documentOperation = operationDefinition.getOperation();
+ GraphQLSchema graphQLSchema = getValidationContext().getSchema();
+ if (documentOperation == OperationDefinition.Operation.MUTATION
+ && graphQLSchema.getMutationType() == null) {
+ String message = i18n(UnknownOperation, "KnownOperationTypes.noOperation", formatOperation(documentOperation));
+ addError(UnknownOperation, operationDefinition.getSourceLocation(), message);
+ } else if (documentOperation == OperationDefinition.Operation.SUBSCRIPTION
+ && graphQLSchema.getSubscriptionType() == null) {
+ String message = i18n(UnknownOperation, "KnownOperationTypes.noOperation", formatOperation(documentOperation));
+ addError(UnknownOperation, operationDefinition.getSourceLocation(), message);
+ } else if (documentOperation == OperationDefinition.Operation.QUERY
+ && graphQLSchema.getQueryType() == null) {
+ // This is unlikely to happen, as a validated GraphQLSchema must have a Query type by definition
+ String message = i18n(UnknownOperation, "KnownOperationTypes.noOperation", formatOperation(documentOperation));
+ addError(UnknownOperation, operationDefinition.getSourceLocation(), message);
+ }
+ }
+
+ private String formatOperation(OperationDefinition.Operation operation) {
+ return StringKit.capitalize(operation.name().toLowerCase());
+ }
+}
diff --git a/src/main/resources/i18n/Validation.properties b/src/main/resources/i18n/Validation.properties
index 4f5c42ab25..a9403bea5b 100644
--- a/src/main/resources/i18n/Validation.properties
+++ b/src/main/resources/i18n/Validation.properties
@@ -38,6 +38,8 @@ KnownFragmentNames.undefinedFragment=Validation error ({0}) : Undefined fragment
#
KnownTypeNames.unknownType=Validation error ({0}) : Unknown type ''{1}''
#
+KnownOperationTypes.noOperation=Validation error ({0}): The ''{1}'' operation is not supported by the schema
+#
LoneAnonymousOperation.withOthers=Validation error ({0}) : Anonymous operation with other operations
LoneAnonymousOperation.namedOperation=Validation error ({0}) : Operation ''{1}'' is following anonymous operation
#
diff --git a/src/main/resources/i18n/Validation_de.properties b/src/main/resources/i18n/Validation_de.properties
index fec637643c..7823c9d511 100644
--- a/src/main/resources/i18n/Validation_de.properties
+++ b/src/main/resources/i18n/Validation_de.properties
@@ -30,6 +30,8 @@ KnownFragmentNames.undefinedFragment=Validierungsfehler ({0}) : Undefiniertes Fr
#
KnownTypeNames.unknownType=Validierungsfehler ({0}) : Unbekannter Typ ''{1}''
#
+KnownOperationTypes.noOperation=Validierungsfehler ({0}): ''{1}'' Operation wird vom Schema nicht unterstützt
+#
LoneAnonymousOperation.withOthers=Validierungsfehler ({0}) : Anonyme Operation mit anderen Operationen
LoneAnonymousOperation.namedOperation=Validierungsfehler ({0}) : Operation ''{1}'' folgt der anonymen Operation
#
diff --git a/src/test/groovy/graphql/GraphQLTest.groovy b/src/test/groovy/graphql/GraphQLTest.groovy
index 8235ac5235..51b0fe9ce3 100644
--- a/src/test/groovy/graphql/GraphQLTest.groovy
+++ b/src/test/groovy/graphql/GraphQLTest.groovy
@@ -350,7 +350,7 @@ class GraphQLTest extends Specification {
thrown(GraphQLException)
}
- def "null mutation type does not throw an npe re: #345 but returns and error"() {
+ def "null mutation type does not throw an npe but returns and error"() {
given:
GraphQLSchema schema = newSchema().query(
@@ -370,7 +370,7 @@ class GraphQLTest extends Specification {
then:
result.errors.size() == 1
- result.errors[0].class == MissingRootTypeException
+ ((ValidationError) result.errors[0]).validationErrorType == ValidationErrorType.UnknownOperation
}
def "#875 a subscription query against a schema that doesn't support subscriptions should result in a GraphQL error"() {
@@ -393,7 +393,7 @@ class GraphQLTest extends Specification {
then:
result.errors.size() == 1
- result.errors[0].class == MissingRootTypeException
+ ((ValidationError) result.errors[0]).validationErrorType == ValidationErrorType.UnknownOperation
}
def "query with int literal too large"() {
diff --git a/src/test/groovy/graphql/ParseAndValidateTest.groovy b/src/test/groovy/graphql/ParseAndValidateTest.groovy
index 949b4aeb5e..fa66c3cbed 100644
--- a/src/test/groovy/graphql/ParseAndValidateTest.groovy
+++ b/src/test/groovy/graphql/ParseAndValidateTest.groovy
@@ -1,6 +1,11 @@
package graphql
+import graphql.language.Document
+import graphql.language.SourceLocation
import graphql.parser.InvalidSyntaxException
+import graphql.parser.Parser
+import graphql.schema.idl.SchemaParser
+import graphql.schema.idl.UnExecutableSchemaGenerator
import graphql.validation.ValidationError
import graphql.validation.ValidationErrorType
import graphql.validation.rules.NoUnusedFragments
@@ -155,4 +160,79 @@ class ParseAndValidateTest extends Specification {
then:
!rs.errors.isEmpty() // all rules apply - we have errors
}
+
+ def "validation error raised if mutation operation does not exist in schema"() {
+ def sdl = '''
+ type Query {
+ myQuery : String!
+ }
+ '''
+
+ def registry = new SchemaParser().parse(sdl)
+ def schema = UnExecutableSchemaGenerator.makeUnExecutableSchema(registry)
+ String request = "mutation MyMutation { myMutation }"
+
+ when:
+ Document inputDocument = new Parser().parseDocument(request)
+ List