diff --git a/README.md b/README.md index 465d37e..a4fdcb3 100644 --- a/README.md +++ b/README.md @@ -261,7 +261,7 @@ The boolean value must be false. - Example : `updateDriver( isDrunk : Boolean @AssertFalse) : DriverDetails` -- Applies to : `Boolean` +- Applies to : `Boolean`, `Lists` - SDL : `directive @AssertFalse(message : String = "graphql.validation.AssertFalse.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` @@ -274,7 +274,7 @@ The boolean value must be true. - Example : `driveCar( hasLicence : Boolean @AssertTrue) : DriverDetails` -- Applies to : `Boolean` +- Applies to : `Boolean`, `Lists` - SDL : `directive @AssertTrue(message : String = "graphql.validation.AssertTrue.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` @@ -287,7 +287,7 @@ The element must be a number whose value must be less than or equal to the speci - Example : `driveCar( bloodAlcoholLevel : Float @DecimalMax(value : "0.05") : DriverDetails` -- Applies to : `String`, `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float` +- Applies to : `String`, `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float`, `Lists` - SDL : `directive @DecimalMax(value : String!, inclusive : Boolean! = true, message : String = "graphql.validation.DecimalMax.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` @@ -300,7 +300,7 @@ The element must be a number whose value must be greater than or equal to the sp - Example : `driveCar( carHorsePower : Float @DecimalMin(value : "300.50") : DriverDetails` -- Applies to : `String`, `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float` +- Applies to : `String`, `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float`, `Lists` - SDL : `directive @DecimalMin(value : String!, inclusive : Boolean! = true, message : String = "graphql.validation.DecimalMin.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` @@ -313,7 +313,7 @@ The element must be a number inside the specified `integer` and `fraction` range - Example : `buyCar( carCost : Float @Digits(integer : 5, fraction : 2) : DriverDetails` -- Applies to : `String`, `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float` +- Applies to : `String`, `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float`, `Lists` - SDL : `directive @Digits(integer : Int!, fraction : Int!, message : String = "graphql.validation.Digits.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` @@ -340,7 +340,7 @@ The element must be a number whose value must be less than or equal to the speci - Example : `driveCar( horsePower : Float @Max(value : 1000) : DriverDetails` -- Applies to : `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float` +- Applies to : `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float`, `Lists` - SDL : `directive @Max(value : Int! = 2147483647, message : String = "graphql.validation.Max.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` @@ -353,7 +353,7 @@ The element must be a number whose value must be greater than or equal to the sp - Example : `driveCar( age : Int @Min(value : 18) : DriverDetails` -- Applies to : `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float` +- Applies to : `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float`, `Lists` - SDL : `directive @Min(value : Int! = 0, message : String = "graphql.validation.Min.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` @@ -366,7 +366,7 @@ The element must be a negative number. - Example : `driveCar( lostLicencePoints : Int @Negative) : DriverDetails` -- Applies to : `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float` +- Applies to : `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float`, `Lists` - SDL : `directive @Negative(message : String = "graphql.validation.Negative.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` @@ -379,7 +379,7 @@ The element must be a negative number or zero. - Example : `driveCar( lostLicencePoints : Int @NegativeOrZero) : DriverDetails` -- Applies to : `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float` +- Applies to : `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float`, `Lists` - SDL : `directive @NegativeOrZero(message : String = "graphql.validation.NegativeOrZero.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` @@ -401,16 +401,28 @@ The String must contain at least one non-whitespace character, according to Java ### @NotEmpty -The element must have a non zero size. +The element must have a non-zero size. -- Example : `updateAccident( accidentNotes : [Notes]! @NotEmpty) : DriverDetails` +- Example : `updateAccident( accidentNotes : String! @NotEmpty) : DriverDetails` -- Applies to : `String`, `ID`, `Lists`, `Input Objects` +- Applies to : `String`, `ID`, `Lists` - SDL : `directive @NotEmpty(message : String = "graphql.validation.NotEmpty.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` - Message : `graphql.validation.NotEmpty.message` +### @ContainerNotEmpty + +The list or input object must have a non-zero size. + +- Example : `updateAccident( accidentNotes : [Notes]! @ContainerNotEmpty) : DriverDetails` + +- Applies to : `Lists`, `Input Objects` + +- SDL : `directive @ContainerNotEmpty(message : String = "graphql.validation.ContainerNotEmpty.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` + +- Message : `graphql.validation.ContainerNotEmpty.message` + ### @Pattern @@ -431,7 +443,7 @@ The element must be a positive number. - Example : `driver( licencePoints : Int @Positive) : DriverDetails` -- Applies to : `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float` +- Applies to : `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float`, `Lists` - SDL : `directive @Positive(message : String = "graphql.validation.Positive.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` @@ -444,7 +456,7 @@ The element must be a positive number or zero. - Example : `driver( licencePoints : Int @PositiveOrZero) : DriverDetails` -- Applies to : `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float` +- Applies to : `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float`, `Lists` - SDL : `directive @PositiveOrZero(message : String = "graphql.validation.PositiveOrZero.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` @@ -457,7 +469,7 @@ The element range must be between the specified `min` and `max` boundaries (incl - Example : `driver( milesTravelled : Int @Range( min : 1000, max : 100000)) : DriverDetails` -- Applies to : `String`, `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float` +- Applies to : `String`, `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float`, `Lists` - SDL : `directive @Range(min : Int = 0, max : Int = 2147483647, message : String = "graphql.validation.Range.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` @@ -466,14 +478,26 @@ The element range must be between the specified `min` and `max` boundaries (incl ### @Size -The element size must be between the specified `min` and `max` boundaries (inclusive). +The string's size must be between the specified `min` and `max` boundaries (inclusive). - Example : `updateDrivingNotes( drivingNote : String @Size( min : 1000, max : 100000)) : DriverDetails` -- Applies to : `String`, `ID`, `Lists`, `Input Objects` +- Applies to : `String`, `ID`, `Lists` - SDL : `directive @Size(min : Int = 0, max : Int = 2147483647, message : String = "graphql.validation.Size.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` - Message : `graphql.validation.Size.message` +### @ContainerSize + +The list's or input object's size must be between the specified `min` and `max` boundaries (inclusive). + +- Example : `updateDrivingNotes( drivingNote : [String!]! @ContainerSize( min : 10, max : 20)) : DriverDetails` + +- Applies to : `Lists`, `Input Objects` + +- SDL : `directive @ContainerSize(min : Int = 0, max : Int = 2147483647, message : String = "graphql.validation.ContainerSize.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` + +- Message : `graphql.validation.ContainerSize.message` + diff --git a/build.gradle b/build.gradle index facdca6..88ad13e 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,6 @@ plugins { id 'java' id 'groovy' id 'java-library' - id 'maven' id 'maven-publish' id 'signing' id "io.github.gradle-nexus.publish-plugin" version "1.0.0" @@ -40,14 +39,14 @@ repositories { dependencies { - compile "com.graphql-java:graphql-java:17.0" - compile "com.graphql-java:graphql-java-extended-scalars:17.0" - compile "org.hibernate.validator:hibernate-validator:7.0.1.Final" - compile "org.glassfish:jakarta.el:4.0.0" - - testCompile 'org.slf4j:slf4j-simple:1.7.31' - testCompile 'org.spockframework:spock-core:1.3-groovy-2.5' - testCompile 'org.codehaus.groovy:groovy-all:2.5.14' + api "com.graphql-java:graphql-java:17.0" + api "com.graphql-java:graphql-java-extended-scalars:17.0" + api "org.hibernate.validator:hibernate-validator:7.0.1.Final" + api "org.glassfish:jakarta.el:4.0.0" + + testImplementation 'org.slf4j:slf4j-simple:1.7.31' + testImplementation 'org.spockframework:spock-core:1.3-groovy-2.5' + testImplementation 'org.codehaus.groovy:groovy-all:2.5.14' } task sourcesJar(type: Jar, dependsOn: classes) { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 12d38de..05679dc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java b/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java index 4b0138d..4cfc79b 100644 --- a/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java +++ b/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java @@ -16,16 +16,15 @@ import graphql.validation.rules.ValidationEnvironment; import graphql.validation.util.DirectivesAndTypeWalker; import graphql.validation.util.Util; - import java.lang.reflect.Array; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; - import static graphql.schema.GraphQLTypeUtil.isList; import static graphql.validation.rules.ValidationEnvironment.ValidatedElement.FIELD; import static graphql.validation.util.Util.mkMap; @@ -70,10 +69,18 @@ public boolean appliesTo(GraphQLArgument argument, GraphQLFieldDefinition fieldD boolean hasNamedDirective = directive.getName().equals(this.getName()); if (hasNamedDirective) { inputType = Util.unwrapNonNull(inputType); - boolean appliesToType = appliesToType(inputType); + + boolean appliesToType; + if (appliesToListElements()) { + appliesToType = appliesToType((GraphQLInputType) GraphQLTypeUtil.unwrapAll(inputType)); + } else { + appliesToType = appliesToType(inputType); + } + if (appliesToType) { return true; } + // if they have a @Directive on there BUT it can't handle that type // then is a really bad situation String argType = GraphQLTypeUtil.simplePrint(inputType); @@ -88,7 +95,6 @@ public boolean appliesTo(GraphQLArgument argument, GraphQLFieldDefinition fieldD * A derived class will be called to indicate whether this input type applies to the constraint * * @param inputType the input type - * * @return true if the constraint can handle that type */ abstract protected boolean appliesToType(GraphQLInputType inputType); @@ -97,22 +103,20 @@ public boolean appliesTo(GraphQLArgument argument, GraphQLFieldDefinition fieldD * This is called to perform the constraint validation * * @param validationEnvironment the validation environment - * * @return a list of errors or an empty one if there are no errors */ abstract protected List runConstraint(ValidationEnvironment validationEnvironment); - @SuppressWarnings("unchecked") @Override public List runValidation(ValidationEnvironment validationEnvironment) { + Object validatedValue = validationEnvironment.getValidatedValue(); // output fields are special if (validationEnvironment.getValidatedElement() == FIELD) { - return runFieldValidationImpl(validationEnvironment); + return runValidationImpl(validationEnvironment); } - Object validatedValue = validationEnvironment.getValidatedValue(); // // all the directives validation code does NOT care for NULL ness since the graphql engine covers that. // eg a @NonNull validation directive makes no sense in graphql like it might in Java @@ -124,15 +128,10 @@ public List runValidation(ValidationEnvironment validationEnvironm GraphQLInputType inputType = Util.unwrapNonNull(validationEnvironment.getValidatedType()); validationEnvironment = validationEnvironment.transform(b -> b.validatedType(inputType)); - return runValidationImpl(validationEnvironment, inputType, validatedValue); + return runValidationImpl(validationEnvironment); } - private List runFieldValidationImpl(ValidationEnvironment validationEnvironment) { - return runConstraintOnDirectives(validationEnvironment); - } - - @SuppressWarnings("unchecked") - private List runValidationImpl(ValidationEnvironment validationEnvironment, GraphQLInputType inputType, Object validatedValue) { + private List runValidationImpl(ValidationEnvironment validationEnvironment) { return runConstraintOnDirectives(validationEnvironment); } @@ -152,22 +151,37 @@ private List runConstraintOnDirectives(ValidationEnvironment valid validationEnvironment = validationEnvironment.transform(b -> b.context(GraphQLDirective.class, directive)); // // now run the directive rule with this directive instance - List ruleErrors = this.runConstraint(validationEnvironment); + List ruleErrors = this.runConstrainOnPossibleListElements(validationEnvironment); errors.addAll(ruleErrors); } + return errors; } + private List runConstrainOnPossibleListElements(ValidationEnvironment validationEnvironment) { + if (appliesToListElements()) { + final GraphQLListElementValidator validator = new GraphQLListElementValidator(); + return validator.runConstraintOnListElements(validationEnvironment, this::runConstraint); + } + + return runConstraint(validationEnvironment); + } + + protected abstract boolean appliesToListElements(); + + + protected boolean isOneOfTheseTypes(GraphQLInputType inputType, GraphQLScalarType... scalarTypes) { + return isOneOfTheseTypes(inputType, Arrays.asList(scalarTypes)); + } /** * Returns true of the input type is one of the specified scalar types, regardless of non null ness * * @param inputType the type to check * @param scalarTypes the array of scalar types - * * @return true if its one of them */ - protected boolean isOneOfTheseTypes(GraphQLInputType inputType, GraphQLScalarType... scalarTypes) { + protected boolean isOneOfTheseTypes(GraphQLInputType inputType, Collection scalarTypes) { GraphQLInputType type = Util.unwrapNonNull(inputType); if (type instanceof GraphQLNamedInputType) { final GraphQLNamedInputType unwrappedType = (GraphQLNamedInputType) type; @@ -185,7 +199,6 @@ protected boolean isOneOfTheseTypes(GraphQLInputType inputType, GraphQLScalarTyp * * @param directive the directive to check * @param argName the argument name - * * @return a non null value */ protected int getIntArg(GraphQLDirective directive, String argName) { @@ -209,7 +222,6 @@ protected int getIntArg(GraphQLDirective directive, String argName) { * * @param directive the directive to check * @param argName the argument name - * * @return a non null value */ protected String getStrArg(GraphQLDirective directive, String argName) { @@ -232,7 +244,6 @@ protected String getStrArg(GraphQLDirective directive, String argName) { * * @param directive the directive to check * @param argName the argument name - * * @return a non null value */ protected boolean getBoolArg(GraphQLDirective directive, String argName) { @@ -255,7 +266,6 @@ protected boolean getBoolArg(GraphQLDirective directive, String argName) { * called "graphql.validation.{name}.message" * * @param directive the directive to check - * * @return a non null value */ protected String getMessageTemplate(GraphQLDirective directive) { @@ -279,7 +289,6 @@ protected String getMessageTemplate(GraphQLDirective directive) { * @param validatedValue the value being validated * @param validationEnvironment the validation environment * @param args must be an key / value array with String keys as the even params and values as then odd params - * * @return a map of message parameters */ protected Map mkMessageParams(Object validatedValue, ValidationEnvironment validationEnvironment, Object... args) { @@ -299,7 +308,6 @@ protected Map mkMessageParams(Object validatedValue, ValidationE * @param validationEnvironment the current validation environment * @param directive the directive being run * @param msgParams the map of parameters - * * @return a list of a single error */ protected List mkError(ValidationEnvironment validationEnvironment, GraphQLDirective directive, Map msgParams) { @@ -308,11 +316,18 @@ protected List mkError(ValidationEnvironment validationEnvironment return singletonList(error); } + protected List mkError(ValidationEnvironment validationEnvironment, Object... messageParameters) { + GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); + String messageTemplate = getMessageTemplate(directive); + Object validatedValue = validationEnvironment.getValidatedValue(); + GraphQLError error = validationEnvironment.getInterpolator().interpolate(messageTemplate, mkMessageParams(validatedValue, validationEnvironment, messageParameters), validationEnvironment); + return singletonList(error); + } + /** * Return true if the type is a String or ID or List type, regardless of non null ness * * @param inputType the type to check - * * @return true if one of the above */ protected boolean isStringOrIDOrList(GraphQLInputType inputType) { @@ -324,21 +339,23 @@ protected boolean isStringOrIDOrList(GraphQLInputType inputType) { * Return true if the type is a String or ID or List type or {@link graphql.schema.GraphQLInputObjectType}, regardless of non null ness * * @param inputType the type to check - * * @return true if one of the above */ protected boolean isStringOrIDOrListOrMap(GraphQLInputType inputType) { - GraphQLInputType unwrappedType = Util.unwrapOneAndAllNonNull(inputType); return isStringOrID(inputType) || isList(inputType) || - (unwrappedType instanceof GraphQLInputObjectType); + isMap(inputType); + } + + protected boolean isMap(GraphQLInputType inputType) { + GraphQLInputType unwrappedType = Util.unwrapOneAndAllNonNull(inputType); + return (unwrappedType instanceof GraphQLInputObjectType); } /** * Return true if the type is a String or ID * * @param inputType the type to check - * * @return true if one of the above */ protected boolean isStringOrID(GraphQLInputType inputType) { @@ -350,7 +367,6 @@ protected boolean isStringOrID(GraphQLInputType inputType) { * Casts the object as a Map with an assertion of it is not one * * @param value the object to turn into a map - * * @return a Map */ @SuppressWarnings("ConstantConditions") @@ -363,7 +379,6 @@ protected Map asMap(Object value) { * Makes the object a BigDecimal with an assertion if we have no conversion of it * * @param value the object to turn into a BigDecimal - * * @return a BigDecimal */ protected BigDecimal asBigDecimal(Object value) throws NumberFormatException { @@ -388,7 +403,6 @@ protected BigDecimal asBigDecimal(Object value) throws NumberFormatException { * Makes the object a boolean with an assertion if we have no conversion of it * * @param value the boolean object - * * @return a boolean */ protected boolean asBoolean(Object value) { @@ -407,7 +421,6 @@ protected boolean asBoolean(Object value) { * * @param inputType the input type * @param value the value - * * @return the length of a String or Map or List */ protected int getStringOrIDOrObjectOrMapLength(GraphQLInputType inputType, Object value) { diff --git a/src/main/java/graphql/validation/constraints/DirectiveConstraints.java b/src/main/java/graphql/validation/constraints/DirectiveConstraints.java index 7c91471..a775d41 100644 --- a/src/main/java/graphql/validation/constraints/DirectiveConstraints.java +++ b/src/main/java/graphql/validation/constraints/DirectiveConstraints.java @@ -7,6 +7,8 @@ import graphql.schema.idl.TypeDefinitionRegistry; import graphql.validation.constraints.standard.AssertFalseConstraint; import graphql.validation.constraints.standard.AssertTrueConstraint; +import graphql.validation.constraints.standard.ContainerNotEmptyConstraint; +import graphql.validation.constraints.standard.ContainerSizeConstraint; import graphql.validation.constraints.standard.DecimalMaxConstraint; import graphql.validation.constraints.standard.DecimalMinConstraint; import graphql.validation.constraints.standard.DigitsConstraint; @@ -61,7 +63,10 @@ public class DirectiveConstraints { new PositiveOrZeroConstraint(), new PositiveConstraint(), new RangeConstraint(), - new SizeConstraint() + new SizeConstraint(), + new ContainerSizeConstraint(), + new ContainerNotEmptyConstraint() + ); private final Map constraints; diff --git a/src/main/java/graphql/validation/constraints/Documentation.java b/src/main/java/graphql/validation/constraints/Documentation.java index 355dba0..d1c746a 100644 --- a/src/main/java/graphql/validation/constraints/Documentation.java +++ b/src/main/java/graphql/validation/constraints/Documentation.java @@ -1,10 +1,11 @@ package graphql.validation.constraints; +import graphql.schema.GraphQLNamedType; import graphql.schema.idl.SchemaParser; import graphql.schema.idl.TypeDefinitionRegistry; - import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; public class Documentation { @@ -80,14 +81,23 @@ public Builder messageTemplate(String messageTemplate) { return this; } + public Builder applicableTypes(List applicableTypes) { + this.applicableTypeNames = applicableTypes.stream().map(GraphQLNamedType::getName).collect(Collectors.toList()); + return this; + } + + public Builder applicableTypes(GraphQLNamedType... applicableTypes) { + return applicableTypes(Arrays.asList(applicableTypes)); + } + + public Builder applicableTypeNames(List applicableTypeNames) { this.applicableTypeNames = applicableTypeNames; return this; } public Builder applicableTypeNames(String... applicableTypeNames) { - this.applicableTypeNames = Arrays.asList(applicableTypeNames); - return this; + return applicableTypeNames(Arrays.asList(applicableTypeNames)); } public Documentation build() { diff --git a/src/main/java/graphql/validation/constraints/GraphQLListElementValidator.java b/src/main/java/graphql/validation/constraints/GraphQLListElementValidator.java new file mode 100644 index 0000000..662e1bb --- /dev/null +++ b/src/main/java/graphql/validation/constraints/GraphQLListElementValidator.java @@ -0,0 +1,41 @@ +package graphql.validation.constraints; + +import graphql.GraphQLError; +import graphql.schema.GraphQLInputType; +import graphql.schema.GraphQLTypeUtil; +import graphql.validation.rules.ValidationEnvironment; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class GraphQLListElementValidator { + public boolean appliesToType(GraphQLInputType inputType, Function appliesToTypeOrListElement) { + if (GraphQLTypeUtil.isList(inputType)) { + return appliesToTypeOrListElement.apply((GraphQLInputType) GraphQLTypeUtil.unwrapAll(inputType)); + } + + return appliesToTypeOrListElement.apply(inputType); + } + + public List runConstraintOnListElements(ValidationEnvironment validationEnvironment, Function> runConstraintOnElement) { + Object validatedValue = validationEnvironment.getValidatedValue(); + + if (validatedValue instanceof Collection) { + final AtomicInteger index = new AtomicInteger(0); + return ((Collection) validatedValue) + .stream() + .flatMap((item) -> item == null ? Stream.empty() : runConstraintOnElement.apply(validationEnvironment.transform((environment) -> { + environment + .validatedValue(item) + .validatedPath(validationEnvironment.getValidatedPath().segment(index.getAndIncrement())) + .validatedType((GraphQLInputType) GraphQLTypeUtil.unwrapAll(validationEnvironment.getValidatedType())); + })).stream()) + .collect(Collectors.toList()); + } + + return runConstraintOnElement.apply(validationEnvironment); + } +} diff --git a/src/main/java/graphql/validation/constraints/GraphQLScalars.java b/src/main/java/graphql/validation/constraints/GraphQLScalars.java new file mode 100644 index 0000000..b8dfb69 --- /dev/null +++ b/src/main/java/graphql/validation/constraints/GraphQLScalars.java @@ -0,0 +1,26 @@ +package graphql.validation.constraints; + +import graphql.Scalars; +import graphql.scalars.ExtendedScalars; +import graphql.schema.GraphQLScalarType; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class GraphQLScalars { + public static final List GRAPHQL_NUMBER_TYPES = Arrays.asList( + Scalars.GraphQLInt, + Scalars.GraphQLFloat, + ExtendedScalars.GraphQLByte, + ExtendedScalars.GraphQLShort, + ExtendedScalars.GraphQLLong, + ExtendedScalars.GraphQLBigDecimal, + ExtendedScalars.GraphQLBigInteger + ); + + public static final List GRAPHQL_NUMBER_AND_STRING_TYPES = Stream.concat( + Stream.of(Scalars.GraphQLString, Scalars.GraphQLID), + GraphQLScalars.GRAPHQL_NUMBER_TYPES.stream() + ).collect(Collectors.toList()); +} diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractAssertConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractAssertConstraint.java index 9f5ddc7..fadd286 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractAssertConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractAssertConstraint.java @@ -1,49 +1,40 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLInputType; import graphql.validation.constraints.AbstractDirectiveConstraint; import graphql.validation.rules.ValidationEnvironment; - import java.util.Collections; import java.util.List; - import static graphql.Scalars.GraphQLBoolean; abstract class AbstractAssertConstraint extends AbstractDirectiveConstraint { - public AbstractAssertConstraint(String name) { super(name); } - @Override public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, - GraphQLBoolean - ); + return isOneOfTheseTypes(inputType, GraphQLBoolean); } @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - //null values are valid - if (validatedValue == null) { - return Collections.emptyList(); - } - - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); boolean isTrue = asBoolean(validatedValue); - if (!isOK(isTrue)) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment)); + if (!isOK(isTrue)) { + return mkError(validationEnvironment); } + return Collections.emptyList(); } protected abstract boolean isOK(boolean isTrue); - + @Override + protected boolean appliesToListElements() { + return true; + } } diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java index 1fb9119..5e92a30 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java @@ -1,60 +1,33 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.Scalars; -import graphql.scalars.ExtendedScalars; import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLInputType; import graphql.schema.GraphQLScalarType; import graphql.validation.constraints.AbstractDirectiveConstraint; import graphql.validation.rules.ValidationEnvironment; - import java.math.BigDecimal; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import static graphql.validation.constraints.GraphQLScalars.GRAPHQL_NUMBER_AND_STRING_TYPES; abstract class AbstractDecimalMinMaxConstraint extends AbstractDirectiveConstraint { - public AbstractDecimalMinMaxConstraint(String name) { super(name); } - @Override - public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, - Scalars.GraphQLString, // note we allow strings - ExtendedScalars.GraphQLByte, - ExtendedScalars.GraphQLShort, - Scalars.GraphQLInt, - ExtendedScalars.GraphQLLong, - ExtendedScalars.GraphQLBigDecimal, - ExtendedScalars.GraphQLBigInteger, - Scalars.GraphQLFloat - ); + public List getApplicableTypes() { + return GRAPHQL_NUMBER_AND_STRING_TYPES; } - public List getApplicableTypeNames() { - return Stream.of(Scalars.GraphQLString, // note we allow strings - ExtendedScalars.GraphQLByte, - ExtendedScalars.GraphQLShort, - Scalars.GraphQLInt, - ExtendedScalars.GraphQLLong, - ExtendedScalars.GraphQLBigDecimal, - ExtendedScalars.GraphQLBigInteger, - Scalars.GraphQLFloat) - .map(GraphQLScalarType::getName) - .collect(Collectors.toList()); + @Override + protected boolean appliesToType(GraphQLInputType inputType) { + return isOneOfTheseTypes(inputType, GRAPHQL_NUMBER_AND_STRING_TYPES); } @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - //null values are valid - if (validatedValue == null) { - return Collections.emptyList(); - } GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); String value = getStrArg(directive, "value"); @@ -66,21 +39,21 @@ protected List runConstraint(ValidationEnvironment validationEnvir BigDecimal argBD = asBigDecimal(validatedValue); int comparisonResult = argBD.compareTo(directiveBD); isOK = isOK(inclusive, comparisonResult); - } catch (NumberFormatException nfe) { isOK = false; } if (!isOK) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment, - "value", validatedValue, - "inclusive", inclusive)); - + return mkError(validationEnvironment, "value", validatedValue, "inclusive", inclusive); } + return Collections.emptyList(); } abstract protected boolean isOK(boolean inclusive, int comparisonResult); - + @Override + protected boolean appliesToListElements() { + return true; + } } diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java index 52efb2e..cdfb40b 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java @@ -1,61 +1,34 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.Scalars; -import graphql.scalars.ExtendedScalars; import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLInputType; import graphql.schema.GraphQLScalarType; import graphql.validation.constraints.AbstractDirectiveConstraint; +import graphql.validation.constraints.GraphQLScalars; import graphql.validation.rules.ValidationEnvironment; - import java.math.BigDecimal; import java.util.Collections; import java.util.List; -import java.util.stream.Stream; - -import static java.util.stream.Collectors.toList; abstract class AbstractMinMaxConstraint extends AbstractDirectiveConstraint { - public AbstractMinMaxConstraint(String name) { super(name); } - @Override - public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, - ExtendedScalars.GraphQLByte, - ExtendedScalars.GraphQLShort, - Scalars.GraphQLInt, - ExtendedScalars.GraphQLLong, - ExtendedScalars.GraphQLBigDecimal, - ExtendedScalars.GraphQLBigInteger, - Scalars.GraphQLFloat - ); + public List getApplicableTypes() { + return GraphQLScalars.GRAPHQL_NUMBER_TYPES; } - public List getApplicableTypeNames() { - return Stream.of(ExtendedScalars.GraphQLByte, - ExtendedScalars.GraphQLShort, - Scalars.GraphQLInt, - ExtendedScalars.GraphQLLong, - ExtendedScalars.GraphQLBigDecimal, - ExtendedScalars.GraphQLBigInteger, - Scalars.GraphQLFloat) - .map(GraphQLScalarType::getName) - .collect(toList()); - } + @Override + protected boolean appliesToType(GraphQLInputType inputType) { + return isOneOfTheseTypes(inputType, GraphQLScalars.GRAPHQL_NUMBER_TYPES); + } @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - //null values are valid - if (validatedValue == null) { - return Collections.emptyList(); - } - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); int value = getIntArg(directive, "value"); @@ -72,12 +45,16 @@ protected List runConstraint(ValidationEnvironment validationEnvir if (!isOK) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment, - "value", value)); - + return mkError(validationEnvironment, "value", value); } + return Collections.emptyList(); } abstract protected boolean isOK(int comparisonResult); + + @Override + protected boolean appliesToListElements() { + return true; + } } diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractNotEmptyRule.java b/src/main/java/graphql/validation/constraints/standard/AbstractNotEmptyRule.java new file mode 100644 index 0000000..1ed3adf --- /dev/null +++ b/src/main/java/graphql/validation/constraints/standard/AbstractNotEmptyRule.java @@ -0,0 +1,28 @@ +package graphql.validation.constraints.standard; + +import graphql.GraphQLError; +import graphql.schema.GraphQLInputType; +import graphql.validation.constraints.AbstractDirectiveConstraint; +import graphql.validation.rules.ValidationEnvironment; +import java.util.Collections; +import java.util.List; + +public abstract class AbstractNotEmptyRule extends AbstractDirectiveConstraint { + public AbstractNotEmptyRule(String name) { + super(name); + } + + @Override + final protected List runConstraint(ValidationEnvironment validationEnvironment) { + Object validatedValue = validationEnvironment.getValidatedValue(); + GraphQLInputType argumentType = validationEnvironment.getValidatedType(); + + int size = getStringOrIDOrObjectOrMapLength(argumentType, validatedValue); + + if (size <= 0) { + return mkError(validationEnvironment, "size", size); + } + + return Collections.emptyList(); + } +} diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java index 916875d..b6090e8 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java @@ -1,20 +1,14 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.Scalars; -import graphql.scalars.ExtendedScalars; -import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLInputType; import graphql.schema.GraphQLScalarType; import graphql.validation.constraints.AbstractDirectiveConstraint; +import graphql.validation.constraints.GraphQLScalars; import graphql.validation.rules.ValidationEnvironment; - import java.math.BigDecimal; import java.util.Collections; import java.util.List; -import java.util.stream.Stream; - -import static java.util.stream.Collectors.toList; abstract class AbstractPositiveNegativeConstraint extends AbstractDirectiveConstraint { @@ -24,39 +18,17 @@ public AbstractPositiveNegativeConstraint(String name) { @Override public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, - ExtendedScalars.GraphQLByte, - ExtendedScalars.GraphQLShort, - Scalars.GraphQLInt, - ExtendedScalars.GraphQLLong, - ExtendedScalars.GraphQLBigDecimal, - ExtendedScalars.GraphQLBigInteger, - Scalars.GraphQLFloat - ); + return isOneOfTheseTypes(inputType, GraphQLScalars.GRAPHQL_NUMBER_TYPES); } - public List getApplicableTypeNames() { - return Stream.of(ExtendedScalars.GraphQLByte, - ExtendedScalars.GraphQLShort, - Scalars.GraphQLInt, - ExtendedScalars.GraphQLLong, - ExtendedScalars.GraphQLBigDecimal, - ExtendedScalars.GraphQLBigInteger, - Scalars.GraphQLFloat) - .map(GraphQLScalarType::getName) - .collect(toList()); + public List getApplicableTypes() { + return GraphQLScalars.GRAPHQL_NUMBER_TYPES; } @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - //null values are valid - if (validatedValue == null) { - return Collections.emptyList(); - } - - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); boolean isOK; try { @@ -67,11 +39,16 @@ protected List runConstraint(ValidationEnvironment validationEnvir } if (!isOK) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment)); + return mkError(validationEnvironment); } return Collections.emptyList(); } abstract protected boolean isOK(BigDecimal bigDecimal); + + @Override + protected boolean appliesToListElements() { + return true; + } } diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractSizeConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractSizeConstraint.java new file mode 100644 index 0000000..329ff95 --- /dev/null +++ b/src/main/java/graphql/validation/constraints/standard/AbstractSizeConstraint.java @@ -0,0 +1,33 @@ +package graphql.validation.constraints.standard; + +import graphql.GraphQLError; +import graphql.schema.GraphQLDirective; +import graphql.schema.GraphQLInputType; +import graphql.validation.constraints.AbstractDirectiveConstraint; +import graphql.validation.rules.ValidationEnvironment; +import java.util.Collections; +import java.util.List; + +public abstract class AbstractSizeConstraint extends AbstractDirectiveConstraint { + public AbstractSizeConstraint(String name) { + super(name); + } + + @Override + final protected List runConstraint(ValidationEnvironment validationEnvironment) { + Object validatedValue = validationEnvironment.getValidatedValue(); + GraphQLInputType argType = validationEnvironment.getValidatedType(); + + GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); + int min = getIntArg(directive, "min"); + int max = getIntArg(directive, "max"); + + int size = getStringOrIDOrObjectOrMapLength(argType, validatedValue); + + if (size < min || size > max) { + return mkError(validationEnvironment, "min", min, "max", max, "size", size); + } + + return Collections.emptyList(); + } +} diff --git a/src/main/java/graphql/validation/constraints/standard/AssertFalseConstraint.java b/src/main/java/graphql/validation/constraints/standard/AssertFalseConstraint.java index 5f5fb01..392b0ee 100644 --- a/src/main/java/graphql/validation/constraints/standard/AssertFalseConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AssertFalseConstraint.java @@ -14,13 +14,9 @@ public AssertFalseConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The boolean value must be false.") - .example("updateDriver( isDrunk : Boolean @AssertFalse) : DriverDetails") - - .applicableTypeNames(GraphQLBoolean.getName()) - + .applicableTypes(GraphQLBoolean) .directiveSDL("directive @AssertFalse(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) diff --git a/src/main/java/graphql/validation/constraints/standard/AssertTrueConstraint.java b/src/main/java/graphql/validation/constraints/standard/AssertTrueConstraint.java index 69a81fb..fa33d40 100644 --- a/src/main/java/graphql/validation/constraints/standard/AssertTrueConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AssertTrueConstraint.java @@ -14,13 +14,9 @@ public AssertTrueConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The boolean value must be true.") - .example("driveCar( hasLicence : Boolean @AssertTrue) : DriverDetails") - - .applicableTypeNames(GraphQLBoolean.getName()) - + .applicableTypes(GraphQLBoolean) .directiveSDL("directive @AssertTrue(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) diff --git a/src/main/java/graphql/validation/constraints/standard/ContainerNotEmptyConstraint.java b/src/main/java/graphql/validation/constraints/standard/ContainerNotEmptyConstraint.java new file mode 100644 index 0000000..e5c4fa0 --- /dev/null +++ b/src/main/java/graphql/validation/constraints/standard/ContainerNotEmptyConstraint.java @@ -0,0 +1,34 @@ +package graphql.validation.constraints.standard; + +import graphql.schema.GraphQLInputType; +import graphql.validation.constraints.Documentation; +import static graphql.schema.GraphQLTypeUtil.isList; + +public class ContainerNotEmptyConstraint extends AbstractNotEmptyRule { + public ContainerNotEmptyConstraint() { + super("ContainerNotEmpty"); + } + + @Override + public Documentation getDocumentation() { + return Documentation.newDocumentation() + .messageTemplate(getMessageTemplate()) + .description("The container must have a non-zero size") + .example("updateAccident( accidentNotes : [Notes]! @ContainerNotEmpty) : DriverDetails") + .applicableTypeNames("Lists", "Input Objects") + .directiveSDL("directive @ContainerNotEmpty(message : String = \"%s\") " + + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", + getMessageTemplate()) + .build(); + } + + @Override + public boolean appliesToType(GraphQLInputType inputType) { + return isList(inputType) || isMap(inputType); + } + + @Override + protected boolean appliesToListElements() { + return false; + } +} diff --git a/src/main/java/graphql/validation/constraints/standard/ContainerSizeConstraint.java b/src/main/java/graphql/validation/constraints/standard/ContainerSizeConstraint.java new file mode 100644 index 0000000..775221b --- /dev/null +++ b/src/main/java/graphql/validation/constraints/standard/ContainerSizeConstraint.java @@ -0,0 +1,35 @@ +package graphql.validation.constraints.standard; + +import graphql.schema.GraphQLInputType; +import graphql.validation.constraints.Documentation; +import static graphql.schema.GraphQLTypeUtil.isList; + +public class ContainerSizeConstraint extends AbstractSizeConstraint { + public ContainerSizeConstraint() { + super("ContainerSize"); + } + + @Override + public Documentation getDocumentation() { + return Documentation.newDocumentation() + .messageTemplate(getMessageTemplate()) + .description("The element size must be between the specified `min` and `max` boundaries (inclusive).") + .example("updateDrivingNotes( drivingNote : String @ContainerSize( min : 1000, max : 100000)) : DriverDetails") + .applicableTypeNames("Lists", "Input Objects") + .directiveSDL("directive @ContainerSize(min : Int = 0, max : Int = %d, message : String = \"%s\") " + + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", + Integer.MAX_VALUE, getMessageTemplate()) + .build(); + } + + + @Override + public boolean appliesToType(GraphQLInputType inputType) { + return isList(inputType) || isMap(inputType); + } + + @Override + protected boolean appliesToListElements() { + return false; + } +} diff --git a/src/main/java/graphql/validation/constraints/standard/DecimalMaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/DecimalMaxConstraint.java index fc2cd58..e06440e 100644 --- a/src/main/java/graphql/validation/constraints/standard/DecimalMaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/DecimalMaxConstraint.java @@ -12,13 +12,9 @@ public DecimalMaxConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element must be a number whose value must be less than or equal to the specified maximum.") - .example("driveCar( bloodAlcoholLevel : Float @DecimalMax(value : \"0.05\") : DriverDetails") - - .applicableTypeNames(getApplicableTypeNames()) - + .applicableTypes(getApplicableTypes()) .directiveSDL("directive @DecimalMax(value : String!, inclusive : Boolean! = true, message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) diff --git a/src/main/java/graphql/validation/constraints/standard/DecimalMinConstraint.java b/src/main/java/graphql/validation/constraints/standard/DecimalMinConstraint.java index aeb6a00..895f1cf 100644 --- a/src/main/java/graphql/validation/constraints/standard/DecimalMinConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/DecimalMinConstraint.java @@ -12,13 +12,9 @@ public DecimalMinConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element must be a number whose value must be greater than or equal to the specified minimum.") - .example("driveCar( carHorsePower : Float @DecimalMin(value : \"300.50\") : DriverDetails") - - .applicableTypeNames(getApplicableTypeNames()) - + .applicableTypes(getApplicableTypes()) .directiveSDL("directive @DecimalMin(value : String!, inclusive : Boolean! = true, message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) diff --git a/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java b/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java index c162c36..3a93513 100644 --- a/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java @@ -1,24 +1,17 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.Scalars; -import graphql.scalars.ExtendedScalars; import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLInputType; -import graphql.schema.GraphQLScalarType; import graphql.validation.constraints.AbstractDirectiveConstraint; import graphql.validation.constraints.Documentation; import graphql.validation.rules.ValidationEnvironment; - import java.math.BigDecimal; import java.util.Collections; import java.util.List; -import java.util.stream.Stream; - -import static java.util.stream.Collectors.toList; +import static graphql.validation.constraints.GraphQLScalars.GRAPHQL_NUMBER_AND_STRING_TYPES; public class DigitsConstraint extends AbstractDirectiveConstraint { - public DigitsConstraint() { super("Digits"); } @@ -27,22 +20,9 @@ public DigitsConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element must be a number inside the specified `integer` and `fraction` range.") - .example("buyCar( carCost : Float @Digits(integer : 5, fraction : 2) : DriverDetails") - - .applicableTypeNames(Stream.of(Scalars.GraphQLString, - ExtendedScalars.GraphQLByte, - ExtendedScalars.GraphQLShort, - Scalars.GraphQLInt, - ExtendedScalars.GraphQLLong, - ExtendedScalars.GraphQLBigDecimal, - ExtendedScalars.GraphQLBigInteger, - Scalars.GraphQLFloat) - .map(GraphQLScalarType::getName) - .collect(toList())) - + .applicableTypes(GRAPHQL_NUMBER_AND_STRING_TYPES) .directiveSDL("directive @Digits(integer : Int!, fraction : Int!, message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) @@ -51,25 +31,13 @@ public Documentation getDocumentation() { @Override public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, - Scalars.GraphQLString, - ExtendedScalars.GraphQLByte, - ExtendedScalars.GraphQLShort, - Scalars.GraphQLInt, - ExtendedScalars.GraphQLLong, - ExtendedScalars.GraphQLBigDecimal, - ExtendedScalars.GraphQLBigInteger, - Scalars.GraphQLFloat - ); + return isOneOfTheseTypes(inputType, GRAPHQL_NUMBER_AND_STRING_TYPES); } @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - if (validatedValue == null) { - return Collections.emptyList(); - } GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); int maxIntegerLength = getIntArg(directive, "integer"); @@ -84,17 +52,21 @@ protected List runConstraint(ValidationEnvironment validationEnvir } if (!isOk) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment, - "integer", maxIntegerLength, - "fraction", maxFractionLength)); + return mkError(validationEnvironment, "integer", maxIntegerLength, "fraction", maxFractionLength); } + return Collections.emptyList(); } private boolean isOk(BigDecimal bigNum, int maxIntegerLength, int maxFractionLength) { int integerPartLength = bigNum.precision() - bigNum.scale(); - int fractionPartLength = bigNum.scale() < 0 ? 0 : bigNum.scale(); + int fractionPartLength = Math.max(bigNum.scale(), 0); return maxIntegerLength >= integerPartLength && maxFractionLength >= fractionPartLength; } + + @Override + protected boolean appliesToListElements() { + return true; + } } diff --git a/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java b/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java index 484cdfb..f2d4d6c 100644 --- a/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java @@ -26,16 +26,12 @@ public ExpressionConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The provided expression must evaluate to true. " + "The expression language is Java EL " + "and expressions MUST resolve to a boolean value, ie. it is valid or not.") - .example("drivers( first : Int, after : String!, last : Int, before : String) \n" + " : DriverConnection @Expression(value : \"${args.containsOneOf('first','last') }\"") - .applicableTypeNames("All Types and Scalars") - .directiveSDL("directive @Expression(value : String!, message : String = \"%s\") " + "on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) @@ -57,18 +53,15 @@ protected List runConstraint(ValidationEnvironment validationEnvir GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); String expression = helpWithCurlyBraces(getStrArg(directive, "value")); - Object validatedValue = validationEnvironment.getValidatedValue(); - Map variables = StandardELVariables.standardELVars(validationEnvironment); ELSupport elSupport = new ELSupport(validationEnvironment.getLocale()); boolean isOK = elSupport.evaluateBoolean(expression, variables); if (!isOK) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment, - "value", expression)); - + return mkError(validationEnvironment,"value", expression); } + return Collections.emptyList(); } @@ -82,4 +75,9 @@ private String helpWithCurlyBraces(String expression) { } return expression; } + + @Override + protected boolean appliesToListElements() { + return false; + } } diff --git a/src/main/java/graphql/validation/constraints/standard/MaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/MaxConstraint.java index bc21d33..899c70d 100644 --- a/src/main/java/graphql/validation/constraints/standard/MaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/MaxConstraint.java @@ -12,13 +12,9 @@ public MaxConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element must be a number whose value must be less than or equal to the specified maximum.") - .example("driveCar( horsePower : Float @Max(value : 1000) : DriverDetails") - - .applicableTypeNames(getApplicableTypeNames()) - + .applicableTypes(getApplicableTypes()) .directiveSDL("directive @Max(value : Int! = %d, message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", Integer.MAX_VALUE, getMessageTemplate()) diff --git a/src/main/java/graphql/validation/constraints/standard/MinConstraint.java b/src/main/java/graphql/validation/constraints/standard/MinConstraint.java index 421abf6..ff16edb 100644 --- a/src/main/java/graphql/validation/constraints/standard/MinConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/MinConstraint.java @@ -12,13 +12,9 @@ public MinConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element must be a number whose value must be greater than or equal to the specified minimum.") - .example("driveCar( age : Int @Min(value : 18) : DriverDetails") - - .applicableTypeNames(getApplicableTypeNames()) - + .applicableTypes(getApplicableTypes()) .directiveSDL("directive @Min(value : Int! = 0, message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) diff --git a/src/main/java/graphql/validation/constraints/standard/NegativeConstraint.java b/src/main/java/graphql/validation/constraints/standard/NegativeConstraint.java index f9acd29..e074771 100644 --- a/src/main/java/graphql/validation/constraints/standard/NegativeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/NegativeConstraint.java @@ -14,13 +14,9 @@ public NegativeConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element must be a negative number.") - .example("driveCar( lostLicencePoints : Int @Negative) : DriverDetails") - - .applicableTypeNames(getApplicableTypeNames()) - + .applicableTypes(getApplicableTypes()) .directiveSDL("directive @Negative(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) diff --git a/src/main/java/graphql/validation/constraints/standard/NegativeOrZeroConstraint.java b/src/main/java/graphql/validation/constraints/standard/NegativeOrZeroConstraint.java index f61f1c1..4806470 100644 --- a/src/main/java/graphql/validation/constraints/standard/NegativeOrZeroConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/NegativeOrZeroConstraint.java @@ -14,23 +14,17 @@ public NegativeOrZeroConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element must be a negative number or zero.") - .example("driveCar( lostLicencePoints : Int @NegativeOrZero) : DriverDetails") - - .applicableTypeNames(getApplicableTypeNames()) - + .applicableTypes(getApplicableTypes()) .directiveSDL("directive @NegativeOrZero(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) .build(); } - @Override protected boolean isOK(BigDecimal bigDecimal) { return bigDecimal.compareTo(BigDecimal.ZERO) <= 0; } - } diff --git a/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java b/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java index 0a89894..3af990d 100644 --- a/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java +++ b/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java @@ -1,16 +1,11 @@ package graphql.validation.constraints.standard; -import static graphql.schema.GraphQLTypeUtil.isList; - import graphql.GraphQLError; import graphql.Scalars; -import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLInputType; import graphql.validation.constraints.AbstractDirectiveConstraint; import graphql.validation.constraints.Documentation; import graphql.validation.rules.ValidationEnvironment; - -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -24,13 +19,9 @@ public NotBlankRule() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The String must contain at least one non-whitespace character, according to Java's Character.isWhitespace().") - .example("updateAccident( accidentNotes : String @NotBlank) : DriverDetails") - .applicableTypeNames(Scalars.GraphQLString.getName(), Scalars.GraphQLID.getName(), "Lists") - .directiveSDL("directive @NotBlank(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) @@ -45,42 +36,16 @@ public boolean appliesToType(GraphQLInputType inputType) { @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - GraphQLInputType argumentType = validationEnvironment.getValidatedType(); - - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); - - if (validatedValue == null) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment)); - } - - List validatedValues; - - if (isList(argumentType)) { - validatedValues = (List)validatedValue; - } else { - validatedValues = Arrays.asList(validatedValue); - } - if (validatedValues.size() <= 0) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment)); - } - - for (Object value : validatedValues) { - if (isBlank(value)) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment)); - } + if (validatedValue.toString().trim().isEmpty()) { + return mkError(validationEnvironment); } return Collections.emptyList(); } - private boolean isBlank(Object value) { - char[] chars = value.toString().toCharArray(); - for (char c : chars) { - if (!Character.isWhitespace(c)) { - return false; - } - } + @Override + protected boolean appliesToListElements() { return true; } } diff --git a/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java b/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java index 06fe5c1..bdb3d0d 100644 --- a/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java +++ b/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java @@ -1,16 +1,10 @@ package graphql.validation.constraints.standard; -import graphql.GraphQLError; import graphql.Scalars; -import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLInputType; -import graphql.validation.constraints.AbstractDirectiveConstraint; import graphql.validation.constraints.Documentation; -import graphql.validation.rules.ValidationEnvironment; -import java.util.Collections; -import java.util.List; -public class NotEmptyRule extends AbstractDirectiveConstraint { +public class NotEmptyRule extends AbstractNotEmptyRule { public NotEmptyRule() { super("NotEmpty"); @@ -20,13 +14,9 @@ public NotEmptyRule() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - - .description("The element must have a non zero size.") - - .example("updateAccident( accidentNotes : [Notes]! @NotEmpty) : DriverDetails") - - .applicableTypeNames(Scalars.GraphQLString.getName(), Scalars.GraphQLID.getName(), "Lists", "Input Objects") - + .description("The element must have a non zero size") + .example("updateAccident( accidentNotes : [String!]! @NotEmpty) : DriverDetails") + .applicableTypeNames(Scalars.GraphQLString.getName(), Scalars.GraphQLID.getName()) .directiveSDL("directive @NotEmpty(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) @@ -35,24 +25,11 @@ public Documentation getDocumentation() { @Override public boolean appliesToType(GraphQLInputType inputType) { - return isStringOrIDOrListOrMap(inputType); + return isStringOrID(inputType); } @Override - protected List runConstraint(ValidationEnvironment validationEnvironment) { - Object validatedValue = validationEnvironment.getValidatedValue(); - GraphQLInputType argumentType = validationEnvironment.getValidatedType(); - - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); - int size = getStringOrIDOrObjectOrMapLength(argumentType, validatedValue); - - if (size <= 0) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment, - "size", size - )); - } - return Collections.emptyList(); + protected boolean appliesToListElements() { + return true; } - - } diff --git a/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java b/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java index 6a73c18..031af69 100644 --- a/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java @@ -7,14 +7,11 @@ import graphql.validation.constraints.AbstractDirectiveConstraint; import graphql.validation.constraints.Documentation; import graphql.validation.rules.ValidationEnvironment; - -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; - import static graphql.schema.GraphQLTypeUtil.isList; import static java.util.Collections.emptyList; @@ -30,13 +27,9 @@ public PatternConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The String must match the specified regular expression, which follows the Java regular expression conventions.") - .example("updateDriver( licencePlate : String @Pattern(regexp : \"[A-Z][A-Z][A-Z]-[0-9][0-9][0-9]\") : DriverDetails") - .applicableTypeNames(Scalars.GraphQLString.getName(), Scalars.GraphQLID.getName(), "Lists") - .directiveSDL("directive @Pattern(regexp : String! =\".*\", message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) @@ -51,34 +44,19 @@ public boolean appliesToType(GraphQLInputType inputType) { @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - GraphQLInputType argumentType = validationEnvironment.getValidatedType(); - if (validatedValue == null) { - return emptyList(); - } + String strValue = String.valueOf(validatedValue); - List validatedValues; + GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); - if (isList(argumentType)) { - validatedValues = (List)validatedValue; - } else { - validatedValues = Arrays.asList(validatedValue); - } + String patternArg = getStrArg(directive, "regexp"); + Pattern pattern = cachedPattern(patternArg); - for (Object value : validatedValues) { - String strValue = String.valueOf(value); - - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); - - String patternArg = getStrArg(directive, "regexp"); - Pattern pattern = cachedPattern(patternArg); - - Matcher matcher = pattern.matcher(strValue); - if (!matcher.matches()) { - return mkError(validationEnvironment, directive, - mkMessageParams(validatedValue, validationEnvironment, "regexp", patternArg)); - } + Matcher matcher = pattern.matcher(strValue); + if (!matcher.matches()) { + return mkError(validationEnvironment, "regexp", patternArg); } + return emptyList(); } @@ -86,5 +64,8 @@ private Pattern cachedPattern(String patternArg) { return SEEN_PATTERNS.computeIfAbsent(patternArg, Pattern::compile); } - + @Override + protected boolean appliesToListElements() { + return true; + } } diff --git a/src/main/java/graphql/validation/constraints/standard/PositiveConstraint.java b/src/main/java/graphql/validation/constraints/standard/PositiveConstraint.java index d48e689..93d164f 100644 --- a/src/main/java/graphql/validation/constraints/standard/PositiveConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/PositiveConstraint.java @@ -5,7 +5,6 @@ import java.math.BigDecimal; public class PositiveConstraint extends AbstractPositiveNegativeConstraint { - public PositiveConstraint() { super("Positive"); } @@ -14,13 +13,9 @@ public PositiveConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element must be a positive number.") - .example("driver( licencePoints : Int @Positive) : DriverDetails") - - .applicableTypeNames(getApplicableTypeNames()) - + .applicableTypes(getApplicableTypes()) .directiveSDL("directive @Positive(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) @@ -31,5 +26,4 @@ public Documentation getDocumentation() { protected boolean isOK(BigDecimal bigDecimal) { return bigDecimal.compareTo(BigDecimal.ZERO) > 0; } - } diff --git a/src/main/java/graphql/validation/constraints/standard/PositiveOrZeroConstraint.java b/src/main/java/graphql/validation/constraints/standard/PositiveOrZeroConstraint.java index 3f1abf7..c8961c4 100644 --- a/src/main/java/graphql/validation/constraints/standard/PositiveOrZeroConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/PositiveOrZeroConstraint.java @@ -14,13 +14,9 @@ public PositiveOrZeroConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element must be a positive number or zero.") - .example("driver( licencePoints : Int @PositiveOrZero) : DriverDetails") - - .applicableTypeNames(getApplicableTypeNames()) - + .applicableTypes(getApplicableTypes()) .directiveSDL("directive @PositiveOrZero(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) @@ -31,5 +27,4 @@ public Documentation getDocumentation() { protected boolean isOK(BigDecimal bigDecimal) { return bigDecimal.compareTo(BigDecimal.ZERO) >= 0; } - } diff --git a/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java b/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java index 17c28a8..73a412c 100644 --- a/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java @@ -1,51 +1,29 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.Scalars; -import graphql.scalars.ExtendedScalars; import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLInputType; -import graphql.schema.GraphQLScalarType; import graphql.validation.constraints.AbstractDirectiveConstraint; import graphql.validation.constraints.Documentation; import graphql.validation.rules.ValidationEnvironment; - import java.math.BigDecimal; import java.util.Collections; import java.util.List; -import java.util.stream.Stream; - -import static graphql.Scalars.GraphQLString; -import static java.util.stream.Collectors.toList; +import static graphql.validation.constraints.GraphQLScalars.GRAPHQL_NUMBER_AND_STRING_TYPES; public class RangeConstraint extends AbstractDirectiveConstraint { - public RangeConstraint() { super("Range"); } - @Override public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element range must be between the specified `min` and `max` boundaries (inclusive). It " + "accepts numbers and strings that represent numerical values.") - .example("driver( milesTravelled : Int @Range( min : 1000, max : 100000)) : DriverDetails") - - .applicableTypeNames(Stream.of(GraphQLString, - ExtendedScalars.GraphQLByte, - ExtendedScalars.GraphQLShort, - Scalars.GraphQLInt, - ExtendedScalars.GraphQLLong, - ExtendedScalars.GraphQLBigDecimal, - ExtendedScalars.GraphQLBigInteger, - Scalars.GraphQLFloat) - .map(GraphQLScalarType::getName) - .collect(toList())) - + .applicableTypes(GRAPHQL_NUMBER_AND_STRING_TYPES) .directiveSDL("directive @Range(min : Int = 0, max : Int = %d, message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", Integer.MAX_VALUE, getMessageTemplate()) @@ -54,27 +32,13 @@ Integer.MAX_VALUE, getMessageTemplate()) @Override public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, - GraphQLString, - ExtendedScalars.GraphQLByte, - ExtendedScalars.GraphQLShort, - Scalars.GraphQLInt, - ExtendedScalars.GraphQLLong, - ExtendedScalars.GraphQLBigDecimal, - ExtendedScalars.GraphQLBigInteger, - Scalars.GraphQLFloat - ); + return isOneOfTheseTypes(inputType, GRAPHQL_NUMBER_AND_STRING_TYPES); } @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { - Object validatedValue = validationEnvironment.getValidatedValue(); - //null values are valid - if (validatedValue == null) { - return Collections.emptyList(); - } GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); BigDecimal min = asBigDecimal(getIntArg(directive, "min")); @@ -89,10 +53,7 @@ protected List runConstraint(ValidationEnvironment validationEnvir } if (!isOK) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment, - "min", min, - "max", max - )); + return mkError(validationEnvironment, "min", min, "max", max); } return Collections.emptyList(); @@ -102,9 +63,12 @@ private boolean isOK(BigDecimal argBD, BigDecimal min, BigDecimal max) { if (argBD.compareTo(max) > 0) { return false; } - if (argBD.compareTo(min) < 0) { - return false; - } + + return argBD.compareTo(min) >= 0; + } + + @Override + protected boolean appliesToListElements() { return true; } } diff --git a/src/main/java/graphql/validation/constraints/standard/SizeConstraint.java b/src/main/java/graphql/validation/constraints/standard/SizeConstraint.java index 700c13d..876205e 100644 --- a/src/main/java/graphql/validation/constraints/standard/SizeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/SizeConstraint.java @@ -1,35 +1,21 @@ package graphql.validation.constraints.standard; -import graphql.GraphQLError; import graphql.Scalars; -import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLInputType; -import graphql.validation.constraints.AbstractDirectiveConstraint; import graphql.validation.constraints.Documentation; -import graphql.validation.rules.ValidationEnvironment; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -public class SizeConstraint extends AbstractDirectiveConstraint { +public class SizeConstraint extends AbstractSizeConstraint { public SizeConstraint() { super("Size"); } - @Override public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element size must be between the specified `min` and `max` boundaries (inclusive).") - .example("updateDrivingNotes( drivingNote : String @Size( min : 1000, max : 100000)) : DriverDetails") - - .applicableTypeNames(Scalars.GraphQLString.getName(), Scalars.GraphQLID.getName(), "Lists", "Input Objects") - + .applicableTypes(Scalars.GraphQLString, Scalars.GraphQLID) .directiveSDL("directive @Size(min : Int = 0, max : Int = %d, message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", Integer.MAX_VALUE, getMessageTemplate()) @@ -39,39 +25,11 @@ Integer.MAX_VALUE, getMessageTemplate()) @Override public boolean appliesToType(GraphQLInputType inputType) { - return isStringOrIDOrListOrMap(inputType); + return isStringOrID(inputType); } @Override - protected List runConstraint(ValidationEnvironment validationEnvironment) { - Object validatedValue = validationEnvironment.getValidatedValue(); - - if(validatedValue == null) { - return Collections.emptyList(); - } - - GraphQLInputType argType = validationEnvironment.getValidatedType(); - - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); - int min = getIntArg(directive, "min"); - int max = getIntArg(directive, "max"); - - int size = getStringOrIDOrObjectOrMapLength(argType, validatedValue); - - if (size < min) { - return mkError(validationEnvironment, directive, mkParams(validatedValue, validationEnvironment, min, max, size)); - } - if (size > max) { - return mkError(validationEnvironment, directive, mkParams(validatedValue, validationEnvironment, min, max, size)); - } - return Collections.emptyList(); - } - - private Map mkParams(Object validatedValue, ValidationEnvironment validationEnvironment, int min, int max, int size) { - return mkMessageParams(validatedValue, validationEnvironment, - "min", min, - "max", max, - "size", size - ); + protected boolean appliesToListElements() { + return true; } } diff --git a/src/main/resources/graphql/validation/ValidationMessages.properties b/src/main/resources/graphql/validation/ValidationMessages.properties index 0ba4dca..e5759ac 100644 --- a/src/main/resources/graphql/validation/ValidationMessages.properties +++ b/src/main/resources/graphql/validation/ValidationMessages.properties @@ -13,9 +13,11 @@ graphql.validation.Negative.message={path} must be less than 0 graphql.validation.NegativeOrZero.message={path} must be less than or equal to 0 graphql.validation.NotBlank.message={path} must not be blank graphql.validation.NotEmpty.message={path} must not be empty +graphql.validation.ContainerNotEmpty.message={path} must contain at least one element graphql.validation.Pattern.message={path} must match "{regexp}" graphql.validation.Positive.message={path} must be greater than 0 graphql.validation.PositiveOrZero.message={path} must be greater than or equal to 0 graphql.validation.Range.message={path} range must be between {min} and {max} graphql.validation.Size.message={path} size must be between {min} and {max} +graphql.validation.ContainerSize.message={path} size must be between {min} and {max} diff --git a/src/test/groovy/graphql/validation/constraints/AbstractDirectiveConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/AbstractDirectiveConstraintTest.groovy index ff17de8..0b76ddb 100644 --- a/src/test/groovy/graphql/validation/constraints/AbstractDirectiveConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/AbstractDirectiveConstraintTest.groovy @@ -1,5 +1,6 @@ package graphql.validation.constraints + import graphql.validation.constraints.standard.SizeConstraint import spock.lang.Unroll @@ -8,7 +9,7 @@ class AbstractDirectiveConstraintTest extends BaseConstraintTestSupport { @Unroll def "complex object argument constraints"() { - def extraSDL = ''' + def extraSDL = """ input ProductItem { code : String @Size(max : 5) price : String @Size(max : 3) @@ -18,9 +19,8 @@ class AbstractDirectiveConstraintTest extends BaseConstraintTestSupport { name : String @Size(max : 7) items : [ProductItem!]! # crazy nulls crazyItems : [[[ProductItem!]!]] # nuts but can we handle it - } - - ''' + } + """ // this tests that we can walk a complex tree of types via one specific implementation // but the same applies to all AbstractDirectiveConstraint classes @@ -33,18 +33,18 @@ class AbstractDirectiveConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | eSize | expectedMessage + fieldDeclaration | argVal | eSize | expectedMessage // size can handle list elements - "field( testArg : [Product!] @Size(max : 2) ) : ID" | [[:], [:], [:]] | 1 | "graphql.validation.Size.message;path=/testArg;val:[[:], [:], [:]];\t" + "field( testArg : [Product!] ) : ID" | [[:], [:], [:]] | 0 | "" // goes down into input types - "field( testArg : [Product!] @Size(max : 2) ) : ID" | [[name: "morethan7"], [:]] | 1 | "graphql.validation.Size.message;path=/testArg[0]/name;val:morethan7;\t" + "field( testArg : [Product!] ) : ID" | [[name: "morethan7"], [:]] | 1 | "graphql.validation.Size.message;path=/testArg[0]/name;val:morethan7;\t" // shows that it traverses down lists - "field( testArg : [Product!] @Size(max : 2) ) : ID" | [[name: "ok"], [name: "notOkHere"]] | 1 | "graphql.validation.Size.message;path=/testArg[1]/name;val:notOkHere;\t" + "field( testArg : [Product!] ) : ID" | [[name: "ok"], [name: "notOkHere"]] | 1 | "graphql.validation.Size.message;path=/testArg[1]/name;val:notOkHere;\t" // shows that it traverses down lists and objects - "field( testArg : [Product!] @Size(max : 2) ) : ID" | [[items: [[code: "morethan5", price: "morethan3"]]]] | 2 | "graphql.validation.Size.message;path=/testArg[0]/items[0]/code;val:morethan5;\tgraphql.validation.Size.message;path=/testArg[0]/items[0]/price;val:morethan3;\t" + "field( testArg : [Product!] ) : ID" | [[items: [[code: "morethan5", price: "morethan3"]]]] | 2 | "graphql.validation.Size.message;path=/testArg[0]/items[0]/code;val:morethan5;\tgraphql.validation.Size.message;path=/testArg[0]/items[0]/price;val:morethan3;\t" // shows that it traverses down crazy lists and objects - "field( testArg : [Product!] @Size(max : 2) ) : ID" | [[crazyItems: [[[[code: "morethan5"]]]]]] | 1 | "graphql.validation.Size.message;path=/testArg[0]/crazyItems[0][0][0]/code;val:morethan5;\t" + "field( testArg : [Product!] ) : ID" | [[crazyItems: [[[[code: "morethan5"]]]]]] | 1 | "graphql.validation.Size.message;path=/testArg[0]/crazyItems[0][0][0]/code;val:morethan5;\t" } } diff --git a/src/test/groovy/graphql/validation/constraints/DirectiveConstraintsTest.groovy b/src/test/groovy/graphql/validation/constraints/DirectiveConstraintsTest.groovy index 0b4a7d4..a8225f7 100644 --- a/src/test/groovy/graphql/validation/constraints/DirectiveConstraintsTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/DirectiveConstraintsTest.groovy @@ -12,7 +12,7 @@ class DirectiveConstraintsTest extends BaseConstraintTestSupport { def rules = DirectiveConstraints.newDirectiveConstraints().build() then: - rules.getConstraints().size() == 17 + rules.getConstraints().size() == 19 when: rules = DirectiveConstraints.newDirectiveConstraints().clearRules().build() @@ -69,8 +69,8 @@ class DirectiveConstraintsTest extends BaseConstraintTestSupport { "field( testArg : NoSizeDirectives ) : ID" | "Range" - "field( testArg : [Product!] @Size(max : 2) ) : ID" | "Size" - "field( testArg : Product! @Size(max : 2) ) : ID" | "Size" + "field( testArg : [Product!] @ContainerSize(max : 2) ) : ID" | "ContainerSize,Size" + "field( testArg : Product! @ContainerSize(max : 2) ) : ID" | "ContainerSize,Size" "field( testArg : [Product!] ) : ID" | "Size" } @@ -116,7 +116,7 @@ class DirectiveConstraintsTest extends BaseConstraintTestSupport { def declaration = directiveValidationRules.getDirectivesDeclaration() then: declaration != null - declaration.getDirectiveDefinitions().size() == 17 + declaration.getDirectiveDefinitions().size() == 19 } } diff --git a/src/test/groovy/graphql/validation/constraints/standard/AssertTrueFalseConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/AssertTrueFalseConstraintTest.groovy index 41c2166..f386f1c 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/AssertTrueFalseConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/AssertTrueFalseConstraintTest.groovy @@ -19,14 +19,19 @@ class AssertTrueFalseConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage - 'field( arg : Boolean @AssertTrue ) : ID' | false | 'AssertTrue;path=/arg;val:false;\t' - 'field( arg : Boolean @AssertTrue ): ID' | true | '' + fieldDeclaration | argVal | expectedMessage + 'field( arg : Boolean @AssertTrue ) : ID' | false | 'AssertTrue;path=/arg;val:false;\t' + 'field( arg : Boolean @AssertTrue ): ID' | true | '' - 'field( arg : Boolean @AssertTrue(message : "custom")) : ID' | false | 'custom;path=/arg;val:false;\t' + 'field( arg : Boolean @AssertTrue(message : "custom")) : ID' | false | 'custom;path=/arg;val:false;\t' // nulls are valid - 'field( arg : Boolean @AssertTrue ) : ID' | null | '' + 'field( arg : Boolean @AssertTrue ) : ID' | null | '' + + // Lists + 'field( arg : [Boolean] @AssertTrue ) : ID' | [true, true] | '' + 'field( arg : [Boolean] @AssertTrue ) : ID' | [true, false] | 'AssertTrue;path=/arg[1];val:false;\t' + 'field( arg : [Boolean] @AssertTrue ) : ID' | [null] | '' } @Unroll @@ -41,13 +46,18 @@ class AssertTrueFalseConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage - 'field( arg : Boolean @AssertFalse ) : ID' | true | 'AssertFalse;path=/arg;val:true;\t' - 'field( arg : Boolean @AssertFalse ): ID' | false | '' + fieldDeclaration | argVal | expectedMessage + 'field( arg : Boolean @AssertFalse ) : ID' | true | 'AssertFalse;path=/arg;val:true;\t' + 'field( arg : Boolean @AssertFalse ): ID' | false | '' - 'field( arg : Boolean @AssertFalse(message : "custom")) : ID' | true | 'custom;path=/arg;val:true;\t' + 'field( arg : Boolean @AssertFalse(message : "custom")) : ID' | true | 'custom;path=/arg;val:true;\t' // nulls are valid - 'field( arg : Boolean @AssertFalse ) : ID' | null | '' + 'field( arg : Boolean @AssertFalse ) : ID' | null | '' + + // Lists + 'field( arg : [Boolean] @AssertFalse ) : ID' | [false, false] | '' + 'field( arg : [Boolean] @AssertFalse ) : ID' | [false, true] | 'AssertFalse;path=/arg[1];val:true;\t' + 'field( arg : [Boolean] @AssertFalse ) : ID' | [null] | '' } } \ No newline at end of file diff --git a/src/test/groovy/graphql/validation/constraints/standard/DecimalMinMaxConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/DecimalMinMaxConstraintTest.groovy index b7cd944..31755a4 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/DecimalMinMaxConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/DecimalMinMaxConstraintTest.groovy @@ -54,6 +54,11 @@ class DecimalMinMaxConstraintTest extends BaseConstraintTestSupport { // nulls are valid 'field( arg : Int @DecimalMin(value : "50" inclusive:false) ) : ID' | null | '' + + // Lists + 'field( arg : [Int] @DecimalMin( value: "50", inclusive: false ) ) : ID' | [50] | 'DecimalMin;path=/arg[0];val:50;\t' + 'field( arg : [Int] @DecimalMin( value: "50", inclusive: false ) ) : ID' | [51, 52] | '' + 'field( arg : [Int] @DecimalMin( value: "50", inclusive: false ) ) : ID' | [null] | '' } @Unroll @@ -68,24 +73,28 @@ class DecimalMinMaxConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage - 'field( arg : Int @DecimalMax(value : "100") ) : ID' | 150 | 'DecimalMax;path=/arg;val:150;\t' - 'field( arg : Int @DecimalMax(value : "100") ) : ID' | 50 | '' + fieldDeclaration | argVal | expectedMessage + 'field( arg : Int @DecimalMax(value : "100") ) : ID' | 150 | 'DecimalMax;path=/arg;val:150;\t' + 'field( arg : Int @DecimalMax(value : "100") ) : ID' | 50 | '' - 'field( arg : Int @DecimalMax(value : "100", message : "custom") ) : ID' | 150 | 'custom;path=/arg;val:150;\t' + 'field( arg : Int @DecimalMax(value : "100", message : "custom") ) : ID' | 150 | 'custom;path=/arg;val:150;\t' // edge case - 'field( arg : Int @DecimalMax(value : "50") ) : ID' | 51 | 'DecimalMax;path=/arg;val:51;\t' - 'field( arg : Int @DecimalMax(value : "50") ) : ID' | 50 | '' - 'field( arg : Int @DecimalMax(value : "50") ) : ID' | 49 | '' + 'field( arg : Int @DecimalMax(value : "50") ) : ID' | 51 | 'DecimalMax;path=/arg;val:51;\t' + 'field( arg : Int @DecimalMax(value : "50") ) : ID' | 50 | '' + 'field( arg : Int @DecimalMax(value : "50") ) : ID' | 49 | '' // exclusive - 'field( arg : Int @DecimalMax(value : "50" inclusive:false) ) : ID' | 50 | 'DecimalMax;path=/arg;val:50;\t' - 'field( arg : Int @DecimalMax(value : "50" inclusive:false) ) : ID' | 51 | 'DecimalMax;path=/arg;val:51;\t' - 'field( arg : Int @DecimalMax(value : "50" inclusive:false) ) : ID' | 49 | '' + 'field( arg : Int @DecimalMax(value : "50" inclusive:false) ) : ID' | 50 | 'DecimalMax;path=/arg;val:50;\t' + 'field( arg : Int @DecimalMax(value : "50" inclusive:false) ) : ID' | 51 | 'DecimalMax;path=/arg;val:51;\t' + 'field( arg : Int @DecimalMax(value : "50" inclusive:false) ) : ID' | 49 | '' // nulls are valid - 'field( arg : Int @DecimalMax(value : "50" inclusive:false) ) : ID' | null | '' + 'field( arg : Int @DecimalMax(value : "50" inclusive:false) ) : ID' | null | '' + // Lists + 'field( arg : [Int] @DecimalMax( value: "50", inclusive: false ) ) : ID' | [50] | 'DecimalMax;path=/arg[0];val:50;\t' + 'field( arg : [Int] @DecimalMax( value: "50", inclusive: false ) ) : ID' | [48, 49] | '' + 'field( arg : [Int] @DecimalMax( value: "50", inclusive: false ) ) : ID' | [null] | '' } } \ No newline at end of file diff --git a/src/test/groovy/graphql/validation/constraints/standard/DigitsConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/DigitsConstraintTest.groovy index 173a142..772ec36 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/DigitsConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/DigitsConstraintTest.groovy @@ -19,33 +19,39 @@ class DigitsConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage - 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | null | '' - 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | Byte.valueOf("0") | '' - 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | Double.valueOf("500.2") | '' + fieldDeclaration | argVal | expectedMessage + 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | null | '' + 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | Byte.valueOf("0") | '' + 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | Double.valueOf("500.2") | '' - 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | new BigDecimal("-12345.12") | '' - 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | new BigDecimal("-123456.12") | 'Digits;path=/arg;val:-123456.12;\t' - 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | new BigDecimal("-123456.123") | 'Digits;path=/arg;val:-123456.123;\t' - 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | new BigDecimal("-12345.123") | 'Digits;path=/arg;val:-12345.123;\t' - 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | new BigDecimal("12345.123") | 'Digits;path=/arg;val:12345.123;\t' + 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | new BigDecimal("-12345.12") | '' + 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | new BigDecimal("-123456.12") | 'Digits;path=/arg;val:-123456.12;\t' + 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | new BigDecimal("-123456.123") | 'Digits;path=/arg;val:-123456.123;\t' + 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | new BigDecimal("-12345.123") | 'Digits;path=/arg;val:-12345.123;\t' + 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | new BigDecimal("12345.123") | 'Digits;path=/arg;val:12345.123;\t' - 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | Float.valueOf("-000000000.22") | '' + 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | Float.valueOf("-000000000.22") | '' - 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | Integer.valueOf("256874") | 'Digits;path=/arg;val:256874;\t' - 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | Double.valueOf("12.0001") | 'Digits;path=/arg;val:12.0001;\t' + 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | Integer.valueOf("256874") | 'Digits;path=/arg;val:256874;\t' + 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | Double.valueOf("12.0001") | 'Digits;path=/arg;val:12.0001;\t' // zero length - 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | null | '' - 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | Byte.valueOf("0") | 'Digits;path=/arg;val:0;\t' - 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | Double.valueOf("0") | 'Digits;path=/arg;val:0.0;\t' - 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | new BigDecimal(0) | 'Digits;path=/arg;val:0;\t' - 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | 0 | 'Digits;path=/arg;val:0;\t' - 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | 0L | 'Digits;path=/arg;val:0;\t' + 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | null | '' + 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | Byte.valueOf("0") | 'Digits;path=/arg;val:0;\t' + 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | Double.valueOf("0") | 'Digits;path=/arg;val:0.0;\t' + 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | new BigDecimal(0) | 'Digits;path=/arg;val:0;\t' + 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | 0 | 'Digits;path=/arg;val:0;\t' + 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | 0L | 'Digits;path=/arg;val:0;\t' // zeroes are trimmed - 'field( arg : String @Digits(integer : 12, fraction : 3) ) : ID' | 0.001d | '' - 'field( arg : String @Digits(integer : 12, fraction : 3) ) : ID' | 0.00100d | '' - 'field( arg : String @Digits(integer : 12, fraction : 3) ) : ID' | 0.0001d | 'Digits;path=/arg;val:1.0E-4;\t' + 'field( arg : String @Digits(integer : 12, fraction : 3) ) : ID' | 0.001d | '' + 'field( arg : String @Digits(integer : 12, fraction : 3) ) : ID' | 0.00100d | '' + 'field( arg : String @Digits(integer : 12, fraction : 3) ) : ID' | 0.0001d | 'Digits;path=/arg;val:1.0E-4;\t' + + + // Lists + 'field( arg : [String] @Digits( integer : 5, fraction : 2 ) ) : ID' | ["500.2", "343.2343"] | 'Digits;path=/arg[1];val:343.2343;\t' + 'field( arg : [String] @Digits( integer : 5, fraction : 2 ) ) : ID' | ["500.2", "343.2"] | '' + 'field( arg : [String] @Digits( integer : 5, fraction : 2 ) ) : ID' | [null] | '' } } \ No newline at end of file diff --git a/src/test/groovy/graphql/validation/constraints/standard/ExpressionConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/ExpressionConstraintTest.groovy index 30475df..835a7dc 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/ExpressionConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/ExpressionConstraintTest.groovy @@ -11,7 +11,6 @@ class ExpressionConstraintTest extends BaseConstraintTestSupport { @Unroll def "expression constraints on a field"() { - DirectiveConstraint ruleUnderTest = new ExpressionConstraint() expect: @@ -27,8 +26,8 @@ class ExpressionConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | args | expectedMessage - 'field( first : Int, last : Int) : ID ' + relayCheck | [first: 10] | "" + fieldDeclaration | args | expectedMessage + 'field( first : Int, last : Int) : ID ' + relayCheck | [first: 10] | "" 'field( first : Int, last : Int) : ID ' + relayCheck | [first: 10, last: 20] | "Expression;path=/field;val:null;\t" } diff --git a/src/test/groovy/graphql/validation/constraints/standard/MinMaxConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/MinMaxConstraintTest.groovy index eda68a3..fd6be57 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/MinMaxConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/MinMaxConstraintTest.groovy @@ -19,19 +19,23 @@ class MinMaxConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage - "field( arg : Int @Min(value : 100) ) : ID" | 50 | "Min;path=/arg;val:50;\t" - "field( arg : Int @Min(value : 10) ) : ID" | 50 | "" + fieldDeclaration | argVal | expectedMessage + "field( arg : Int @Min(value : 100) ) : ID" | 50 | "Min;path=/arg;val:50;\t" + "field( arg : Int @Min(value : 10) ) : ID" | 50 | "" - 'field( arg : Int @Min(value : 100, message : "custom") ) : ID' | 50 | "custom;path=/arg;val:50;\t" + 'field( arg : Int @Min(value : 100, message : "custom") ) : ID' | 50 | "custom;path=/arg;val:50;\t" // edge case - "field( arg : Int @Min(value : 50) ) : ID" | 49 | "Min;path=/arg;val:49;\t" - "field( arg : Int @Min(value : 50) ) : ID" | 50 | "" - "field( arg : Int @Min(value : 50) ) : ID" | 51 | "" + "field( arg : Int @Min(value : 50) ) : ID" | 49 | "Min;path=/arg;val:49;\t" + "field( arg : Int @Min(value : 50) ) : ID" | 50 | "" + "field( arg : Int @Min(value : 50) ) : ID" | 51 | "" // nulls are valid - 'field( arg : Int @Min(value : 50) ) : ID' | null | '' + 'field( arg : Int @Min(value : 50) ) : ID' | null | '' + // Lists + 'field( arg : [Int] @Min( value : 50 ) ) : ID' | [50, 49] | 'Min;path=/arg[1];val:49;\t' + 'field( arg : [Int] @Min( value : 50 ) ) : ID' | [50, 51] | '' + 'field( arg : [Int] @Min( value : 50 ) ) : ID' | [null] | '' } @Unroll @@ -46,17 +50,22 @@ class MinMaxConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage - "field( arg : Int @Max(value : 100) ) : ID" | 150 | "Max;path=/arg;val:150;\t" - "field( arg : Int @Max(value : 100) ) : ID" | 50 | "" + fieldDeclaration | argVal | expectedMessage + "field( arg : Int @Max(value : 100) ) : ID" | 150 | "Max;path=/arg;val:150;\t" + "field( arg : Int @Max(value : 100) ) : ID" | 50 | "" - 'field( arg : Int @Max(value : 100, message : "custom") ) : ID' | 150 | "custom;path=/arg;val:150;\t" + 'field( arg : Int @Max(value : 100, message : "custom") ) : ID' | 150 | "custom;path=/arg;val:150;\t" // edge case - "field( arg : Int @Max(value : 50) ) : ID" | 51 | "Max;path=/arg;val:51;\t" - "field( arg : Int @Max(value : 50) ) : ID" | 50 | "" - "field( arg : Int @Max(value : 50) ) : ID" | 49 | "" + "field( arg : Int @Max(value : 50) ) : ID" | 51 | "Max;path=/arg;val:51;\t" + "field( arg : Int @Max(value : 50) ) : ID" | 50 | "" + "field( arg : Int @Max(value : 50) ) : ID" | 49 | "" // nulls are valid - 'field( arg : Int @Max(value : 50) ) : ID' | null | '' + 'field( arg : Int @Max(value : 50) ) : ID' | null | '' + + // Lists + 'field( arg : [Int] @Max( value : 50 ) ) : ID' | [50, 51] | 'Max;path=/arg[1];val:51;\t' + 'field( arg : [Int] @Max( value : 50 ) ) : ID' | [50, 49] | '' + 'field( arg : [Int] @Max( value : 50 ) ) : ID' | [null] | '' } } \ No newline at end of file diff --git a/src/test/groovy/graphql/validation/constraints/standard/NegativePositiveConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/NegativePositiveConstraintTest.groovy index 81a0309..823853c 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/NegativePositiveConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/NegativePositiveConstraintTest.groovy @@ -19,19 +19,23 @@ class NegativePositiveConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage - "field( arg : Int @Positive) : ID" | -50 | "Positive;path=/arg;val:-50;\t" - "field( arg : Int @Positive) : ID" | 50 | "" + fieldDeclaration | argVal | expectedMessage + "field( arg : Int @Positive) : ID" | -50 | "Positive;path=/arg;val:-50;\t" + "field( arg : Int @Positive) : ID" | 50 | "" - 'field( arg : Int @Positive(message : "custom") ) : ID' | -50 | "custom;path=/arg;val:-50;\t" + 'field( arg : Int @Positive(message : "custom") ) : ID' | -50 | "custom;path=/arg;val:-50;\t" // edge case - "field( arg : Int @Positive) : ID" | 0 | "Positive;path=/arg;val:0;\t" - "field( arg : Int @Positive) : ID" | 1 | "" + "field( arg : Int @Positive) : ID" | 0 | "Positive;path=/arg;val:0;\t" + "field( arg : Int @Positive) : ID" | 1 | "" // nulls are valid - 'field( arg : Int @Positive ) : ID' | null | '' + 'field( arg : Int @Positive ) : ID' | null | '' + // Lists + 'field( arg : [Int] @Positive ) : ID' | [50, 0] | 'Positive;path=/arg[1];val:0;\t' + 'field( arg : [Int] @Positive ) : ID' | [50, 51] | '' + 'field( arg : [Int] @Positive ) : ID' | [null] | '' } @Unroll @@ -46,19 +50,24 @@ class NegativePositiveConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage - "field( arg : Int @PositiveOrZero) : ID" | -50 | "PositiveOrZero;path=/arg;val:-50;\t" - "field( arg : Int @PositiveOrZero) : ID" | 50 | "" + fieldDeclaration | argVal | expectedMessage + "field( arg : Int @PositiveOrZero) : ID" | -50 | "PositiveOrZero;path=/arg;val:-50;\t" + "field( arg : Int @PositiveOrZero) : ID" | 50 | "" - 'field( arg : Int @PositiveOrZero(message : "custom") ) : ID' | -50 | "custom;path=/arg;val:-50;\t" + 'field( arg : Int @PositiveOrZero(message : "custom") ) : ID' | -50 | "custom;path=/arg;val:-50;\t" // edge case - "field( arg : Int @PositiveOrZero) : ID" | -1 | "PositiveOrZero;path=/arg;val:-1;\t" - "field( arg : Int @PositiveOrZero) : ID" | 0 | "" - "field( arg : Int @PositiveOrZero) : ID" | 1 | "" + "field( arg : Int @PositiveOrZero) : ID" | -1 | "PositiveOrZero;path=/arg;val:-1;\t" + "field( arg : Int @PositiveOrZero) : ID" | 0 | "" + "field( arg : Int @PositiveOrZero) : ID" | 1 | "" // nulls are valid - 'field( arg : Int @PositiveOrZero ) : ID' | null | '' + 'field( arg : Int @PositiveOrZero ) : ID' | null | '' + + // Lists + 'field( arg : [Int] @PositiveOrZero ) : ID' | [50, -1] | 'PositiveOrZero;path=/arg[1];val:-1;\t' + 'field( arg : [Int] @PositiveOrZero ) : ID' | [50, 0] | '' + 'field( arg : [Int] @PositiveOrZero ) : ID' | [null] | '' } @Unroll @@ -73,18 +82,23 @@ class NegativePositiveConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage - "field( arg : Int @Negative) : ID" | 50 | "Negative;path=/arg;val:50;\t" - "field( arg : Int @Negative) : ID" | -50 | "" + fieldDeclaration | argVal | expectedMessage + "field( arg : Int @Negative) : ID" | 50 | "Negative;path=/arg;val:50;\t" + "field( arg : Int @Negative) : ID" | -50 | "" - 'field( arg : Int @Negative(message : "custom") ) : ID' | 50 | "custom;path=/arg;val:50;\t" + 'field( arg : Int @Negative(message : "custom") ) : ID' | 50 | "custom;path=/arg;val:50;\t" // edge case - "field( arg : Int @Negative) : ID" | 0 | "Negative;path=/arg;val:0;\t" - "field( arg : Int @Negative) : ID" | -1 | "" + "field( arg : Int @Negative) : ID" | 0 | "Negative;path=/arg;val:0;\t" + "field( arg : Int @Negative) : ID" | -1 | "" // nulls are valid - 'field( arg : Int @Negative ) : ID' | null | '' + 'field( arg : Int @Negative ) : ID' | null | '' + + // Lists + 'field( arg : [Int] @Negative ) : ID' | [0, -1] | 'Negative;path=/arg[0];val:0;\t' + 'field( arg : [Int] @Negative ) : ID' | [-50, -1] | '' + 'field( arg : [Int] @Negative ) : ID' | [null] | '' } @Unroll @@ -99,18 +113,23 @@ class NegativePositiveConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage - 'field( arg : Int @NegativeOrZero) : ID' | 50 | 'NegativeOrZero;path=/arg;val:50;\t' - "field( arg : Int @NegativeOrZero) : ID" | -50 | '' + fieldDeclaration | argVal | expectedMessage + 'field( arg : Int @NegativeOrZero) : ID' | 50 | 'NegativeOrZero;path=/arg;val:50;\t' + "field( arg : Int @NegativeOrZero) : ID" | -50 | '' - 'field( arg : Int @NegativeOrZero(message : "custom") ) : ID' | 50 | 'custom;path=/arg;val:50;\t' + 'field( arg : Int @NegativeOrZero(message : "custom") ) : ID' | 50 | 'custom;path=/arg;val:50;\t' // edge case - 'field( arg : Int @NegativeOrZero) : ID' | 1 | 'NegativeOrZero;path=/arg;val:1;\t' - 'field( arg : Int @NegativeOrZero) : ID' | 0 | '' - 'field( arg : Int @NegativeOrZero) : ID' | -1 | '' + 'field( arg : Int @NegativeOrZero) : ID' | 1 | 'NegativeOrZero;path=/arg;val:1;\t' + 'field( arg : Int @NegativeOrZero) : ID' | 0 | '' + 'field( arg : Int @NegativeOrZero) : ID' | -1 | '' // nulls are valid - 'field( arg : Int @NegativeOrZero ) : ID' | null | '' + 'field( arg : Int @NegativeOrZero ) : ID' | null | '' + + // Lists + 'field( arg : [Int] @NegativeOrZero ) : ID' | [1, -1] | 'NegativeOrZero;path=/arg[0];val:1;\t' + 'field( arg : [Int] @NegativeOrZero ) : ID' | [0, -1] | '' + 'field( arg : [Int] @NegativeOrZero ) : ID' | [null] | '' } } \ No newline at end of file diff --git a/src/test/groovy/graphql/validation/constraints/standard/NoEmptyBlankConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/NoEmptyBlankConstraintTest.groovy index 0dd29d2..ce2c668 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/NoEmptyBlankConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/NoEmptyBlankConstraintTest.groovy @@ -9,7 +9,6 @@ class NoEmptyBlankConstraintTest extends BaseConstraintTestSupport { @Unroll def "not blank rule constraints"() { - DirectiveConstraint ruleUnderTest = new NotBlankRule() expect: @@ -19,35 +18,34 @@ class NoEmptyBlankConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage + fieldDeclaration | argVal | expectedMessage // strings - 'field( arg : String @NotBlank ) : ID' | "\t\n\r " | 'NotBlank;path=/arg;val:\t\n\r ;\t' - 'field( arg : String @NotBlank ) : ID' | "" | 'NotBlank;path=/arg;val:;\t' - 'field( arg : String @NotBlank ) : ID' | "\t\n\r X" | '' - 'field( arg : String @NotBlank ) : ID' | null | '' + 'field( arg : String @NotBlank ) : ID' | "\t\n\r " | 'NotBlank;path=/arg;val:\t\n\r ;\t' + 'field( arg : String @NotBlank ) : ID' | "" | 'NotBlank;path=/arg;val:;\t' + 'field( arg : String @NotBlank ) : ID' | "\t\n\r X" | '' + 'field( arg : String @NotBlank ) : ID' | null | '' // IDs - 'field( arg : ID @NotBlank ) : ID' | "\t\n\r " | 'NotBlank;path=/arg;val:\t\n\r ;\t' - 'field( arg : ID @NotBlank ) : ID' | "" | 'NotBlank;path=/arg;val:;\t' - 'field( arg : ID @NotBlank ) : ID' | "\t\n\r X" | '' - 'field( arg : ID @NotBlank ) : ID' | null | '' + 'field( arg : ID @NotBlank ) : ID' | "\t\n\r " | 'NotBlank;path=/arg;val:\t\n\r ;\t' + 'field( arg : ID @NotBlank ) : ID' | "" | 'NotBlank;path=/arg;val:;\t' + 'field( arg : ID @NotBlank ) : ID' | "\t\n\r X" | '' + 'field( arg : ID @NotBlank ) : ID' | null | '' // Lists - 'field( arg : [String] @NotBlank ) : ID' | [] | 'NotBlank;path=/arg;val:[];\t' - 'field( arg : [String] @NotBlank ) : ID' | null | '' - 'field( arg : [String] @NotBlank ) : ID' | ["x"] | '' - 'field( arg : [String] @NotBlank ) : ID' | ["x", "y"] | '' - 'field( arg : [String] @NotBlank ) : ID' | ["x", " "] | 'NotBlank;path=/arg;val:[x, ];\t' - 'field( arg : [ID] @NotBlank ) : ID' | [] | 'NotBlank;path=/arg;val:[];\t' - 'field( arg : [ID] @NotBlank ) : ID' | null | '' - 'field( arg : [ID] @NotBlank ) : ID' | ["x"] | '' - 'field( arg : [ID] @NotBlank ) : ID' | ["x", "y"] | '' - 'field( arg : [String] @NotBlank ) : ID' | ["x", " "] | 'NotBlank;path=/arg;val:[x, ];\t' + 'field( arg : [String] @NotBlank ) : ID' | [] | '' + 'field( arg : [String] @NotBlank ) : ID' | null | '' + 'field( arg : [String] @NotBlank ) : ID' | ["x"] | '' + 'field( arg : [String] @NotBlank ) : ID' | ["x", "y"] | '' + 'field( arg : [String] @NotBlank ) : ID' | ["x", " "] | 'NotBlank;path=/arg[1];val: ;\t' + 'field( arg : [ID] @NotBlank ) : ID' | [] | '' + 'field( arg : [ID] @NotBlank ) : ID' | null | '' + 'field( arg : [ID] @NotBlank ) : ID' | ["x"] | '' + 'field( arg : [ID] @NotBlank ) : ID' | ["x", "y"] | '' + 'field( arg : [String] @NotBlank ) : ID' | ["x", " "] | 'NotBlank;path=/arg[1];val: ;\t' } @Unroll def "not empty rule constraints"() { - DirectiveConstraint ruleUnderTest = new NotEmptyRule() expect: @@ -65,10 +63,10 @@ class NoEmptyBlankConstraintTest extends BaseConstraintTestSupport { 'field( arg : String @NotEmpty ) : ID' | "ABC" | '' // IDs - 'field( arg : ID @NotEmpty ) : ID' | "" | 'NotEmpty;path=/arg;val:;\t' - 'field( arg : ID @NotEmpty ) : ID' | null | '' - 'field( arg : ID @NotEmpty ) : ID' | "\t\n\r" | '' - 'field( arg : ID @NotEmpty ) : ID' | "ABC" | '' + 'field( arg : ID @NotEmpty ) : ID' | "" | 'NotEmpty;path=/arg;val:;\t' + 'field( arg : ID @NotEmpty ) : ID' | null | '' + 'field( arg : ID @NotEmpty ) : ID' | "\t\n\r" | '' + 'field( arg : ID @NotEmpty ) : ID' | "ABC" | '' // objects @@ -77,13 +75,42 @@ class NoEmptyBlankConstraintTest extends BaseConstraintTestSupport { 'field( arg : InputObject @NotEmpty ) : ID' | [name: "x"] | '' // lists - 'field( arg : [String] @NotEmpty ) : ID' | [] | 'NotEmpty;path=/arg;val:[];\t' + 'field( arg : [String] @NotEmpty ) : ID' | [] | '' // Validated by @ContainerNotEmpty 'field( arg : [String] @NotEmpty ) : ID' | null | '' 'field( arg : [String] @NotEmpty ) : ID' | ["x"] | '' - 'field( arg : [ID] @NotEmpty ) : ID' | [] | 'NotEmpty;path=/arg;val:[];\t' - 'field( arg : [ID] @NotEmpty ) : ID' | null | '' - 'field( arg : [ID] @NotEmpty ) : ID' | ["x"] | '' + 'field( arg : [String] @NotEmpty ) : ID' | ["\t"] | '' + 'field( arg : [String] @NotEmpty ) : ID' | [""] | 'NotEmpty;path=/arg[0];val:;\t' + 'field( arg : [ID] @NotEmpty ) : ID' | [] | '' + 'field( arg : [ID] @NotEmpty ) : ID' | null | '' + 'field( arg : [ID] @NotEmpty ) : ID' | ["x"] | '' + 'field( arg : [ID] @NotEmpty ) : ID' | ["\t"] | '' + 'field( arg : [ID] @NotEmpty ) : ID' | [""] | 'NotEmpty;path=/arg[0];val:;\t' + } + + @Unroll + def "container not empty rule constraints"() { + + DirectiveConstraint ruleUnderTest = new ContainerNotEmptyConstraint() + + expect: + def errors = runValidation(ruleUnderTest, fieldDeclaration, "arg", argVal) + assertErrors(errors, expectedMessage) + + where: + + fieldDeclaration | argVal | expectedMessage + // lists + 'field( arg : [String] @ContainerNotEmpty ) : ID' | [] | 'ContainerNotEmpty;path=/arg;val:[];\t' + 'field( arg : [String] @ContainerNotEmpty ) : ID' | null | '' + 'field( arg : [String] @ContainerNotEmpty ) : ID' | ["x"] | '' + 'field( arg : [String] @ContainerNotEmpty ) : ID' | ["\t"] | '' + 'field( arg : [String] @ContainerNotEmpty ) : ID' | [""] | '' + 'field( arg : [ID] @ContainerNotEmpty ) : ID' | [] | 'ContainerNotEmpty;path=/arg;val:[];\t' + 'field( arg : [ID] @ContainerNotEmpty ) : ID' | null | '' + 'field( arg : [ID] @ContainerNotEmpty ) : ID' | ["x"] | '' + 'field( arg : [ID] @ContainerNotEmpty ) : ID' | ["\t"] | '' + 'field( arg : [ID] @ContainerNotEmpty ) : ID' | [""] | '' } } \ No newline at end of file diff --git a/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy index 56bae51..3e932a4 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy @@ -24,10 +24,12 @@ class PatternConstraintTest extends BaseConstraintTestSupport { 'field( arg : String @Pattern(regexp:"[A-Z]*") ) : ID' | "ABC" | '' 'field( arg : ID @Pattern(regexp:"[A-Z]*") ) : ID' | "ABCd" | 'Pattern;path=/arg;val:ABCd;\t' 'field( arg : ID @Pattern(regexp:"[A-Z]*") ) : ID' | "ABC" | '' + + // Lists 'field( arg : [String] @Pattern(regexp:"[A-Z]*") ) : ID' | ["ABC"] | '' - 'field( arg : [String] @Pattern(regexp:"[A-Z]*") ) : ID' | ["ABC", "ABCd"] | 'Pattern;path=/arg;val:[ABC, ABCd];\t' + 'field( arg : [String] @Pattern(regexp:"[A-Z]*") ) : ID' | ["ABC", "ABCd"] | 'Pattern;path=/arg[1];val:ABCd;\t' 'field( arg : [ID] @Pattern(regexp:"[A-Z]*") ) : ID' | ["ABC"] | '' - 'field( arg : [ID] @Pattern(regexp:"[A-Z]*") ) : ID' | ["ABC", "ABCd"] | 'Pattern;path=/arg;val:[ABC, ABCd];\t' + 'field( arg : [ID] @Pattern(regexp:"[A-Z]*") ) : ID' | ["ABC", "ABCd"] | 'Pattern;path=/arg[1];val:ABCd;\t' // nulls are valid 'field( arg : String @Pattern(regexp:"[A-Z]*") ) : ID' | null | '' diff --git a/src/test/groovy/graphql/validation/constraints/standard/RangeConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/RangeConstraintTest.groovy index 3c618f0..e47791d 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/RangeConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/RangeConstraintTest.groovy @@ -33,5 +33,10 @@ class RangeConstraintTest extends BaseConstraintTestSupport { "field( arg : String @Range(max : 10) ) : ID" | Long.valueOf("12") | "Range;path=/arg;val:12;\t" "field( arg : String @Range(max : 10) ) : ID" | Integer.valueOf("12") | "Range;path=/arg;val:12;\t" "field( arg : String @Range(max : 10) ) : ID" | Short.valueOf("12") | "Range;path=/arg;val:12;\t" + + // Lists + 'field( arg : [String] @Range(max : 10) ) : ID' | [50, 0] | 'Range;path=/arg[0];val:50;\t' + 'field( arg : [String] @Range(max : 10) ) : ID' | [9, 8] | '' + 'field( arg : [String] @Range(max : 10) ) : ID' | [null] | '' } } \ No newline at end of file diff --git a/src/test/groovy/graphql/validation/constraints/standard/SizeConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/SizeConstraintTest.groovy index b34f24a..a67ae2e 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/SizeConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/SizeConstraintTest.groovy @@ -5,11 +5,8 @@ import graphql.validation.constraints.DirectiveConstraint import spock.lang.Unroll class SizeConstraintTest extends BaseConstraintTestSupport { - - @Unroll def "size rule constraints"() { - DirectiveConstraint ruleUnderTest = new SizeConstraint() expect: @@ -28,12 +25,36 @@ class SizeConstraintTest extends BaseConstraintTestSupport { 'field( arg : String @Size(min : 5, message : "custom") ) : ID' | "123" | "custom;path=/arg;val:123;\t" "field( arg : String @Size(min : 5) ) : ID" | null | "" - //IDs - "field( arg : ID @Size(max : 10) ) : ID" | "1234567891011" | "Size;path=/arg;val:1234567891011;\t" - "field( arg : ID @Size(max : 100) ) : ID" | "1234567891011" | "" - "field( arg : ID @Size(max : 10, min : 5) ) : ID" | "123" | "Size;path=/arg;val:123;\t" + // IDs + "field( arg : ID @Size(max : 10) ) : ID" | "1234567891011" | "Size;path=/arg;val:1234567891011;\t" + "field( arg : ID @Size(max : 100) ) : ID" | "1234567891011" | "" + "field( arg : ID @Size(max : 10, min : 5) ) : ID" | "123" | "Size;path=/arg;val:123;\t" + + 'field( arg : ID @Size(min : 5, message : "custom") ) : ID' | "123" | "custom;path=/arg;val:123;\t" + "field( arg : ID @Size(min : 5) ) : ID" | null | "" + + // Lists + 'field (arg : [String] @Size(min: 5)) : ID' | [] | "" // Validated by @ContainerSize + } + + @Unroll + def "container size rule constraints"() { + DirectiveConstraint ruleUnderTest = new ContainerSizeConstraint() + + expect: + + def errors = runValidation(ruleUnderTest, fieldDeclaration, "arg", argVal) + assertErrors(errors, expectedMessage) + + where: - 'field( arg : ID @Size(min : 5, message : "custom") ) : ID' | "123" | "custom;path=/arg;val:123;\t" - "field( arg : ID @Size(min : 5) ) : ID" | null | "" + fieldDeclaration | argVal | expectedMessage + // Lists + 'field (arg : [String] @ContainerSize(min: 2)) : ID' | [] | "ContainerSize;path=/arg;val:[];\t" + 'field (arg : [String] @ContainerSize(max: 1)) : ID' | ["asd", "sdf"] | "ContainerSize;path=/arg;val:[asd, sdf];\t" + 'field (arg : [String] @ContainerSize(min: 2)) : ID' | ["a", "b"] | "" + 'field (arg : [String] @ContainerSize(max: 2)) : ID' | ["a", "b"] | "" + 'field (arg : [String] @ContainerSize(max: 5)) : ID' | [] | "" + 'field (arg : [String] @ContainerSize(min: 2)) : ID' | null | "" } } \ No newline at end of file