diff --git a/src/main/java/graphql/ExecutionInput.java b/src/main/java/graphql/ExecutionInput.java index 1596952901..8a74a8dec3 100644 --- a/src/main/java/graphql/ExecutionInput.java +++ b/src/main/java/graphql/ExecutionInput.java @@ -225,7 +225,7 @@ public static class Builder { // private DataLoaderRegistry dataLoaderRegistry = DataLoaderDispatcherInstrumentationState.EMPTY_DATALOADER_REGISTRY; private CacheControl cacheControl = CacheControl.newCacheControl(); - private Locale locale; + private Locale locale = Locale.getDefault(); private ExecutionId executionId; public Builder query(String query) { diff --git a/src/main/java/graphql/GraphQL.java b/src/main/java/graphql/GraphQL.java index 618621d38d..3e6c88d05b 100644 --- a/src/main/java/graphql/GraphQL.java +++ b/src/main/java/graphql/GraphQL.java @@ -623,7 +623,7 @@ private List validate(ExecutionInput executionInput, Document d validationCtx.onDispatched(cf); Predicate> validationRulePredicate = executionInput.getGraphQLContext().getOrDefault(ParseAndValidate.INTERNAL_VALIDATION_PREDICATE_HINT, r -> true); - List validationErrors = ParseAndValidate.validate(graphQLSchema, document, validationRulePredicate); + List validationErrors = ParseAndValidate.validate(graphQLSchema, document, validationRulePredicate, executionInput.getLocale()); validationCtx.onCompleted(validationErrors, null); cf.complete(validationErrors); diff --git a/src/main/java/graphql/ParseAndValidate.java b/src/main/java/graphql/ParseAndValidate.java index 8e42cdb2bf..7bb12d22f8 100644 --- a/src/main/java/graphql/ParseAndValidate.java +++ b/src/main/java/graphql/ParseAndValidate.java @@ -9,6 +9,7 @@ import graphql.validation.Validator; import java.util.List; +import java.util.Locale; import java.util.function.Predicate; /** @@ -40,7 +41,7 @@ public class ParseAndValidate { public static ParseAndValidateResult parseAndValidate(GraphQLSchema graphQLSchema, ExecutionInput executionInput) { ParseAndValidateResult result = parse(executionInput); if (!result.isFailure()) { - List errors = validate(graphQLSchema, result.getDocument()); + List errors = validate(graphQLSchema, result.getDocument(), executionInput.getLocale()); return result.transform(builder -> builder.validationErrors(errors)); } return result; @@ -71,11 +72,24 @@ public static ParseAndValidateResult parse(ExecutionInput executionInput) { * * @param graphQLSchema the graphql schema to validate against * @param parsedDocument the previously parsed document + * @param locale the current locale + * + * @return a result object that indicates how this operation went + */ + public static List validate(GraphQLSchema graphQLSchema, Document parsedDocument, Locale locale) { + return validate(graphQLSchema, parsedDocument, ruleClass -> true, locale); + } + + /** + * This can be called to validate a parsed graphql query, with the JVM default locale. + * + * @param graphQLSchema the graphql schema to validate against + * @param parsedDocument the previously parsed document * * @return a result object that indicates how this operation went */ public static List validate(GraphQLSchema graphQLSchema, Document parsedDocument) { - return validate(graphQLSchema, parsedDocument, ruleClass -> true); + return validate(graphQLSchema, parsedDocument, ruleClass -> true, Locale.getDefault()); } /** @@ -84,11 +98,26 @@ public static List validate(GraphQLSchema graphQLSchema, Docume * @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 + * @param locale the current locale + * + * @return a result object that indicates how this operation went + */ + public static List validate(GraphQLSchema graphQLSchema, Document parsedDocument, Predicate> rulePredicate, Locale locale) { + Validator validator = new Validator(); + return validator.validateDocument(graphQLSchema, parsedDocument, rulePredicate, locale); + } + + /** + * This can be called to validate a parsed graphql query, with the JVM default locale. + * + * @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 validate(GraphQLSchema graphQLSchema, Document parsedDocument, Predicate> rulePredicate) { Validator validator = new Validator(); - return validator.validateDocument(graphQLSchema, parsedDocument, rulePredicate); + return validator.validateDocument(graphQLSchema, parsedDocument, rulePredicate, Locale.getDefault()); } } diff --git a/src/main/java/graphql/i18n/I18n.java b/src/main/java/graphql/i18n/I18n.java new file mode 100644 index 0000000000..dd9efae54e --- /dev/null +++ b/src/main/java/graphql/i18n/I18n.java @@ -0,0 +1,70 @@ +package graphql.i18n; + +import graphql.Internal; +import graphql.VisibleForTesting; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import static graphql.Assert.assertNotNull; +import static graphql.Assert.assertShouldNeverHappen; + +@Internal +public class I18n { + + /** + * This enum is a type safe way to control what resource bundle to load from + */ + public enum BundleType { + Validation, + Execution, + General; + + private final String baseName; + + BundleType() { + this.baseName = "i18n." + this.name(); + } + } + + private final ResourceBundle resourceBundle; + + @VisibleForTesting + protected I18n(BundleType bundleType, Locale locale) { + assertNotNull(bundleType); + assertNotNull(locale); + this.resourceBundle = ResourceBundle.getBundle(bundleType.baseName, locale); + } + + public ResourceBundle getResourceBundle() { + return resourceBundle; + } + + public static I18n i18n(BundleType bundleType, Locale locale) { + return new I18n(bundleType, locale); + } + + + /** + * Creates an I18N message using the key and arguments + * + * @param msgKey the key in the underlying message bundle + * @param msgArgs the message arguments + * + * @return the formatted I18N message + */ + @SuppressWarnings("UnnecessaryLocalVariable") + public String msg(String msgKey, Object... msgArgs) { + String msgPattern = null; + try { + msgPattern = resourceBundle.getString(msgKey); + } catch (MissingResourceException e) { + assertShouldNeverHappen("There must be a resource bundle key called %s", msgKey); + } + + String formattedMsg = new MessageFormat(msgPattern).format(msgArgs); + return formattedMsg; + } +} diff --git a/src/main/java/graphql/i18n/I18nMsg.java b/src/main/java/graphql/i18n/I18nMsg.java new file mode 100644 index 0000000000..2db09cc947 --- /dev/null +++ b/src/main/java/graphql/i18n/I18nMsg.java @@ -0,0 +1,42 @@ +package graphql.i18n; + +import java.util.ArrayList; +import java.util.List; + +import static java.util.Arrays.asList; + +/** + * A class that represents the intention to create a I18n message + */ +public class I18nMsg { + private final String msgKey; + private final List msgArguments; + + public I18nMsg(String msgKey, List msgArguments) { + this.msgKey = msgKey; + this.msgArguments = msgArguments; + } + + public I18nMsg(String msgKey, Object... msgArguments) { + this.msgKey = msgKey; + this.msgArguments = asList(msgArguments); + } + + public String getMsgKey() { + return msgKey; + } + + public Object[] getMsgArguments() { + return msgArguments.toArray(); + } + + public I18nMsg addArgumentAt(int index, Object argument) { + List newArgs = new ArrayList<>(this.msgArguments); + newArgs.add(index, argument); + return new I18nMsg(this.msgKey, newArgs); + } + + public String toI18n(I18n i18n) { + return i18n.msg(msgKey, msgArguments); + } +} diff --git a/src/main/java/graphql/nextgen/GraphQL.java b/src/main/java/graphql/nextgen/GraphQL.java index e3fb231a1d..dc65416124 100644 --- a/src/main/java/graphql/nextgen/GraphQL.java +++ b/src/main/java/graphql/nextgen/GraphQL.java @@ -265,7 +265,7 @@ private List validate(ExecutionInput executionInput, Document d validationCtx.onDispatched(cf); Predicate> validationRulePredicate = executionInput.getGraphQLContext().getOrDefault(ParseAndValidate.INTERNAL_VALIDATION_PREDICATE_HINT, r -> true); - List validationErrors = ParseAndValidate.validate(graphQLSchema, document, validationRulePredicate); + List validationErrors = ParseAndValidate.validate(graphQLSchema, document, validationRulePredicate, executionInput.getLocale()); validationCtx.onCompleted(validationErrors, null); cf.complete(validationErrors); diff --git a/src/main/java/graphql/validation/AbstractRule.java b/src/main/java/graphql/validation/AbstractRule.java index 57f2ffb22d..eca44a20e9 100644 --- a/src/main/java/graphql/validation/AbstractRule.java +++ b/src/main/java/graphql/validation/AbstractRule.java @@ -2,6 +2,7 @@ import graphql.Internal; +import graphql.i18n.I18nMsg; import graphql.language.Argument; import graphql.language.Directive; import graphql.language.Document; @@ -22,21 +23,20 @@ import java.util.List; import static graphql.validation.ValidationError.newValidationError; +import static java.lang.System.arraycopy; @Internal public class AbstractRule { private final ValidationContext validationContext; private final ValidationErrorCollector validationErrorCollector; - - + private final ValidationUtil validationUtil; private boolean visitFragmentSpreads; - private ValidationUtil validationUtil = new ValidationUtil(); - public AbstractRule(ValidationContext validationContext, ValidationErrorCollector validationErrorCollector) { this.validationContext = validationContext; this.validationErrorCollector = validationErrorCollector; + this.validationUtil = new ValidationUtil(); } public boolean isVisitFragmentSpreads() { @@ -47,7 +47,6 @@ public void setVisitFragmentSpreads(boolean visitFragmentSpreads) { this.visitFragmentSpreads = visitFragmentSpreads; } - public ValidationUtil getValidationUtil() { return validationUtil; } @@ -78,7 +77,6 @@ public List getErrors() { return validationErrorCollector.getErrors(); } - public ValidationContext getValidationContext() { return validationContext; } @@ -91,6 +89,45 @@ protected List getQueryPath() { return validationContext.getQueryPath(); } + /** + * Creates an I18n message using the {@link graphql.i18n.I18nMsg} + * + * @param validationErrorType the type of validation failure + * @param i18nMsg the i18n message object + * + * @return the formatted I18n message + */ + public String i18n(ValidationErrorType validationErrorType, I18nMsg i18nMsg) { + return i18n(validationErrorType, i18nMsg.getMsgKey(), i18nMsg.getMsgArguments()); + } + + /** + * Creates an I18N message using the key and arguments + * + * @param validationErrorType the type of validation failure + * @param msgKey the key in the underlying message bundle + * @param msgArgs the message arguments + * + * @return the formatted I18N message + */ + public String i18n(ValidationErrorType validationErrorType, String msgKey, Object... msgArgs) { + Object[] params = new Object[msgArgs.length + 1]; + params[0] = mkTypeAndPath(validationErrorType); + arraycopy(msgArgs, 0, params, 1, msgArgs.length); + + return validationContext.i18n(msgKey, params); + } + + private String mkTypeAndPath(ValidationErrorType validationErrorType) { + List queryPath = getQueryPath(); + StringBuilder sb = new StringBuilder(); + sb.append(validationErrorType); + if (queryPath != null) { + sb.append("@[").append(String.join("/", queryPath)).append("]"); + } + return sb.toString(); + } + public void checkDocument(Document document) { } diff --git a/src/main/java/graphql/validation/ArgumentValidationUtil.java b/src/main/java/graphql/validation/ArgumentValidationUtil.java index 1beda0b178..18261d4b0d 100644 --- a/src/main/java/graphql/validation/ArgumentValidationUtil.java +++ b/src/main/java/graphql/validation/ArgumentValidationUtil.java @@ -2,6 +2,7 @@ import graphql.GraphQLError; import graphql.Internal; +import graphql.i18n.I18nMsg; import graphql.language.Argument; import graphql.language.ObjectField; import graphql.language.Value; @@ -20,7 +21,7 @@ public class ArgumentValidationUtil extends ValidationUtil { private final List argumentNames = new ArrayList<>(); private Value argumentValue; - private String errorMessage; + private String errMsgKey; private final List arguments = new ArrayList<>(); private Map errorExtensions; @@ -33,13 +34,17 @@ public ArgumentValidationUtil(Argument argument) { @Override protected void handleNullError(Value value, GraphQLType type) { - errorMessage = "must not be null"; + errMsgKey = "ArgumentValidationUtil.handleNullError"; argumentValue = value; } @Override protected void handleScalarError(Value value, GraphQLScalarType type, GraphQLError invalid) { - errorMessage = "is not a valid '%s' - %s"; + if (invalid.getMessage() == null) { + errMsgKey = "ArgumentValidationUtil.handleScalarError"; + } else { + errMsgKey = "ArgumentValidationUtil.handleScalarErrorCustomMessage"; + } arguments.add(type.getName()); arguments.add(invalid.getMessage()); argumentValue = value; @@ -48,7 +53,11 @@ protected void handleScalarError(Value value, GraphQLScalarType type, GraphQL @Override protected void handleEnumError(Value value, GraphQLEnumType type, GraphQLError invalid) { - errorMessage = "is not a valid '%s' - %s"; + if (invalid.getMessage() == null) { + errMsgKey = "ArgumentValidationUtil.handleEnumError"; + } else { + errMsgKey = "ArgumentValidationUtil.handleEnumErrorCustomMessage"; + } arguments.add(type.getName()); arguments.add(invalid.getMessage()); argumentValue = value; @@ -56,18 +65,18 @@ protected void handleEnumError(Value value, GraphQLEnumType type, GraphQLErro @Override protected void handleNotObjectError(Value value, GraphQLInputObjectType type) { - errorMessage = "must be an object type"; + errMsgKey = "ArgumentValidationUtil.handleNotObjectError"; } @Override protected void handleMissingFieldsError(Value value, GraphQLInputObjectType type, Set missingFields) { - errorMessage = "is missing required fields '%s'"; + errMsgKey = "ArgumentValidationUtil.handleMissingFieldsError"; arguments.add(missingFields); } @Override protected void handleExtraFieldError(Value value, GraphQLInputObjectType type, ObjectField objectField) { - errorMessage = "contains a field not in '%s': '%s'"; + errMsgKey = "ArgumentValidationUtil.handleExtraFieldError"; arguments.add(type.getName()); arguments.add(objectField.getName()); } @@ -82,7 +91,7 @@ protected void handleFieldNotValidError(Value value, GraphQLType type, int in argumentNames.add(0, String.format("[%s]", index)); } - public String getMessage() { + public I18nMsg getMsgAndArgs() { StringBuilder argument = new StringBuilder(argumentName); for (String name : argumentNames) { if (name.startsWith("[")) { @@ -94,9 +103,7 @@ public String getMessage() { arguments.add(0, argument.toString()); arguments.add(1, argumentValue); - String message = "argument '%s' with value '%s'" + " " + errorMessage; - - return String.format(message, arguments.toArray()); + return new I18nMsg(errMsgKey, arguments); } public Map getErrorExtensions() { diff --git a/src/main/java/graphql/validation/ValidationContext.java b/src/main/java/graphql/validation/ValidationContext.java index 6347f0a99b..ad94f603ee 100644 --- a/src/main/java/graphql/validation/ValidationContext.java +++ b/src/main/java/graphql/validation/ValidationContext.java @@ -2,6 +2,7 @@ import graphql.Internal; +import graphql.i18n.I18n; import graphql.language.Definition; import graphql.language.Document; import graphql.language.FragmentDefinition; @@ -25,12 +26,13 @@ public class ValidationContext { private final TraversalContext traversalContext; private final Map fragmentDefinitionMap = new LinkedHashMap<>(); + private final I18n i18n; - - public ValidationContext(GraphQLSchema schema, Document document) { + public ValidationContext(GraphQLSchema schema, Document document, I18n i18n) { this.schema = schema; this.document = document; this.traversalContext = new TraversalContext(schema); + this.i18n = i18n; buildFragmentMap(); } @@ -82,11 +84,26 @@ public GraphQLOutputType getOutputType() { return traversalContext.getOutputType(); } - public List getQueryPath() { return traversalContext.getQueryPath(); } + public I18n getI18n() { + return i18n; + } + + /** + * Creates an I18N message using the key and arguments + * + * @param msgKey the key in the underlying message bundle + * @param msgArgs the message arguments + * + * @return the formatted I18N message + */ + public String i18n(String msgKey, Object... msgArgs) { + return i18n.msg(msgKey, msgArgs); + } + @Override public String toString() { return "ValidationContext{" + getQueryPath() + "}"; diff --git a/src/main/java/graphql/validation/ValidationError.java b/src/main/java/graphql/validation/ValidationError.java index 040bdc3306..2b257b047b 100644 --- a/src/main/java/graphql/validation/ValidationError.java +++ b/src/main/java/graphql/validation/ValidationError.java @@ -22,11 +22,13 @@ public class ValidationError implements GraphQLError { private final List queryPath; private final Map extensions; + @Deprecated public ValidationError(ValidationErrorClassification validationErrorType) { this(newValidationError() .validationErrorType(validationErrorType)); } + @Deprecated public ValidationError(ValidationErrorClassification validationErrorType, SourceLocation sourceLocation, String description) { this(newValidationError() .validationErrorType(validationErrorType) @@ -34,6 +36,7 @@ public ValidationError(ValidationErrorClassification validationErrorType, Source .description(description)); } + @Deprecated public ValidationError(ValidationErrorType validationErrorType, SourceLocation sourceLocation, String description, List queryPath) { this(newValidationError() .validationErrorType(validationErrorType) @@ -42,6 +45,7 @@ public ValidationError(ValidationErrorType validationErrorType, SourceLocation s .queryPath(queryPath)); } + @Deprecated public ValidationError(ValidationErrorType validationErrorType, List sourceLocations, String description) { this(newValidationError() .validationErrorType(validationErrorType) @@ -49,6 +53,7 @@ public ValidationError(ValidationErrorType validationErrorType, List sourceLocations, String description, List queryPath) { this(newValidationError() .validationErrorType(validationErrorType) @@ -63,22 +68,11 @@ private ValidationError(Builder builder) { this.locations.addAll(builder.sourceLocations); } this.description = builder.description; - this.message = mkMessage(builder.validationErrorType, builder.description, builder.queryPath); + this.message = builder.description; this.queryPath = builder.queryPath; this.extensions = builder.extensions; } - private String mkMessage(ValidationErrorClassification validationErrorType, String description, List queryPath) { - return String.format("Validation error of type %s: %s%s", validationErrorType, description, toPath(queryPath)); - } - - private String toPath(List queryPath) { - if (queryPath == null) { - return ""; - } - return String.format(" @ '%s'", String.join("/", queryPath)); - } - public ValidationErrorClassification getValidationErrorType() { return validationErrorType; } diff --git a/src/main/java/graphql/validation/ValidationErrorCollector.java b/src/main/java/graphql/validation/ValidationErrorCollector.java index 7cf1efd496..cdb21aca05 100644 --- a/src/main/java/graphql/validation/ValidationErrorCollector.java +++ b/src/main/java/graphql/validation/ValidationErrorCollector.java @@ -59,7 +59,7 @@ public boolean containsValidationError(ValidationErrorType validationErrorType) public boolean containsValidationError(ValidationErrorType validationErrorType, String description) { for (ValidationError validationError : errors) { if (validationError.getValidationErrorType() == validationErrorType) { - return description == null || validationError.getDescription().equals(description); + return description == null || validationError.getDescription().contains(description); } } return false; diff --git a/src/main/java/graphql/validation/ValidationErrorType.java b/src/main/java/graphql/validation/ValidationErrorType.java index 7b00692adc..5646aac2f0 100644 --- a/src/main/java/graphql/validation/ValidationErrorType.java +++ b/src/main/java/graphql/validation/ValidationErrorType.java @@ -10,8 +10,8 @@ public enum ValidationErrorType implements ValidationErrorClassification { DefaultForNonNullArgument, WrongType, UnknownType, - SubSelectionRequired, - SubSelectionNotAllowed, + SubselectionRequired, + SubselectionNotAllowed, InvalidSyntax, BadValueForDefaultArg, FieldUndefined, diff --git a/src/main/java/graphql/validation/Validator.java b/src/main/java/graphql/validation/Validator.java index 0351c05cbb..51dfd40cbb 100644 --- a/src/main/java/graphql/validation/Validator.java +++ b/src/main/java/graphql/validation/Validator.java @@ -2,6 +2,7 @@ import graphql.Internal; +import graphql.i18n.I18n; import graphql.language.Document; import graphql.schema.GraphQLSchema; import graphql.validation.rules.ArgumentsOfCorrectType; @@ -20,19 +21,20 @@ import graphql.validation.rules.OverlappingFieldsCanBeMerged; import graphql.validation.rules.PossibleFragmentSpreads; import graphql.validation.rules.ProvidedNonNullArguments; -import graphql.validation.rules.ScalarLeafs; +import graphql.validation.rules.ScalarLeaves; import graphql.validation.rules.SubscriptionUniqueRootField; -import graphql.validation.rules.UniqueArgumentNamesRule; +import graphql.validation.rules.UniqueArgumentNames; import graphql.validation.rules.UniqueDirectiveNamesPerLocation; import graphql.validation.rules.UniqueFragmentNames; import graphql.validation.rules.UniqueOperationNames; -import graphql.validation.rules.UniqueVariableNamesRule; +import graphql.validation.rules.UniqueVariableNames; import graphql.validation.rules.VariableDefaultValuesOfCorrectType; -import graphql.validation.rules.VariableTypesMatchRule; +import graphql.validation.rules.VariableTypesMatch; import graphql.validation.rules.VariablesAreInputTypes; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -58,12 +60,13 @@ public static int getMaxValidationErrors() { return MAX_VALIDATION_ERRORS; } - public List validateDocument(GraphQLSchema schema, Document document) { - return validateDocument(schema, document, ruleClass -> true); + public List validateDocument(GraphQLSchema schema, Document document, Locale locale) { + return validateDocument(schema, document, ruleClass -> true, locale); } - public List validateDocument(GraphQLSchema schema, Document document, Predicate> applyRule) { - ValidationContext validationContext = new ValidationContext(schema, document); + public List validateDocument(GraphQLSchema schema, Document document, Predicate> applyRule, Locale locale) { + I18n i18n = I18n.i18n(I18n.BundleType.Validation, locale); + ValidationContext validationContext = new ValidationContext(schema, document, i18n); ValidationErrorCollector validationErrorCollector = new ValidationErrorCollector(MAX_VALIDATION_ERRORS); List rules = createRules(validationContext, validationErrorCollector); @@ -119,15 +122,15 @@ public List createRules(ValidationContext validationContext, Valid ProvidedNonNullArguments providedNonNullArguments = new ProvidedNonNullArguments(validationContext, validationErrorCollector); rules.add(providedNonNullArguments); - ScalarLeafs scalarLeafs = new ScalarLeafs(validationContext, validationErrorCollector); - rules.add(scalarLeafs); + ScalarLeaves scalarLeaves = new ScalarLeaves(validationContext, validationErrorCollector); + rules.add(scalarLeaves); VariableDefaultValuesOfCorrectType variableDefaultValuesOfCorrectType = new VariableDefaultValuesOfCorrectType(validationContext, validationErrorCollector); rules.add(variableDefaultValuesOfCorrectType); VariablesAreInputTypes variablesAreInputTypes = new VariablesAreInputTypes(validationContext, validationErrorCollector); rules.add(variablesAreInputTypes); - VariableTypesMatchRule variableTypesMatchRule = new VariableTypesMatchRule(validationContext, validationErrorCollector); - rules.add(variableTypesMatchRule); + VariableTypesMatch variableTypesMatch = new VariableTypesMatch(validationContext, validationErrorCollector); + rules.add(variableTypesMatch); LoneAnonymousOperation loneAnonymousOperation = new LoneAnonymousOperation(validationContext, validationErrorCollector); rules.add(loneAnonymousOperation); @@ -141,10 +144,10 @@ public List createRules(ValidationContext validationContext, Valid UniqueDirectiveNamesPerLocation uniqueDirectiveNamesPerLocation = new UniqueDirectiveNamesPerLocation(validationContext, validationErrorCollector); rules.add(uniqueDirectiveNamesPerLocation); - UniqueArgumentNamesRule uniqueArgumentNamesRule = new UniqueArgumentNamesRule(validationContext, validationErrorCollector); + UniqueArgumentNames uniqueArgumentNamesRule = new UniqueArgumentNames(validationContext, validationErrorCollector); rules.add(uniqueArgumentNamesRule); - UniqueVariableNamesRule uniqueVariableNamesRule = new UniqueVariableNamesRule(validationContext, validationErrorCollector); + UniqueVariableNames uniqueVariableNamesRule = new UniqueVariableNames(validationContext, validationErrorCollector); rules.add(uniqueVariableNamesRule); SubscriptionUniqueRootField uniqueSubscriptionRootField = new SubscriptionUniqueRootField(validationContext, validationErrorCollector); diff --git a/src/main/java/graphql/validation/rules/ArgumentsOfCorrectType.java b/src/main/java/graphql/validation/rules/ArgumentsOfCorrectType.java index 2ed526a950..0f942bdfc8 100644 --- a/src/main/java/graphql/validation/rules/ArgumentsOfCorrectType.java +++ b/src/main/java/graphql/validation/rules/ArgumentsOfCorrectType.java @@ -9,7 +9,8 @@ import graphql.validation.ValidationContext; import graphql.validation.ValidationError; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; + +import static graphql.validation.ValidationErrorType.WrongType; @Internal public class ArgumentsOfCorrectType extends AbstractRule { @@ -26,10 +27,11 @@ public void checkArgument(Argument argument) { } ArgumentValidationUtil validationUtil = new ArgumentValidationUtil(argument); if (!validationUtil.isValidLiteralValue(argument.getValue(), fieldArgument.getType(), getValidationContext().getSchema())) { + String message = i18n(WrongType, validationUtil.getMsgAndArgs()); addError(ValidationError.newValidationError() - .validationErrorType(ValidationErrorType.WrongType) + .validationErrorType(WrongType) .sourceLocation(argument.getSourceLocation()) - .description(validationUtil.getMessage()) + .description(message) .extensions(validationUtil.getErrorExtensions())); } } diff --git a/src/main/java/graphql/validation/rules/ExecutableDefinitions.java b/src/main/java/graphql/validation/rules/ExecutableDefinitions.java index b870db3548..9a81f9c4aa 100644 --- a/src/main/java/graphql/validation/rules/ExecutableDefinitions.java +++ b/src/main/java/graphql/validation/rules/ExecutableDefinitions.java @@ -2,6 +2,7 @@ import graphql.Internal; import graphql.language.Definition; +import graphql.language.DirectiveDefinition; import graphql.language.Document; import graphql.language.FragmentDefinition; import graphql.language.OperationDefinition; @@ -10,7 +11,8 @@ import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; + +import static graphql.validation.ValidationErrorType.NonExecutableDefinition; @Internal public class ExecutableDefinitions extends AbstractRule { @@ -32,26 +34,20 @@ public void checkDocument(Document document) { && !(definition instanceof FragmentDefinition)) { String message = nonExecutableDefinitionMessage(definition); - addError(ValidationErrorType.NonExecutableDefinition, definition.getSourceLocation(), message); + addError(NonExecutableDefinition, definition.getSourceLocation(), message); } }); } private String nonExecutableDefinitionMessage(Definition definition) { - - String definitionName; if (definition instanceof TypeDefinition) { - definitionName = ((TypeDefinition) definition).getName(); + return i18n(NonExecutableDefinition, "ExecutableDefinitions.notExecutableType", ((TypeDefinition) definition).getName()); } else if (definition instanceof SchemaDefinition) { - definitionName = "schema"; - } else { - definitionName = "provided"; + return i18n(NonExecutableDefinition, "ExecutableDefinitions.notExecutableSchema"); + } else if (definition instanceof DirectiveDefinition) { + return i18n(NonExecutableDefinition, "ExecutableDefinitions.notExecutableDirective", ((DirectiveDefinition) definition).getName()); } - return nonExecutableDefinitionMessage(definitionName); - } - - static String nonExecutableDefinitionMessage(String definitionName) { - return String.format("The %s definition is not executable.", definitionName); + return i18n(NonExecutableDefinition, "ExecutableDefinitions.notExecutableDefinition"); } } diff --git a/src/main/java/graphql/validation/rules/FieldsOnCorrectType.java b/src/main/java/graphql/validation/rules/FieldsOnCorrectType.java index 84a590a7e7..df9a403e26 100644 --- a/src/main/java/graphql/validation/rules/FieldsOnCorrectType.java +++ b/src/main/java/graphql/validation/rules/FieldsOnCorrectType.java @@ -8,7 +8,8 @@ import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; + +import static graphql.validation.ValidationErrorType.FieldUndefined; @Internal public class FieldsOnCorrectType extends AbstractRule { @@ -26,8 +27,8 @@ public void checkField(Field field) { if (parentType == null) return; GraphQLFieldDefinition fieldDef = getValidationContext().getFieldDef(); if (fieldDef == null) { - String message = String.format("Field '%s' in type '%s' is undefined", field.getName(), parentType.getName()); - addError(ValidationErrorType.FieldUndefined, field.getSourceLocation(), message); + String message = i18n(FieldUndefined, "FieldsOnCorrectType.unknownField", field.getName(), parentType.getName()); + addError(FieldUndefined, field.getSourceLocation(), message); } } diff --git a/src/main/java/graphql/validation/rules/FragmentsOnCompositeType.java b/src/main/java/graphql/validation/rules/FragmentsOnCompositeType.java index 4932bd6c3f..fd7cd3c21f 100644 --- a/src/main/java/graphql/validation/rules/FragmentsOnCompositeType.java +++ b/src/main/java/graphql/validation/rules/FragmentsOnCompositeType.java @@ -9,7 +9,9 @@ import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; + +import static graphql.validation.ValidationErrorType.FragmentTypeConditionInvalid; +import static graphql.validation.ValidationErrorType.InlineFragmentTypeConditionInvalid; @Internal public class FragmentsOnCompositeType extends AbstractRule { @@ -27,8 +29,8 @@ public void checkInlineFragment(InlineFragment inlineFragment) { GraphQLType type = getValidationContext().getSchema().getType(inlineFragment.getTypeCondition().getName()); if (type == null) return; if (!(type instanceof GraphQLCompositeType)) { - String message = "Inline fragment type condition is invalid, must be on Object/Interface/Union"; - addError(ValidationErrorType.InlineFragmentTypeConditionInvalid, inlineFragment.getSourceLocation(), message); + String message = i18n(InlineFragmentTypeConditionInvalid, "FragmentsOnCompositeType.invalidInlineTypeCondition"); + addError(InlineFragmentTypeConditionInvalid, inlineFragment.getSourceLocation(), message); } } @@ -37,8 +39,8 @@ public void checkFragmentDefinition(FragmentDefinition fragmentDefinition) { GraphQLType type = getValidationContext().getSchema().getType(fragmentDefinition.getTypeCondition().getName()); if (type == null) return; if (!(type instanceof GraphQLCompositeType)) { - String message = "Fragment type condition is invalid, must be on Object/Interface/Union"; - addError(ValidationErrorType.FragmentTypeConditionInvalid, fragmentDefinition.getSourceLocation(), message); + String message = i18n(FragmentTypeConditionInvalid, "FragmentsOnCompositeType.invalidFragmentTypeCondition"); + addError(FragmentTypeConditionInvalid, fragmentDefinition.getSourceLocation(), message); } } } diff --git a/src/main/java/graphql/validation/rules/KnownArgumentNames.java b/src/main/java/graphql/validation/rules/KnownArgumentNames.java index 12cec4ff2c..df0348e900 100644 --- a/src/main/java/graphql/validation/rules/KnownArgumentNames.java +++ b/src/main/java/graphql/validation/rules/KnownArgumentNames.java @@ -10,6 +10,9 @@ import graphql.validation.ValidationErrorCollector; import graphql.validation.ValidationErrorType; +import static graphql.validation.ValidationErrorType.UnknownArgument; +import static graphql.validation.ValidationErrorType.UnknownDirective; + @Internal public class KnownArgumentNames extends AbstractRule { @@ -25,8 +28,8 @@ public void checkArgument(Argument argument) { if (directiveDef != null) { GraphQLArgument directiveArgument = directiveDef.getArgument(argument.getName()); if (directiveArgument == null) { - String message = String.format("Unknown directive argument %s", argument.getName()); - addError(ValidationErrorType.UnknownDirective, argument.getSourceLocation(), message); + String message = i18n(UnknownDirective, "KnownArgumentNames.unknownDirectiveArg", argument.getName()); + addError(UnknownDirective, argument.getSourceLocation(), message); } return; @@ -36,8 +39,8 @@ public void checkArgument(Argument argument) { if (fieldDef == null) return; GraphQLArgument fieldArgument = fieldDef.getArgument(argument.getName()); if (fieldArgument == null) { - String message = String.format("Unknown field argument %s", argument.getName()); - addError(ValidationErrorType.UnknownArgument, argument.getSourceLocation(), message); + String message = i18n(UnknownArgument, "KnownArgumentNames.unknownFieldArg", argument.getName()); + addError(UnknownArgument, argument.getSourceLocation(), message); } } } diff --git a/src/main/java/graphql/validation/rules/KnownDirectives.java b/src/main/java/graphql/validation/rules/KnownDirectives.java index 7534b81086..eb011fb682 100644 --- a/src/main/java/graphql/validation/rules/KnownDirectives.java +++ b/src/main/java/graphql/validation/rules/KnownDirectives.java @@ -16,11 +16,13 @@ import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; import java.util.List; import java.util.EnumSet; +import static graphql.validation.ValidationErrorType.MisplacedDirective; +import static graphql.validation.ValidationErrorType.UnknownDirective; + @Internal public class KnownDirectives extends AbstractRule { @@ -33,15 +35,15 @@ public KnownDirectives(ValidationContext validationContext, ValidationErrorColle public void checkDirective(Directive directive, List ancestors) { GraphQLDirective graphQLDirective = getValidationContext().getSchema().getDirective(directive.getName()); if (graphQLDirective == null) { - String message = String.format("Unknown directive %s", directive.getName()); - addError(ValidationErrorType.UnknownDirective, directive.getSourceLocation(), message); + String message = i18n(UnknownDirective, "KnownDirectives.unknownDirective", directive.getName()); + addError(UnknownDirective, directive.getSourceLocation(), message); return; } Node ancestor = ancestors.get(ancestors.size() - 1); if (hasInvalidLocation(graphQLDirective, ancestor)) { - String message = String.format("Directive %s not allowed here", directive.getName()); - addError(ValidationErrorType.MisplacedDirective, directive.getSourceLocation(), message); + String message = i18n(MisplacedDirective, "KnownDirectives.directiveNotAllowed", directive.getName()); + addError(MisplacedDirective, directive.getSourceLocation(), message); } } diff --git a/src/main/java/graphql/validation/rules/KnownFragmentNames.java b/src/main/java/graphql/validation/rules/KnownFragmentNames.java index 6ad624326f..0ec66508a0 100644 --- a/src/main/java/graphql/validation/rules/KnownFragmentNames.java +++ b/src/main/java/graphql/validation/rules/KnownFragmentNames.java @@ -7,7 +7,8 @@ import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; + +import static graphql.validation.ValidationErrorType.UndefinedFragment; @Internal public class KnownFragmentNames extends AbstractRule { @@ -20,8 +21,8 @@ public KnownFragmentNames(ValidationContext validationContext, ValidationErrorCo public void checkFragmentSpread(FragmentSpread fragmentSpread) { FragmentDefinition fragmentDefinition = getValidationContext().getFragment(fragmentSpread.getName()); if (fragmentDefinition == null) { - String message = String.format("Undefined fragment %s", fragmentSpread.getName()); - addError(ValidationErrorType.UndefinedFragment, fragmentSpread.getSourceLocation(), message); + String message = i18n(UndefinedFragment, "KnownFragmentNames.undefinedFragment", fragmentSpread.getName()); + addError(UndefinedFragment, fragmentSpread.getSourceLocation(), message); } } } diff --git a/src/main/java/graphql/validation/rules/KnownTypeNames.java b/src/main/java/graphql/validation/rules/KnownTypeNames.java index d797ede05c..d28db91925 100644 --- a/src/main/java/graphql/validation/rules/KnownTypeNames.java +++ b/src/main/java/graphql/validation/rules/KnownTypeNames.java @@ -6,7 +6,8 @@ import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; + +import static graphql.validation.ValidationErrorType.UnknownType; @Internal public class KnownTypeNames extends AbstractRule { @@ -19,8 +20,8 @@ public KnownTypeNames(ValidationContext validationContext, ValidationErrorCollec @Override public void checkTypeName(TypeName typeName) { if ((getValidationContext().getSchema().getType(typeName.getName())) == null) { - String message = String.format("Unknown type %s", typeName.getName()); - addError(ValidationErrorType.UnknownType, typeName.getSourceLocation(), message); + String message = i18n(UnknownType, "KnownTypeNames.unknownType", typeName.getName()); + addError(UnknownType, typeName.getSourceLocation(), message); } } } diff --git a/src/main/java/graphql/validation/rules/LoneAnonymousOperation.java b/src/main/java/graphql/validation/rules/LoneAnonymousOperation.java index 746b473124..e89bec68e9 100644 --- a/src/main/java/graphql/validation/rules/LoneAnonymousOperation.java +++ b/src/main/java/graphql/validation/rules/LoneAnonymousOperation.java @@ -8,6 +8,8 @@ import graphql.validation.ValidationErrorCollector; import graphql.validation.ValidationErrorType; +import static graphql.validation.ValidationErrorType.LoneAnonymousOperationViolation; + @Internal public class LoneAnonymousOperation extends AbstractRule { @@ -22,22 +24,20 @@ public LoneAnonymousOperation(ValidationContext validationContext, ValidationErr public void checkOperationDefinition(OperationDefinition operationDefinition) { super.checkOperationDefinition(operationDefinition); String name = operationDefinition.getName(); - String message = null; if (name == null) { hasAnonymousOp = true; if (count > 0) { - message = "Anonymous operation with other operations."; + String message = i18n(LoneAnonymousOperationViolation, "LoneAnonymousOperation.withOthers"); + addError(ValidationErrorType.LoneAnonymousOperationViolation, operationDefinition.getSourceLocation(), message); } } else { if (hasAnonymousOp) { - message = "Operation " + name + " is following anonymous operation."; + String message = i18n(LoneAnonymousOperationViolation, "LoneAnonymousOperation.namedOperation", name); + addError(ValidationErrorType.LoneAnonymousOperationViolation, operationDefinition.getSourceLocation(), message); } } count++; - if (message != null) { - addError(ValidationErrorType.LoneAnonymousOperationViolation, operationDefinition.getSourceLocation(), message); - } } @Override diff --git a/src/main/java/graphql/validation/rules/NoFragmentCycles.java b/src/main/java/graphql/validation/rules/NoFragmentCycles.java index 28ea2b2dee..1ffc00d877 100644 --- a/src/main/java/graphql/validation/rules/NoFragmentCycles.java +++ b/src/main/java/graphql/validation/rules/NoFragmentCycles.java @@ -11,13 +11,14 @@ import graphql.validation.LanguageTraversal; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import static graphql.validation.ValidationErrorType.FragmentCycle; + @Internal public class NoFragmentCycles extends AbstractRule { @@ -70,7 +71,7 @@ public void checkFragmentDefinition(FragmentDefinition fragmentDefinition) { private void detectCycleRecursive(String fragmentName, String initialName, List spreadPath) { List fragmentSpreads = this.fragmentSpreads.get(fragmentName); if (fragmentSpreads == null) { - // KnownFragmentNames will have picked this up. Lets not NPE + // KnownFragmentNames will have picked this up. Let's not NPE return; } @@ -78,8 +79,8 @@ private void detectCycleRecursive(String fragmentName, String initialName, List< for (FragmentSpread fragmentSpread : fragmentSpreads) { if (fragmentSpread.getName().equals(initialName)) { - String message = "Fragment cycles not allowed"; - addError(ValidationErrorType.FragmentCycle, spreadPath, message); + String message = i18n(FragmentCycle, "NoFragmentCycles.cyclesNotAllowed"); + addError(FragmentCycle, spreadPath, message); continue; } for (FragmentSpread spread : spreadPath) { diff --git a/src/main/java/graphql/validation/rules/NoUndefinedVariables.java b/src/main/java/graphql/validation/rules/NoUndefinedVariables.java index 19bbd99630..f0592bb7df 100644 --- a/src/main/java/graphql/validation/rules/NoUndefinedVariables.java +++ b/src/main/java/graphql/validation/rules/NoUndefinedVariables.java @@ -9,11 +9,12 @@ import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; import java.util.LinkedHashSet; import java.util.Set; +import static graphql.validation.ValidationErrorType.UndefinedVariable; + @Internal public class NoUndefinedVariables extends AbstractRule { @@ -37,8 +38,8 @@ public void checkFragmentDefinition(FragmentDefinition fragmentDefinition) { @Override public void checkVariable(VariableReference variableReference) { if (!variableNames.contains(variableReference.getName())) { - String message = String.format("Undefined variable %s", variableReference.getName()); - addError(ValidationErrorType.UndefinedVariable, variableReference.getSourceLocation(), message); + String message = i18n(UndefinedVariable, "NoUndefinedVariables.undefinedVariable", variableReference.getName()); + addError(UndefinedVariable, variableReference.getSourceLocation(), message); } } diff --git a/src/main/java/graphql/validation/rules/NoUnusedFragments.java b/src/main/java/graphql/validation/rules/NoUnusedFragments.java index 5dc9afd183..c0a61a50a6 100644 --- a/src/main/java/graphql/validation/rules/NoUnusedFragments.java +++ b/src/main/java/graphql/validation/rules/NoUnusedFragments.java @@ -9,13 +9,14 @@ import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import static graphql.validation.ValidationErrorType.UnusedFragment; + @Internal public class NoUnusedFragments extends AbstractRule { @@ -61,8 +62,8 @@ public void documentFinished(Document document) { for (FragmentDefinition fragmentDefinition : allDeclaredFragments) { if (!allUsedFragments.contains(fragmentDefinition.getName())) { - String message = String.format("Unused fragment %s", fragmentDefinition.getName()); - addError(ValidationErrorType.UnusedFragment, fragmentDefinition.getSourceLocation(), message); + String message = i18n(UnusedFragment, "NoUnusedFragments.unusedFragments", fragmentDefinition.getName()); + addError(UnusedFragment, fragmentDefinition.getSourceLocation(), message); } } diff --git a/src/main/java/graphql/validation/rules/NoUnusedVariables.java b/src/main/java/graphql/validation/rules/NoUnusedVariables.java index 2158b04298..165fa2092c 100644 --- a/src/main/java/graphql/validation/rules/NoUnusedVariables.java +++ b/src/main/java/graphql/validation/rules/NoUnusedVariables.java @@ -15,6 +15,8 @@ import java.util.List; import java.util.Set; +import static graphql.validation.ValidationErrorType.UnusedVariable; + @Internal public class NoUnusedVariables extends AbstractRule { @@ -30,8 +32,8 @@ public NoUnusedVariables(ValidationContext validationContext, ValidationErrorCol public void leaveOperationDefinition(OperationDefinition operationDefinition) { for (VariableDefinition variableDefinition : variableDefinitions) { if (!usedVariables.contains(variableDefinition.getName())) { - String message = String.format("Unused variable %s", variableDefinition.getName()); - addError(ValidationErrorType.UnusedVariable, variableDefinition.getSourceLocation(), message); + String message = i18n(UnusedVariable, "NoUnusedVariables.unusedVariable", variableDefinition.getName()); + addError(UnusedVariable, variableDefinition.getSourceLocation(), message); } } } diff --git a/src/main/java/graphql/validation/rules/OverlappingFieldsCanBeMerged.java b/src/main/java/graphql/validation/rules/OverlappingFieldsCanBeMerged.java index dccb001531..526d11e514 100644 --- a/src/main/java/graphql/validation/rules/OverlappingFieldsCanBeMerged.java +++ b/src/main/java/graphql/validation/rules/OverlappingFieldsCanBeMerged.java @@ -239,11 +239,11 @@ private Conflict requireSameNameAndArguments(ImmutableList path, Set path, Set path, Set path, List fields, GraphQLType typeA, GraphQLType typeB) { String name1 = typeA != null ? simplePrint(typeA) : "null"; String name2 = typeB != null ? simplePrint(typeB) : "null"; - String reason = format("%s: they return differing types %s and %s", pathToString(path), name1, name2); + String reason = i18n(FieldsConflict, "OverlappingFieldsCanBeMerged.differentReturnTypes", pathToString(path), name1, name2); return new Conflict(reason, fields); } diff --git a/src/main/java/graphql/validation/rules/PossibleFragmentSpreads.java b/src/main/java/graphql/validation/rules/PossibleFragmentSpreads.java index 0f626e9cf6..a14740e113 100644 --- a/src/main/java/graphql/validation/rules/PossibleFragmentSpreads.java +++ b/src/main/java/graphql/validation/rules/PossibleFragmentSpreads.java @@ -16,12 +16,12 @@ import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; import java.util.Collections; import java.util.List; import static graphql.schema.GraphQLTypeUtil.simplePrint; +import static graphql.validation.ValidationErrorType.InvalidFragmentType; @Internal public class PossibleFragmentSpreads extends AbstractRule { @@ -30,7 +30,6 @@ public PossibleFragmentSpreads(ValidationContext validationContext, ValidationEr super(validationContext, validationErrorCollector); } - @Override public void checkInlineFragment(InlineFragment inlineFragment) { GraphQLOutputType fragType = getValidationContext().getOutputType(); @@ -38,9 +37,8 @@ public void checkInlineFragment(InlineFragment inlineFragment) { if (fragType == null || parentType == null) return; if (isValidTargetCompositeType(fragType) && isValidTargetCompositeType(parentType) && !doTypesOverlap(fragType, parentType)) { - String message = String.format("Fragment cannot be spread here as objects of " + - "type %s can never be of type %s", parentType.getName(), simplePrint(fragType)); - addError(ValidationErrorType.InvalidFragmentType, inlineFragment.getSourceLocation(), message); + String message = i18n(InvalidFragmentType, "PossibleFragmentSpreads.inlineIncompatibleTypes", parentType.getName(), simplePrint(fragType)); + addError(InvalidFragmentType, inlineFragment.getSourceLocation(), message); } } @@ -53,9 +51,8 @@ public void checkFragmentSpread(FragmentSpread fragmentSpread) { if (typeCondition == null || parentType == null) return; if (isValidTargetCompositeType(typeCondition) && isValidTargetCompositeType(parentType) && !doTypesOverlap(typeCondition, parentType)) { - String message = String.format("Fragment %s cannot be spread here as objects of " + - "type %s can never be of type %s", fragmentSpread.getName(), parentType.getName(), simplePrint(typeCondition)); - addError(ValidationErrorType.InvalidFragmentType, fragmentSpread.getSourceLocation(), message); + String message = i18n(InvalidFragmentType, "PossibleFragmentSpreads.fragmentIncompatibleTypes", fragmentSpread.getName(), parentType.getName(), simplePrint(typeCondition)); + addError(InvalidFragmentType, fragmentSpread.getSourceLocation(), message); } } diff --git a/src/main/java/graphql/validation/rules/ProvidedNonNullArguments.java b/src/main/java/graphql/validation/rules/ProvidedNonNullArguments.java index 8a4d0efb1a..765e4ac276 100644 --- a/src/main/java/graphql/validation/rules/ProvidedNonNullArguments.java +++ b/src/main/java/graphql/validation/rules/ProvidedNonNullArguments.java @@ -14,13 +14,15 @@ import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import static graphql.schema.GraphQLTypeUtil.isNonNull; +import static graphql.validation.ValidationErrorType.MissingDirectiveArgument; +import static graphql.validation.ValidationErrorType.MissingFieldArgument; +import static graphql.validation.ValidationErrorType.NullValueForNonNullArgument; @Internal public class ProvidedNonNullArguments extends AbstractRule { @@ -42,15 +44,15 @@ public void checkField(Field field) { boolean nonNullType = isNonNull(graphQLArgument.getType()); boolean noDefaultValue = graphQLArgument.getArgumentDefaultValue().isNotSet(); if (argument == null && nonNullType && noDefaultValue) { - String message = String.format("Missing field argument %s", graphQLArgument.getName()); - addError(ValidationErrorType.MissingFieldArgument, field.getSourceLocation(), message); + String message = i18n(MissingFieldArgument, "ProvidedNonNullArguments.missingFieldArg", graphQLArgument.getName()); + addError(MissingFieldArgument, field.getSourceLocation(), message); } if (argument != null) { Value value = argument.getValue(); if ((value == null || value instanceof NullValue) && nonNullType && noDefaultValue) { - String message = String.format("null value for non-null field argument %s", graphQLArgument.getName()); - addError(ValidationErrorType.NullValueForNonNullArgument, field.getSourceLocation(), message); + String message = i18n(NullValueForNonNullArgument, "ProvidedNonNullArguments.nullValue", graphQLArgument.getName()); + addError(NullValueForNonNullArgument, field.getSourceLocation(), message); } } } @@ -70,8 +72,8 @@ public void checkDirective(Directive directive, List ancestors) { boolean nonNullType = isNonNull(graphQLArgument.getType()); boolean noDefaultValue = graphQLArgument.getArgumentDefaultValue().isNotSet(); if (argument == null && nonNullType && noDefaultValue) { - String message = String.format("Missing directive argument %s", graphQLArgument.getName()); - addError(ValidationErrorType.MissingDirectiveArgument, directive.getSourceLocation(), message); + String message = i18n(MissingDirectiveArgument, "ProvidedNonNullArguments.missingDirectiveArg", graphQLArgument.getName()); + addError(MissingDirectiveArgument, directive.getSourceLocation(), message); } } } diff --git a/src/main/java/graphql/validation/rules/ScalarLeafs.java b/src/main/java/graphql/validation/rules/ScalarLeaves.java similarity index 52% rename from src/main/java/graphql/validation/rules/ScalarLeafs.java rename to src/main/java/graphql/validation/rules/ScalarLeaves.java index c60be7e2c3..072ba15080 100644 --- a/src/main/java/graphql/validation/rules/ScalarLeafs.java +++ b/src/main/java/graphql/validation/rules/ScalarLeaves.java @@ -7,15 +7,16 @@ import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; import static graphql.schema.GraphQLTypeUtil.isLeaf; import static graphql.schema.GraphQLTypeUtil.simplePrint; +import static graphql.validation.ValidationErrorType.SubselectionNotAllowed; +import static graphql.validation.ValidationErrorType.SubselectionRequired; @Internal -public class ScalarLeafs extends AbstractRule { +public class ScalarLeaves extends AbstractRule { - public ScalarLeafs(ValidationContext validationContext, ValidationErrorCollector validationErrorCollector) { + public ScalarLeaves(ValidationContext validationContext, ValidationErrorCollector validationErrorCollector) { super(validationContext, validationErrorCollector); } @@ -25,13 +26,13 @@ public void checkField(Field field) { if (type == null) return; if (isLeaf(type)) { if (field.getSelectionSet() != null) { - String message = String.format("Sub selection not allowed on leaf type %s of field %s", simplePrint(type), field.getName()); - addError(ValidationErrorType.SubSelectionNotAllowed, field.getSourceLocation(), message); + String message = i18n(SubselectionNotAllowed, "ScalarLeaves.subselectionOnLeaf", simplePrint(type), field.getName()); + addError(SubselectionNotAllowed, field.getSourceLocation(), message); } } else { if (field.getSelectionSet() == null) { - String message = String.format("Sub selection required for type %s of field %s", simplePrint(type), field.getName()); - addError(ValidationErrorType.SubSelectionRequired, field.getSourceLocation(), message); + String message = i18n(SubselectionRequired, "ScalarLeaves.subselectionRequired", simplePrint(type), field.getName()); + addError(SubselectionRequired, field.getSourceLocation(), message); } } } diff --git a/src/main/java/graphql/validation/rules/SubscriptionUniqueRootField.java b/src/main/java/graphql/validation/rules/SubscriptionUniqueRootField.java index 6b15e13596..bd73db3dd5 100644 --- a/src/main/java/graphql/validation/rules/SubscriptionUniqueRootField.java +++ b/src/main/java/graphql/validation/rules/SubscriptionUniqueRootField.java @@ -9,11 +9,12 @@ import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; import java.util.List; import static graphql.language.OperationDefinition.Operation.SUBSCRIPTION; +import static graphql.validation.ValidationErrorType.SubscriptionIntrospectionRootField; +import static graphql.validation.ValidationErrorType.SubscriptionMultipleRootFields; /** @@ -33,15 +34,14 @@ public void checkOperationDefinition(OperationDefinition operationDef) { List subscriptionSelections = operationDef.getSelectionSet().getSelections(); if (subscriptionSelections.size() > 1) { - String message = String.format("Subscription operation %s must have exactly one root field.", operationDef.getName()); - addError(ValidationErrorType.SubscriptionMultipleRootFields, operationDef.getSourceLocation(), message); + String message = i18n(SubscriptionMultipleRootFields, "SubscriptionUniqueRootField.multipleRootFields", operationDef.getName()); + addError(SubscriptionMultipleRootFields, operationDef.getSourceLocation(), message); } else { // Only one item in selection set, size == 1 Selection rootSelection = subscriptionSelections.get(0); if (isIntrospectionField(rootSelection)) { - String message = String.format("Subscription operation %s root field %s cannot be an introspection field.", - operationDef.getName(), ((Field) rootSelection).getName()); - addError(ValidationErrorType.SubscriptionIntrospectionRootField, rootSelection.getSourceLocation(), message); + String message = i18n(SubscriptionIntrospectionRootField, "SubscriptionIntrospectionRootField.introspectionRootField", operationDef.getName(), ((Field) rootSelection).getName()); + addError(SubscriptionIntrospectionRootField, rootSelection.getSourceLocation(), message); } else if (rootSelection instanceof FragmentSpread) { // If the only item in selection set is a fragment, inspect the fragment. String fragmentName = ((FragmentSpread) rootSelection).getName(); @@ -49,12 +49,11 @@ public void checkOperationDefinition(OperationDefinition operationDef) { List fragmentSelections = fragmentDef.getSelectionSet().getSelections(); if (fragmentSelections.size() > 1) { - String message = String.format("Subscription operation %s must have exactly one root field with fragments.", operationDef.getName()); - addError(ValidationErrorType.SubscriptionMultipleRootFields, rootSelection.getSourceLocation(), message); + String message = i18n(SubscriptionMultipleRootFields, "SubscriptionUniqueRootField.multipleRootFieldsWithFragment", operationDef.getName()); + addError(SubscriptionMultipleRootFields, rootSelection.getSourceLocation(), message); } else if (isIntrospectionField(fragmentSelections.get(0))) { - String message = String.format("Subscription operation %s fragment root field %s cannot be an introspection field.", - operationDef.getName(), ((Field) fragmentSelections.get(0)).getName()); - addError(ValidationErrorType.SubscriptionIntrospectionRootField, rootSelection.getSourceLocation(), message); + String message = i18n(SubscriptionIntrospectionRootField, "SubscriptionIntrospectionRootField.introspectionRootFieldWithFragment", operationDef.getName(), ((Field) fragmentSelections.get(0)).getName()); + addError(SubscriptionIntrospectionRootField, rootSelection.getSourceLocation(), message); } } } diff --git a/src/main/java/graphql/validation/rules/UniqueArgumentNamesRule.java b/src/main/java/graphql/validation/rules/UniqueArgumentNames.java similarity index 70% rename from src/main/java/graphql/validation/rules/UniqueArgumentNamesRule.java rename to src/main/java/graphql/validation/rules/UniqueArgumentNames.java index b43f19e93b..8122446a13 100644 --- a/src/main/java/graphql/validation/rules/UniqueArgumentNamesRule.java +++ b/src/main/java/graphql/validation/rules/UniqueArgumentNames.java @@ -9,19 +9,21 @@ import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; import java.util.List; import java.util.Set; +import static graphql.validation.ValidationErrorType.DuplicateArgumentNames; + + /** * Unique argument names * * A GraphQL field or directive is only valid if all supplied arguments are uniquely named. */ @Internal -public class UniqueArgumentNamesRule extends AbstractRule { - public UniqueArgumentNamesRule(ValidationContext validationContext, ValidationErrorCollector validationErrorCollector) { +public class UniqueArgumentNames extends AbstractRule { + public UniqueArgumentNames(ValidationContext validationContext, ValidationErrorCollector validationErrorCollector) { super(validationContext, validationErrorCollector); } @@ -35,7 +37,8 @@ public void checkField(Field field) { for (Argument argument : field.getArguments()) { if (arguments.contains(argument.getName())) { - addError(ValidationErrorType.DuplicateArgumentNames, field.getSourceLocation(), duplicateArgumentNameMessage(argument.getName())); + String message = i18n(DuplicateArgumentNames, "UniqueArgumentNames.uniqueArgument", argument.getName()); + addError(DuplicateArgumentNames, field.getSourceLocation(), message); } else { arguments.add(argument.getName()); } @@ -52,15 +55,12 @@ public void checkDirective(Directive directive, List ancestors) { for (Argument argument : directive.getArguments()) { if (arguments.contains(argument.getName())) { - addError(ValidationErrorType.DuplicateArgumentNames, directive.getSourceLocation(), duplicateArgumentNameMessage(argument.getName())); + String message = i18n(DuplicateArgumentNames, "UniqueArgumentNames.uniqueArgument", argument.getName()); + addError(DuplicateArgumentNames, directive.getSourceLocation(), message); } else { arguments.add(argument.getName()); } } } - - static String duplicateArgumentNameMessage(String argumentName) { - return String.format("There can be only one argument named '%s'", argumentName); - } } diff --git a/src/main/java/graphql/validation/rules/UniqueDirectiveNamesPerLocation.java b/src/main/java/graphql/validation/rules/UniqueDirectiveNamesPerLocation.java index 97da8faad7..0aa5eb68ac 100644 --- a/src/main/java/graphql/validation/rules/UniqueDirectiveNamesPerLocation.java +++ b/src/main/java/graphql/validation/rules/UniqueDirectiveNamesPerLocation.java @@ -13,12 +13,14 @@ import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import static graphql.validation.ValidationErrorType.DuplicateDirectiveName; + + /** * https://facebook.github.io/graphql/June2018/#sec-Directives-Are-Unique-Per-Location */ @@ -66,16 +68,12 @@ private void checkDirectivesUniqueness(Node directivesContainer, List * A GraphQL operation is only valid if all its variables are uniquely named. */ @Internal -public class UniqueVariableNamesRule extends AbstractRule { +public class UniqueVariableNames extends AbstractRule { - public UniqueVariableNamesRule(ValidationContext validationContext, ValidationErrorCollector validationErrorCollector) { + public UniqueVariableNames(ValidationContext validationContext, ValidationErrorCollector validationErrorCollector) { super(validationContext, validationErrorCollector); } @@ -36,15 +37,11 @@ public void checkOperationDefinition(OperationDefinition operationDefinition) { for (VariableDefinition variableDefinition : variableDefinitions) { if (variableNameList.contains(variableDefinition.getName())) { - addError(ValidationErrorType.DuplicateVariableName, variableDefinition.getSourceLocation(), duplicateVariableNameMessage(variableDefinition.getName())); + String message = i18n(DuplicateVariableName, "UniqueVariableNames.oneVariable", variableDefinition.getName()); + addError(DuplicateVariableName, variableDefinition.getSourceLocation(), message); } else { variableNameList.add(variableDefinition.getName()); } } } - - static String duplicateVariableNameMessage(String variableName) { - return String.format("There can be only one variable named '%s'", variableName); - } - } diff --git a/src/main/java/graphql/validation/rules/VariableDefaultValuesOfCorrectType.java b/src/main/java/graphql/validation/rules/VariableDefaultValuesOfCorrectType.java index 17a6233c16..e00f1b87da 100644 --- a/src/main/java/graphql/validation/rules/VariableDefaultValuesOfCorrectType.java +++ b/src/main/java/graphql/validation/rules/VariableDefaultValuesOfCorrectType.java @@ -6,10 +6,9 @@ import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; import static graphql.schema.GraphQLTypeUtil.simplePrint; - +import static graphql.validation.ValidationErrorType.BadValueForDefaultArg; @Internal public class VariableDefaultValuesOfCorrectType extends AbstractRule { @@ -19,15 +18,16 @@ public VariableDefaultValuesOfCorrectType(ValidationContext validationContext, V super(validationContext, validationErrorCollector); } - @Override public void checkVariableDefinition(VariableDefinition variableDefinition) { GraphQLInputType inputType = getValidationContext().getInputType(); - if (inputType == null) return; + if (inputType == null) { + return; + } if (variableDefinition.getDefaultValue() != null && !getValidationUtil().isValidLiteralValue(variableDefinition.getDefaultValue(), inputType, getValidationContext().getSchema())) { - String message = String.format("Bad default value %s for type %s", variableDefinition.getDefaultValue(), simplePrint(inputType)); - addError(ValidationErrorType.BadValueForDefaultArg, variableDefinition.getSourceLocation(), message); + String message = i18n(BadValueForDefaultArg, "VariableDefaultValuesOfCorrectType.badDefault", variableDefinition.getDefaultValue(), simplePrint(inputType)); + addError(BadValueForDefaultArg, variableDefinition.getSourceLocation(), message); } } } diff --git a/src/main/java/graphql/validation/rules/VariableTypesMatchRule.java b/src/main/java/graphql/validation/rules/VariableTypesMatch.java similarity index 83% rename from src/main/java/graphql/validation/rules/VariableTypesMatchRule.java rename to src/main/java/graphql/validation/rules/VariableTypesMatch.java index 2d3c7c773d..72ca603df2 100644 --- a/src/main/java/graphql/validation/rules/VariableTypesMatchRule.java +++ b/src/main/java/graphql/validation/rules/VariableTypesMatch.java @@ -15,24 +15,25 @@ import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; +import static graphql.validation.ValidationErrorType.VariableTypeMismatch; + @Internal -public class VariableTypesMatchRule extends AbstractRule { +public class VariableTypesMatch extends AbstractRule { final VariablesTypesMatcher variablesTypesMatcher; private Map variableDefinitionMap; - public VariableTypesMatchRule(ValidationContext validationContext, ValidationErrorCollector validationErrorCollector) { + public VariableTypesMatch(ValidationContext validationContext, ValidationErrorCollector validationErrorCollector) { this(validationContext, validationErrorCollector, new VariablesTypesMatcher()); } - VariableTypesMatchRule(ValidationContext validationContext, ValidationErrorCollector validationErrorCollector, VariablesTypesMatcher variablesTypesMatcher) { + VariableTypesMatch(ValidationContext validationContext, ValidationErrorCollector validationErrorCollector, VariablesTypesMatcher variablesTypesMatcher) { super(validationContext, validationErrorCollector); setVisitFragmentSpreads(true); this.variablesTypesMatcher = variablesTypesMatcher; @@ -73,12 +74,10 @@ public void checkVariable(VariableReference variableReference) { if (!variablesTypesMatcher.doesVariableTypesMatch(variableType, variableDefinition.getDefaultValue(), expectedType) && !variablesTypesMatcher.doesVariableTypesMatch(variableType, schemaDefaultValue, expectedType)) { GraphQLType effectiveType = variablesTypesMatcher.effectiveType(variableType, variableDefinition.getDefaultValue()); - String message = String.format("Variable type '%s' doesn't match expected type '%s'", + String message = i18n(VariableTypeMismatch, "VariableTypesMatchRule.unexpectedType", GraphQLTypeUtil.simplePrint(effectiveType), GraphQLTypeUtil.simplePrint(expectedType)); - addError(ValidationErrorType.VariableTypeMismatch, variableReference.getSourceLocation(), message); + addError(VariableTypeMismatch, variableReference.getSourceLocation(), message); } } - - } diff --git a/src/main/java/graphql/validation/rules/VariablesAreInputTypes.java b/src/main/java/graphql/validation/rules/VariablesAreInputTypes.java index 1e735684a6..3962ffeef4 100644 --- a/src/main/java/graphql/validation/rules/VariablesAreInputTypes.java +++ b/src/main/java/graphql/validation/rules/VariablesAreInputTypes.java @@ -8,9 +8,9 @@ import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; import static graphql.schema.GraphQLTypeUtil.isInput; +import static graphql.validation.ValidationErrorType.NonInputTypeOnVariable; @Internal public class VariablesAreInputTypes extends AbstractRule { @@ -24,10 +24,12 @@ public void checkVariableDefinition(VariableDefinition variableDefinition) { TypeName unmodifiedAstType = getValidationUtil().getUnmodifiedType(variableDefinition.getType()); GraphQLType type = getValidationContext().getSchema().getType(unmodifiedAstType.getName()); - if (type == null) return; + if (type == null) { + return; + } if (!isInput(type)) { - String message = "Wrong type for a variable"; - addError(ValidationErrorType.NonInputTypeOnVariable, variableDefinition.getSourceLocation(), message); + String message = i18n(NonInputTypeOnVariable, "VariablesAreInputTypes.wrongType", variableDefinition.getName(), unmodifiedAstType.getName()); + addError(NonInputTypeOnVariable, variableDefinition.getSourceLocation(), message); } } } diff --git a/src/main/resources/i18n/Execution.properties b/src/main/resources/i18n/Execution.properties new file mode 100644 index 0000000000..62b872a9d3 --- /dev/null +++ b/src/main/resources/i18n/Execution.properties @@ -0,0 +1,6 @@ +# +# This resource bundle is used for the query execution code to produce i18n messages +# +# REMEMBER - a single quote ' in MessageFormat means things that are never replaced within them +# so use 2 '' characters to make it one ' on output. This will take for the form ''{0}'' +# \ No newline at end of file diff --git a/src/main/resources/i18n/General.properties b/src/main/resources/i18n/General.properties new file mode 100644 index 0000000000..c4022715c4 --- /dev/null +++ b/src/main/resources/i18n/General.properties @@ -0,0 +1,6 @@ +# +# This resource bundle is used for the general code to produce i18n messages +# +# REMEMBER - a single quote ' in MessageFormat means things that are never replaced within them +# so use 2 '' characters to make it one ' on output. This will take for the form ''{0}'' +# \ No newline at end of file diff --git a/src/main/resources/i18n/Validation.properties b/src/main/resources/i18n/Validation.properties new file mode 100644 index 0000000000..c6eb1ae49b --- /dev/null +++ b/src/main/resources/i18n/Validation.properties @@ -0,0 +1,97 @@ +# +# This resource bundle is used for the query validation code to produce i18n messages +# +# The keys have the format of rule class name and then message type within that. Most rules +# will only have 1 or 2 message keys +# +# Please try and keep this sorted within rule class and use # between sections so the IDEA Ctrl-Alt-L reformat does not bunch +# them too tightly. +# +# REMEMBER - a single quote ' in MessageFormat means things that are never replaced within them +# so use 2 '' characters to make it one ' on output. This will take for the form ''{0}'' +# +ExecutableDefinitions.notExecutableType=Validation error ({0}) : Type ''{1}'' definition is not executable +ExecutableDefinitions.notExecutableSchema=Validation error ({0}) : Schema definition is not executable +ExecutableDefinitions.notExecutableDirective=Validation error ({0}) : Directive ''{1}'' definition is not executable +ExecutableDefinitions.notExecutableDefinition=Validation error ({0}) : Provided definition is not executable +# +FieldsOnCorrectType.unknownField=Validation error ({0}) : Field ''{1}'' in type ''{2}'' is undefined +# +FragmentsOnCompositeType.invalidInlineTypeCondition=Validation error ({0}) : Inline fragment type condition is invalid, must be on Object/Interface/Union +FragmentsOnCompositeType.invalidFragmentTypeCondition=Validation error ({0}) : Fragment type condition is invalid, must be on Object/Interface/Union +# +KnownArgumentNames.unknownDirectiveArg=Validation error ({0}) : Unknown directive argument ''{1}'' +KnownArgumentNames.unknownFieldArg=Validation error ({0}) : Unknown field argument ''{1}'' +# +KnownDirectives.unknownDirective=Validation error ({0}) : Unknown directive ''{1}'' +KnownDirectives.directiveNotAllowed=Validation error ({0}) : Directive ''{1}'' not allowed here +# +KnownFragmentNames.undefinedFragment=Validation error ({0}) : Undefined fragment ''{1}'' +# +KnownTypeNames.unknownType=Validation error ({0}) : Unknown type ''{1}'' +# +LoneAnonymousOperation.withOthers=Validation error ({0}) : Anonymous operation with other operations +LoneAnonymousOperation.namedOperation=Validation error ({0}) : Operation ''{1}'' is following anonymous operation +# +NoFragmentCycles.cyclesNotAllowed=Validation error ({0}) : Fragment cycles not allowed +# +NoUndefinedVariables.undefinedVariable=Validation error ({0}) : Undefined variable ''{1}'' +# +NoUnusedFragments.unusedFragments=Validation error ({0}) : Unused fragment ''{1}'' +# +NoUnusedVariables.unusedVariable=Validation error ({0}) : Unused variable ''{1}'' +# +OverlappingFieldsCanBeMerged.differentFields=Validation error ({0}) : ''{1}'' : ''{2}'' and ''{3}'' are different fields +OverlappingFieldsCanBeMerged.differentArgs=Validation error ({0}) : ''{1}'' : fields have different arguments +OverlappingFieldsCanBeMerged.differentNullability=Validation error ({0}) : ''{1}'' : fields have different nullability shapes +OverlappingFieldsCanBeMerged.differentLists=Validation error ({0}) : ''{1}'' : fields have different list shapes +OverlappingFieldsCanBeMerged.differentReturnTypes=Validation error ({0}) : ''{1}'' : returns different types ''{2}'' and ''{3}'' +# +PossibleFragmentSpreads.inlineIncompatibleTypes=Validation error ({0}) : Fragment cannot be spread here as objects of type ''{1}'' can never be of type ''{2}'' +PossibleFragmentSpreads.fragmentIncompatibleTypes=Validation error ({0}) : Fragment ''{1}'' cannot be spread here as objects of type ''{2}'' can never be of type ''{3}'' +# +ProvidedNonNullArguments.missingFieldArg=Validation error ({0}) : Missing field argument ''{1}'' +ProvidedNonNullArguments.missingDirectiveArg=Validation error ({0}) : Missing directive argument ''{1}'' +ProvidedNonNullArguments.nullValue=Validation error ({0}) : Null value for non-null field argument ''{1}'' +# +ScalarLeaves.subselectionOnLeaf=Validation error ({0}) : Subselection not allowed on leaf type ''{1}'' of field ''{2}'' +ScalarLeaves.subselectionRequired=Validation error ({0}) : Subselection required for type ''{1}'' of field ''{2}'' +# +SubscriptionUniqueRootField.multipleRootFields=Validation error ({0}) : Subscription operation ''{1}'' must have exactly one root field +SubscriptionUniqueRootField.multipleRootFieldsWithFragment=Validation error ({0}) : Subscription operation ''{1}'' must have exactly one root field with fragments +SubscriptionIntrospectionRootField.introspectionRootField=Validation error ({0}) : Subscription operation ''{1}'' root field ''{2}'' cannot be an introspection field +SubscriptionIntrospectionRootField.introspectionRootFieldWithFragment=Validation error ({0}) : Subscription operation ''{1}'' fragment root field ''{2}'' cannot be an introspection field +# +UniqueArgumentNames.uniqueArgument=Validation error ({0}) : There can be only one argument named ''{1}'' +# +UniqueDirectiveNamesPerLocation.uniqueDirectives=Validation error ({0}) : Non repeatable directives must be uniquely named within a location. The directive ''{1}'' used on a ''{2}'' is not unique +# +UniqueFragmentNames.oneFragment=Validation error ({0}) : There can be only one fragment named ''{1}'' +# +UniqueOperationNames.oneOperation=Validation error ({0}) : There can be only one operation named ''{1}'' +# +UniqueVariableNames.oneVariable=Validation error ({0}) : There can be only one variable named ''{1}'' +# +VariableDefaultValuesOfCorrectType.badDefault=Validation error ({0}) : Bad default value ''{1}'' for type ''{2}'' +# +VariablesAreInputTypes.wrongType=Validation error ({0}) : Input variable ''{1}'' type ''{2}'' is not an input type +# +VariableTypesMatchRule.unexpectedType=Validation error ({0}) : Variable type ''{1}'' does not match expected type ''{2}'' +# +# These are used but IDEA cant find them easily as being called +# +# suppress inspection "UnusedProperty" +ArgumentValidationUtil.handleNullError=Validation error ({0}) : argument ''{1}'' with value ''{2}'' must not be null +# suppress inspection "UnusedProperty" +ArgumentValidationUtil.handleScalarError=Validation error ({0}) : argument ''{1}'' with value ''{2}'' is not a valid ''{3}'' +ArgumentValidationUtil.handleScalarErrorCustomMessage=Validation error ({0}) : argument ''{1}'' with value ''{2}'' is not a valid ''{3}'' - {4} +# suppress inspection "UnusedProperty" +ArgumentValidationUtil.handleEnumError=Validation error ({0}) : argument ''{1}'' with value ''{2}'' is not a valid ''{3}'' +ArgumentValidationUtil.handleEnumErrorCustomMessage=Validation error ({0}) : argument ''{1}'' with value ''{2}'' is not a valid ''{3}'' - {4} +# suppress inspection "UnusedProperty" +ArgumentValidationUtil.handleNotObjectError=Validation error ({0}) : argument ''{1}'' with value ''{2}'' must be an object type +# suppress inspection "UnusedProperty" +ArgumentValidationUtil.handleMissingFieldsError=Validation error ({0}) : argument ''{1}'' with value ''{2}'' is missing required fields ''{3}'' +# suppress inspection "UnusedProperty" +ArgumentValidationUtil.handleExtraFieldError=Validation error ({0}) : argument ''{1}'' with value ''{2}'' contains a field not in ''{3}'': ''{4}'' +# diff --git a/src/test/groovy/graphql/ExecutionInputTest.groovy b/src/test/groovy/graphql/ExecutionInputTest.groovy index 9ab6a8930a..2a55d073d1 100644 --- a/src/test/groovy/graphql/ExecutionInputTest.groovy +++ b/src/test/groovy/graphql/ExecutionInputTest.groovy @@ -2,7 +2,6 @@ package graphql import graphql.cachecontrol.CacheControl import graphql.execution.ExecutionId -import graphql.execution.RawVariables import graphql.schema.DataFetcher import graphql.schema.DataFetchingEnvironment import org.dataloader.DataLoaderRegistry @@ -86,6 +85,14 @@ class ExecutionInputTest extends Specification { executionInput.graphQLContext instanceof GraphQLContext } + def "locale defaults to JVM default"() { + when: + def executionInput = ExecutionInput.newExecutionInput().query(query) + .build() + then: + executionInput.getLocale() == Locale.getDefault() + } + def "transform works and copies values"() { when: def executionInputOld = ExecutionInput.newExecutionInput().query(query) @@ -141,11 +148,13 @@ class ExecutionInputTest extends Specification { def "defaults query into builder as expected"() { when: - def executionInput = ExecutionInput.newExecutionInput("{ q }").build() + def executionInput = ExecutionInput.newExecutionInput("{ q }") + .locale(Locale.ENGLISH) + .build() then: executionInput.query == "{ q }" executionInput.cacheControl != null - executionInput.locale == null + executionInput.locale == Locale.ENGLISH executionInput.dataLoaderRegistry != null executionInput.variables == [:] } diff --git a/src/test/groovy/graphql/GraphQLErrorTest.groovy b/src/test/groovy/graphql/GraphQLErrorTest.groovy index 8e282e67c8..7560419be8 100644 --- a/src/test/groovy/graphql/GraphQLErrorTest.groovy +++ b/src/test/groovy/graphql/GraphQLErrorTest.groovy @@ -28,7 +28,7 @@ class GraphQLErrorTest extends Specification { new ValidationError(ValidationErrorType.UnknownType, mkLocations(), "Test ValidationError") | [ locations: [[line: 666, column: 999], [line: 333, column: 0]], - message : "Validation error of type UnknownType: Test ValidationError", + message : "Test ValidationError", extensions:[classification:"ValidationError"], ] diff --git a/src/test/groovy/graphql/GraphQLTest.groovy b/src/test/groovy/graphql/GraphQLTest.groovy index caa4fe9d10..ff320234eb 100644 --- a/src/test/groovy/graphql/GraphQLTest.groovy +++ b/src/test/groovy/graphql/GraphQLTest.groovy @@ -369,7 +369,7 @@ class GraphQLTest extends Specification { then: result.errors.size() == 1 - result.errors[0].message == "Validation error of type WrongType: argument 'bar' with value 'IntValue{value=12345678910}' is not a valid 'Int' - Expected value to be in the Integer range but it was '12345678910' @ 'foo'" + result.errors[0].message == "Validation error (WrongType@[foo]) : argument 'bar' with value 'IntValue{value=12345678910}' is not a valid 'Int' - Expected value to be in the Integer range but it was '12345678910'" } @SuppressWarnings("GroovyAssignabilityCheck") @@ -813,7 +813,7 @@ class GraphQLTest extends Specification { then: result.errors.size() == 1 - result.errors[0].message.contains("Sub selection required") + result.errors[0].message.contains("Subselection required") where: instrumentationName | instrumentation @@ -1010,7 +1010,7 @@ many lines'''] def graphQL = TestUtil.graphQL(spec, ["Query": ["sayHello": df]]).build() when: - def data = graphQL.execute('query($var:String){sayHello(name:$var)}').getData(); + def data = graphQL.execute('query($var:String){sayHello(name:$var)}').getData() then: data == [sayHello: "amigo"] @@ -1028,7 +1028,7 @@ many lines'''] def graphQL = TestUtil.graphQL(spec, ["Query": ["sayHello": df]]).build() when: - def data = graphQL.execute('query($var:String = "amigo"){sayHello(name:$var)}').getData(); + def data = graphQL.execute('query($var:String = "amigo"){sayHello(name:$var)}').getData() then: data == [sayHello: "amigo"] @@ -1046,7 +1046,7 @@ many lines'''] def graphQL = TestUtil.graphQL(spec, ["Query": ["sayHello": df]]).build() when: - def data = graphQL.execute('query($var:String! = "amigo"){sayHello(name:$var)}').getData(); + def data = graphQL.execute('query($var:String! = "amigo"){sayHello(name:$var)}').getData() then: data == [sayHello: "amigo"] @@ -1059,13 +1059,13 @@ many lines'''] sayHello(name: String): String }""" def df = { dfe -> - boolean isNullValue = dfe.containsArgument("name") && dfe.getArgument("name") == null; - return isNullValue ? "is null" : "error"; + boolean isNullValue = dfe.containsArgument("name") && dfe.getArgument("name") == null + return isNullValue ? "is null" : "error" } as DataFetcher def graphQL = TestUtil.graphQL(spec, ["Query": ["sayHello": df]]).build() when: - def data = graphQL.execute('query($var:String = null){sayHello(name:$var)}').getData(); + def data = graphQL.execute('query($var:String = null){sayHello(name:$var)}').getData() then: data == [sayHello: "is null"] @@ -1078,13 +1078,13 @@ many lines'''] sayHello(name: String = null): String }""" def df = { dfe -> - boolean isNullValue = dfe.containsArgument("name") && dfe.getArgument("name") == null; - return isNullValue ? "is null" : "error"; + boolean isNullValue = dfe.containsArgument("name") && dfe.getArgument("name") == null + return isNullValue ? "is null" : "error" } as DataFetcher def graphQL = TestUtil.graphQL(spec, ["Query": ["sayHello": df]]).build() when: - def data = graphQL.execute('query($var:String){sayHello(name:$var)}').getData(); + def data = graphQL.execute('query($var:String){sayHello(name:$var)}').getData() then: data == [sayHello: "is null"] @@ -1102,7 +1102,7 @@ many lines'''] def graphQL = TestUtil.graphQL(spec, ["Query": ["sayHello": df]]).build() when: - def data = graphQL.execute('query($var:String){sayHello(name:$var)}').getData(); + def data = graphQL.execute('query($var:String){sayHello(name:$var)}').getData() then: data == [sayHello: "not provided"] @@ -1120,7 +1120,7 @@ many lines'''] def graphQL = TestUtil.graphQL(spec, ["Query": ["sayHello": df]]).build() when: - def errors = graphQL.execute('query($var:String=null){sayHello(name:$var)}').getErrors(); + def errors = graphQL.execute('query($var:String=null){sayHello(name:$var)}').getErrors() then: errors.size() == 1 @@ -1139,7 +1139,7 @@ many lines'''] def graphQL = TestUtil.graphQL(spec, ["Query": ["sayHello": df]]).build() when: - def result = graphQL.execute('query($var:String){sayHello(name:$var)}'); + def result = graphQL.execute('query($var:String){sayHello(name:$var)}') then: result.errors.isEmpty() @@ -1149,10 +1149,10 @@ many lines'''] def "specified url can be defined and queried via introspection"() { given: - GraphQLSchema schema = TestUtil.schema('type Query {foo: MyScalar} scalar MyScalar @specifiedBy(url:"myUrl")'); + GraphQLSchema schema = TestUtil.schema('type Query {foo: MyScalar} scalar MyScalar @specifiedBy(url:"myUrl")') when: - def result = GraphQL.newGraphQL(schema).build().execute('{__type(name: "MyScalar") {name specifiedByUrl}}').getData(); + def result = GraphQL.newGraphQL(schema).build().execute('{__type(name: "MyScalar") {name specifiedByUrl}}').getData() then: result == [__type: [name: "MyScalar", specifiedByUrl: "myUrl"]] diff --git a/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy b/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy index 53c2efb895..34684b6ce6 100644 --- a/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy +++ b/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy @@ -36,7 +36,7 @@ class GraphqlErrorHelperTest extends Specification { class ExtensionAddingError implements GraphQLError { - Map extensions; + Map extensions ExtensionAddingError(Map extensions) { this.extensions = extensions @@ -59,7 +59,7 @@ class GraphqlErrorHelperTest extends Specification { @Override Map getExtensions() { - return extensions; + return extensions } } @@ -72,7 +72,7 @@ class GraphqlErrorHelperTest extends Specification { then: specMap == [ locations : [[line: 6, column: 9]], - message : "Validation error of type InvalidFragmentType: Things are not valid", + message : "Things are not valid", extensions: [classification: "ValidationError"], ] diff --git a/src/test/groovy/graphql/Issue1440.groovy b/src/test/groovy/graphql/Issue1440.groovy deleted file mode 100644 index f1b9fef5bf..0000000000 --- a/src/test/groovy/graphql/Issue1440.groovy +++ /dev/null @@ -1,90 +0,0 @@ -package graphql - -import graphql.validation.ValidationError -import graphql.validation.ValidationErrorType -import spock.lang.Specification - -class Issue1440 extends Specification { - - def schema = TestUtil.schema(""" - type Query { - nothing: String - } - - type Mutation { - updateUDI(input: UDIInput!): UDIOutput - } - - type UDIOutput { - device: String - version: String - } - - input UDIInput { - device: String - version: String - } - """) - - def graphQL = GraphQL.newGraphQL(schema).build() - - - def "#1440 when fragment type condition is input type it should return validation error - not classCastException"() { - when: - def executionInput = ExecutionInput.newExecutionInput() - .query(''' - mutation UpdateUDI($input: UDIInput!) { - updateUDI(input: $input) { - ...fragOnInputType - __typename - } - } - - # fragment should only target composite types - fragment fragOnInputType on UDIInput { - device - version - __typename - } - - ''') - .variables([input: [device: 'device', version: 'version'] ]) - .build() - - def executionResult = graphQL.execute(executionInput) - - then: - - executionResult.data == null - executionResult.errors.size() == 1 - (executionResult.errors[0] as ValidationError).validationErrorType == ValidationErrorType.FragmentTypeConditionInvalid - } - - def "#1440 when inline fragment type condition is input type it should return validation error - not classCastException"() { - when: - def executionInput = ExecutionInput.newExecutionInput() - .query(''' - mutation UpdateUDI($input: UDIInput!) { - updateUDI(input: $input) { - # fragment should only target composite types - ... on UDIInput { - device - version - __typename - } - __typename - } - } - ''') - .variables([input: [device: 'device', version: 'version'] ]) - .build() - - def executionResult = graphQL.execute(executionInput) - - then: - - executionResult.data == null - executionResult.errors.size() == 1 - (executionResult.errors[0] as ValidationError).validationErrorType == ValidationErrorType.InlineFragmentTypeConditionInvalid - } -} diff --git a/src/test/groovy/graphql/IssueNonNullDefaultAttribute.groovy b/src/test/groovy/graphql/IssueNonNullDefaultAttribute.groovy index a26efcde6b..b92435efe8 100644 --- a/src/test/groovy/graphql/IssueNonNullDefaultAttribute.groovy +++ b/src/test/groovy/graphql/IssueNonNullDefaultAttribute.groovy @@ -65,7 +65,7 @@ class IssueNonNullDefaultAttribute extends Specification { then: result.errors.size() == 1 result.errors[0].errorType == ErrorType.ValidationError - result.errors[0].message == "Validation error of type WrongType: argument 'characterNumber' with value 'NullValue{}' must not be null @ 'name'" + result.errors[0].message == "Validation error (WrongType@[name]) : argument 'characterNumber' with value 'NullValue{}' must not be null" result.errors[0].locations == [new SourceLocation(3, 26)] result.data == null diff --git a/src/test/groovy/graphql/ParseAndValidateTest.groovy b/src/test/groovy/graphql/ParseAndValidateTest.groovy index 8a4378b673..3b676bd8d4 100644 --- a/src/test/groovy/graphql/ParseAndValidateTest.groovy +++ b/src/test/groovy/graphql/ParseAndValidateTest.groovy @@ -48,7 +48,7 @@ class ParseAndValidateTest extends Specification { def result = ParseAndValidate.parse(input) when: - def errors = ParseAndValidate.validate(StarWarsSchema.starWarsSchema, result.getDocument()) + def errors = ParseAndValidate.validate(StarWarsSchema.starWarsSchema, result.getDocument(), input.getLocale()) then: errors.isEmpty() @@ -60,11 +60,11 @@ class ParseAndValidateTest extends Specification { def result = ParseAndValidate.parse(input) when: - def errors = ParseAndValidate.validate(StarWarsSchema.starWarsSchema, result.getDocument()) + def errors = ParseAndValidate.validate(StarWarsSchema.starWarsSchema, result.getDocument(), input.getLocale()) then: !errors.isEmpty() - errors[0].validationErrorType == ValidationErrorType.SubSelectionRequired + errors[0].validationErrorType == ValidationErrorType.SubselectionRequired } def "can combine parse and validation on valid input"() { @@ -94,7 +94,7 @@ class ParseAndValidateTest extends Specification { result.variables == [var1: 1] result.syntaxException == null - (result.errors[0] as ValidationError).validationErrorType == ValidationErrorType.SubSelectionRequired + (result.errors[0] as ValidationError).validationErrorType == ValidationErrorType.SubselectionRequired } def "can shortcut on parse and validation on INVALID syntax"() { diff --git a/src/test/groovy/graphql/StarWarsValidationTest.groovy b/src/test/groovy/graphql/StarWarsValidationTest.groovy index 86bcf76cac..4343f0f580 100644 --- a/src/test/groovy/graphql/StarWarsValidationTest.groovy +++ b/src/test/groovy/graphql/StarWarsValidationTest.groovy @@ -7,10 +7,9 @@ import spock.lang.Specification class StarWarsValidationTest extends Specification { - - List validate(String query) { + static List validate(String query) { def document = new Parser().parseDocument(query) - return new Validator().validateDocument(StarWarsSchema.starWarsSchema, document) + return new Validator().validateDocument(StarWarsSchema.starWarsSchema, document, Locale.ENGLISH) } def 'Validates a complex but valid query'() { diff --git a/src/test/groovy/graphql/execution/directives/RepeatableDirectivesTest.groovy b/src/test/groovy/graphql/execution/directives/RepeatableDirectivesTest.groovy index 89b60add50..8d6338a885 100644 --- a/src/test/groovy/graphql/execution/directives/RepeatableDirectivesTest.groovy +++ b/src/test/groovy/graphql/execution/directives/RepeatableDirectivesTest.groovy @@ -38,7 +38,7 @@ class RepeatableDirectivesTest extends Specification { when: def document = TestUtil.parseQuery(spec) def validator = new Validator() - def validationErrors = validator.validateDocument(schema, document) + def validationErrors = validator.validateDocument(schema, document, Locale.ENGLISH) then: validationErrors.size() == 0 @@ -55,11 +55,11 @@ class RepeatableDirectivesTest extends Specification { when: def document = TestUtil.parseQuery(spec) def validator = new Validator() - def validationErrors = validator.validateDocument(schema, document) + def validationErrors = validator.validateDocument(schema, document, Locale.ENGLISH) then: validationErrors.size() == 1 - validationErrors[0].message == "Validation error of type DuplicateDirectiveName: Non repeatable directives must be uniquely named within a location. The directive 'nonRepeatableDirective' used on a 'Field' is not unique. @ 'namedField'" + validationErrors[0].message == "Validation error (DuplicateDirectiveName@[namedField]) : Non repeatable directives must be uniquely named within a location. The directive 'nonRepeatableDirective' used on a 'Field' is not unique" } def "getRepeatableDirectivesInfo"() { @@ -73,7 +73,7 @@ class RepeatableDirectivesTest extends Specification { when: def document = TestUtil.parseQuery(spec) def validator = new Validator() - def validationErrors = validator.validateDocument(schema, document) + def validationErrors = validator.validateDocument(schema, document, Locale.ENGLISH) OperationDefinition operationDefinition = document.getDefinitions()[0] Field field = operationDefinition.getSelectionSet().getSelections()[0] @@ -105,13 +105,13 @@ class RepeatableDirectivesTest extends Specification { ''' when: - SchemaParser parser = new SchemaParser(); - def typeDefinitionRegistry = parser.parse(spec); + SchemaParser parser = new SchemaParser() + def typeDefinitionRegistry = parser.parse(spec) def runtimeWiring = RuntimeWiring.newRuntimeWiring() .wiringFactory(new MockedWiringFactory()) - .build(); - def schemaGenerator = new SchemaGenerator(); - def schema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring); + .build() + def schemaGenerator = new SchemaGenerator() + def schema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring) def pType = schema.getObjectType("PType") then: diff --git a/src/test/groovy/graphql/execution/directives/VariableDirectiveTest.groovy b/src/test/groovy/graphql/execution/directives/VariableDirectiveTest.groovy index 68489cdf28..4d230b7a64 100644 --- a/src/test/groovy/graphql/execution/directives/VariableDirectiveTest.groovy +++ b/src/test/groovy/graphql/execution/directives/VariableDirectiveTest.groovy @@ -34,8 +34,8 @@ class VariableDirectiveTest extends Specification { when: def document = TestUtil.parseQuery(spec) - def validator = new Validator(); - def validationErrors = validator.validateDocument(schema, document); + def validator = new Validator() + def validationErrors = validator.validateDocument(schema, document, Locale.ENGLISH) then: validationErrors.size() == 0 @@ -52,12 +52,12 @@ class VariableDirectiveTest extends Specification { when: def document = TestUtil.parseQuery(spec) - def validator = new Validator(); - def validationErrors = validator.validateDocument(schema, document); + def validator = new Validator() + def validationErrors = validator.validateDocument(schema, document, Locale.ENGLISH) then: validationErrors.size() == 1 - validationErrors[0].message == "Validation error of type MisplacedDirective: Directive variableDirective not allowed here @ 'f'" + validationErrors[0].message == "Validation error (MisplacedDirective@[f]) : Directive 'variableDirective' not allowed here" } def "invalid directive for variable"() { @@ -71,12 +71,12 @@ class VariableDirectiveTest extends Specification { when: def document = TestUtil.parseQuery(spec) - def validator = new Validator(); - def validationErrors = validator.validateDocument(schema, document); + def validator = new Validator() + def validationErrors = validator.validateDocument(schema, document, Locale.ENGLISH) then: validationErrors.size() == 1 - validationErrors[0].message == "Validation error of type MisplacedDirective: Directive argumentDirective not allowed here" + validationErrors[0].message == "Validation error (MisplacedDirective) : Directive 'argumentDirective' not allowed here" } diff --git a/src/test/groovy/graphql/i18n/I18nTest.groovy b/src/test/groovy/graphql/i18n/I18nTest.groovy new file mode 100644 index 0000000000..7abe1346a6 --- /dev/null +++ b/src/test/groovy/graphql/i18n/I18nTest.groovy @@ -0,0 +1,67 @@ +package graphql.i18n + +import graphql.AssertException +import graphql.i18n.I18n.BundleType +import spock.lang.Specification + +class I18nTest extends Specification { + + def "missing resource keys cause an assert"() { + def i18n = I18n.i18n(BundleType.Validation, Locale.ENGLISH) + when: + i18n.msg("nonExistent") + then: + thrown(AssertException) + } + + def "all enums have resources and decent shapes"() { + when: + def bundleTypes = BundleType.values() + then: + for (BundleType bundleType : (bundleTypes)) { + // Currently only testing the default English bundles + def i18n = I18n.i18n(bundleType, Locale.ENGLISH) + assert i18n.resourceBundle != null + assertBundleStaticShape(i18n.resourceBundle) + } + } + + static def assertBundleStaticShape(ResourceBundle bundle) { + def enumeration = bundle.getKeys() + while (enumeration.hasMoreElements()) { + def msgKey = enumeration.nextElement() + def pattern = bundle.getString(msgKey) + quotesAreBalanced(msgKey, pattern, '\'') + quotesAreBalanced(msgKey, pattern, '"') + curlyBracesAreBalanced(msgKey, pattern) + noStringFormatPercentLeftOver(msgKey, pattern) + placeHoldersNotRepeated(msgKey, pattern) + } + } + + static quotesAreBalanced(String msgKey, String msg, String c) { + def quoteCount = msg.count(c) + assert quoteCount % 2 == 0, "The I18n message $msgKey quotes are unbalanced : $msg" + } + + static placeHoldersNotRepeated(String msgKey, String msg) { + for (int i = 0; i < 200; i++) { + def count = msg.count("{$i}") + assert count < 2, "The I18n message $msgKey has repeated positional placeholders : $msg" + } + } + + static noStringFormatPercentLeftOver(String msgKey, String msg) { + assert !msg.contains("%s"), "The I18n message $msgKey has a %s in it : $msg" + assert !msg.contains("%d"), "The I18n message $msgKey has a %d in it : $msg" + } + + static def curlyBracesAreBalanced(String msgKey, String msg) { + def leftCount = msg.count("{") + def rightCount = msg.count("}") + if (leftCount > 0 || rightCount > 0) { + assert leftCount == rightCount, "The I18n message $msgKey left curly quote are unbalanced : $msg" + } + } + +} \ No newline at end of file diff --git a/src/test/groovy/graphql/schema/CoercingTest.groovy b/src/test/groovy/graphql/schema/CoercingTest.groovy index 96c2168d89..ad1d306603 100644 --- a/src/test/groovy/graphql/schema/CoercingTest.groovy +++ b/src/test/groovy/graphql/schema/CoercingTest.groovy @@ -196,7 +196,7 @@ class CoercingTest extends Specification { def er = customScalarSchema.execute(ei) then: er.errors.size() == 1 - er.errors[0].message.contains("parseLiteral message") + er.errors[0].message == "Validation error (WrongType@[field]) : argument 'arg2' with value 'StringValue{value='bang'}' is not a valid 'CustomScalar' - parseLiteral message" er.errors[0].extensions == [parseLiteral: true] } @@ -213,7 +213,7 @@ class CoercingTest extends Specification { def er = customScalarSchema.execute(ei) then: er.errors.size() == 1 - er.errors[0].message.contains("serialize message") + er.errors[0].message.contains("Can't serialize value (/field) : serialize message") er.errors[0].extensions == [serialize: true] } diff --git a/src/test/groovy/graphql/validation/RulesVisitorTest.groovy b/src/test/groovy/graphql/validation/RulesVisitorTest.groovy index 64e2330695..2b0de3a993 100644 --- a/src/test/groovy/graphql/validation/RulesVisitorTest.groovy +++ b/src/test/groovy/graphql/validation/RulesVisitorTest.groovy @@ -1,6 +1,7 @@ package graphql.validation import graphql.TestUtil +import graphql.i18n.I18n import graphql.language.Document import graphql.parser.Parser import spock.lang.Specification @@ -15,7 +16,8 @@ class RulesVisitorTest extends Specification { def traverse(String query) { Document document = new Parser().parseDocument(query) - ValidationContext validationContext = new ValidationContext(TestUtil.dummySchema, document) + I18n i18n = I18n.i18n(I18n.BundleType.Validation, Locale.ENGLISH) + ValidationContext validationContext = new ValidationContext(TestUtil.dummySchema, document, i18n) LanguageTraversal languageTraversal = new LanguageTraversal() languageTraversal.traverse(document, new RulesVisitor(validationContext, [simpleRule, visitsSpreadsRule])) } diff --git a/src/test/groovy/graphql/validation/SpecValidation51Test.groovy b/src/test/groovy/graphql/validation/SpecValidation51Test.groovy index 173459238b..78cda9ab7f 100644 --- a/src/test/groovy/graphql/validation/SpecValidation51Test.groovy +++ b/src/test/groovy/graphql/validation/SpecValidation51Test.groovy @@ -66,95 +66,6 @@ mutation dogOperation { id } } -""" - when: - def validationErrors = validate(query) - - then: - !validationErrors.empty - } - - - def '5.1.2.1 Lone Anonymous Operation Valid'() { - def query = """ -{ - dog { - name - } -} -""" - when: - def validationErrors = validate(query) - - then: - validationErrors.empty - } - - - def '5.1.2.1 Lone Anonymous Operation Not Valid'() { - def query = """ -{ - dog { - name - } -} - -query getName { - dog { - owner { - name - } - } -} -""" - when: - def validationErrors = validate(query) - - then: - !validationErrors.empty - } - - def '5.1.2.1 Lone Anonymous Operation Not Valid (reverse order) '() { - def query = """ - -query getName { - dog { - owner { - name - } - } -} - -{ - dog { - name - } -} - -""" - when: - def validationErrors = validate(query) - - then: - !validationErrors.empty - } - - def '5.1.2.1 Lone Anonymous Operation Not Valid (not really alone)'() { - def query = """ -{ - dog { - owner { - name - } - } -} - -{ - dog { - name - } -} - """ when: def validationErrors = validate(query) diff --git a/src/test/groovy/graphql/validation/SpecValidation521Test.groovy b/src/test/groovy/graphql/validation/SpecValidation521Test.groovy deleted file mode 100644 index 88c86898db..0000000000 --- a/src/test/groovy/graphql/validation/SpecValidation521Test.groovy +++ /dev/null @@ -1,133 +0,0 @@ -package graphql.validation -/** - * validation examples used in the spec in given section - * http://facebook.github.io/graphql/#sec-Validation - * @author dwinsor - * - */ -class SpecValidation521Test extends SpecValidationBase { - - def '5.2.1 Field Selections on ... fieldNotDefined'() { - def query = """ -{ - dog { - ... fieldNotDefined - } -} -fragment fieldNotDefined on Dog { - meowVolume -} -""" - when: - def validationErrors = validate(query) - - then: - !validationErrors.empty - validationErrors.size() == 1 - validationErrors.get(0).getValidationErrorType() == ValidationErrorType.FieldUndefined - } - - def '5.2.1 Field Selections on ... aliasedLyingFieldTargetNotDefined'() { - def query = """ -{ - dog { - ... aliasedLyingFieldTargetNotDefined - } -} -fragment aliasedLyingFieldTargetNotDefined on Dog { - barkVolume: kawVolume -} -""" - when: - def validationErrors = validate(query) - - then: - !validationErrors.empty - validationErrors.size() == 1 - validationErrors.get(0).getValidationErrorType() == ValidationErrorType.FieldUndefined - } - - def '5.2.1 Field Selections on ... interfaceFieldSelection'() { - def query = """ -{ - dog { - ... interfaceFieldSelection - } -} -fragment interfaceFieldSelection on Pet { - name -} -""" - when: - def validationErrors = validate(query) - - then: - validationErrors.empty - } - - def '5.2.1 Field Selections on ... definedOnImplementorsButNotInterface'() { - def query = """ -{ - dog { - ... definedOnImplementorsButNotInterface - } -} -fragment definedOnImplementorsButNotInterface on Pet { - nickname -} -""" - when: - def validationErrors = validate(query) - - then: - !validationErrors.empty - validationErrors.size() == 1 - validationErrors.get(0).getValidationErrorType() == ValidationErrorType.FieldUndefined - } - - def '5.2.1 Field Selections on ... inDirectFieldSelectionOnUnion'() { - def query = """ -{ - dog { - ... inDirectFieldSelectionOnUnion - } -} -fragment inDirectFieldSelectionOnUnion on CatOrDog { - __typename - ... on Pet { - name - } - ... on Dog { - barkVolume - } -} -""" - when: - def validationErrors = validate(query) - - then: - validationErrors.empty - } - - def '5.2.1 Field Selections on ... directFieldSelectionOnUnion'() { - def query = """ -{ - dog { - ... directFieldSelectionOnUnion - } -} -fragment directFieldSelectionOnUnion on CatOrDog { - name - barkVolume -} -""" - when: - def validationErrors = validate(query) - - then: - !validationErrors.empty - validationErrors.size() == 2 - validationErrors.get(0).getValidationErrorType() == ValidationErrorType.FieldUndefined - validationErrors.get(1).getValidationErrorType() == ValidationErrorType.FieldUndefined - } -} diff --git a/src/test/groovy/graphql/validation/SpecValidation5421Test.groovy b/src/test/groovy/graphql/validation/SpecValidation5421Test.groovy deleted file mode 100644 index b937fd8f88..0000000000 --- a/src/test/groovy/graphql/validation/SpecValidation5421Test.groovy +++ /dev/null @@ -1,26 +0,0 @@ -package graphql.validation -/** - * validation examples used in the spec in given section - * http://facebook.github.io/graphql/#sec-Validation - * @author dwinsor - * - */ -class SpecValidation5421Test extends SpecValidationBase { - - def '5.4.2.1 Fragment spread target defined '() { - def query = """ - query getDogName { - dog { - ... FragmentDoesNotExist - } - } - """ - when: - def validationErrors = validate(query) - - then: - !validationErrors.empty - validationErrors.size() == 1 - validationErrors.get(0).getValidationErrorType() == ValidationErrorType.UndefinedFragment - } -} diff --git a/src/test/groovy/graphql/validation/SpecValidation573Test.groovy b/src/test/groovy/graphql/validation/SpecValidation573Test.groovy index 4fc5827cdc..01b400cc56 100644 --- a/src/test/groovy/graphql/validation/SpecValidation573Test.groovy +++ b/src/test/groovy/graphql/validation/SpecValidation573Test.groovy @@ -21,52 +21,4 @@ query madDog(\$dogCommand: DogCommand){ validationErrors.size() == 1 validationErrors.get(0).getValidationErrorType() == ValidationErrorType.VariableTypeMismatch } - - def '5.7.3 Variables Are Input Types - unknown type'() { - def query = """ -query madDog(\$dogCommand: UnknownType){ - dog { - doesKnowCommand(dogCommand: \$dogCommand) - } -}""" - when: - def validationErrors = validate(query) - - then: - !validationErrors.empty - validationErrors.size() == 1 - validationErrors.get(0).getValidationErrorType() == ValidationErrorType.UnknownType - } - - def '5.7.3 Variables Are Input Types - non-null unknown type'() { - def query = """ -query madDog(\$dogCommand: UnknownType!){ - dog { - doesKnowCommand(dogCommand: \$dogCommand) - } -}""" - when: - def validationErrors = validate(query) - - then: - !validationErrors.empty - validationErrors.size() == 1 - validationErrors.get(0).getValidationErrorType() == ValidationErrorType.UnknownType - } - - def '5.7.3 Variables Are Input Types - non-null list unknown type'() { - def query = """ -query madDog(\$dogCommand: [UnknownType]){ - dog { - doesKnowCommand(dogCommand: \$dogCommand) - } -}""" - when: - def validationErrors = validate(query) - - then: - !validationErrors.empty - validationErrors.size() == 1 - validationErrors.get(0).getValidationErrorType() == ValidationErrorType.UnknownType - } } diff --git a/src/test/groovy/graphql/validation/SpecValidationBase.groovy b/src/test/groovy/graphql/validation/SpecValidationBase.groovy index 6611670f45..8d192c8148 100644 --- a/src/test/groovy/graphql/validation/SpecValidationBase.groovy +++ b/src/test/groovy/graphql/validation/SpecValidationBase.groovy @@ -11,8 +11,8 @@ import spock.lang.Specification */ class SpecValidationBase extends Specification { - List validate(String query) { + static List validate(String query) { def document = new Parser().parseDocument(query) - return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document) + return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) } } diff --git a/src/test/groovy/graphql/validation/SpecValidationSchema.java b/src/test/groovy/graphql/validation/SpecValidationSchema.java index a8fa453c02..a59f502dbd 100644 --- a/src/test/groovy/graphql/validation/SpecValidationSchema.java +++ b/src/test/groovy/graphql/validation/SpecValidationSchema.java @@ -5,6 +5,8 @@ import graphql.schema.GraphQLArgument; import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLEnumType; +import graphql.schema.GraphQLInputObjectField; +import graphql.schema.GraphQLInputObjectType; import graphql.schema.GraphQLInterfaceType; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; @@ -203,13 +205,38 @@ public GraphQLObjectType getType(TypeResolutionEnvironment env) { .validLocations(FIELD, FRAGMENT_SPREAD, FRAGMENT_DEFINITION, INLINE_FRAGMENT, QUERY) .build(); + public static final GraphQLDirective nonNullDirective = GraphQLDirective.newDirective() + .name("nonNullDirective") + .argument(newArgument().name("arg1").type(nonNull(GraphQLString)).build()) + .validLocations(FIELD, FRAGMENT_SPREAD, FRAGMENT_DEFINITION, INLINE_FRAGMENT, QUERY) + .build(); + + public static final GraphQLInputObjectType inputType = GraphQLInputObjectType.newInputObject() + .name("Input") + .field(GraphQLInputObjectField.newInputObjectField() + .name("id") + .type(GraphQLString) + .build()) + .field(GraphQLInputObjectField.newInputObjectField() + .name("name") + .type(nonNull(GraphQLString)) + .build()) + .build(); + + public static final GraphQLDirective objectArgumentDirective = GraphQLDirective.newDirective() + .name("objectArgumentDirective") + .argument(newArgument().name("myObject").type(nonNull(inputType)).build()) + .validLocations(FIELD, FRAGMENT_SPREAD, FRAGMENT_DEFINITION, INLINE_FRAGMENT, QUERY) + .build(); + public static final GraphQLSchema specValidationSchema = GraphQLSchema.newSchema() .query(queryRoot) .subscription(subscriptionRoot) .additionalDirective(upperDirective) .additionalDirective(lowerDirective) .additionalDirective(dogDirective) + .additionalDirective(nonNullDirective) + .additionalDirective(objectArgumentDirective) .build(specValidationDictionary); - } diff --git a/src/test/groovy/graphql/validation/ValidateCustomDirectives.groovy b/src/test/groovy/graphql/validation/ValidateCustomDirectives.groovy index 7c2c0e1351..f4f96470dc 100644 --- a/src/test/groovy/graphql/validation/ValidateCustomDirectives.groovy +++ b/src/test/groovy/graphql/validation/ValidateCustomDirectives.groovy @@ -53,11 +53,11 @@ query { then: validationErrors.size() == 1 validationErrors.get(0).getValidationErrorType() == ValidationErrorType.UnknownDirective - validationErrors.get(0).getDescription() == 'Unknown directive argument dummy' + validationErrors.get(0).getDescription() == "Validation error (UnknownDirective@[dog/name]) : Unknown directive argument 'dummy'" } List validate(String query) { def document = new Parser().parseDocument(query) - return new Validator().validateDocument(customDirectiveSchema, document) + return new Validator().validateDocument(customDirectiveSchema, document, Locale.ENGLISH) } } diff --git a/src/test/groovy/graphql/validation/rules/ArgumentsOfCorrectTypeTest.groovy b/src/test/groovy/graphql/validation/rules/ArgumentsOfCorrectTypeTest.groovy index 26415fe017..b2b99db2bd 100644 --- a/src/test/groovy/graphql/validation/rules/ArgumentsOfCorrectTypeTest.groovy +++ b/src/test/groovy/graphql/validation/rules/ArgumentsOfCorrectTypeTest.groovy @@ -8,14 +8,18 @@ import graphql.language.ObjectField import graphql.language.ObjectValue import graphql.language.StringValue import graphql.language.VariableReference +import graphql.parser.Parser import graphql.schema.GraphQLArgument import graphql.schema.GraphQLInputObjectField import graphql.schema.GraphQLInputObjectType import graphql.schema.GraphQLList import graphql.schema.GraphQLNonNull +import graphql.validation.SpecValidationSchema import graphql.validation.ValidationContext +import graphql.validation.ValidationError import graphql.validation.ValidationErrorCollector import graphql.validation.ValidationErrorType +import graphql.validation.Validator import spock.lang.Specification import static graphql.Scalars.GraphQLBoolean @@ -56,8 +60,24 @@ class ArgumentsOfCorrectTypeTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.WrongType) errorCollector.errors.size() == 1 - errorCollector.errors[0].message == - "Validation error of type WrongType: argument 'arg' with value 'StringValue{value='string'}' is not a valid 'Boolean' - Expected AST type 'BooleanValue' but was 'StringValue'." + } + + def "invalid type scalar results in error with message"() { + def query = """ + query getDog { + dog(arg1: 1) { + name + } + } + """ + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors.get(0).getValidationErrorType() == ValidationErrorType.WrongType + validationErrors.get(0).message == "Validation error (WrongType@[dog]) : argument 'arg1' with value 'IntValue{value=1}' is not a valid 'String' - Expected AST type 'StringValue' but was 'IntValue'." } def "invalid input object type results in error"() { @@ -73,8 +93,6 @@ class ArgumentsOfCorrectTypeTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.WrongType) errorCollector.errors.size() == 1 - errorCollector.errors[0].message == - "Validation error of type WrongType: argument 'arg.foo' with value 'StringValue{value='string'}' is not a valid 'Boolean' - Expected AST type 'BooleanValue' but was 'StringValue'." } def "invalid list object type results in error"() { @@ -94,8 +112,6 @@ class ArgumentsOfCorrectTypeTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.WrongType) errorCollector.errors.size() == 1 - errorCollector.errors[0].message == - "Validation error of type WrongType: argument 'arg[1].foo' with value 'StringValue{value='string'}' is not a valid 'Boolean' - Expected AST type 'BooleanValue' but was 'StringValue'." } def "invalid list inside object type results in error"() { @@ -115,8 +131,6 @@ class ArgumentsOfCorrectTypeTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.WrongType) errorCollector.errors.size() == 1 - errorCollector.errors[0].message == - "Validation error of type WrongType: argument 'arg[0].foo[1]' with value 'StringValue{value='string'}' is not a valid 'Boolean' - Expected AST type 'BooleanValue' but was 'StringValue'." } def "invalid list simple type results in error"() { @@ -134,8 +148,6 @@ class ArgumentsOfCorrectTypeTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.WrongType) errorCollector.errors.size() == 1 - errorCollector.errors[0].message == - "Validation error of type WrongType: argument 'arg[1]' with value 'StringValue{value='string'}' is not a valid 'Boolean' - Expected AST type 'BooleanValue' but was 'StringValue'." } def "type missing fields results in error"() { @@ -157,8 +169,24 @@ class ArgumentsOfCorrectTypeTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.WrongType) errorCollector.errors.size() == 1 - errorCollector.errors[0].message == - "Validation error of type WrongType: argument 'arg' with value 'ObjectValue{objectFields=[ObjectField{name='foo', value=StringValue{value='string'}}]}' is missing required fields '[bar]'" + } + + def "type missing fields results in error with message"() { + def query = """ + query getDog { + dog @objectArgumentDirective(myObject: { id: "1" }) { + name + } + } + """ + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors.get(0).getValidationErrorType() == ValidationErrorType.WrongType + validationErrors.get(0).message == "Validation error (WrongType@[dog]) : argument 'myObject' with value 'ObjectValue{objectFields=[ObjectField{name='id', value=StringValue{value='1'}}]}' is missing required fields '[name]'" } def "type not object results in error"() { @@ -178,8 +206,24 @@ class ArgumentsOfCorrectTypeTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.WrongType) errorCollector.errors.size() == 1 - errorCollector.errors[0].message == - "Validation error of type WrongType: argument 'arg' with value 'StringValue{value='string'}' must be an object type" + } + + def "invalid not object type results in error with message"() { + def query = """ + query getDog { + dog @objectArgumentDirective(myObject: 1) { + name + } + } + """ + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors.get(0).getValidationErrorType() == ValidationErrorType.WrongType + validationErrors.get(0).message == "Validation error (WrongType@[dog]) : argument 'myObject' with value 'IntValue{value=1}' must be an object type" } def "type null fields results in error"() { @@ -201,8 +245,24 @@ class ArgumentsOfCorrectTypeTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.WrongType) errorCollector.errors.size() == 1 - errorCollector.errors[0].message == - "Validation error of type WrongType: argument 'arg.bar' with value 'NullValue{}' must not be null" + } + + def "type null results in error with message"() { + def query = """ + query getDog { + dog { + doesKnowCommand(dogCommand: null) + } + } + """ + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 2 // First error is NullValueForNonNullArgument + validationErrors.get(1).getValidationErrorType() == ValidationErrorType.WrongType + validationErrors.get(1).message == "Validation error (WrongType@[dog/doesKnowCommand]) : argument 'dogCommand' with value 'NullValue{}' must not be null" } def "type with extra fields results in error"() { @@ -224,8 +284,24 @@ class ArgumentsOfCorrectTypeTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.WrongType) errorCollector.errors.size() == 1 - errorCollector.errors[0].message == - "Validation error of type WrongType: argument 'arg' with value 'ObjectValue{objectFields=[ObjectField{name='foo', value=StringValue{value='string'}}, ObjectField{name='bar', value=StringValue{value='string'}}, ObjectField{name='fooBar', value=BooleanValue{value=true}}]}' contains a field not in 'ArgumentObjectType': 'fooBar'" + } + + def "type with extra fields results in error with message"() { + def query = """ + query getDog { + dog @objectArgumentDirective(myObject: { name: "Gary", extraField: "ShouldNotBeHere" }) { + name + } + } + """ + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors.get(0).getValidationErrorType() == ValidationErrorType.WrongType + validationErrors.get(0).message == "Validation error (WrongType@[dog]) : argument 'myObject' with value 'ObjectValue{objectFields=[ObjectField{name='name', value=StringValue{value='Gary'}}, ObjectField{name='extraField', value=StringValue{value='ShouldNotBeHere'}}]}' contains a field not in 'Input': 'extraField'" } def "current null argument from context is no error"() { @@ -237,4 +313,27 @@ class ArgumentsOfCorrectTypeTest extends Specification { then: argumentsOfCorrectType.getErrors().isEmpty() } + + def "invalid enum type results in error with message"() { + def query = """ + query getDog { + dog { + doesKnowCommand(dogCommand: PRETTY) + } + } + """ + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors.get(0).getValidationErrorType() == ValidationErrorType.WrongType + validationErrors.get(0).message == "Validation error (WrongType@[dog/doesKnowCommand]) : argument 'dogCommand' with value 'EnumValue{name='PRETTY'}' is not a valid 'DogCommand' - Expected enum literal value not in allowable values - 'EnumValue{name='PRETTY'}'." + } + + static List validate(String query) { + def document = new Parser().parseDocument(query) + return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) + } } diff --git a/src/test/groovy/graphql/validation/rules/ExecutableDefinitionsTest.groovy b/src/test/groovy/graphql/validation/rules/ExecutableDefinitionsTest.groovy index 9ec8693104..4b0e278487 100644 --- a/src/test/groovy/graphql/validation/rules/ExecutableDefinitionsTest.groovy +++ b/src/test/groovy/graphql/validation/rules/ExecutableDefinitionsTest.groovy @@ -2,13 +2,12 @@ package graphql.validation.rules import graphql.language.SourceLocation import graphql.parser.Parser +import graphql.validation.SpecValidationSchema import graphql.validation.ValidationError import graphql.validation.ValidationErrorType import graphql.validation.Validator import spock.lang.Specification -import static graphql.validation.rules.ExecutableDefinitions.nonExecutableDefinitionMessage - class ExecutableDefinitionsTest extends Specification { def 'Executable Definitions with only operation'() { @@ -68,9 +67,12 @@ class ExecutableDefinitionsTest extends Specification { then: !validationErrors.empty validationErrors.size() == 2 - validationErrors[0] == nonExecutableDefinition("Cow", 8, 1) - validationErrors[1] == nonExecutableDefinition("Dog", 12, 1) - + validationErrors[0].validationErrorType == ValidationErrorType.NonExecutableDefinition + validationErrors[0].locations == [new SourceLocation(8, 1)] + validationErrors[0].message == "Validation error (NonExecutableDefinition) : Type 'Cow' definition is not executable" + validationErrors[1].validationErrorType == ValidationErrorType.NonExecutableDefinition + validationErrors[1].locations == [new SourceLocation(12, 1)] + validationErrors[1].message == "Validation error (NonExecutableDefinition) : Type 'Dog' definition is not executable" } def 'Executable Definitions with schema definition'() { @@ -89,8 +91,12 @@ class ExecutableDefinitionsTest extends Specification { then: !validationErrors.empty validationErrors.size() == 2 - validationErrors[0] == nonExecutableDefinition("schema", 2, 1) - validationErrors[1] == nonExecutableDefinition("QueryRoot", 6, 1) + validationErrors[0].validationErrorType == ValidationErrorType.NonExecutableDefinition + validationErrors[0].locations == [new SourceLocation(2, 1)] + validationErrors[0].message == "Validation error (NonExecutableDefinition) : Schema definition is not executable" + validationErrors[1].validationErrorType == ValidationErrorType.NonExecutableDefinition + validationErrors[1].locations == [new SourceLocation(6, 1)] + validationErrors[1].message == "Validation error (NonExecutableDefinition) : Type 'QueryRoot' definition is not executable" } def 'Executable Definitions with input value type definition'() { @@ -105,18 +111,29 @@ class ExecutableDefinitionsTest extends Specification { then: !validationErrors.empty validationErrors.size() == 1 - validationErrors[0] == nonExecutableDefinition("QueryRoot", 2, 1) + validationErrors[0].validationErrorType == ValidationErrorType.NonExecutableDefinition + validationErrors[0].locations == [new SourceLocation(2, 1)] + validationErrors[0].message == "Validation error (NonExecutableDefinition) : Type 'QueryRoot' definition is not executable" } + def 'Executable Definitions with no directive definition'() { + def query = """ + directive @nope on INPUT_OBJECT + """.stripIndent() + when: + def document = new Parser().parseDocument(query) + def validationErrors = new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) - ValidationError nonExecutableDefinition(String defName, int line, int column) { - return new ValidationError(ValidationErrorType.NonExecutableDefinition, - [new SourceLocation(line, column)], - nonExecutableDefinitionMessage(defName)) + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors[0].validationErrorType == ValidationErrorType.NonExecutableDefinition + validationErrors[0].locations == [new SourceLocation(2, 1)] + validationErrors[0].message == "Validation error (NonExecutableDefinition) : Directive 'nope' definition is not executable" } - List validate(String query) { + static List validate(String query) { def document = new Parser().parseDocument(query) - return new Validator().validateDocument(Harness.Schema, document) + return new Validator().validateDocument(Harness.Schema, document, Locale.ENGLISH) } } diff --git a/src/test/groovy/graphql/validation/rules/FieldsOnCorrectTypeTest.groovy b/src/test/groovy/graphql/validation/rules/FieldsOnCorrectTypeTest.groovy index f16b804ae9..8bbb8052e3 100644 --- a/src/test/groovy/graphql/validation/rules/FieldsOnCorrectTypeTest.groovy +++ b/src/test/groovy/graphql/validation/rules/FieldsOnCorrectTypeTest.groovy @@ -1,11 +1,15 @@ package graphql.validation.rules import graphql.language.Field +import graphql.parser.Parser import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLObjectType +import graphql.validation.SpecValidationSchema import graphql.validation.ValidationContext +import graphql.validation.ValidationError import graphql.validation.ValidationErrorCollector import graphql.validation.ValidationErrorType +import graphql.validation.Validator import spock.lang.Specification class FieldsOnCorrectTypeTest extends Specification { @@ -28,7 +32,6 @@ class FieldsOnCorrectTypeTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.FieldUndefined) errorCollector.errors.size() == 1 - errorCollector.errors[0].message == "Validation error of type FieldUndefined: Field 'name' in type 'parentType' is undefined" } def "should results in no error when field definition is filled"() { @@ -56,4 +59,138 @@ class FieldsOnCorrectTypeTest extends Specification { then: errorCollector.errors.isEmpty() } + + def '5.2.1 Field Selections on ... fieldNotDefined'() { + def query = """ +{ + dog { + ... fieldNotDefined + } +} +fragment fieldNotDefined on Dog { + meowVolume +} +""" + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors.get(0).getValidationErrorType() == ValidationErrorType.FieldUndefined + validationErrors.get(0).message == "Validation error (FieldUndefined@[fieldNotDefined/meowVolume]) : Field 'meowVolume' in type 'Dog' is undefined" + } + + def '5.2.1 Field Selections on ... aliasedLyingFieldTargetNotDefined'() { + def query = """ +{ + dog { + ... aliasedLyingFieldTargetNotDefined + } +} +fragment aliasedLyingFieldTargetNotDefined on Dog { + barkVolume: kawVolume +} +""" + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors.get(0).getValidationErrorType() == ValidationErrorType.FieldUndefined + validationErrors.get(0).message == "Validation error (FieldUndefined@[aliasedLyingFieldTargetNotDefined/kawVolume]) : Field 'kawVolume' in type 'Dog' is undefined" + } + + def '5.2.1 Field Selections on ... interfaceFieldSelection'() { + def query = """ +{ + dog { + ... interfaceFieldSelection + } +} +fragment interfaceFieldSelection on Pet { + name +} +""" + when: + def validationErrors = validate(query) + + then: + validationErrors.empty + } + + def '5.2.1 Field Selections on ... definedOnImplementorsButNotInterface'() { + def query = """ +{ + dog { + ... definedOnImplementorsButNotInterface + } +} +fragment definedOnImplementorsButNotInterface on Pet { + nickname +} +""" + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors.get(0).getValidationErrorType() == ValidationErrorType.FieldUndefined + validationErrors.get(0).message == "Validation error (FieldUndefined@[definedOnImplementorsButNotInterface/nickname]) : Field 'nickname' in type 'Pet' is undefined" + } + + def '5.2.1 Field Selections on ... inDirectFieldSelectionOnUnion'() { + def query = """ +{ + dog { + ... inDirectFieldSelectionOnUnion + } +} +fragment inDirectFieldSelectionOnUnion on CatOrDog { + __typename + ... on Pet { + name + } + ... on Dog { + barkVolume + } +} +""" + when: + def validationErrors = validate(query) + + then: + validationErrors.empty + } + + def '5.2.1 Field Selections on ... directFieldSelectionOnUnion'() { + def query = """ +{ + dog { + ... directFieldSelectionOnUnion + } +} +fragment directFieldSelectionOnUnion on CatOrDog { + name + barkVolume +} +""" + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 2 + validationErrors.get(0).getValidationErrorType() == ValidationErrorType.FieldUndefined + validationErrors.get(0).message == "Validation error (FieldUndefined@[directFieldSelectionOnUnion/name]) : Field 'name' in type 'CatOrDog' is undefined" + validationErrors.get(1).getValidationErrorType() == ValidationErrorType.FieldUndefined + validationErrors.get(1).message == "Validation error (FieldUndefined@[directFieldSelectionOnUnion/barkVolume]) : Field 'barkVolume' in type 'CatOrDog' is undefined" + } + + static List validate(String query) { + def document = new Parser().parseDocument(query) + return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) + } } diff --git a/src/test/groovy/graphql/validation/rules/FragmentsOnCompositeTypeTest.groovy b/src/test/groovy/graphql/validation/rules/FragmentsOnCompositeTypeTest.groovy index 5b8aa6a05e..ccd918cbdb 100644 --- a/src/test/groovy/graphql/validation/rules/FragmentsOnCompositeTypeTest.groovy +++ b/src/test/groovy/graphql/validation/rules/FragmentsOnCompositeTypeTest.groovy @@ -1,10 +1,14 @@ package graphql.validation.rules +import graphql.ExecutionInput +import graphql.GraphQL import graphql.StarWarsSchema +import graphql.TestUtil import graphql.language.FragmentDefinition import graphql.language.InlineFragment import graphql.language.TypeName import graphql.validation.ValidationContext +import graphql.validation.ValidationError import graphql.validation.ValidationErrorCollector import graphql.validation.ValidationErrorType import spock.lang.Specification @@ -26,7 +30,6 @@ class FragmentsOnCompositeTypeTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.InlineFragmentTypeConditionInvalid) errorCollector.errors.size() == 1 - errorCollector.errors[0].message == "Validation error of type InlineFragmentTypeConditionInvalid: Inline fragment type condition is invalid, must be on Object/Interface/Union" } def "should results in no error"(InlineFragment inlineFragment) { @@ -71,5 +74,88 @@ class FragmentsOnCompositeTypeTest extends Specification { errorCollector.containsValidationError(ValidationErrorType.FragmentTypeConditionInvalid) } + def schema = TestUtil.schema(""" + type Query { + nothing: String + } + + type Mutation { + updateUDI(input: UDIInput!): UDIOutput + } + + type UDIOutput { + device: String + version: String + } + + input UDIInput { + device: String + version: String + } + """) + + def graphQL = GraphQL.newGraphQL(schema).build() + + + def "#1440 when fragment type condition is input type it should return validation error - not classCastException"() { + when: + def executionInput = ExecutionInput.newExecutionInput() + .query(''' + mutation UpdateUDI($input: UDIInput!) { + updateUDI(input: $input) { + ...fragOnInputType + __typename + } + } + + # fragment should only target composite types + fragment fragOnInputType on UDIInput { + device + version + __typename + } + + ''') + .variables([input: [device: 'device', version: 'version'] ]) + .build() + + def executionResult = graphQL.execute(executionInput) + + then: + + executionResult.data == null + executionResult.errors.size() == 1 + (executionResult.errors[0] as ValidationError).validationErrorType == ValidationErrorType.FragmentTypeConditionInvalid + (executionResult.errors[0] as ValidationError).message == "Validation error (FragmentTypeConditionInvalid@[fragOnInputType]) : Fragment type condition is invalid, must be on Object/Interface/Union" + } + + def "#1440 when inline fragment type condition is input type it should return validation error - not classCastException"() { + when: + def executionInput = ExecutionInput.newExecutionInput() + .query(''' + mutation UpdateUDI($input: UDIInput!) { + updateUDI(input: $input) { + # fragment should only target composite types + ... on UDIInput { + device + version + __typename + } + __typename + } + } + ''') + .variables([input: [device: 'device', version: 'version'] ]) + .build() + + def executionResult = graphQL.execute(executionInput) + + then: + + executionResult.data == null + executionResult.errors.size() == 1 + (executionResult.errors[0] as ValidationError).validationErrorType == ValidationErrorType.InlineFragmentTypeConditionInvalid + (executionResult.errors[0] as ValidationError).message == "Validation error (InlineFragmentTypeConditionInvalid@[updateUDI]) : Inline fragment type condition is invalid, must be on Object/Interface/Union" + } } diff --git a/src/test/groovy/graphql/validation/rules/KnownArgumentNamesTest.groovy b/src/test/groovy/graphql/validation/rules/KnownArgumentNamesTest.groovy index 3911b13c52..302f34b7a1 100644 --- a/src/test/groovy/graphql/validation/rules/KnownArgumentNamesTest.groovy +++ b/src/test/groovy/graphql/validation/rules/KnownArgumentNamesTest.groovy @@ -3,12 +3,16 @@ package graphql.validation.rules import graphql.language.Argument import graphql.language.BooleanValue import graphql.language.StringValue +import graphql.parser.Parser import graphql.schema.GraphQLArgument import graphql.schema.GraphQLDirective import graphql.schema.GraphQLFieldDefinition +import graphql.validation.SpecValidationSchema import graphql.validation.ValidationContext +import graphql.validation.ValidationError import graphql.validation.ValidationErrorCollector import graphql.validation.ValidationErrorType +import graphql.validation.Validator import spock.lang.Specification import static graphql.Scalars.GraphQLBoolean @@ -86,4 +90,45 @@ class KnownArgumentNamesTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.UnknownDirective) } + + def "directive missing argument validation error with message"() { + def query = """ + query getDogName { + dog @dogDirective(notArgument: "value"){ + name + } + } + """ + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors.get(0).getValidationErrorType() == ValidationErrorType.UnknownDirective + validationErrors.get(0).message == "Validation error (UnknownDirective@[dog]) : Unknown directive argument 'notArgument'" + } + + def "field missing argument validation error with message"() { + def query = """ + query getDog { + dog { + doesKnowCommand(dogCommand: SIT, notArgument: false) + } + } + """ + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors.get(0).getValidationErrorType() == ValidationErrorType.UnknownArgument + validationErrors.get(0).message == "Validation error (UnknownArgument@[dog/doesKnowCommand]) : Unknown field argument 'notArgument'" + } + + static List validate(String query) { + def document = new Parser().parseDocument(query) + return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) + } } diff --git a/src/test/groovy/graphql/validation/rules/KnownDirectivesTest.groovy b/src/test/groovy/graphql/validation/rules/KnownDirectivesTest.groovy index 43fb74ee9b..2149a0a171 100644 --- a/src/test/groovy/graphql/validation/rules/KnownDirectivesTest.groovy +++ b/src/test/groovy/graphql/validation/rules/KnownDirectivesTest.groovy @@ -250,8 +250,7 @@ class KnownDirectivesTest extends Specification { def schema = TestUtil.schema(sdl) - - def "invalid directive on SUBSCRIPTION"() { + def "invalid directive on SUBSCRIPTION"() { def spec = ''' subscription sub @queryDirective{ field @@ -260,15 +259,34 @@ class KnownDirectivesTest extends Specification { when: def document = TestUtil.parseQuery(spec) - def validator = new Validator(); - def validationErrors = validator.validateDocument(schema, document); + def validator = new Validator() + def validationErrors = validator.validateDocument(schema, document, Locale.ENGLISH) + + then: + validationErrors.size() == 1 + validationErrors.get(0).validationErrorType == ValidationErrorType.MisplacedDirective + validationErrors.get(0).message == "Validation error (MisplacedDirective) : Directive 'queryDirective' not allowed here" + } + + def "unknown directive on SUBSCRIPTION"() { + def spec = ''' + subscription sub @unknownDirective{ + field + } + ''' + + when: + def document = TestUtil.parseQuery(spec) + def validator = new Validator() + def validationErrors = validator.validateDocument(schema, document, Locale.ENGLISH) then: validationErrors.size() == 1 - validationErrors.get(0).message == "Validation error of type MisplacedDirective: Directive queryDirective not allowed here" + validationErrors.get(0).validationErrorType == ValidationErrorType.UnknownDirective + validationErrors.get(0).message == "Validation error (UnknownDirective) : Unknown directive 'unknownDirective'" } - def "valid directive on SUBSCRIPTION"() { + def "valid directive on SUBSCRIPTION"() { def spec = ''' subscription sub @subDirective{ field @@ -277,8 +295,8 @@ class KnownDirectivesTest extends Specification { when: def document = TestUtil.parseQuery(spec) - def validator = new Validator(); - def validationErrors = validator.validateDocument(schema, document); + def validator = new Validator() + def validationErrors = validator.validateDocument(schema, document, Locale.ENGLISH) then: validationErrors.size() == 0 diff --git a/src/test/groovy/graphql/validation/rules/KnownFragmentNamesTest.groovy b/src/test/groovy/graphql/validation/rules/KnownFragmentNamesTest.groovy index 6ef084edd6..5a56b13514 100644 --- a/src/test/groovy/graphql/validation/rules/KnownFragmentNamesTest.groovy +++ b/src/test/groovy/graphql/validation/rules/KnownFragmentNamesTest.groovy @@ -1,9 +1,13 @@ package graphql.validation.rules import graphql.language.FragmentSpread +import graphql.parser.Parser +import graphql.validation.SpecValidationSchema import graphql.validation.ValidationContext +import graphql.validation.ValidationError import graphql.validation.ValidationErrorCollector import graphql.validation.ValidationErrorType +import graphql.validation.Validator import spock.lang.Specification class KnownFragmentNamesTest extends Specification { @@ -21,8 +25,28 @@ class KnownFragmentNamesTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.UndefinedFragment) - } + def '5.4.2.1 Fragment spread target defined '() { + def query = """ + query getDogName { + dog { + ... FragmentDoesNotExist + } + } + """ + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors.get(0).getValidationErrorType() == ValidationErrorType.UndefinedFragment + validationErrors.get(0).message == "Validation error (UndefinedFragment@[dog]) : Undefined fragment 'FragmentDoesNotExist'" + } + static List validate(String query) { + def document = new Parser().parseDocument(query) + return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) + } } diff --git a/src/test/groovy/graphql/validation/rules/KnownTypeNamesTest.groovy b/src/test/groovy/graphql/validation/rules/KnownTypeNamesTest.groovy index 0135d80a3e..fb84d8739d 100644 --- a/src/test/groovy/graphql/validation/rules/KnownTypeNamesTest.groovy +++ b/src/test/groovy/graphql/validation/rules/KnownTypeNamesTest.groovy @@ -2,9 +2,13 @@ package graphql.validation.rules import graphql.StarWarsSchema import graphql.language.TypeName +import graphql.parser.Parser +import graphql.validation.SpecValidationSchema import graphql.validation.ValidationContext +import graphql.validation.ValidationError import graphql.validation.ValidationErrorCollector import graphql.validation.ValidationErrorType +import graphql.validation.Validator import spock.lang.Specification class KnownTypeNamesTest extends Specification { @@ -22,6 +26,61 @@ class KnownTypeNamesTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.UnknownType) + } + + def '5.7.3 Variables Are Input Types - unknown type'() { + def query = """ + query madDog(\$dogCommand: UnknownType){ + dog { + doesKnowCommand(dogCommand: \$dogCommand) + } + }""" + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors.get(0).getValidationErrorType() == ValidationErrorType.UnknownType + validationErrors.get(0).message == "Validation error (UnknownType) : Unknown type 'UnknownType'" + } + + def '5.7.3 Variables Are Input Types - non-null unknown type'() { + def query = """ + query madDog(\$dogCommand: UnknownType!){ + dog { + doesKnowCommand(dogCommand: \$dogCommand) + } + }""" + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors.get(0).getValidationErrorType() == ValidationErrorType.UnknownType + validationErrors.get(0).message == "Validation error (UnknownType) : Unknown type 'UnknownType'" + } + + def '5.7.3 Variables Are Input Types - non-null list unknown type'() { + def query = """ + query madDog(\$dogCommand: [UnknownType]){ + dog { + doesKnowCommand(dogCommand: \$dogCommand) + } + }""" + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors.get(0).getValidationErrorType() == ValidationErrorType.UnknownType + validationErrors.get(0).message == "Validation error (UnknownType) : Unknown type 'UnknownType'" + } + static List validate(String query) { + def document = new Parser().parseDocument(query) + return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) } } diff --git a/src/test/groovy/graphql/validation/rules/LoneAnonymousOperationTest.groovy b/src/test/groovy/graphql/validation/rules/LoneAnonymousOperationTest.groovy new file mode 100644 index 0000000000..f2543b7815 --- /dev/null +++ b/src/test/groovy/graphql/validation/rules/LoneAnonymousOperationTest.groovy @@ -0,0 +1,107 @@ +package graphql.validation.rules + +import graphql.parser.Parser +import graphql.validation.SpecValidationSchema +import graphql.validation.ValidationError +import graphql.validation.ValidationErrorType +import graphql.validation.Validator +import spock.lang.Specification + +class LoneAnonymousOperationTest extends Specification { + def '5.1.2.1 Lone Anonymous Operation Valid'() { + def query = """ + { + dog { + name + } + } + """ + when: + def validationErrors = validate(query) + + then: + validationErrors.empty + } + + + def '5.1.2.1 Lone Anonymous Operation Not Valid'() { + def query = """ + { + dog { + name + } + } + + query getName { + dog { + owner { + name + } + } + } + """ + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors[0].validationErrorType == ValidationErrorType.LoneAnonymousOperationViolation + validationErrors[0].message == "Validation error (LoneAnonymousOperationViolation) : Operation 'getName' is following anonymous operation" + } + + def '5.1.2.1 Lone Anonymous Operation Not Valid (reverse order) '() { + def query = """ + query getName { + dog { + owner { + name + } + } + } + + { + dog { + name + } + } + """ + + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors[0].validationErrorType == ValidationErrorType.LoneAnonymousOperationViolation + validationErrors[0].message == "Validation error (LoneAnonymousOperationViolation) : Anonymous operation with other operations" + } + + def '5.1.2.1 Lone Anonymous Operation Not Valid (not really alone)'() { + def query = """ + { + dog { + owner { + name + } + } + } + + { + dog { + name + } + } + """ + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors[0].validationErrorType == ValidationErrorType.LoneAnonymousOperationViolation + validationErrors[0].message == "Validation error (LoneAnonymousOperationViolation) : Anonymous operation with other operations" + } + + static List validate(String query) { + def document = new Parser().parseDocument(query) + return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) + } +} diff --git a/src/test/groovy/graphql/validation/rules/NoFragmentCyclesTest.groovy b/src/test/groovy/graphql/validation/rules/NoFragmentCyclesTest.groovy index e057c4a8d2..ae17e7186f 100644 --- a/src/test/groovy/graphql/validation/rules/NoFragmentCyclesTest.groovy +++ b/src/test/groovy/graphql/validation/rules/NoFragmentCyclesTest.groovy @@ -1,6 +1,7 @@ package graphql.validation.rules import graphql.TestUtil +import graphql.i18n.I18n import graphql.language.Document import graphql.parser.Parser import graphql.validation.LanguageTraversal @@ -17,7 +18,8 @@ class NoFragmentCyclesTest extends Specification { def traverse(String query) { Document document = new Parser().parseDocument(query) - ValidationContext validationContext = new ValidationContext(TestUtil.dummySchema, document) + I18n i18n = I18n.i18n(I18n.BundleType.Validation, Locale.ENGLISH) + ValidationContext validationContext = new ValidationContext(TestUtil.dummySchema, document, i18n) NoFragmentCycles noFragmentCycles = new NoFragmentCycles(validationContext, errorCollector) LanguageTraversal languageTraversal = new LanguageTraversal() languageTraversal.traverse(document, new RulesVisitor(validationContext, [noFragmentCycles])) @@ -46,7 +48,6 @@ class NoFragmentCyclesTest extends Specification { traverse(query) then: errorCollector.getErrors().isEmpty() - } def 'spreading twice indirectly is not circular'() { @@ -81,7 +82,6 @@ class NoFragmentCyclesTest extends Specification { errorCollector.getErrors().isEmpty() } - def "circular fragments"() { given: def query = """ @@ -93,6 +93,7 @@ class NoFragmentCyclesTest extends Specification { traverse(query) then: errorCollector.containsValidationError(ValidationErrorType.FragmentCycle) + errorCollector.getErrors()[0].message == "Validation error (FragmentCycle@[fragA]) : Fragment cycles not allowed" } def 'no spreading itself directly'() { @@ -104,6 +105,7 @@ class NoFragmentCyclesTest extends Specification { traverse(query) then: errorCollector.containsValidationError(ValidationErrorType.FragmentCycle) + errorCollector.getErrors()[0].message == "Validation error (FragmentCycle@[fragA]) : Fragment cycles not allowed" } def "no spreading itself indirectly within inline fragment"() { @@ -124,7 +126,7 @@ class NoFragmentCyclesTest extends Specification { traverse(query) then: errorCollector.containsValidationError(ValidationErrorType.FragmentCycle) - + errorCollector.getErrors()[0].message == "Validation error (FragmentCycle@[fragA]) : Fragment cycles not allowed" } def "no spreading itself deeply two paths"() { @@ -138,7 +140,7 @@ class NoFragmentCyclesTest extends Specification { traverse(query) then: errorCollector.containsValidationError(ValidationErrorType.FragmentCycle) - + errorCollector.getErrors()[0].message == "Validation error (FragmentCycle@[fragA]) : Fragment cycles not allowed" } def "no self-spreading in floating fragments"() { @@ -154,6 +156,7 @@ class NoFragmentCyclesTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.FragmentCycle) + errorCollector.getErrors()[0].message == "Validation error (FragmentCycle@[fragA]) : Fragment cycles not allowed" } def "no co-recursive spreads in floating fragments"() { @@ -168,6 +171,7 @@ class NoFragmentCyclesTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.FragmentCycle) + errorCollector.getErrors()[0].message == "Validation error (FragmentCycle@[fragB]) : Fragment cycles not allowed" } def "no self-spread fragments used in multiple operations"() { @@ -183,6 +187,7 @@ class NoFragmentCyclesTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.FragmentCycle) + errorCollector.getErrors()[0].message == "Validation error (FragmentCycle@[fragA]) : Fragment cycles not allowed" } def "#583 no npe on undefined fragment"() { @@ -213,7 +218,9 @@ class NoFragmentCyclesTest extends Specification { """ def document = Parser.parse(query) - def validationContext = new ValidationContext(TestUtil.dummySchema, document) + + I18n i18n = I18n.i18n(I18n.BundleType.Validation, Locale.ENGLISH) + def validationContext = new ValidationContext(TestUtil.dummySchema, document, i18n) def rules = new Validator().createRules(validationContext, errorCollector) when: LanguageTraversal languageTraversal = new LanguageTraversal() @@ -223,5 +230,6 @@ class NoFragmentCyclesTest extends Specification { !errorCollector.getErrors().isEmpty() errorCollector.containsValidationError(ValidationErrorType.FragmentCycle) + errorCollector.getErrors()[0].message == "Validation error (FragmentCycle@[MyFrag]) : Fragment cycles not allowed" } } diff --git a/src/test/groovy/graphql/validation/rules/NoUndefinedVariablesTest.groovy b/src/test/groovy/graphql/validation/rules/NoUndefinedVariablesTest.groovy index 64c05dde5f..90f9976a4d 100644 --- a/src/test/groovy/graphql/validation/rules/NoUndefinedVariablesTest.groovy +++ b/src/test/groovy/graphql/validation/rules/NoUndefinedVariablesTest.groovy @@ -1,6 +1,7 @@ package graphql.validation.rules import graphql.TestUtil +import graphql.i18n.I18n import graphql.language.Document import graphql.parser.Parser import graphql.validation.LanguageTraversal @@ -15,7 +16,8 @@ class NoUndefinedVariablesTest extends Specification { def traverse(String query) { Document document = new Parser().parseDocument(query) - ValidationContext validationContext = new ValidationContext(TestUtil.dummySchema, document) + I18n i18n = I18n.i18n(I18n.BundleType.Validation, Locale.ENGLISH) + ValidationContext validationContext = new ValidationContext(TestUtil.dummySchema, document, i18n) NoUndefinedVariables noUndefinedVariables = new NoUndefinedVariables(validationContext, errorCollector) LanguageTraversal languageTraversal = new LanguageTraversal() @@ -35,7 +37,7 @@ class NoUndefinedVariablesTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.UndefinedVariable) - + errorCollector.getErrors()[0].message == "Validation error (UndefinedVariable@[field]) : Undefined variable 'd'" } def "all variables defined"() { @@ -106,6 +108,7 @@ class NoUndefinedVariablesTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.UndefinedVariable) + errorCollector.getErrors()[0].message == "Validation error (UndefinedVariable@[FragA/field/FragB/field/FragC/field]) : Undefined variable 'c'" } def "floating fragment with variables"() { @@ -157,6 +160,7 @@ class NoUndefinedVariablesTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.UndefinedVariable) + errorCollector.getErrors()[0].message == "Validation error (UndefinedVariable@[A/field]) : Undefined variable 'a'" } def "multiple operations with undefined variables"() { @@ -175,5 +179,6 @@ class NoUndefinedVariablesTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.UndefinedVariable) + errorCollector.getErrors()[0].message == "Validation error (UndefinedVariable@[A/field]) : Undefined variable 'a'" } } diff --git a/src/test/groovy/graphql/validation/rules/NoUnusedFragmentsTest.groovy b/src/test/groovy/graphql/validation/rules/NoUnusedFragmentsTest.groovy index c6a12eec02..3fde31a3fb 100644 --- a/src/test/groovy/graphql/validation/rules/NoUnusedFragmentsTest.groovy +++ b/src/test/groovy/graphql/validation/rules/NoUnusedFragmentsTest.groovy @@ -4,10 +4,13 @@ import graphql.language.Document import graphql.parser.Parser import graphql.validation.LanguageTraversal import graphql.validation.RulesVisitor +import graphql.validation.SpecValidationSchema import graphql.validation.TraversalContext import graphql.validation.ValidationContext +import graphql.validation.ValidationError import graphql.validation.ValidationErrorCollector import graphql.validation.ValidationErrorType +import graphql.validation.Validator import spock.lang.Specification class NoUnusedFragmentsTest extends Specification { @@ -174,4 +177,28 @@ class NoUnusedFragmentsTest extends Specification { errorCollector.containsValidationError(ValidationErrorType.UnusedFragment) errorCollector.getErrors().size() == 2 } + + def "contains unused fragment with error message"() { + def query = """ + query getDogName { + dog { + name + } + } + fragment dogFragment on Dog { barkVolume } + """.stripIndent() + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors[0].validationErrorType == ValidationErrorType.UnusedFragment + validationErrors[0].message == "Validation error (UnusedFragment) : Unused fragment 'dogFragment'" + } + + static List validate(String query) { + def document = new Parser().parseDocument(query) + return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) + } } \ No newline at end of file diff --git a/src/test/groovy/graphql/validation/rules/NoUnusedVariablesTest.groovy b/src/test/groovy/graphql/validation/rules/NoUnusedVariablesTest.groovy index 444f14f983..9b7d0dc8de 100644 --- a/src/test/groovy/graphql/validation/rules/NoUnusedVariablesTest.groovy +++ b/src/test/groovy/graphql/validation/rules/NoUnusedVariablesTest.groovy @@ -1,13 +1,16 @@ package graphql.validation.rules import graphql.TestUtil +import graphql.i18n.I18n import graphql.language.Document import graphql.parser.Parser import graphql.validation.LanguageTraversal import graphql.validation.RulesVisitor +import graphql.validation.SpecValidationSchema import graphql.validation.ValidationContext import graphql.validation.ValidationErrorCollector import graphql.validation.ValidationErrorType +import graphql.validation.Validator import spock.lang.Specification class NoUnusedVariablesTest extends Specification { @@ -15,7 +18,8 @@ class NoUnusedVariablesTest extends Specification { def traverse(String query) { Document document = new Parser().parseDocument(query) - ValidationContext validationContext = new ValidationContext(TestUtil.dummySchema, document) + I18n i18n = I18n.i18n(I18n.BundleType.Validation, Locale.ENGLISH) + ValidationContext validationContext = new ValidationContext(TestUtil.dummySchema, document, i18n) NoUnusedVariables noUnusedVariables = new NoUnusedVariables(validationContext, errorCollector) LanguageTraversal languageTraversal = new LanguageTraversal() @@ -114,4 +118,23 @@ class NoUnusedVariablesTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.UnusedVariable) } + + def "variables not used in fragments with error message"() { + def query = ''' + query getDogName($arg1: String, $unusedArg: Int) { + dog(arg1: $arg1) { + name + } + } + ''' + when: + def document = Parser.parse(query) + def validationErrors = new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors.get(0).getValidationErrorType() == ValidationErrorType.UnusedVariable + validationErrors.get(0).message == "Validation error (UnusedVariable) : Unused variable 'unusedArg'" + } } diff --git a/src/test/groovy/graphql/validation/rules/OverlappingFieldsCanBeMergedTest.groovy b/src/test/groovy/graphql/validation/rules/OverlappingFieldsCanBeMergedTest.groovy index 075fa90504..deedfbed77 100644 --- a/src/test/groovy/graphql/validation/rules/OverlappingFieldsCanBeMergedTest.groovy +++ b/src/test/groovy/graphql/validation/rules/OverlappingFieldsCanBeMergedTest.groovy @@ -2,6 +2,7 @@ package graphql.validation.rules import graphql.TypeResolutionEnvironment +import graphql.i18n.I18n import graphql.language.Document import graphql.language.SourceLocation import graphql.parser.Parser @@ -27,7 +28,6 @@ class OverlappingFieldsCanBeMergedTest extends Specification { ValidationErrorCollector errorCollector = new ValidationErrorCollector() - def traverse(String query, GraphQLSchema schema) { if (schema == null) { def objectType = newObject() @@ -39,7 +39,8 @@ class OverlappingFieldsCanBeMergedTest extends Specification { } Document document = new Parser().parseDocument(query) - ValidationContext validationContext = new ValidationContext(schema, document) + I18n i18n = I18n.i18n(I18n.BundleType.Validation, Locale.ENGLISH) + ValidationContext validationContext = new ValidationContext(schema, document, i18n) OverlappingFieldsCanBeMerged overlappingFieldsCanBeMerged = new OverlappingFieldsCanBeMerged(validationContext, errorCollector) LanguageTraversal languageTraversal = new LanguageTraversal() @@ -74,7 +75,7 @@ class OverlappingFieldsCanBeMergedTest extends Specification { then: errorCollector.getErrors().size() == 1 - errorCollector.getErrors()[0].message == "Validation error of type FieldsConflict: myName: name and nickname are different fields @ 'f'" + errorCollector.getErrors()[0].message == "Validation error (FieldsConflict@[f]) : 'myName' : 'name' and 'nickname' are different fields" errorCollector.getErrors()[0].locations == [new SourceLocation(3, 17), new SourceLocation(4, 17)] } @@ -135,7 +136,7 @@ class OverlappingFieldsCanBeMergedTest extends Specification { then: errorCollector.getErrors().size() == 1 - errorCollector.getErrors()[0].message == "Validation error of type FieldsConflict: scalar: they return differing types Int and String @ 'boxUnion'" + errorCollector.getErrors()[0].message == "Validation error (FieldsConflict@[boxUnion]) : 'scalar' : returns different types 'Int' and 'String'" } @@ -183,7 +184,7 @@ class OverlappingFieldsCanBeMergedTest extends Specification { then: errorCollector.getErrors().size() == 1 - errorCollector.getErrors()[0].message == "Validation error of type FieldsConflict: scalar: fields have different nullability shapes @ 'boxUnion'" + errorCollector.getErrors()[0].message == "Validation error (FieldsConflict@[boxUnion]) : 'scalar' : fields have different nullability shapes" } def 'not the same list return types'() { @@ -207,7 +208,7 @@ class OverlappingFieldsCanBeMergedTest extends Specification { then: errorCollector.getErrors().size() == 1 - errorCollector.getErrors()[0].message == "Validation error of type FieldsConflict: scalar: fields have different list shapes @ 'boxUnion'" + errorCollector.getErrors()[0].message == "Validation error (FieldsConflict@[boxUnion]) : 'scalar' : fields have different list shapes" } @@ -323,7 +324,7 @@ class OverlappingFieldsCanBeMergedTest extends Specification { then: errorCollector.getErrors().size() == 1 - errorCollector.getErrors()[0].message == "Validation error of type FieldsConflict: fido: name and nickname are different fields @ 'sameAliasesWithDifferentFieldTargets'" + errorCollector.getErrors()[0].message == "Validation error (FieldsConflict@[sameAliasesWithDifferentFieldTargets]) : 'fido' : 'name' and 'nickname' are different fields" errorCollector.getErrors()[0].locations == [new SourceLocation(3, 13), new SourceLocation(4, 13)] } @@ -346,7 +347,7 @@ class OverlappingFieldsCanBeMergedTest extends Specification { then: errorCollector.getErrors().size() == 1 - errorCollector.getErrors()[0].message == "Validation error of type FieldsConflict: name: nickname and name are different fields @ 'aliasMaskingDirectFieldAccess'" + errorCollector.getErrors()[0].message == "Validation error (FieldsConflict@[aliasMaskingDirectFieldAccess]) : 'name' : 'nickname' and 'name' are different fields" errorCollector.getErrors()[0].locations == [new SourceLocation(3, 13), new SourceLocation(4, 13)] } @@ -372,7 +373,7 @@ class OverlappingFieldsCanBeMergedTest extends Specification { then: errorCollector.getErrors().size() == 1 - errorCollector.getErrors()[0].message == "Validation error of type FieldsConflict: doesKnowCommand: they have differing arguments @ 'conflictingArgs'" + errorCollector.getErrors()[0].message == "Validation error (FieldsConflict@[conflictingArgs]) : 'doesKnowCommand' : fields have different arguments" errorCollector.getErrors()[0].locations == [new SourceLocation(3, 13), new SourceLocation(4, 13)] } @@ -425,7 +426,7 @@ class OverlappingFieldsCanBeMergedTest extends Specification { then: errorCollector.getErrors().size() == 1 - errorCollector.getErrors()[0].message == "Validation error of type FieldsConflict: x: a and b are different fields" + errorCollector.getErrors()[0].message == "Validation error (FieldsConflict) : 'x' : 'a' and 'b' are different fields" errorCollector.getErrors()[0].locations == [new SourceLocation(7, 13), new SourceLocation(10, 13)] } @@ -472,9 +473,8 @@ class OverlappingFieldsCanBeMergedTest extends Specification { then: errorCollector.getErrors().size() == 1 - errorCollector.getErrors()[0].message == "Validation error of type FieldsConflict: x: a and b are different fields @ 'f1'" + errorCollector.getErrors()[0].message == "Validation error (FieldsConflict@[f1]) : 'x' : 'a' and 'b' are different fields" errorCollector.getErrors()[0].locations == [new SourceLocation(18, 13), new SourceLocation(21, 13)] - } @@ -503,7 +503,7 @@ class OverlappingFieldsCanBeMergedTest extends Specification { then: errorCollector.getErrors().size() == 1 - errorCollector.getErrors()[0].message == "Validation error of type FieldsConflict: field/x: a and b are different fields" + errorCollector.getErrors()[0].message == "Validation error (FieldsConflict) : 'field/x' : 'a' and 'b' are different fields" errorCollector.getErrors()[0].locations.size() == 2 } @@ -537,10 +537,10 @@ class OverlappingFieldsCanBeMergedTest extends Specification { then: errorCollector.getErrors().size() == 2 - errorCollector.getErrors()[0].message == "Validation error of type FieldsConflict: field/x: a and b are different fields" + errorCollector.getErrors()[0].message == "Validation error (FieldsConflict) : 'field/x' : 'a' and 'b' are different fields" errorCollector.getErrors()[0].locations.size() == 2 - errorCollector.getErrors()[1].message == "Validation error of type FieldsConflict: field/y: c and d are different fields" + errorCollector.getErrors()[1].message == "Validation error (FieldsConflict) : 'field/y' : 'c' and 'd' are different fields" errorCollector.getErrors()[1].locations.size() == 2 } @@ -579,7 +579,7 @@ class OverlappingFieldsCanBeMergedTest extends Specification { then: errorCollector.getErrors().size() == 1 - errorCollector.getErrors()[0].message == "Validation error of type FieldsConflict: field/deepField/x: a and b are different fields" + errorCollector.getErrors()[0].message == "Validation error (FieldsConflict) : 'field/deepField/x' : 'a' and 'b' are different fields" errorCollector.getErrors()[0].locations.size() == 2 } @@ -621,7 +621,7 @@ class OverlappingFieldsCanBeMergedTest extends Specification { then: errorCollector.getErrors().size() == 1 - errorCollector.getErrors()[0].message == "Validation error of type FieldsConflict: deepField/x: a and b are different fields @ 'field'" + errorCollector.getErrors()[0].message == "Validation error (FieldsConflict@[field]) : 'deepField/x' : 'a' and 'b' are different fields" errorCollector.getErrors()[0].locations.size() == 2 } @@ -841,7 +841,7 @@ class OverlappingFieldsCanBeMergedTest extends Specification { then: errorCollector.getErrors().size() == 1 - errorCollector.getErrors()[0].message == "Validation error of type FieldsConflict: friends/conflict: they return differing types Int and Float @ 'pets'" + errorCollector.getErrors()[0].message == "Validation error (FieldsConflict@[pets]) : 'friends/conflict' : returns different types 'Int' and 'Float'" } diff --git a/src/test/groovy/graphql/validation/rules/PossibleFragmentSpreadsTest.groovy b/src/test/groovy/graphql/validation/rules/PossibleFragmentSpreadsTest.groovy index ec30d3039d..87c465fd38 100644 --- a/src/test/groovy/graphql/validation/rules/PossibleFragmentSpreadsTest.groovy +++ b/src/test/groovy/graphql/validation/rules/PossibleFragmentSpreadsTest.groovy @@ -1,5 +1,6 @@ package graphql.validation.rules +import graphql.i18n.I18n import graphql.language.Document import graphql.parser.Parser import graphql.validation.LanguageTraversal @@ -15,7 +16,8 @@ class PossibleFragmentSpreadsTest extends Specification { def traverse(String query) { Document document = new Parser().parseDocument(query) - ValidationContext validationContext = new ValidationContext(Harness.Schema, document) + I18n i18n = I18n.i18n(I18n.BundleType.Validation, Locale.ENGLISH) + ValidationContext validationContext = new ValidationContext(Harness.Schema, document, i18n) PossibleFragmentSpreads possibleFragmentSpreads = new PossibleFragmentSpreads(validationContext, errorCollector) LanguageTraversal languageTraversal = new LanguageTraversal() @@ -179,7 +181,7 @@ class PossibleFragmentSpreadsTest extends Specification { then: errorCollector.getErrors().size() == 1 - errorCollector.getErrors().get(0).message == 'Validation error of type InvalidFragmentType: Fragment cannot be spread here as objects of type Cat can never be of type Dog @ \'invalidObjectWithinObjectAnon\'' + errorCollector.getErrors().get(0).message == "Validation error (InvalidFragmentType@[invalidObjectWithinObjectAnon]) : Fragment cannot be spread here as objects of type 'Cat' can never be of type 'Dog'" } def 'object into not implementing interface'() { @@ -192,7 +194,7 @@ class PossibleFragmentSpreadsTest extends Specification { then: errorCollector.getErrors().size() == 1 - errorCollector.getErrors().get(0).message == 'Validation error of type InvalidFragmentType: Fragment humanFragment cannot be spread here as objects of type Pet can never be of type Human @ \'invalidObjectWithinInterface\'' + errorCollector.getErrors().get(0).message == "Validation error (InvalidFragmentType@[invalidObjectWithinInterface]) : Fragment 'humanFragment' cannot be spread here as objects of type 'Pet' can never be of type 'Human'" } def 'object into not containing union'() { diff --git a/src/test/groovy/graphql/validation/rules/ProvidedNonNullArgumentsTest.groovy b/src/test/groovy/graphql/validation/rules/ProvidedNonNullArgumentsTest.groovy index 7708a1b6fb..8de2808b66 100644 --- a/src/test/groovy/graphql/validation/rules/ProvidedNonNullArgumentsTest.groovy +++ b/src/test/groovy/graphql/validation/rules/ProvidedNonNullArgumentsTest.groovy @@ -3,16 +3,19 @@ package graphql.validation.rules import graphql.language.Argument import graphql.language.Directive import graphql.language.Field -import graphql.language.NonNullType import graphql.language.NullValue import graphql.language.StringValue +import graphql.parser.Parser import graphql.schema.GraphQLArgument import graphql.schema.GraphQLDirective import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLNonNull +import graphql.validation.SpecValidationSchema import graphql.validation.ValidationContext +import graphql.validation.ValidationError import graphql.validation.ValidationErrorCollector import graphql.validation.ValidationErrorType +import graphql.validation.Validator import spock.lang.Specification import static graphql.Scalars.GraphQLString @@ -43,6 +46,24 @@ class ProvidedNonNullArgumentsTest extends Specification { errorCollector.containsValidationError(ValidationErrorType.MissingFieldArgument) } + def "not provided and not defaulted non null field argument with error message"() { + def query = """ + query getDogName { + dog { + doesKnowCommand + } + } + """.stripIndent() + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors[0].validationErrorType == ValidationErrorType.MissingFieldArgument + validationErrors[0].message == "Validation error (MissingFieldArgument@[dog/doesKnowCommand]) : Missing field argument 'dogCommand'" + } + def "not provided and but defaulted non null field argument"() { given: def fieldArg = GraphQLArgument.newArgument().name("arg") @@ -64,7 +85,6 @@ class ProvidedNonNullArgumentsTest extends Specification { errorCollector.getErrors().isEmpty() } - def "all field arguments are provided"() { given: def fieldArg = GraphQLArgument.newArgument().name("arg") @@ -104,6 +124,24 @@ class ProvidedNonNullArgumentsTest extends Specification { errorCollector.containsValidationError(ValidationErrorType.MissingDirectiveArgument) } + def "not provided and not defaulted non null directive argument with error message"() { + def query = """ + query getDogName { + dog @nonNullDirective { + name + } + } + """.stripIndent() + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors[0].validationErrorType == ValidationErrorType.MissingDirectiveArgument + validationErrors[0].message == "Validation error (MissingDirectiveArgument@[dog]) : Missing directive argument 'arg1'" + } + def "not provided but defaulted directive argument"() { given: def directiveArg = GraphQLArgument.newArgument() @@ -124,7 +162,6 @@ class ProvidedNonNullArgumentsTest extends Specification { errorCollector.getErrors().isEmpty() } - def "all directive arguments are provided"() { given: def directiveArg = GraphQLArgument.newArgument().name("arg").type(GraphQLNonNull.nonNull(GraphQLString)) @@ -166,4 +203,27 @@ class ProvidedNonNullArgumentsTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.NullValueForNonNullArgument) } + + def "provide the explicit value null is not valid for non null argument with error message"() { + def query = """ + query getDogName { + dog { + doesKnowCommand(dogCommand: null) + } + } + """.stripIndent() + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 2 + validationErrors[0].validationErrorType == ValidationErrorType.NullValueForNonNullArgument + validationErrors[0].message == "Validation error (NullValueForNonNullArgument@[dog/doesKnowCommand]) : Null value for non-null field argument 'dogCommand'" + } + + static List validate(String query) { + def document = new Parser().parseDocument(query) + return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) + } } diff --git a/src/test/groovy/graphql/validation/rules/ScalarLeafsTest.groovy b/src/test/groovy/graphql/validation/rules/ScalarLeafsTest.groovy deleted file mode 100644 index 0416486042..0000000000 --- a/src/test/groovy/graphql/validation/rules/ScalarLeafsTest.groovy +++ /dev/null @@ -1,47 +0,0 @@ -package graphql.validation.rules - -import graphql.Scalars -import graphql.language.Field -import graphql.language.SelectionSet -import graphql.schema.GraphQLObjectType -import graphql.validation.ValidationContext -import graphql.validation.ValidationErrorCollector -import graphql.validation.ValidationErrorType -import spock.lang.Specification - -import static graphql.language.Field.newField - -class ScalarLeafsTest extends Specification { - - ValidationErrorCollector errorCollector = new ValidationErrorCollector() - ValidationContext validationContext = Mock(ValidationContext) - ScalarLeafs scalarLeafs = new ScalarLeafs(validationContext, errorCollector) - - def "sub selection not allowed"() { - given: - Field field = newField("hello", SelectionSet.newSelectionSet([newField("world").build()]).build()).build() - validationContext.getOutputType() >> Scalars.GraphQLString - when: - scalarLeafs.checkField(field) - - then: - errorCollector.containsValidationError( - ValidationErrorType.SubSelectionNotAllowed, - "Sub selection not allowed on leaf type String of field hello" - ) - } - - def "sub selection required"() { - given: - Field field = newField("hello").build() - validationContext.getOutputType() >> GraphQLObjectType.newObject().name("objectType").build() - when: - scalarLeafs.checkField(field) - - then: - errorCollector.containsValidationError( - ValidationErrorType.SubSelectionRequired, - "Sub selection required for type objectType of field hello" - ) - } -} diff --git a/src/test/groovy/graphql/validation/rules/ScalarLeavesTest.groovy b/src/test/groovy/graphql/validation/rules/ScalarLeavesTest.groovy new file mode 100644 index 0000000000..14934a4846 --- /dev/null +++ b/src/test/groovy/graphql/validation/rules/ScalarLeavesTest.groovy @@ -0,0 +1,86 @@ +package graphql.validation.rules + +import graphql.Scalars +import graphql.language.Field +import graphql.language.SelectionSet +import graphql.parser.Parser +import graphql.schema.GraphQLObjectType +import graphql.validation.SpecValidationSchema +import graphql.validation.ValidationContext +import graphql.validation.ValidationError +import graphql.validation.ValidationErrorCollector +import graphql.validation.ValidationErrorType +import graphql.validation.Validator +import spock.lang.Specification + +import static graphql.language.Field.newField + +class ScalarLeavesTest extends Specification { + + ValidationErrorCollector errorCollector = new ValidationErrorCollector() + ValidationContext validationContext = Mock(ValidationContext) + ScalarLeaves scalarLeaves = new ScalarLeaves(validationContext, errorCollector) + + def "subselection not allowed"() { + given: + Field field = newField("hello", SelectionSet.newSelectionSet([newField("world").build()]).build()).build() + validationContext.getOutputType() >> Scalars.GraphQLString + when: + scalarLeaves.checkField(field) + + then: + errorCollector.containsValidationError(ValidationErrorType.SubselectionNotAllowed) + } + + def "subselection not allowed with error message"() { + def query = """ + query dogOperation { + dog { + name { + id + } + } + } + """.stripIndent() + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors[0].validationErrorType == ValidationErrorType.SubselectionNotAllowed + validationErrors[0].message == "Validation error (SubselectionNotAllowed@[dog/name]) : Subselection not allowed on leaf type 'String!' of field 'name'" + } + + def "subselection required"() { + given: + Field field = newField("hello").build() + validationContext.getOutputType() >> GraphQLObjectType.newObject().name("objectType").build() + when: + scalarLeaves.checkField(field) + + then: + errorCollector.containsValidationError(ValidationErrorType.SubselectionRequired) + } + + def "subselection required with error message"() { + def query = """ + query dogOperation { + dog + } + """.stripIndent() + when: + def validationErrors = validate(query) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors[0].validationErrorType == ValidationErrorType.SubselectionRequired + validationErrors[0].message == "Validation error (SubselectionRequired@[dog]) : Subselection required for type 'Dog' of field 'dog'" + } + + static List validate(String query) { + def document = new Parser().parseDocument(query) + return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) + } +} diff --git a/src/test/groovy/graphql/validation/rules/SubscriptionUniqueRootFieldTest.groovy b/src/test/groovy/graphql/validation/rules/SubscriptionUniqueRootFieldTest.groovy index 9a7f0b5929..1e2d72756f 100644 --- a/src/test/groovy/graphql/validation/rules/SubscriptionUniqueRootFieldTest.groovy +++ b/src/test/groovy/graphql/validation/rules/SubscriptionUniqueRootFieldTest.groovy @@ -65,6 +65,7 @@ class SubscriptionUniqueRootFieldTest extends Specification { !validationErrors.empty validationErrors.size() == 1 validationErrors[0].validationErrorType == ValidationErrorType.SubscriptionMultipleRootFields + validationErrors[0].message == "Validation error (SubscriptionMultipleRootFields) : Subscription operation 'pets' must have exactly one root field" } def "5.2.3.1 subscription with more than one root field with fragment fails validation"() { @@ -90,6 +91,7 @@ class SubscriptionUniqueRootFieldTest extends Specification { !validationErrors.empty validationErrors.size() == 1 validationErrors[0].validationErrorType == ValidationErrorType.SubscriptionMultipleRootFields + validationErrors[0].message == "Validation error (SubscriptionMultipleRootFields) : Subscription operation 'whoIsAGoodBoy' must have exactly one root field with fragments" } def "5.2.3.1 document can contain multiple operations with different root fields"() { @@ -128,10 +130,32 @@ class SubscriptionUniqueRootFieldTest extends Specification { !validationErrors.empty validationErrors.size() == 1 validationErrors[0].validationErrorType == ValidationErrorType.SubscriptionIntrospectionRootField + validationErrors[0].message == "Validation error (SubscriptionIntrospectionRootField) : Subscription operation 'doggo' root field '__typename' cannot be an introspection field" + } + + def "5.2.3.1 subscription root field via fragment must not be an introspection field"() { + given: + def subscriptionIntrospectionField = ''' + subscription doggo { + ...dogs + } + + fragment dogs on SubscriptionRoot { + __typename + } + ''' + when: + def validationErrors = validate(subscriptionIntrospectionField) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors[0].validationErrorType == ValidationErrorType.SubscriptionIntrospectionRootField + validationErrors[0].message == "Validation error (SubscriptionIntrospectionRootField) : Subscription operation 'doggo' fragment root field '__typename' cannot be an introspection field" } static List validate(String query) { def document = new Parser().parseDocument(query) - return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document) + return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) } } diff --git a/src/test/groovy/graphql/validation/rules/UniqueArgumentNamesTest.groovy b/src/test/groovy/graphql/validation/rules/UniqueArgumentNamesTest.groovy index db1018ddc6..4e76fc69a4 100644 --- a/src/test/groovy/graphql/validation/rules/UniqueArgumentNamesTest.groovy +++ b/src/test/groovy/graphql/validation/rules/UniqueArgumentNamesTest.groovy @@ -11,20 +11,20 @@ class UniqueArgumentNamesTest extends Specification { def "unique argument name"() { def query = """ query getDogName { - dog(arg1:"argValue") @dogDirective(arg1: "vlue"){ + dog(arg1:"argValue") @dogDirective(arg1: "value"){ name @include(if: true) } } """ when: def document = Parser.parse(query) - def validationErrors = new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document) + def validationErrors = new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) then: validationErrors.empty } - def "duplicate arguemnt name on field"() { + def "duplicate argument name on field"() { def query = """ query getDogName { dog(arg1:"value1",arg1:"value2") { @@ -34,15 +34,16 @@ class UniqueArgumentNamesTest extends Specification { """ when: def document = Parser.parse(query) - def validationErrors = new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document) + def validationErrors = new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) then: !validationErrors.empty validationErrors.size() == 1 validationErrors.get(0).getValidationErrorType() == ValidationErrorType.DuplicateArgumentNames + validationErrors.get(0).message == "Validation error (DuplicateArgumentNames@[dog]) : There can be only one argument named 'arg1'" } - def "duplicate arguemnt name on directive"() { + def "duplicate argument name on directive"() { def query = """ query getDogName { dog(arg1:"argValue") { @@ -52,30 +53,32 @@ class UniqueArgumentNamesTest extends Specification { """ when: def document = Parser.parse(query) - def validationErrors = new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document) + def validationErrors = new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) then: !validationErrors.empty validationErrors.size() == 1 validationErrors.get(0).getValidationErrorType() == ValidationErrorType.DuplicateArgumentNames + validationErrors.get(0).message == "Validation error (DuplicateArgumentNames@[dog/name]) : There can be only one argument named 'if'" } - def "duplicate arguemnt name on customed directive"() { + def "duplicate argument name on custom directive"() { def query = """ query getDogName { - dog(arg1:"argValue") @dogDirective(arg1: "vlue",arg1: "vlue2"){ + dog(arg1:"argValue") @dogDirective(arg1: "value",arg1: "value2"){ name } } """ when: def document = Parser.parse(query) - def validationErrors = new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document) + def validationErrors = new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) then: !validationErrors.empty validationErrors.size() == 1 validationErrors.get(0).getValidationErrorType() == ValidationErrorType.DuplicateArgumentNames + validationErrors.get(0).message == "Validation error (DuplicateArgumentNames@[dog]) : There can be only one argument named 'arg1'" } } diff --git a/src/test/groovy/graphql/validation/rules/UniqueDirectiveNamesPerLocationTest.groovy b/src/test/groovy/graphql/validation/rules/UniqueDirectiveNamesPerLocationTest.groovy index c1e355754f..107a140295 100644 --- a/src/test/groovy/graphql/validation/rules/UniqueDirectiveNamesPerLocationTest.groovy +++ b/src/test/groovy/graphql/validation/rules/UniqueDirectiveNamesPerLocationTest.groovy @@ -32,7 +32,10 @@ class UniqueDirectiveNamesPerLocationTest extends Specification { then: !validationErrors.empty validationErrors.size() == 1 - assertDuplicateDirectiveName("upper", "FragmentDefinition", 12, 39, validationErrors[0]) + validationErrors[0].locations[0].line == 12 + validationErrors[0].locations[0].column == 39 + validationErrors[0].validationErrorType == ValidationErrorType.DuplicateDirectiveName + validationErrors[0].message == "Validation error (DuplicateDirectiveName@[FragDef]) : Non repeatable directives must be uniquely named within a location. The directive 'upper' used on a 'FragmentDefinition' is not unique" } def '5.7.3 Directives Are Unique Per Location - OperationDefinition'() { @@ -57,7 +60,10 @@ class UniqueDirectiveNamesPerLocationTest extends Specification { then: !validationErrors.empty validationErrors.size() == 1 - assertDuplicateDirectiveName("upper", "OperationDefinition", 2, 29, validationErrors[0]) + validationErrors[0].locations[0].line == 2 + validationErrors[0].locations[0].column == 29 + validationErrors[0].validationErrorType == ValidationErrorType.DuplicateDirectiveName + validationErrors[0].message == "Validation error (DuplicateDirectiveName) : Non repeatable directives must be uniquely named within a location. The directive 'upper' used on a 'OperationDefinition' is not unique" } def '5.7.3 Directives Are Unique Per Location - Field'() { @@ -82,7 +88,10 @@ class UniqueDirectiveNamesPerLocationTest extends Specification { then: !validationErrors.empty validationErrors.size() == 1 - assertDuplicateDirectiveName("upper", "Field", 4, 28, validationErrors[0]) + validationErrors[0].locations[0].line == 4 + validationErrors[0].locations[0].column == 28 + validationErrors[0].validationErrorType == ValidationErrorType.DuplicateDirectiveName + validationErrors[0].message == "Validation error (DuplicateDirectiveName@[dog/name]) : Non repeatable directives must be uniquely named within a location. The directive 'upper' used on a 'Field' is not unique" } def '5.7.3 Directives Are Unique Per Location - FragmentSpread'() { @@ -107,7 +116,10 @@ class UniqueDirectiveNamesPerLocationTest extends Specification { then: !validationErrors.empty validationErrors.size() == 1 - assertDuplicateDirectiveName("upper", "FragmentSpread", 5, 35, validationErrors[0]) + validationErrors[0].locations[0].line == 5 + validationErrors[0].locations[0].column == 35 + validationErrors[0].validationErrorType == ValidationErrorType.DuplicateDirectiveName + validationErrors[0].message == "Validation error (DuplicateDirectiveName@[dog]) : Non repeatable directives must be uniquely named within a location. The directive 'upper' used on a 'FragmentSpread' is not unique" } def '5.7.3 Directives Are Unique Per Location - InlineFragment'() { @@ -132,22 +144,14 @@ class UniqueDirectiveNamesPerLocationTest extends Specification { then: !validationErrors.empty validationErrors.size() == 1 - assertDuplicateDirectiveName("upper", "InlineFragment", 6, 27, validationErrors[0]) + validationErrors[0].locations[0].line == 6 + validationErrors[0].locations[0].column == 27 + validationErrors[0].validationErrorType == ValidationErrorType.DuplicateDirectiveName + validationErrors[0].message == "Validation error (DuplicateDirectiveName@[dog]) : Non repeatable directives must be uniquely named within a location. The directive 'upper' used on a 'InlineFragment' is not unique" } - - - def assertDuplicateDirectiveName(String name, String type, int line, int column, ValidationError error) { - assert error.locations[0].line == line - assert error.locations[0].column == column - assert error.validationErrorType == ValidationErrorType.DuplicateDirectiveName - def expectedMsg = "Non repeatable directives must be uniquely named within a location. The directive '${name}' used on a '${type}' is not unique." - assert error.message.contains(expectedMsg) - true - } - - List validate(String query) { + static List validate(String query) { def document = new Parser().parseDocument(query) - return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document) + return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) } } diff --git a/src/test/groovy/graphql/validation/rules/UniqueFragmentNamesTest.groovy b/src/test/groovy/graphql/validation/rules/UniqueFragmentNamesTest.groovy index 53b1f8beb3..f3d049aba1 100644 --- a/src/test/groovy/graphql/validation/rules/UniqueFragmentNamesTest.groovy +++ b/src/test/groovy/graphql/validation/rules/UniqueFragmentNamesTest.groovy @@ -27,15 +27,14 @@ class UniqueFragmentNamesTest extends Specification { """.stripIndent() when: def document = Parser.parse(query) - def validationErrors = new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document) + def validationErrors = new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) then: !validationErrors.empty validationErrors.size() == 1 validationErrors[0].locations == [new SourceLocation(10, 1)] - validationErrors[0].message.contains("There can be only one fragment named 'F'") + validationErrors[0].message == "Validation error (DuplicateFragmentName@[F]) : There can be only one fragment named 'F'" validationErrors[0].validationErrorType == ValidationErrorType.DuplicateFragmentName - } } diff --git a/src/test/groovy/graphql/validation/rules/UniqueOperationNamesTest.groovy b/src/test/groovy/graphql/validation/rules/UniqueOperationNamesTest.groovy index 94854ee987..1fa03eb24c 100644 --- a/src/test/groovy/graphql/validation/rules/UniqueOperationNamesTest.groovy +++ b/src/test/groovy/graphql/validation/rules/UniqueOperationNamesTest.groovy @@ -8,8 +8,6 @@ import graphql.validation.ValidationErrorType import graphql.validation.Validator import spock.lang.Specification -import static graphql.validation.rules.UniqueOperationNames.duplicateOperationNameMessage - class UniqueOperationNamesTest extends Specification { def '5.1.1.1 Operation Name Uniqueness Not Valid'() { @@ -34,7 +32,9 @@ class UniqueOperationNamesTest extends Specification { then: !validationErrors.empty validationErrors.size() == 1 - validationErrors[0] == duplicateOperationName("getName", 8, 1) + validationErrors[0].validationErrorType == ValidationErrorType.DuplicateOperationName + validationErrors[0].locations == [new SourceLocation(8, 1)] + validationErrors[0].message == "Validation error (DuplicateOperationName) : There can be only one operation named 'getName'" } def '5.1.1.1 Operation Name Uniqueness Not Valid Different Operations'() { @@ -57,17 +57,13 @@ class UniqueOperationNamesTest extends Specification { then: !validationErrors.empty validationErrors.size() == 1 - validationErrors[0] == duplicateOperationName("dogOperation", 8, 1) - } - - ValidationError duplicateOperationName(String defName, int line, int column) { - return new ValidationError(ValidationErrorType.DuplicateOperationName, - [new SourceLocation(line, column)], - duplicateOperationNameMessage(defName)) + validationErrors[0].validationErrorType == ValidationErrorType.DuplicateOperationName + validationErrors[0].locations == [new SourceLocation(8, 1)] + validationErrors[0].message == "Validation error (DuplicateOperationName) : There can be only one operation named 'dogOperation'" } - List validate(String query) { + static List validate(String query) { def document = new Parser().parseDocument(query) - return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document) + return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) } } diff --git a/src/test/groovy/graphql/validation/rules/UniqueVariableNamesRulerTest.groovy b/src/test/groovy/graphql/validation/rules/UniqueVariableNamesTest.groovy similarity index 82% rename from src/test/groovy/graphql/validation/rules/UniqueVariableNamesRulerTest.groovy rename to src/test/groovy/graphql/validation/rules/UniqueVariableNamesTest.groovy index 12e2796f67..79e80c05ba 100644 --- a/src/test/groovy/graphql/validation/rules/UniqueVariableNamesRulerTest.groovy +++ b/src/test/groovy/graphql/validation/rules/UniqueVariableNamesTest.groovy @@ -7,7 +7,7 @@ import graphql.validation.ValidationErrorType import graphql.validation.Validator import spock.lang.Specification -class UniqueVariableNamesRulerTest extends Specification { +class UniqueVariableNamesTest extends Specification { def schema = TestUtil.schema(''' type Query { @@ -25,7 +25,7 @@ class UniqueVariableNamesRulerTest extends Specification { when: def document = Parser.parse(query) - def validationResult = new Validator().validateDocument(schema, document) + def validationResult = new Validator().validateDocument(schema, document, Locale.ENGLISH) then: validationResult.size() == 0 @@ -41,11 +41,12 @@ class UniqueVariableNamesRulerTest extends Specification { when: def document = Parser.parse(query) - def validationResult = new Validator().validateDocument(schema, document) + def validationResult = new Validator().validateDocument(schema, document, Locale.ENGLISH) then: validationResult.size() == 1 (validationResult[0] as ValidationError).validationErrorType == ValidationErrorType.DuplicateVariableName + validationResult[0].message == "Validation error (DuplicateVariableName) : There can be only one variable named 'arg'" } } diff --git a/src/test/groovy/graphql/validation/rules/VariableDefaultValuesOfCorrectTypeTest.groovy b/src/test/groovy/graphql/validation/rules/VariableDefaultValuesOfCorrectTypeTest.groovy index fa90a7b52a..cbf1a3a518 100644 --- a/src/test/groovy/graphql/validation/rules/VariableDefaultValuesOfCorrectTypeTest.groovy +++ b/src/test/groovy/graphql/validation/rules/VariableDefaultValuesOfCorrectTypeTest.groovy @@ -1,11 +1,13 @@ package graphql.validation.rules +import graphql.TestUtil import graphql.language.BooleanValue import graphql.language.TypeName import graphql.language.VariableDefinition import graphql.validation.ValidationContext import graphql.validation.ValidationErrorCollector import graphql.validation.ValidationErrorType +import graphql.validation.Validator import spock.lang.Specification import static graphql.Scalars.GraphQLString @@ -27,4 +29,38 @@ class VariableDefaultValuesOfCorrectTypeTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.BadValueForDefaultArg) } -} + + def "default value has wrong type with error message"() { + setup: + def schema = ''' + type User { + id: String + } + + type Query { + getUsers(howMany: Int) : [User] + } + ''' + + def query = ''' + query($howMany: Int = "NotANumber") { + getUsers(howMany: $howMany) { + id + } + } + ''' + + def graphQlSchema = TestUtil.schema(schema) + def document = TestUtil.parseQuery(query) + def validator = new Validator() + + when: + def validationErrors = validator.validateDocument(graphQlSchema, document, Locale.ENGLISH) + + then: + !validationErrors.empty + validationErrors.size() == 1 + validationErrors[0].getValidationErrorType() == ValidationErrorType.BadValueForDefaultArg + validationErrors[0].message == "Validation error (BadValueForDefaultArg) : Bad default value 'StringValue{value='NotANumber'}' for type 'Int'" + } +} \ No newline at end of file diff --git a/src/test/groovy/graphql/validation/rules/VariableTypesMatchRuleTest.groovy b/src/test/groovy/graphql/validation/rules/VariableTypesMatchTest.groovy similarity index 76% rename from src/test/groovy/graphql/validation/rules/VariableTypesMatchRuleTest.groovy rename to src/test/groovy/graphql/validation/rules/VariableTypesMatchTest.groovy index e2ae3760cc..dab3510daa 100644 --- a/src/test/groovy/graphql/validation/rules/VariableTypesMatchRuleTest.groovy +++ b/src/test/groovy/graphql/validation/rules/VariableTypesMatchTest.groovy @@ -2,6 +2,7 @@ package graphql.validation.rules import graphql.StarWarsSchema +import graphql.i18n.I18n import graphql.parser.Parser import graphql.validation.LanguageTraversal import graphql.validation.RulesVisitor @@ -10,13 +11,14 @@ import graphql.validation.ValidationErrorCollector import graphql.validation.ValidationErrorType import spock.lang.Specification -class VariableTypesMatchRuleTest extends Specification { +class VariableTypesMatchTest extends Specification { ValidationErrorCollector errorCollector = new ValidationErrorCollector() def traverse(String query) { def document = Parser.parse(query) - def validationContext = new ValidationContext(StarWarsSchema.starWarsSchema, document) - def variableTypesMatchRule = new VariableTypesMatchRule(validationContext, errorCollector) + I18n i18n = I18n.i18n(I18n.BundleType.Validation, Locale.ENGLISH) + def validationContext = new ValidationContext(StarWarsSchema.starWarsSchema, document, i18n) + def variableTypesMatchRule = new VariableTypesMatch(validationContext, errorCollector) def languageTraversal = new LanguageTraversal() languageTraversal.traverse(document, new RulesVisitor(validationContext, [variableTypesMatchRule])) } @@ -54,7 +56,7 @@ class VariableTypesMatchRuleTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.VariableTypeMismatch) // #991: describe which types were mismatched in error message - errorCollector.errors[0].message.contains("Variable type 'String' doesn't match expected type 'String!'") + errorCollector.errors[0].message == "Validation error (VariableTypeMismatch@[human]) : Variable type 'String' does not match expected type 'String!'" } def "invalid variables in fragment spread"() { @@ -76,7 +78,7 @@ class VariableTypesMatchRuleTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.VariableTypeMismatch) - errorCollector.errors[0].message.contains("Variable type 'String' doesn't match expected type 'String!'") + errorCollector.errors[0].message == "Validation error (VariableTypeMismatch@[QueryType/human]) : Variable type 'String' does not match expected type 'String!'" } def "mixed validity operations, valid first"() { @@ -102,7 +104,7 @@ class VariableTypesMatchRuleTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.VariableTypeMismatch) - errorCollector.errors[0].message.contains("Variable type 'String' doesn't match expected type 'String!'") + errorCollector.errors[0].message == "Validation error (VariableTypeMismatch@[QueryType/human]) : Variable type 'String' does not match expected type 'String!'" } def "mixed validity operations, invalid first"() { @@ -128,7 +130,7 @@ class VariableTypesMatchRuleTest extends Specification { then: errorCollector.containsValidationError(ValidationErrorType.VariableTypeMismatch) - errorCollector.errors[0].message.contains("Variable type 'String' doesn't match expected type 'String!'") + errorCollector.errors[0].message == "Validation error (VariableTypeMismatch@[QueryType/human]) : Variable type 'String' does not match expected type 'String!'" } def "multiple invalid operations"() { @@ -156,11 +158,11 @@ class VariableTypesMatchRuleTest extends Specification { errorCollector.getErrors().size() == 2 errorCollector.errors.any { it.validationErrorType == ValidationErrorType.VariableTypeMismatch && - it.message.contains("Variable type 'String' doesn't match expected type 'String!'") + it.message == "Validation error (VariableTypeMismatch@[QueryType/human]) : Variable type 'String' does not match expected type 'String!'" } errorCollector.errors.any { it.validationErrorType == ValidationErrorType.VariableTypeMismatch && - it.message.contains("Variable type 'Boolean' doesn't match expected type 'String!'") + it.message == "Validation error (VariableTypeMismatch@[QueryType/human]) : Variable type 'Boolean' does not match expected type 'String!'" } } } diff --git a/src/test/groovy/graphql/validation/rules/VariablesAreInputTypesTest.groovy b/src/test/groovy/graphql/validation/rules/VariablesAreInputTypesTest.groovy index fb7febd3c2..b01eebabc3 100644 --- a/src/test/groovy/graphql/validation/rules/VariablesAreInputTypesTest.groovy +++ b/src/test/groovy/graphql/validation/rules/VariablesAreInputTypesTest.groovy @@ -18,7 +18,6 @@ class VariablesAreInputTypesTest extends Specification { ValidationErrorCollector errorCollector = new ValidationErrorCollector() VariablesAreInputTypes variablesAreInputTypes = new VariablesAreInputTypes(validationContext, errorCollector) - def "the unmodified ast type is not a schema input type"() { given: def astType = new NonNullType(new ListType(new TypeName(StarWarsSchema.droidType.getName()))) @@ -65,12 +64,13 @@ class VariablesAreInputTypesTest extends Specification { def validator = new Validator() when: - def validationErrors = validator.validateDocument(graphQlSchema, document) + def validationErrors = validator.validateDocument(graphQlSchema, document, Locale.ENGLISH) then: !validationErrors.empty validationErrors.size() == 2 validationErrors.validationErrorType as Set == [ValidationErrorType.VariableTypeMismatch, ValidationErrorType.NonInputTypeOnVariable] as Set + validationErrors[0].message == "Validation error (NonInputTypeOnVariable) : Input variable 'user' type 'User' is not an input type" } } diff --git a/src/test/java/benchmark/OverlappingFieldValidationBenchmark.java b/src/test/java/benchmark/OverlappingFieldValidationBenchmark.java index b57313f068..87c4579102 100644 --- a/src/test/java/benchmark/OverlappingFieldValidationBenchmark.java +++ b/src/test/java/benchmark/OverlappingFieldValidationBenchmark.java @@ -4,6 +4,7 @@ import com.google.common.io.Resources; import graphql.ExecutionResult; import graphql.GraphQL; +import graphql.i18n.I18n; import graphql.language.Document; import graphql.parser.Parser; import graphql.schema.GraphQLSchema; @@ -31,6 +32,7 @@ import java.net.URL; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.concurrent.TimeUnit; import static com.google.common.io.Resources.getResource; @@ -87,10 +89,10 @@ public void overlappingFieldValidationThroughput(MyState myState, Blackhole blac blackhole.consume(validateQuery(myState.schema, myState.document)); } - private List validateQuery(GraphQLSchema schema, Document document) { ValidationErrorCollector errorCollector = new ValidationErrorCollector(); - ValidationContext validationContext = new ValidationContext(schema, document); + I18n i18n = I18n.i18n(I18n.BundleType.Validation, Locale.ENGLISH); + ValidationContext validationContext = new ValidationContext(schema, document, i18n); OverlappingFieldsCanBeMerged overlappingFieldsCanBeMerged = new OverlappingFieldsCanBeMerged(validationContext, errorCollector); LanguageTraversal languageTraversal = new LanguageTraversal(); languageTraversal.traverse(document, new RulesVisitor(validationContext, Collections.singletonList(overlappingFieldsCanBeMerged))); diff --git a/src/test/java/benchmark/ValidatorBenchmark.java b/src/test/java/benchmark/ValidatorBenchmark.java index 33dca45d25..3a397d562e 100644 --- a/src/test/java/benchmark/ValidatorBenchmark.java +++ b/src/test/java/benchmark/ValidatorBenchmark.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.net.URL; +import java.util.Locale; import java.util.concurrent.TimeUnit; import com.google.common.base.Charsets; @@ -73,6 +74,6 @@ private String readFromClasspath(String file) throws IOException { @Benchmark public void runValidator(MyState state) { Validator validator = new Validator(); - validator.validateDocument(state.schema, state.document); + validator.validateDocument(state.schema, state.document, Locale.ENGLISH); } }