From be31fff3b535a8cc60d6b9791bccf058e9144fe9 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Sat, 19 Sep 2020 10:02:41 -0400 Subject: [PATCH] Adding support for ID scalar type to Pattern, NotEmpty, NotBlank and Size constraints as per https://github.com/graphql-java/graphql-java-extended-validation/issues/21 --- README.md | 10 +++---- .../AbstractDirectiveConstraint.java | 26 ++++++++++++++----- .../constraints/standard/NotBlankRule.java | 4 +-- .../constraints/standard/NotEmptyRule.java | 7 +++-- .../standard/PatternConstraint.java | 5 ++-- .../constraints/standard/SizeConstraint.java | 6 ++--- .../NoEmptyBlankConstraintTest.groovy | 19 ++++++++++++-- .../standard/PatternConstraintTest.groovy | 4 +++ .../standard/SizeConstraintTest.groovy | 9 +++++++ 9 files changed, 64 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 5f08b08..80257dc 100644 --- a/README.md +++ b/README.md @@ -386,7 +386,7 @@ The String must contain at least one non-whitespace character, according to Java - Example : `updateAccident( accidentNotes : String @NotBlank) : DriverDetails` -- Applies to : `String` +- Applies to : `String`, `ID` - SDL : `directive @NotBlank(message : String = "graphql.validation.NotBlank.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` @@ -399,7 +399,7 @@ The element must have a non zero size. - Example : `updateAccident( accidentNotes : [Notes]! @NotEmpty) : DriverDetails` -- Applies to : `String`, `Lists`, `Input Objects` +- Applies to : `String`, `ID`, `Lists`, `Input Objects` - SDL : `directive @NotEmpty(message : String = "graphql.validation.NotEmpty.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` @@ -410,9 +410,9 @@ The element must have a non zero size. The String must match the specified regular expression, which follows the Java regular expression conventions. -- Example : `updateDriver( licencePlate : String @Patttern(regex : "[A-Z][A-Z][A-Z]-[0-9][0-9][0-9]") : DriverDetails` +- Example : `updateDriver( licencePlate : String @Pattern(regexp : "[A-Z][A-Z][A-Z]-[0-9][0-9][0-9]") : DriverDetails` -- Applies to : `String`, `Lists` +- Applies to : `String`, `ID`, `Lists` - SDL : `directive @Pattern(regexp : String! =".*", message : String = "graphql.validation.Pattern.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` @@ -464,7 +464,7 @@ The element size must be between the specified `min` and `max` boundaries (inclu - Example : `updateDrivingNotes( drivingNote : String @Size( min : 1000, max : 100000)) : DriverDetails` -- Applies to : `String`, `Lists`, `Input Objects` +- Applies to : `String`, `ID`, `Lists`, `Input Objects` - SDL : `directive @Size(min : Int = 0, max : Int = 2147483647, message : String = "graphql.validation.Size.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` diff --git a/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java b/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java index 984456a..54e8442 100644 --- a/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java +++ b/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java @@ -161,7 +161,7 @@ private List runConstraintOnDirectives(ValidationEnvironment valid * @param inputType the type to check * @param scalarTypes the array of scalar types * - * @return true ifits oneof them + * @return true if its one of them */ protected boolean isOneOfTheseTypes(GraphQLInputType inputType, GraphQLScalarType... scalarTypes) { GraphQLInputType type = Util.unwrapNonNull(inputType); @@ -304,19 +304,31 @@ protected List mkError(ValidationEnvironment validationEnvironment } /** - * Return true if the type is a String or List type or {@link graphql.schema.GraphQLInputObjectType}, regardless of non null ness + * 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 isStringOrListOrMap(GraphQLInputType inputType) { + protected boolean isStringOrIDOrListOrMap(GraphQLInputType inputType) { GraphQLInputType unwrappedType = Util.unwrapOneAndAllNonNull(inputType); - return Scalars.GraphQLString.equals(unwrappedType) || + return isStringOrID(inputType) || isList(inputType) || (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) { + GraphQLInputType unwrappedType = Util.unwrapNonNull(inputType); + return Scalars.GraphQLString.equals(unwrappedType) || Scalars.GraphQLID.equals(unwrappedType); + } + /** * Casts the object as a Map with an assertion of it is not one * @@ -374,18 +386,18 @@ protected boolean asBoolean(Object value) { } /** - * Returns the length of a String of the size of a list or size of a Map + * Returns the length of a String, ID, size of a list or size of a Map * * @param inputType the input type * @param value the value * * @return the length of a String or Map or List */ - protected int getStringOrObjectOrMapLength(GraphQLInputType inputType, Object value) { + protected int getStringOrIDOrObjectOrMapLength(GraphQLInputType inputType, Object value) { int valLen; if (value == null) { valLen = 0; - } else if (Scalars.GraphQLString.equals(Util.unwrapNonNull(inputType))) { + } else if (isStringOrID(inputType)) { valLen = String.valueOf(value).length(); } else if (isList(inputType)) { valLen = getListLength(value); diff --git a/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java b/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java index f36a21e..d63da23 100644 --- a/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java +++ b/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java @@ -26,7 +26,7 @@ public Documentation getDocumentation() { .example("updateAccident( accidentNotes : String @NotBlank) : DriverDetails") - .applicableTypeNames(Scalars.GraphQLString.getName()) + .applicableTypeNames(Scalars.GraphQLString.getName(), Scalars.GraphQLID.getName()) .directiveSDL("directive @NotBlank(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", @@ -36,7 +36,7 @@ public Documentation getDocumentation() { @Override public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, Scalars.GraphQLString); + return isStringOrID(inputType); } @Override diff --git a/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java b/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java index 999998d..06fe5c1 100644 --- a/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java +++ b/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java @@ -7,7 +7,6 @@ import graphql.validation.constraints.AbstractDirectiveConstraint; import graphql.validation.constraints.Documentation; import graphql.validation.rules.ValidationEnvironment; - import java.util.Collections; import java.util.List; @@ -26,7 +25,7 @@ public Documentation getDocumentation() { .example("updateAccident( accidentNotes : [Notes]! @NotEmpty) : DriverDetails") - .applicableTypeNames(Scalars.GraphQLString.getName(), "Lists", "Input Objects") + .applicableTypeNames(Scalars.GraphQLString.getName(), Scalars.GraphQLID.getName(), "Lists", "Input Objects") .directiveSDL("directive @NotEmpty(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", @@ -36,7 +35,7 @@ public Documentation getDocumentation() { @Override public boolean appliesToType(GraphQLInputType inputType) { - return isStringOrListOrMap(inputType); + return isStringOrIDOrListOrMap(inputType); } @Override @@ -45,7 +44,7 @@ protected List runConstraint(ValidationEnvironment validationEnvir GraphQLInputType argumentType = validationEnvironment.getValidatedType(); GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); - int size = getStringOrObjectOrMapLength(argumentType, validatedValue); + int size = getStringOrIDOrObjectOrMapLength(argumentType, validatedValue); if (size <= 0) { return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment, diff --git a/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java b/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java index 32f5830..6a73c18 100644 --- a/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java @@ -35,7 +35,7 @@ public Documentation getDocumentation() { .example("updateDriver( licencePlate : String @Pattern(regexp : \"[A-Z][A-Z][A-Z]-[0-9][0-9][0-9]\") : DriverDetails") - .applicableTypeNames(Scalars.GraphQLString.getName(), "Lists") + .applicableTypeNames(Scalars.GraphQLString.getName(), Scalars.GraphQLID.getName(), "Lists") .directiveSDL("directive @Pattern(regexp : String! =\".*\", message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", @@ -45,8 +45,7 @@ public Documentation getDocumentation() { @Override public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, - Scalars.GraphQLString) || isList(inputType); + return isStringOrID(inputType) || isList(inputType); } @Override diff --git a/src/main/java/graphql/validation/constraints/standard/SizeConstraint.java b/src/main/java/graphql/validation/constraints/standard/SizeConstraint.java index 58a14a3..5027f12 100644 --- a/src/main/java/graphql/validation/constraints/standard/SizeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/SizeConstraint.java @@ -28,7 +28,7 @@ public Documentation getDocumentation() { .example("updateDrivingNotes( drivingNote : String @Size( min : 1000, max : 100000)) : DriverDetails") - .applicableTypeNames(Scalars.GraphQLString.getName(), "Lists", "Input Objects") + .applicableTypeNames(Scalars.GraphQLString.getName(), Scalars.GraphQLID.getName(), "Lists", "Input Objects") .directiveSDL("directive @Size(min : Int = 0, max : Int = %d, message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", @@ -39,7 +39,7 @@ Integer.MAX_VALUE, getMessageTemplate()) @Override public boolean appliesToType(GraphQLInputType inputType) { - return isStringOrListOrMap(inputType); + return isStringOrIDOrListOrMap(inputType); } @Override @@ -52,7 +52,7 @@ protected List runConstraint(ValidationEnvironment validationEnvir int max = getIntArg(directive, "max"); - int size = getStringOrObjectOrMapLength(argType, validatedValue); + int size = getStringOrIDOrObjectOrMapLength(argType, validatedValue); if (size < min) { return mkError(validationEnvironment, directive, mkParams(validatedValue, validationEnvironment, min, max, size)); diff --git a/src/test/groovy/graphql/validation/constraints/standard/NoEmptyBlankConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/NoEmptyBlankConstraintTest.groovy index b65ad81..a23851f 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/NoEmptyBlankConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/NoEmptyBlankConstraintTest.groovy @@ -20,12 +20,17 @@ class NoEmptyBlankConstraintTest extends BaseConstraintTestSupport { where: 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" | '' - - // nulls are INVALID 'field( arg : String @NotBlank ) : ID' | null | 'NotBlank;path=/arg;val:null;\t' + + // 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 | 'NotBlank;path=/arg;val:null;\t' } @Unroll @@ -47,6 +52,13 @@ class NoEmptyBlankConstraintTest extends BaseConstraintTestSupport { 'field( arg : String @NotEmpty ) : ID' | "\t\n\r" | '' 'field( arg : String @NotEmpty ) : ID' | "ABC" | '' + // IDs + 'field( arg : ID @NotEmpty ) : ID' | "" | 'NotEmpty;path=/arg;val:;\t' + 'field( arg : ID @NotEmpty ) : ID' | null | 'NotEmpty;path=/arg;val:null;\t' + 'field( arg : ID @NotEmpty ) : ID' | "\t\n\r" | '' + 'field( arg : ID @NotEmpty ) : ID' | "ABC" | '' + + // objects 'field( arg : InputObject @NotEmpty ) : ID' | [:] | 'NotEmpty;path=/arg;val:[:];\t' 'field( arg : InputObject @NotEmpty ) : ID' | null | 'NotEmpty;path=/arg;val:null;\t' @@ -56,6 +68,9 @@ class NoEmptyBlankConstraintTest extends BaseConstraintTestSupport { 'field( arg : [String] @NotEmpty ) : ID' | [] | 'NotEmpty;path=/arg;val:[];\t' 'field( arg : [String] @NotEmpty ) : ID' | null | 'NotEmpty;path=/arg;val:null;\t' 'field( arg : [String] @NotEmpty ) : ID' | ["x"] | '' + 'field( arg : [ID] @NotEmpty ) : ID' | [] | 'NotEmpty;path=/arg;val:[];\t' + 'field( arg : [ID] @NotEmpty ) : ID' | null | 'NotEmpty;path=/arg;val:null;\t' + 'field( arg : [ID] @NotEmpty ) : ID' | ["x"] | '' } diff --git a/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy index d46d02d..56bae51 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy @@ -22,8 +22,12 @@ class PatternConstraintTest extends BaseConstraintTestSupport { fieldDeclaration | argVal | expectedMessage 'field( arg : String @Pattern(regexp:"[A-Z]*") ) : ID' | "ABCd" | 'Pattern;path=/arg;val:ABCd;\t' '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" | '' '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 : [ID] @Pattern(regexp:"[A-Z]*") ) : ID' | ["ABC"] | '' + 'field( arg : [ID] @Pattern(regexp:"[A-Z]*") ) : ID' | ["ABC", "ABCd"] | 'Pattern;path=/arg;val:[ABC, ABCd];\t' // nulls are valid 'field( arg : String @Pattern(regexp:"[A-Z]*") ) : ID' | null | '' diff --git a/src/test/groovy/graphql/validation/constraints/standard/SizeConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/SizeConstraintTest.groovy index a17ff43..3c9316a 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/SizeConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/SizeConstraintTest.groovy @@ -20,11 +20,20 @@ class SizeConstraintTest extends BaseConstraintTestSupport { where: fieldDeclaration | argVal | expectedMessage + // strings "field( arg : String @Size(max : 10) ) : ID" | "1234567891011" | "Size;path=/arg;val:1234567891011;\t" "field( arg : String @Size(max : 100) ) : ID" | "1234567891011" | "" "field( arg : String @Size(max : 10, min : 5) ) : ID" | "123" | "Size;path=/arg;val:123;\t" 'field( arg : String @Size(min : 5, message : "custom") ) : ID' | "123" | "custom;path=/arg;val:123;\t" "field( arg : String @Size(min : 5) ) : ID" | null | "Size;path=/arg;val:null;\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 | "Size;path=/arg;val:null;\t" } } \ No newline at end of file