From 7ad838df9952fddd3bb3fadec3807833f242c51d Mon Sep 17 00:00:00 2001 From: John power Date: Tue, 29 Sep 2020 08:46:15 -0400 Subject: [PATCH 01/20] fix schema wiring --- .../FieldValidatorDataFetcher.java | 79 +++++++++++++++++++ .../schemawiring/ValidationSchemaWiring.java | 44 ++++------- 2 files changed, 94 insertions(+), 29 deletions(-) create mode 100644 src/main/java/graphql/validation/schemawiring/FieldValidatorDataFetcher.java diff --git a/src/main/java/graphql/validation/schemawiring/FieldValidatorDataFetcher.java b/src/main/java/graphql/validation/schemawiring/FieldValidatorDataFetcher.java new file mode 100644 index 0000000..a02836a --- /dev/null +++ b/src/main/java/graphql/validation/schemawiring/FieldValidatorDataFetcher.java @@ -0,0 +1,79 @@ +package graphql.validation.schemawiring; + +import graphql.GraphQLError; +import graphql.schema.*; +import graphql.validation.interpolation.MessageInterpolator; +import graphql.validation.rules.OnValidationErrorStrategy; +import graphql.validation.rules.TargetedValidationRules; +import graphql.validation.rules.ValidationRule; +import graphql.validation.rules.ValidationRules; +import graphql.validation.util.Util; + +import java.util.List; +import java.util.Locale; + +public class FieldValidatorDataFetcher implements DataFetcher { + private final OnValidationErrorStrategy errorStrategy; + private final MessageInterpolator messageInterpolator; + private final DataFetcher defaultDataFetcher; + private final Locale defaultLocale; + private final ValidationRules validationRules; + private TargetedValidationRules applicableRules; + + public FieldValidatorDataFetcher(OnValidationErrorStrategy errorStrategy, + MessageInterpolator messageInterpolator, + DataFetcher defaultDataFetcher, + Locale defaultLocale, + ValidationRules validationRules) { + this.errorStrategy = errorStrategy; + this.messageInterpolator = messageInterpolator; + this.defaultDataFetcher = defaultDataFetcher; + this.defaultLocale = defaultLocale; + this.validationRules = validationRules; + this.applicableRules = null; + } + + @Override + public Object get(DataFetchingEnvironment environment) throws Exception { + if (!wereApplicableRulesFetched()) { + fetchApplicableRules(environment); + } + + // When no validation is performed, this data fetcher is a pass-through + if (applicableRules.isEmpty()) { + return defaultDataFetcher.get(environment); + } + + List errors = applicableRules.runValidationRules(environment, messageInterpolator, defaultLocale); + if (!errors.isEmpty()) { + if (!errorStrategy.shouldContinue(errors, environment)) { + return errorStrategy.onErrorValue(errors, environment); + } + } + + Object returnValue = defaultDataFetcher.get(environment); + if (errors.isEmpty()) { + return returnValue; + } + return Util.mkDFRFromFetchedResult(errors, returnValue); + } + + private void fetchApplicableRules(DataFetchingEnvironment environment) { + final GraphQLFieldDefinition field = environment.getFieldDefinition(); + final GraphQLFieldsContainer container = asContainer(environment); + + applicableRules = validationRules.buildRulesFor(field, container); + } + + private GraphQLFieldsContainer asContainer(DataFetchingEnvironment environment) { + final GraphQLType parent = environment.getParentType(); + if (parent == null) { + return null; + } + return (GraphQLFieldsContainer) environment.getParentType(); + } + + private boolean wereApplicableRulesFetched() { + return applicableRules != null; + } +} diff --git a/src/main/java/graphql/validation/schemawiring/ValidationSchemaWiring.java b/src/main/java/graphql/validation/schemawiring/ValidationSchemaWiring.java index 26d9c97..36cf6ba 100644 --- a/src/main/java/graphql/validation/schemawiring/ValidationSchemaWiring.java +++ b/src/main/java/graphql/validation/schemawiring/ValidationSchemaWiring.java @@ -1,6 +1,5 @@ package graphql.validation.schemawiring; -import graphql.GraphQLError; import graphql.PublicApi; import graphql.schema.DataFetcher; import graphql.schema.GraphQLFieldDefinition; @@ -11,17 +10,15 @@ import graphql.validation.rules.OnValidationErrorStrategy; import graphql.validation.rules.TargetedValidationRules; import graphql.validation.rules.ValidationRules; -import graphql.validation.util.Util; -import java.util.List; import java.util.Locale; /** - * A {@link graphql.schema.idl.SchemaDirectiveWiring} that can be used to inject validation rules into the data fetchers + * A {@link SchemaDirectiveWiring} that can be used to inject validation rules into the data fetchers * when the graphql schema is being built. It will use the validation rules and ask each one of they apply to the field and or its * arguments. *

- * If there are rules that apply then it will it will change the {@link graphql.schema.DataFetcher} of that field so that rules get run + * If there are rules that apply then it will it will change the {@link DataFetcher} of that field so that rules get run * BEFORE the original field fetch is run. */ @PublicApi @@ -38,40 +35,29 @@ public GraphQLFieldDefinition onField(SchemaDirectiveWiringEnvironment currentDF = env.getCodeRegistry().getDataFetcher(fieldsContainer, fieldDefinition); + final DataFetcher newDF = buildValidatingDataFetcher(errorStrategy, messageInterpolator, currentDF, locale); env.getCodeRegistry().dataFetcher(fieldsContainer, fieldDefinition, newDF); return fieldDefinition; } - private DataFetcher buildValidatingDataFetcher(TargetedValidationRules rules, OnValidationErrorStrategy errorStrategy, MessageInterpolator messageInterpolator, DataFetcher currentDF, final Locale defaultLocale) { - // ok we have some rules that need to be applied to this field and its arguments - return environment -> { - List errors = rules.runValidationRules(environment, messageInterpolator, defaultLocale); - if (!errors.isEmpty()) { - // should we continue? - if (!errorStrategy.shouldContinue(errors, environment)) { - return errorStrategy.onErrorValue(errors, environment); - } - } - // we have no validation errors or they said continue so call the underlying data fetcher - Object returnValue = currentDF.get(environment); - if (errors.isEmpty()) { - return returnValue; - } - return Util.mkDFRFromFetchedResult(errors, returnValue); - }; + private DataFetcher buildValidatingDataFetcher(OnValidationErrorStrategy errorStrategy, + MessageInterpolator messageInterpolator, + DataFetcher currentDF, + final Locale defaultLocale) { + return new FieldValidatorDataFetcher( + errorStrategy, + messageInterpolator, + currentDF, + defaultLocale, + ruleCandidates + ); } } From 00c9e367ebe8db6f73862dba8b243c6f5c919d06 Mon Sep 17 00:00:00 2001 From: Sunny Pelletier Date: Mon, 12 Jul 2021 20:46:44 -0400 Subject: [PATCH 02/20] refactor validation rules --- build.gradle | 13 +++--- gradle/wrapper/gradle-wrapper.properties | 2 +- .../AbstractDirectiveConstraint.java | 8 ++-- .../constraints/GraphQLScalars.java | 18 ++++++++ .../standard/AbstractAssertConstraint.java | 8 +--- .../AbstractDecimalMinMaxConstraint.java | 30 +++---------- .../standard/AbstractMinMaxConstraint.java | 24 ++-------- .../AbstractPositiveNegativeConstraint.java | 29 ++---------- .../standard/AssertFalseConstraint.java | 4 -- .../standard/AssertTrueConstraint.java | 4 -- .../standard/DecimalMaxConstraint.java | 4 -- .../standard/DecimalMinConstraint.java | 4 -- .../standard/DigitsConstraint.java | 36 ++++----------- .../standard/ExpressionConstraint.java | 4 -- .../constraints/standard/MaxConstraint.java | 4 -- .../constraints/standard/MinConstraint.java | 4 -- .../standard/NegativeConstraint.java | 4 -- .../standard/NegativeOrZeroConstraint.java | 4 -- .../constraints/standard/NotBlankRule.java | 44 +++++-------------- .../constraints/standard/NotEmptyRule.java | 6 --- .../standard/PatternConstraint.java | 18 ++------ 21 files changed, 70 insertions(+), 202 deletions(-) create mode 100644 src/main/java/graphql/validation/constraints/GraphQLScalars.java diff --git a/build.gradle b/build.gradle index 8cd0cca..f28e97b 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,6 @@ import java.text.SimpleDateFormat plugins { id 'java' id 'java-library' - id 'maven' id 'maven-publish' id 'signing' id "io.github.gradle-nexus.publish-plugin" version "1.0.0" @@ -39,13 +38,13 @@ repositories { dependencies { - compile "com.graphql-java:graphql-java:16.2" - compile "org.hibernate.validator:hibernate-validator:6.2.0.Final" - compile "org.glassfish:jakarta.el:4.0.0" + api "com.graphql-java:graphql-java:16.2" + api "org.hibernate.validator:hibernate-validator:6.2.0.Final" + api "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' + 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 d268689..a768ecd 100644 --- a/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java +++ b/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java @@ -20,6 +20,7 @@ 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; @@ -103,7 +104,6 @@ public boolean appliesTo(GraphQLArgument argument, GraphQLFieldDefinition fieldD abstract protected List runConstraint(ValidationEnvironment validationEnvironment); - @SuppressWarnings("unchecked") @Override public List runValidation(ValidationEnvironment validationEnvironment) { @@ -131,7 +131,6 @@ private List runFieldValidationImpl(ValidationEnvironment validati return runConstraintOnDirectives(validationEnvironment); } - @SuppressWarnings("unchecked") private List runValidationImpl(ValidationEnvironment validationEnvironment, GraphQLInputType inputType, Object validatedValue) { return runConstraintOnDirectives(validationEnvironment); } @@ -159,6 +158,9 @@ private List runConstraintOnDirectives(ValidationEnvironment valid } + 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 * @@ -167,7 +169,7 @@ private List runConstraintOnDirectives(ValidationEnvironment valid * * @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; 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..51a4a06 --- /dev/null +++ b/src/main/java/graphql/validation/constraints/GraphQLScalars.java @@ -0,0 +1,18 @@ +package graphql.validation.constraints; + +import graphql.Scalars; +import graphql.schema.GraphQLScalarType; +import java.util.Arrays; +import java.util.List; + +public class GraphQLScalars { + public static final List GRAPHQL_NUMBER_TYPES = Arrays.asList( + Scalars.GraphQLByte, + Scalars.GraphQLShort, + Scalars.GraphQLInt, + Scalars.GraphQLLong, + Scalars.GraphQLBigDecimal, + Scalars.GraphQLBigInteger, + Scalars.GraphQLFloat + ); +} diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractAssertConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractAssertConstraint.java index 9f5ddc7..1068181 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractAssertConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractAssertConstraint.java @@ -20,18 +20,12 @@ public AbstractAssertConstraint(String 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); diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java index f02688d..ae33fae 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java @@ -6,8 +6,8 @@ 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; @@ -15,6 +15,10 @@ import java.util.stream.Stream; abstract class AbstractDecimalMinMaxConstraint extends AbstractDirectiveConstraint { + private static final List SUPPORTED_SCALARS = Stream.concat( + Stream.of(Scalars.GraphQLString), + GraphQLScalars.GRAPHQL_NUMBER_TYPES.stream() + ).collect(Collectors.toList()); public AbstractDecimalMinMaxConstraint(String name) { super(name); @@ -22,27 +26,11 @@ public AbstractDecimalMinMaxConstraint(String name) { @Override public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, - Scalars.GraphQLString, // note we allow strings - Scalars.GraphQLByte, - Scalars.GraphQLShort, - Scalars.GraphQLInt, - Scalars.GraphQLLong, - Scalars.GraphQLBigDecimal, - Scalars.GraphQLBigInteger, - Scalars.GraphQLFloat - ); + return isOneOfTheseTypes(inputType, SUPPORTED_SCALARS); } public List getApplicableTypeNames() { - return Stream.of(Scalars.GraphQLString, // note we allow strings - Scalars.GraphQLByte, - Scalars.GraphQLShort, - Scalars.GraphQLInt, - Scalars.GraphQLLong, - Scalars.GraphQLBigDecimal, - Scalars.GraphQLBigInteger, - Scalars.GraphQLFloat) + return SUPPORTED_SCALARS.stream() .map(GraphQLScalarType::getName) .collect(Collectors.toList()); } @@ -50,10 +38,6 @@ public List getApplicableTypeNames() { @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"); diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java index 1463e1c..0e4e685 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java @@ -1,18 +1,15 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.Scalars; 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 { @@ -23,25 +20,12 @@ public AbstractMinMaxConstraint(String name) { @Override public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, - Scalars.GraphQLByte, - Scalars.GraphQLShort, - Scalars.GraphQLInt, - Scalars.GraphQLLong, - Scalars.GraphQLBigDecimal, - Scalars.GraphQLBigInteger, - Scalars.GraphQLFloat - ); + return isOneOfTheseTypes(inputType, GraphQLScalars.GRAPHQL_NUMBER_TYPES); } public List getApplicableTypeNames() { - return Stream.of(Scalars.GraphQLByte, - Scalars.GraphQLShort, - Scalars.GraphQLInt, - Scalars.GraphQLLong, - Scalars.GraphQLBigDecimal, - Scalars.GraphQLBigInteger, - Scalars.GraphQLFloat) + return GraphQLScalars.GRAPHQL_NUMBER_TYPES + .stream() .map(GraphQLScalarType::getName) .collect(toList()); } diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java index b2a89e3..b88e50c 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java @@ -1,18 +1,15 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.Scalars; 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 { @@ -23,25 +20,12 @@ public AbstractPositiveNegativeConstraint(String name) { @Override public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, - Scalars.GraphQLByte, - Scalars.GraphQLShort, - Scalars.GraphQLInt, - Scalars.GraphQLLong, - Scalars.GraphQLBigDecimal, - Scalars.GraphQLBigInteger, - Scalars.GraphQLFloat - ); + return isOneOfTheseTypes(inputType, GraphQLScalars.GRAPHQL_NUMBER_TYPES); } public List getApplicableTypeNames() { - return Stream.of(Scalars.GraphQLByte, - Scalars.GraphQLShort, - Scalars.GraphQLInt, - Scalars.GraphQLLong, - Scalars.GraphQLBigDecimal, - Scalars.GraphQLBigInteger, - Scalars.GraphQLFloat) + return GraphQLScalars.GRAPHQL_NUMBER_TYPES + .stream() .map(GraphQLScalarType::getName) .collect(toList()); } @@ -50,11 +34,6 @@ public List getApplicableTypeNames() { @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; diff --git a/src/main/java/graphql/validation/constraints/standard/AssertFalseConstraint.java b/src/main/java/graphql/validation/constraints/standard/AssertFalseConstraint.java index 5f5fb01..b4a4247 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()) - .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..f7938ea 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()) - .directiveSDL("directive @AssertTrue(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) diff --git a/src/main/java/graphql/validation/constraints/standard/DecimalMaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/DecimalMaxConstraint.java index fc2cd58..db3a500 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()) - .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..9bf1fc8 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()) - .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 11da22b..734ef66 100644 --- a/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java @@ -7,16 +7,20 @@ import graphql.schema.GraphQLScalarType; import graphql.validation.constraints.AbstractDirectiveConstraint; import graphql.validation.constraints.Documentation; +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.Collectors; import java.util.stream.Stream; - import static java.util.stream.Collectors.toList; public class DigitsConstraint extends AbstractDirectiveConstraint { + private static final List SUPPORTED_SCALARS = Stream.concat( + Stream.of(Scalars.GraphQLString), + GraphQLScalars.GRAPHQL_NUMBER_TYPES.stream() + ).collect(Collectors.toList()); public DigitsConstraint() { super("Digits"); @@ -26,22 +30,12 @@ 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, - Scalars.GraphQLByte, - Scalars.GraphQLShort, - Scalars.GraphQLInt, - Scalars.GraphQLLong, - Scalars.GraphQLBigDecimal, - Scalars.GraphQLBigInteger, - Scalars.GraphQLFloat) + .applicableTypeNames(SUPPORTED_SCALARS + .stream() .map(GraphQLScalarType::getName) .collect(toList())) - .directiveSDL("directive @Digits(integer : Int!, fraction : Int!, message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) @@ -50,25 +44,13 @@ public Documentation getDocumentation() { @Override public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, - Scalars.GraphQLString, - Scalars.GraphQLByte, - Scalars.GraphQLShort, - Scalars.GraphQLInt, - Scalars.GraphQLLong, - Scalars.GraphQLBigDecimal, - Scalars.GraphQLBigInteger, - Scalars.GraphQLFloat - ); + return isOneOfTheseTypes(inputType, SUPPORTED_SCALARS); } @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"); diff --git a/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java b/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java index 484cdfb..63e73f7 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()) diff --git a/src/main/java/graphql/validation/constraints/standard/MaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/MaxConstraint.java index bc21d33..9f26723 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()) - .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..a92ed33 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()) - .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..8f524bb 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()) - .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..d3132ad 100644 --- a/src/main/java/graphql/validation/constraints/standard/NegativeOrZeroConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/NegativeOrZeroConstraint.java @@ -14,13 +14,9 @@ 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()) - .directiveSDL("directive @NegativeOrZero(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) diff --git a/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java b/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java index 0a89894..dfc38ca 100644 --- a/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java +++ b/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java @@ -1,7 +1,5 @@ package graphql.validation.constraints.standard; -import static graphql.schema.GraphQLTypeUtil.isList; - import graphql.GraphQLError; import graphql.Scalars; import graphql.schema.GraphQLDirective; @@ -9,10 +7,10 @@ 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; +import java.util.stream.Collectors; +import static graphql.schema.GraphQLTypeUtil.isList; public class NotBlankRule extends AbstractDirectiveConstraint { @@ -24,13 +22,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()) @@ -49,38 +43,22 @@ protected List runConstraint(ValidationEnvironment validationEnvir GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); - if (validatedValue == null) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment)); - } - - List validatedValues; + List validatedValues; if (isList(argumentType)) { - validatedValues = (List)validatedValue; + validatedValues = (List) validatedValue; } else { - validatedValues = Arrays.asList(validatedValue); + validatedValues = Collections.singletonList(validatedValue); } - if (validatedValues.size() <= 0) { + if (validatedValues.isEmpty()) { return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment)); } - for (Object value : validatedValues) { - if (isBlank(value)) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment)); - } - } - - return Collections.emptyList(); - } - - private boolean isBlank(Object value) { - char[] chars = value.toString().toCharArray(); - for (char c : chars) { - if (!Character.isWhitespace(c)) { - return false; - } - } - return true; + return validatedValues + .stream() + .filter((value) -> !value.toString().trim().isEmpty()) + .flatMap((value) -> mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment)).stream()) + .collect(Collectors.toList()); } } diff --git a/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java b/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java index 06fe5c1..064866e 100644 --- a/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java +++ b/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java @@ -20,13 +20,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") - .directiveSDL("directive @NotEmpty(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) @@ -53,6 +49,4 @@ protected List runConstraint(ValidationEnvironment validationEnvir } return Collections.emptyList(); } - - } diff --git a/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java b/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java index 6a73c18..141b239 100644 --- a/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java @@ -7,14 +7,12 @@ 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.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 +28,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()) @@ -53,16 +47,12 @@ protected List runConstraint(ValidationEnvironment validationEnvir Object validatedValue = validationEnvironment.getValidatedValue(); GraphQLInputType argumentType = validationEnvironment.getValidatedType(); - if (validatedValue == null) { - return emptyList(); - } - - List validatedValues; + List validatedValues; if (isList(argumentType)) { - validatedValues = (List)validatedValue; + validatedValues = (List)validatedValue; } else { - validatedValues = Arrays.asList(validatedValue); + validatedValues = Collections.singletonList(validatedValue); } for (Object value : validatedValues) { From a885fa6d9028e5036a3c639b6dce70c035052555 Mon Sep 17 00:00:00 2001 From: Sunny Pelletier Date: Wed, 21 Jul 2021 19:28:39 -0400 Subject: [PATCH 03/20] refactor a little --- .../AbstractDirectiveConstraint.java | 25 ++++++--------- .../standard/AbstractAssertConstraint.java | 10 ++---- .../AbstractDecimalMinMaxConstraint.java | 8 ++--- .../standard/AbstractMinMaxConstraint.java | 10 ++---- .../standard/AbstractNotEmptyRule.java | 28 ++++++++++++++++ .../constraints/standard/NotBlankRule.java | 24 ++------------ .../standard/NotEmptyContainerRule.java | 31 ++++++++++++++++++ .../constraints/standard/NotEmptyRule.java | 32 +++---------------- .../validation/ValidationMessages.properties | 1 + 9 files changed, 84 insertions(+), 85 deletions(-) create mode 100644 src/main/java/graphql/validation/constraints/standard/AbstractNotEmptyRule.java create mode 100644 src/main/java/graphql/validation/constraints/standard/NotEmptyContainerRule.java diff --git a/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java b/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java index fa4cf00..15afd56 100644 --- a/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java +++ b/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java @@ -89,7 +89,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); @@ -98,7 +97,6 @@ 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); @@ -161,12 +159,12 @@ private List runConstraintOnDirectives(ValidationEnvironment valid 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, Collection scalarTypes) { @@ -187,7 +185,6 @@ protected boolean isOneOfTheseTypes(GraphQLInputType inputType, Collection mkMessageParams(Object validatedValue, ValidationEnvironment validationEnvironment, Object... args) { @@ -301,7 +294,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) { @@ -310,11 +302,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) { @@ -326,7 +325,6 @@ 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) { @@ -340,7 +338,6 @@ protected boolean isStringOrIDOrListOrMap(GraphQLInputType inputType) { * 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) { @@ -352,7 +349,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") @@ -365,7 +361,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 { @@ -390,7 +385,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) { @@ -409,7 +403,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/standard/AbstractAssertConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractAssertConstraint.java index 1068181..498e641 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractAssertConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractAssertConstraint.java @@ -1,14 +1,11 @@ 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 { @@ -27,13 +24,12 @@ public boolean appliesToType(GraphQLInputType inputType) { protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - 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(); } diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java index dfc640d..77fe294 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java @@ -2,7 +2,6 @@ import graphql.GraphQLError; import graphql.Scalars; -import graphql.scalars.ExtendedScalars; import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLInputType; import graphql.schema.GraphQLScalarType; @@ -50,17 +49,14 @@ 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(); } diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java index 0e4e685..95e9172 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java @@ -34,11 +34,6 @@ public List getApplicableTypeNames() { @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"); @@ -55,10 +50,9 @@ protected List runConstraint(ValidationEnvironment validationEnvir if (!isOK) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment, - "value", value)); - + return mkError(validationEnvironment,"value", value); } + return Collections.emptyList(); } 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/NotBlankRule.java b/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java index dfc38ca..4fcbfc9 100644 --- a/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java +++ b/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java @@ -2,15 +2,12 @@ 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.stream.Collectors; -import static graphql.schema.GraphQLTypeUtil.isList; public class NotBlankRule extends AbstractDirectiveConstraint { @@ -39,26 +36,11 @@ 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); - - List validatedValues; - - if (isList(argumentType)) { - validatedValues = (List) validatedValue; - } else { - validatedValues = Collections.singletonList(validatedValue); - } - - if (validatedValues.isEmpty()) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment)); + if (validatedValue.toString().trim().isEmpty()) { + return mkError(validationEnvironment); } - return validatedValues - .stream() - .filter((value) -> !value.toString().trim().isEmpty()) - .flatMap((value) -> mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment)).stream()) - .collect(Collectors.toList()); + return Collections.emptyList(); } } diff --git a/src/main/java/graphql/validation/constraints/standard/NotEmptyContainerRule.java b/src/main/java/graphql/validation/constraints/standard/NotEmptyContainerRule.java new file mode 100644 index 0000000..4c13f19 --- /dev/null +++ b/src/main/java/graphql/validation/constraints/standard/NotEmptyContainerRule.java @@ -0,0 +1,31 @@ +package graphql.validation.constraints.standard; + +import graphql.schema.GraphQLInputObjectType; +import graphql.schema.GraphQLInputType; +import graphql.validation.constraints.Documentation; +import static graphql.schema.GraphQLTypeUtil.isList; + +public class NotEmptyContainerRule extends AbstractNotEmptyRule { + + public NotEmptyContainerRule() { + super("NotEmptyContainer"); + } + + @Override + public Documentation getDocumentation() { + return Documentation.newDocumentation() + .messageTemplate(getMessageTemplate()) + .description("The container must have a non-zero size") + .example("updateAccident( accidentNotes : [Notes]! @NotEmptyContainer) : DriverDetails") + .applicableTypeNames("Lists", "Input Objects") + .directiveSDL("directive @NotEmptyContainer(message : String = \"%s\") " + + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", + getMessageTemplate()) + .build(); + } + + @Override + public boolean appliesToType(GraphQLInputType inputType) { + return isList(inputType) || inputType instanceof GraphQLInputObjectType; + } +} diff --git a/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java b/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java index 064866e..e388a84 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,9 +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()) @@ -31,22 +25,6 @@ public Documentation getDocumentation() { @Override public boolean appliesToType(GraphQLInputType inputType) { - return isStringOrIDOrListOrMap(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(); + return isStringOrID(inputType); } } diff --git a/src/main/resources/graphql/validation/ValidationMessages.properties b/src/main/resources/graphql/validation/ValidationMessages.properties index 0ba4dca..a1471cd 100644 --- a/src/main/resources/graphql/validation/ValidationMessages.properties +++ b/src/main/resources/graphql/validation/ValidationMessages.properties @@ -13,6 +13,7 @@ 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.NotEmptyContainer.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 From 1a6becee3f2f1d87f312784e9f3dbfb62dadf93d Mon Sep 17 00:00:00 2001 From: Sunny Pelletier Date: Wed, 21 Jul 2021 19:30:39 -0400 Subject: [PATCH 04/20] continue --- .../standard/AbstractPositiveNegativeConstraint.java | 3 +-- .../validation/constraints/standard/DigitsConstraint.java | 6 ++---- .../constraints/standard/ExpressionConstraint.java | 7 ++----- .../constraints/standard/NegativeOrZeroConstraint.java | 2 -- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java index b88e50c..afb673d 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java @@ -34,7 +34,6 @@ public List getApplicableTypeNames() { @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); boolean isOK; try { @@ -45,7 +44,7 @@ protected List runConstraint(ValidationEnvironment validationEnvir } if (!isOK) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment)); + return mkError(validationEnvironment); } return Collections.emptyList(); diff --git a/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java b/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java index 4bf05b6..f79703f 100644 --- a/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java @@ -2,7 +2,6 @@ import graphql.GraphQLError; import graphql.Scalars; -import graphql.scalars.ExtendedScalars; import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLInputType; import graphql.schema.GraphQLScalarType; @@ -66,10 +65,9 @@ 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(); } diff --git a/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java b/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java index 63e73f7..bf1baff 100644 --- a/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java @@ -53,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(); } diff --git a/src/main/java/graphql/validation/constraints/standard/NegativeOrZeroConstraint.java b/src/main/java/graphql/validation/constraints/standard/NegativeOrZeroConstraint.java index d3132ad..0938166 100644 --- a/src/main/java/graphql/validation/constraints/standard/NegativeOrZeroConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/NegativeOrZeroConstraint.java @@ -23,10 +23,8 @@ public Documentation getDocumentation() { .build(); } - @Override protected boolean isOK(BigDecimal bigDecimal) { return bigDecimal.compareTo(BigDecimal.ZERO) <= 0; } - } From 1d2844e6ac62f8429a01a07bada2e3609ae0d730 Mon Sep 17 00:00:00 2001 From: Sunny Pelletier Date: Wed, 21 Jul 2021 20:49:16 -0400 Subject: [PATCH 05/20] simplify all constraints --- .../validation/constraints/Documentation.java | 16 +++++++++-- .../constraints/GraphQLScalars.java | 7 +++++ .../AbstractDecimalMinMaxConstraint.java | 18 +++--------- .../standard/AbstractMinMaxConstraint.java | 8 ++---- .../AbstractPositiveNegativeConstraint.java | 9 ++---- .../standard/AssertFalseConstraint.java | 2 +- .../standard/AssertTrueConstraint.java | 2 +- .../standard/DecimalMaxConstraint.java | 2 +- .../standard/DecimalMinConstraint.java | 2 +- .../standard/DigitsConstraint.java | 21 +++----------- .../constraints/standard/MaxConstraint.java | 2 +- .../constraints/standard/MinConstraint.java | 2 +- .../standard/NegativeConstraint.java | 2 +- .../standard/NegativeOrZeroConstraint.java | 2 +- .../standard/PatternConstraint.java | 28 ++++++------------- .../standard/PositiveConstraint.java | 6 +--- .../standard/PositiveOrZeroConstraint.java | 6 +--- .../constraints/standard/RangeConstraint.java | 24 +++++----------- 18 files changed, 57 insertions(+), 102 deletions(-) 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/GraphQLScalars.java b/src/main/java/graphql/validation/constraints/GraphQLScalars.java index 0982f62..027ac3a 100644 --- a/src/main/java/graphql/validation/constraints/GraphQLScalars.java +++ b/src/main/java/graphql/validation/constraints/GraphQLScalars.java @@ -5,6 +5,8 @@ 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( @@ -16,4 +18,9 @@ public class GraphQLScalars { ExtendedScalars.GraphQLBigDecimal, ExtendedScalars.GraphQLBigInteger ); + + public static final List GRAPHQL_NUMBER_AND_STRING_TYPES = Stream.concat( + Stream.of(Scalars.GraphQLString), + GraphQLScalars.GRAPHQL_NUMBER_TYPES.stream() + ).collect(Collectors.toList()); } diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java index 77fe294..6f19f09 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java @@ -1,38 +1,28 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.Scalars; 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.Collectors; -import java.util.stream.Stream; +import static graphql.validation.constraints.GraphQLScalars.GRAPHQL_NUMBER_AND_STRING_TYPES; abstract class AbstractDecimalMinMaxConstraint extends AbstractDirectiveConstraint { - private static final List SUPPORTED_SCALARS = Stream.concat( - Stream.of(Scalars.GraphQLString), - GraphQLScalars.GRAPHQL_NUMBER_TYPES.stream() - ).collect(Collectors.toList()); - public AbstractDecimalMinMaxConstraint(String name) { super(name); } @Override public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, SUPPORTED_SCALARS); + return isOneOfTheseTypes(inputType, GRAPHQL_NUMBER_AND_STRING_TYPES); } - public List getApplicableTypeNames() { - return SUPPORTED_SCALARS.stream() - .map(GraphQLScalarType::getName) - .collect(Collectors.toList()); + public List getApplicableTypes() { + return GRAPHQL_NUMBER_AND_STRING_TYPES; } @Override diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java index 95e9172..7bbbc7e 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java @@ -10,7 +10,6 @@ import java.math.BigDecimal; import java.util.Collections; import java.util.List; -import static java.util.stream.Collectors.toList; abstract class AbstractMinMaxConstraint extends AbstractDirectiveConstraint { @@ -23,11 +22,8 @@ public boolean appliesToType(GraphQLInputType inputType) { return isOneOfTheseTypes(inputType, GraphQLScalars.GRAPHQL_NUMBER_TYPES); } - public List getApplicableTypeNames() { - return GraphQLScalars.GRAPHQL_NUMBER_TYPES - .stream() - .map(GraphQLScalarType::getName) - .collect(toList()); + public List getApplicableTypes() { + return GraphQLScalars.GRAPHQL_NUMBER_TYPES; } diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java index afb673d..549e16d 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java @@ -1,7 +1,6 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLInputType; import graphql.schema.GraphQLScalarType; import graphql.validation.constraints.AbstractDirectiveConstraint; @@ -10,7 +9,6 @@ import java.math.BigDecimal; import java.util.Collections; import java.util.List; -import static java.util.stream.Collectors.toList; abstract class AbstractPositiveNegativeConstraint extends AbstractDirectiveConstraint { @@ -23,11 +21,8 @@ public boolean appliesToType(GraphQLInputType inputType) { return isOneOfTheseTypes(inputType, GraphQLScalars.GRAPHQL_NUMBER_TYPES); } - public List getApplicableTypeNames() { - return GraphQLScalars.GRAPHQL_NUMBER_TYPES - .stream() - .map(GraphQLScalarType::getName) - .collect(toList()); + public List getApplicableTypes() { + return GraphQLScalars.GRAPHQL_NUMBER_TYPES; } diff --git a/src/main/java/graphql/validation/constraints/standard/AssertFalseConstraint.java b/src/main/java/graphql/validation/constraints/standard/AssertFalseConstraint.java index b4a4247..392b0ee 100644 --- a/src/main/java/graphql/validation/constraints/standard/AssertFalseConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AssertFalseConstraint.java @@ -16,7 +16,7 @@ public Documentation getDocumentation() { .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 f7938ea..fa33d40 100644 --- a/src/main/java/graphql/validation/constraints/standard/AssertTrueConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AssertTrueConstraint.java @@ -16,7 +16,7 @@ public Documentation getDocumentation() { .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/DecimalMaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/DecimalMaxConstraint.java index db3a500..e06440e 100644 --- a/src/main/java/graphql/validation/constraints/standard/DecimalMaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/DecimalMaxConstraint.java @@ -14,7 +14,7 @@ public Documentation getDocumentation() { .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 9bf1fc8..895f1cf 100644 --- a/src/main/java/graphql/validation/constraints/standard/DecimalMinConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/DecimalMinConstraint.java @@ -14,7 +14,7 @@ public Documentation getDocumentation() { .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 f79703f..f8ebb08 100644 --- a/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java @@ -1,27 +1,17 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.Scalars; 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.constraints.GraphQLScalars; 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 java.util.stream.Collectors.toList; +import static graphql.validation.constraints.GraphQLScalars.GRAPHQL_NUMBER_AND_STRING_TYPES; public class DigitsConstraint extends AbstractDirectiveConstraint { - private static final List SUPPORTED_SCALARS = Stream.concat( - Stream.of(Scalars.GraphQLString), - GraphQLScalars.GRAPHQL_NUMBER_TYPES.stream() - ).collect(Collectors.toList()); - public DigitsConstraint() { super("Digits"); } @@ -32,10 +22,7 @@ public Documentation getDocumentation() { .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(SUPPORTED_SCALARS - .stream() - .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()) @@ -44,7 +31,7 @@ public Documentation getDocumentation() { @Override public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, SUPPORTED_SCALARS); + return isOneOfTheseTypes(inputType, GRAPHQL_NUMBER_AND_STRING_TYPES); } @@ -73,7 +60,7 @@ protected List runConstraint(ValidationEnvironment validationEnvir 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; } diff --git a/src/main/java/graphql/validation/constraints/standard/MaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/MaxConstraint.java index 9f26723..899c70d 100644 --- a/src/main/java/graphql/validation/constraints/standard/MaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/MaxConstraint.java @@ -14,7 +14,7 @@ public Documentation getDocumentation() { .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 a92ed33..ff16edb 100644 --- a/src/main/java/graphql/validation/constraints/standard/MinConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/MinConstraint.java @@ -14,7 +14,7 @@ public Documentation getDocumentation() { .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 8f524bb..e074771 100644 --- a/src/main/java/graphql/validation/constraints/standard/NegativeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/NegativeConstraint.java @@ -16,7 +16,7 @@ public Documentation getDocumentation() { .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 0938166..4806470 100644 --- a/src/main/java/graphql/validation/constraints/standard/NegativeOrZeroConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/NegativeOrZeroConstraint.java @@ -16,7 +16,7 @@ public Documentation getDocumentation() { .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()) diff --git a/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java b/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java index 141b239..82ff048 100644 --- a/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/PatternConstraint.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.HashMap; import java.util.List; import java.util.Map; @@ -45,30 +44,19 @@ public boolean appliesToType(GraphQLInputType inputType) { @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - GraphQLInputType argumentType = validationEnvironment.getValidatedType(); - List validatedValues; + String strValue = String.valueOf(validatedValue); - if (isList(argumentType)) { - validatedValues = (List)validatedValue; - } else { - validatedValues = Collections.singletonList(validatedValue); - } - - for (Object value : validatedValues) { - String strValue = String.valueOf(value); + GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); + String patternArg = getStrArg(directive, "regexp"); + Pattern pattern = cachedPattern(patternArg); - 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(); } diff --git a/src/main/java/graphql/validation/constraints/standard/PositiveConstraint.java b/src/main/java/graphql/validation/constraints/standard/PositiveConstraint.java index d48e689..43bba5e 100644 --- a/src/main/java/graphql/validation/constraints/standard/PositiveConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/PositiveConstraint.java @@ -14,13 +14,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()) diff --git a/src/main/java/graphql/validation/constraints/standard/PositiveOrZeroConstraint.java b/src/main/java/graphql/validation/constraints/standard/PositiveOrZeroConstraint.java index 3f1abf7..e49d17b 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()) diff --git a/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java b/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java index 17c28a8..35fa3a5 100644 --- a/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java @@ -8,17 +8,20 @@ import graphql.schema.GraphQLScalarType; import graphql.validation.constraints.AbstractDirectiveConstraint; import graphql.validation.constraints.Documentation; +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.Collectors; import java.util.stream.Stream; - import static graphql.Scalars.GraphQLString; -import static java.util.stream.Collectors.toList; public class RangeConstraint extends AbstractDirectiveConstraint { + private static final List SUPPORTED_SCALARS = Stream.concat( + Stream.of(Scalars.GraphQLString), + GraphQLScalars.GRAPHQL_NUMBER_TYPES.stream() + ).collect(Collectors.toList()); public RangeConstraint() { super("Range"); @@ -29,23 +32,10 @@ public RangeConstraint() { 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(SUPPORTED_SCALARS) .directiveSDL("directive @Range(min : Int = 0, max : Int = %d, message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", Integer.MAX_VALUE, getMessageTemplate()) From b9e3fd1b5a9d825bca641e1df586947580cde08b Mon Sep 17 00:00:00 2001 From: Sunny Pelletier Date: Wed, 21 Jul 2021 21:01:07 -0400 Subject: [PATCH 06/20] fix constraints --- .../AbstractDirectiveConstraint.java | 8 ++- .../standard/AbstractSizeConstraint.java | 33 ++++++++++++ ...erRule.java => ContainerNotEmptyRule.java} | 14 +++-- .../standard/ContainerSizeConstraint.java | 30 +++++++++++ .../constraints/standard/SizeConstraint.java | 52 ++----------------- .../validation/ValidationMessages.properties | 3 +- 6 files changed, 80 insertions(+), 60 deletions(-) create mode 100644 src/main/java/graphql/validation/constraints/standard/AbstractSizeConstraint.java rename src/main/java/graphql/validation/constraints/standard/{NotEmptyContainerRule.java => ContainerNotEmptyRule.java} (69%) create mode 100644 src/main/java/graphql/validation/constraints/standard/ContainerSizeConstraint.java diff --git a/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java b/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java index 15afd56..3487205 100644 --- a/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java +++ b/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java @@ -328,10 +328,14 @@ protected boolean isStringOrIDOrList(GraphQLInputType inputType) { * @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); } /** 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/NotEmptyContainerRule.java b/src/main/java/graphql/validation/constraints/standard/ContainerNotEmptyRule.java similarity index 69% rename from src/main/java/graphql/validation/constraints/standard/NotEmptyContainerRule.java rename to src/main/java/graphql/validation/constraints/standard/ContainerNotEmptyRule.java index 4c13f19..06ff85e 100644 --- a/src/main/java/graphql/validation/constraints/standard/NotEmptyContainerRule.java +++ b/src/main/java/graphql/validation/constraints/standard/ContainerNotEmptyRule.java @@ -1,14 +1,12 @@ package graphql.validation.constraints.standard; -import graphql.schema.GraphQLInputObjectType; import graphql.schema.GraphQLInputType; import graphql.validation.constraints.Documentation; import static graphql.schema.GraphQLTypeUtil.isList; -public class NotEmptyContainerRule extends AbstractNotEmptyRule { - - public NotEmptyContainerRule() { - super("NotEmptyContainer"); +public class ContainerNotEmptyRule extends AbstractNotEmptyRule { + public ContainerNotEmptyRule() { + super("ContainerNotEmpty"); } @Override @@ -16,9 +14,9 @@ public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) .description("The container must have a non-zero size") - .example("updateAccident( accidentNotes : [Notes]! @NotEmptyContainer) : DriverDetails") + .example("updateAccident( accidentNotes : [Notes]! @ContainerNotEmpty) : DriverDetails") .applicableTypeNames("Lists", "Input Objects") - .directiveSDL("directive @NotEmptyContainer(message : String = \"%s\") " + + .directiveSDL("directive @ContainerNotEmpty(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) .build(); @@ -26,6 +24,6 @@ public Documentation getDocumentation() { @Override public boolean appliesToType(GraphQLInputType inputType) { - return isList(inputType) || inputType instanceof GraphQLInputObjectType; + return isList(inputType) || isMap(inputType); } } 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..938c8bf --- /dev/null +++ b/src/main/java/graphql/validation/constraints/standard/ContainerSizeConstraint.java @@ -0,0 +1,30 @@ +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 @Size( min : 1000, max : 100000)) : DriverDetails") + .applicableTypeNames("Lists", "Input Objects") + .directiveSDL("directive @Size(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); + } +} diff --git a/src/main/java/graphql/validation/constraints/standard/SizeConstraint.java b/src/main/java/graphql/validation/constraints/standard/SizeConstraint.java index 700c13d..391f489 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,7 @@ 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 - ); - } } diff --git a/src/main/resources/graphql/validation/ValidationMessages.properties b/src/main/resources/graphql/validation/ValidationMessages.properties index a1471cd..e5759ac 100644 --- a/src/main/resources/graphql/validation/ValidationMessages.properties +++ b/src/main/resources/graphql/validation/ValidationMessages.properties @@ -13,10 +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.NotEmptyContainer.message={path} must contain at least one element +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} From e6f9e67ecf82d6d45326609f458f2cb624f83135 Mon Sep 17 00:00:00 2001 From: Sunny Pelletier Date: Sat, 24 Jul 2021 12:13:59 -0400 Subject: [PATCH 07/20] add support for list elements for every type of constraint --- .../AbstractDirectiveConstraint.java | 46 ++++++++++++------- ...bstractListElementValidatorConstraint.java | 28 +++++++++++ .../GraphQLListElementValidator.java | 40 ++++++++++++++++ .../standard/AbstractAssertConstraint.java | 7 +-- .../AbstractDecimalMinMaxConstraint.java | 15 +++--- .../standard/AbstractMinMaxConstraint.java | 18 +++++--- .../AbstractPositiveNegativeConstraint.java | 5 ++ .../standard/ContainerNotEmptyRule.java | 5 ++ .../standard/ContainerSizeConstraint.java | 5 ++ .../standard/DigitsConstraint.java | 7 ++- .../standard/ExpressionConstraint.java | 5 ++ .../constraints/standard/NotBlankRule.java | 5 ++ .../constraints/standard/NotEmptyRule.java | 5 ++ .../standard/PatternConstraint.java | 5 +- .../standard/PositiveConstraint.java | 2 - .../standard/PositiveOrZeroConstraint.java | 1 - .../constraints/standard/RangeConstraint.java | 5 ++ .../constraints/standard/SizeConstraint.java | 4 ++ 18 files changed, 171 insertions(+), 37 deletions(-) create mode 100644 src/main/java/graphql/validation/constraints/AbstractListElementValidatorConstraint.java create mode 100644 src/main/java/graphql/validation/constraints/GraphQLListElementValidator.java diff --git a/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java b/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java index 3487205..31354c5 100644 --- a/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java +++ b/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java @@ -16,7 +16,6 @@ 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; @@ -26,7 +25,6 @@ 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; @@ -71,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); @@ -104,13 +110,8 @@ public boolean appliesTo(GraphQLArgument argument, GraphQLFieldDefinition fieldD @Override public List runValidation(ValidationEnvironment validationEnvironment) { - - // output fields are special - if (validationEnvironment.getValidatedElement() == FIELD) { - return runFieldValidationImpl(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 @@ -119,17 +120,18 @@ public List runValidation(ValidationEnvironment validationEnvironm return Collections.emptyList(); } + // output fields are special + if (validationEnvironment.getValidatedElement() == FIELD) { + return runValidationImpl(validationEnvironment); + } + GraphQLInputType inputType = Util.unwrapNonNull(validationEnvironment.getValidatedType()); validationEnvironment = validationEnvironment.transform(b -> b.validatedType(inputType)); - return runValidationImpl(validationEnvironment, inputType, validatedValue); - } - - private List runFieldValidationImpl(ValidationEnvironment validationEnvironment) { - return runConstraintOnDirectives(validationEnvironment); + return runValidationImpl(validationEnvironment); } - private List runValidationImpl(ValidationEnvironment validationEnvironment, GraphQLInputType inputType, Object validatedValue) { + private List runValidationImpl(ValidationEnvironment validationEnvironment) { return runConstraintOnDirectives(validationEnvironment); } @@ -149,12 +151,24 @@ 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)); diff --git a/src/main/java/graphql/validation/constraints/AbstractListElementValidatorConstraint.java b/src/main/java/graphql/validation/constraints/AbstractListElementValidatorConstraint.java new file mode 100644 index 0000000..8635b79 --- /dev/null +++ b/src/main/java/graphql/validation/constraints/AbstractListElementValidatorConstraint.java @@ -0,0 +1,28 @@ +package graphql.validation.constraints; + +import graphql.GraphQLError; +import graphql.schema.GraphQLInputType; +import graphql.validation.rules.ValidationEnvironment; +import java.util.List; + +public abstract class AbstractListElementValidatorConstraint extends AbstractDirectiveConstraint { + private final GraphQLListElementValidator validator = new GraphQLListElementValidator(); + + public AbstractListElementValidatorConstraint(String name) { + super(name); + } + + @Override + final protected boolean appliesToType(GraphQLInputType inputType) { + return validator.appliesToType(inputType, this::appliesToTypeOrListElement); + } + + protected abstract boolean appliesToTypeOrListElement(GraphQLInputType inputType); + + @Override + final protected List runConstraint(ValidationEnvironment validationEnvironment) { + return validator.runConstraintOnListElements(validationEnvironment, this::runConstraintOnElement); + } + + protected abstract List runConstraintOnElement(ValidationEnvironment validationEnvironment); +} 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..3dbfd43 --- /dev/null +++ b/src/main/java/graphql/validation/constraints/GraphQLListElementValidator.java @@ -0,0 +1,40 @@ +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; + +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) -> 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/standard/AbstractAssertConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractAssertConstraint.java index 498e641..fadd286 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractAssertConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractAssertConstraint.java @@ -9,12 +9,10 @@ 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); @@ -35,5 +33,8 @@ protected List runConstraint(ValidationEnvironment validationEnvir 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 6f19f09..5e92a30 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java @@ -16,15 +16,15 @@ public AbstractDecimalMinMaxConstraint(String name) { super(name); } - @Override - public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, GRAPHQL_NUMBER_AND_STRING_TYPES); - } - public List getApplicableTypes() { return GRAPHQL_NUMBER_AND_STRING_TYPES; } + @Override + protected boolean appliesToType(GraphQLInputType inputType) { + return isOneOfTheseTypes(inputType, GRAPHQL_NUMBER_AND_STRING_TYPES); + } + @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); @@ -52,5 +52,8 @@ protected List runConstraint(ValidationEnvironment validationEnvir 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 7bbbc7e..cdfb40b 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java @@ -12,21 +12,20 @@ import java.util.List; abstract class AbstractMinMaxConstraint extends AbstractDirectiveConstraint { - public AbstractMinMaxConstraint(String name) { super(name); } - @Override - public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, GraphQLScalars.GRAPHQL_NUMBER_TYPES); - } - public List getApplicableTypes() { return GraphQLScalars.GRAPHQL_NUMBER_TYPES; } + @Override + protected boolean appliesToType(GraphQLInputType inputType) { + return isOneOfTheseTypes(inputType, GraphQLScalars.GRAPHQL_NUMBER_TYPES); + } + @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); @@ -46,11 +45,16 @@ protected List runConstraint(ValidationEnvironment validationEnvir if (!isOK) { - return mkError(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/AbstractPositiveNegativeConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java index 549e16d..b6090e8 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java @@ -46,4 +46,9 @@ protected List runConstraint(ValidationEnvironment validationEnvir } abstract protected boolean isOK(BigDecimal bigDecimal); + + @Override + protected boolean appliesToListElements() { + return true; + } } diff --git a/src/main/java/graphql/validation/constraints/standard/ContainerNotEmptyRule.java b/src/main/java/graphql/validation/constraints/standard/ContainerNotEmptyRule.java index 06ff85e..219ecb2 100644 --- a/src/main/java/graphql/validation/constraints/standard/ContainerNotEmptyRule.java +++ b/src/main/java/graphql/validation/constraints/standard/ContainerNotEmptyRule.java @@ -26,4 +26,9 @@ public Documentation getDocumentation() { 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 index 938c8bf..570a7e5 100644 --- a/src/main/java/graphql/validation/constraints/standard/ContainerSizeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/ContainerSizeConstraint.java @@ -27,4 +27,9 @@ Integer.MAX_VALUE, getMessageTemplate()) 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/DigitsConstraint.java b/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java index f8ebb08..3a93513 100644 --- a/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java @@ -52,7 +52,7 @@ protected List runConstraint(ValidationEnvironment validationEnvir } if (!isOk) { - return mkError(validationEnvironment, "integer", maxIntegerLength,"fraction", maxFractionLength); + return mkError(validationEnvironment, "integer", maxIntegerLength, "fraction", maxFractionLength); } return Collections.emptyList(); @@ -64,4 +64,9 @@ private boolean isOk(BigDecimal bigNum, int maxIntegerLength, int maxFractionLen 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 bf1baff..47bc931 100644 --- a/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java @@ -75,4 +75,9 @@ private String helpWithCurlyBraces(String expression) { } return expression; } + + @Override + protected boolean appliesToListElements() { + return false; // @Expression allow you to perform some things on a list elements individually if you want + } } diff --git a/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java b/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java index 4fcbfc9..3af990d 100644 --- a/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java +++ b/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java @@ -43,4 +43,9 @@ protected List runConstraint(ValidationEnvironment validationEnvir return Collections.emptyList(); } + + @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 e388a84..bdb3d0d 100644 --- a/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java +++ b/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java @@ -27,4 +27,9 @@ public Documentation getDocumentation() { public boolean appliesToType(GraphQLInputType inputType) { return isStringOrID(inputType); } + + @Override + 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 82ff048..031af69 100644 --- a/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java @@ -64,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 43bba5e..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"); } @@ -27,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 e49d17b..c8961c4 100644 --- a/src/main/java/graphql/validation/constraints/standard/PositiveOrZeroConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/PositiveOrZeroConstraint.java @@ -27,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 35fa3a5..5afa90d 100644 --- a/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java @@ -97,4 +97,9 @@ private boolean isOK(BigDecimal argBD, BigDecimal min, BigDecimal max) { } return true; } + + @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 391f489..876205e 100644 --- a/src/main/java/graphql/validation/constraints/standard/SizeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/SizeConstraint.java @@ -28,4 +28,8 @@ public boolean appliesToType(GraphQLInputType inputType) { return isStringOrID(inputType); } + @Override + protected boolean appliesToListElements() { + return true; + } } From b73b0d3dcd90b7e34f528c58d97d79cb6a3862d3 Mon Sep 17 00:00:00 2001 From: Sunny Pelletier Date: Sat, 24 Jul 2021 12:16:44 -0400 Subject: [PATCH 08/20] test --- .../constraints/standard/PatternConstraintTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy index 56bae51..60160a2 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy @@ -25,7 +25,7 @@ class PatternConstraintTest extends BaseConstraintTestSupport { '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 : [String] @Pattern(regexp:"[A-Z]*") ) : ID' | ["ABC", "ABCd"] | 'Pattern;path=/arg[0];val:[ABC, ABCd];\t\nPattern;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' From 84226ece2676a577b5c304c89b7a0a8e56e02ed5 Mon Sep 17 00:00:00 2001 From: Sunny Pelletier Date: Sat, 24 Jul 2021 12:46:17 -0400 Subject: [PATCH 09/20] it works --- .../constraints/standard/PatternConstraintTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy index 60160a2..56bae51 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy @@ -25,7 +25,7 @@ class PatternConstraintTest extends BaseConstraintTestSupport { '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[0];val:[ABC, ABCd];\t\nPattern;path=/arg;val:[ABC, ABCd];\t' + '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' From 23e952db03a12aca2ba26747d251715922cd31a0 Mon Sep 17 00:00:00 2001 From: Sunny Pelletier Date: Sat, 24 Jul 2021 16:03:44 -0400 Subject: [PATCH 10/20] fix tests --- .../NoEmptyBlankConstraintTest.groovy | 59 ++++++++++--------- .../standard/PatternConstraintTest.groovy | 4 +- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/test/groovy/graphql/validation/constraints/standard/NoEmptyBlankConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/NoEmptyBlankConstraintTest.groovy index 0dd29d2..bb037e5 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/NoEmptyBlankConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/NoEmptyBlankConstraintTest.groovy @@ -19,30 +19,30 @@ 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 @@ -65,10 +65,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 +77,16 @@ 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' } } \ 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..1c36fcc 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy @@ -25,9 +25,9 @@ class PatternConstraintTest extends BaseConstraintTestSupport { '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 : [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 | '' From 520e02bbf2e91826daef647ec378342ceace243c Mon Sep 17 00:00:00 2001 From: Sunny Pelletier Date: Sun, 25 Jul 2021 15:21:59 -0400 Subject: [PATCH 11/20] fix stuff --- .../constraints/DirectiveConstraints.java | 7 +++- ....java => ContainerNotEmptyConstraint.java} | 4 +- .../standard/ContainerSizeConstraint.java | 4 +- .../standard/ExpressionConstraint.java | 2 +- .../AbstractDirectiveConstraintTest.groovy | 25 +++++++------ .../standard/ExpressionConstraintTest.groovy | 8 ++-- .../NoEmptyBlankConstraintTest.groovy | 28 +++++++++++++- .../standard/SizeConstraintTest.groovy | 37 ++++++++++++++----- 8 files changed, 84 insertions(+), 31 deletions(-) rename src/main/java/graphql/validation/constraints/standard/{ContainerNotEmptyRule.java => ContainerNotEmptyConstraint.java} (90%) 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/standard/ContainerNotEmptyRule.java b/src/main/java/graphql/validation/constraints/standard/ContainerNotEmptyConstraint.java similarity index 90% rename from src/main/java/graphql/validation/constraints/standard/ContainerNotEmptyRule.java rename to src/main/java/graphql/validation/constraints/standard/ContainerNotEmptyConstraint.java index 219ecb2..e5c4fa0 100644 --- a/src/main/java/graphql/validation/constraints/standard/ContainerNotEmptyRule.java +++ b/src/main/java/graphql/validation/constraints/standard/ContainerNotEmptyConstraint.java @@ -4,8 +4,8 @@ import graphql.validation.constraints.Documentation; import static graphql.schema.GraphQLTypeUtil.isList; -public class ContainerNotEmptyRule extends AbstractNotEmptyRule { - public ContainerNotEmptyRule() { +public class ContainerNotEmptyConstraint extends AbstractNotEmptyRule { + public ContainerNotEmptyConstraint() { super("ContainerNotEmpty"); } diff --git a/src/main/java/graphql/validation/constraints/standard/ContainerSizeConstraint.java b/src/main/java/graphql/validation/constraints/standard/ContainerSizeConstraint.java index 570a7e5..775221b 100644 --- a/src/main/java/graphql/validation/constraints/standard/ContainerSizeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/ContainerSizeConstraint.java @@ -14,9 +14,9 @@ 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") + .example("updateDrivingNotes( drivingNote : String @ContainerSize( min : 1000, max : 100000)) : DriverDetails") .applicableTypeNames("Lists", "Input Objects") - .directiveSDL("directive @Size(min : Int = 0, max : Int = %d, message : String = \"%s\") " + + .directiveSDL("directive @ContainerSize(min : Int = 0, max : Int = %d, message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", Integer.MAX_VALUE, getMessageTemplate()) .build(); diff --git a/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java b/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java index 47bc931..f2d4d6c 100644 --- a/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java @@ -78,6 +78,6 @@ private String helpWithCurlyBraces(String expression) { @Override protected boolean appliesToListElements() { - return false; // @Expression allow you to perform some things on a list elements individually if you want + return false; } } diff --git a/src/test/groovy/graphql/validation/constraints/AbstractDirectiveConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/AbstractDirectiveConstraintTest.groovy index ff17de8..f15f555 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.ContainerSizeConstraint import graphql.validation.constraints.standard.SizeConstraint import spock.lang.Unroll @@ -7,8 +8,11 @@ class AbstractDirectiveConstraintTest extends BaseConstraintTestSupport { @Unroll def "complex object argument constraints"() { + def sizeConstraint = new SizeConstraint() + + def extraSDL = """ + ${sizeConstraint.getDocumentation().getDirectiveSDL()} - def extraSDL = ''' input ProductItem { code : String @Size(max : 5) price : String @Size(max : 3) @@ -18,13 +22,12 @@ 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 - def constraintUnderTest = new SizeConstraint() + def constraintUnderTest = new ContainerSizeConstraint() expect: @@ -33,18 +36,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!] @ContainerSize(max : 2) ) : ID" | [[:], [:], [:]] | 1 | "graphql.validation.Size.message;path=/testArg;val:[[:], [:], [:]];\t" // 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!] @ContainerSize(max : 2) ) : 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!] @ContainerSize(max : 2) ) : 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!] @ContainerSize(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" // 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!] @ContainerSize(max : 2) ) : 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/standard/ExpressionConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/ExpressionConstraintTest.groovy index 30475df..8cbcec7 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/ExpressionConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/ExpressionConstraintTest.groovy @@ -11,6 +11,8 @@ class ExpressionConstraintTest extends BaseConstraintTestSupport { @Unroll def "expression constraints on a field"() { + // TODO - is this useful? Those tests don't pass anymore because `validatedValue` is null. + // Those also validate the arguments, even it the directive is on the field. An alternative for the user could be to wrap the arguments in another abject and put the directive on that wrapped type. DirectiveConstraint ruleUnderTest = new ExpressionConstraint() @@ -27,9 +29,9 @@ class ExpressionConstraintTest extends BaseConstraintTestSupport { where: - 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" + 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/NoEmptyBlankConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/NoEmptyBlankConstraintTest.groovy index bb037e5..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: @@ -47,7 +46,6 @@ class NoEmptyBlankConstraintTest extends BaseConstraintTestSupport { @Unroll def "not empty rule constraints"() { - DirectiveConstraint ruleUnderTest = new NotEmptyRule() expect: @@ -89,4 +87,30 @@ class NoEmptyBlankConstraintTest extends BaseConstraintTestSupport { '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/SizeConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/SizeConstraintTest.groovy index b34f24a..72e01e9 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,34 @@ 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(min: 2)) : ID' | ["a", "b"] | '' + 'field (arg : [String] @ContainerSize(max: 5)) : ID' | [] | '' + 'field (arg : [String] @ContainerSize(max: 2)) : ID' | ["asd", "sdf"] | 'ContainerSize;path=/arg;val:[];\t' } } \ No newline at end of file From b59a3698f99717a7b332f9b7e4dffefd77efa824 Mon Sep 17 00:00:00 2001 From: Sunny Pelletier Date: Sun, 25 Jul 2021 15:25:17 -0400 Subject: [PATCH 12/20] add tests --- .../constraints/standard/SizeConstraintTest.groovy | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/test/groovy/graphql/validation/constraints/standard/SizeConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/SizeConstraintTest.groovy index 72e01e9..a67ae2e 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/SizeConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/SizeConstraintTest.groovy @@ -34,7 +34,7 @@ class SizeConstraintTest extends BaseConstraintTestSupport { "field( arg : ID @Size(min : 5) ) : ID" | null | "" // Lists - 'field (arg : [String] @Size(min: 5)) : ID' | [] | '' // Validated by @ContainerSize + 'field (arg : [String] @Size(min: 5)) : ID' | [] | "" // Validated by @ContainerSize } @Unroll @@ -50,9 +50,11 @@ class SizeConstraintTest extends BaseConstraintTestSupport { fieldDeclaration | argVal | expectedMessage // Lists - 'field (arg : [String] @ContainerSize(min: 2)) : ID' | [] | 'ContainerSize;path=/arg;val:[];\t' - 'field (arg : [String] @ContainerSize(min: 2)) : ID' | ["a", "b"] | '' - 'field (arg : [String] @ContainerSize(max: 5)) : ID' | [] | '' - 'field (arg : [String] @ContainerSize(max: 2)) : ID' | ["asd", "sdf"] | 'ContainerSize;path=/arg;val:[];\t' + '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 From a3be90a61f67bf80a8375564222e2f483df2dd79 Mon Sep 17 00:00:00 2001 From: Sunny Pelletier Date: Sun, 25 Jul 2021 15:28:23 -0400 Subject: [PATCH 13/20] revert small change --- .../constraints/AbstractDirectiveConstraint.java | 10 +++++----- .../standard/ExpressionConstraintTest.groovy | 5 +---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java b/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java index 31354c5..4cfc79b 100644 --- a/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java +++ b/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java @@ -112,6 +112,11 @@ public boolean appliesTo(GraphQLArgument argument, GraphQLFieldDefinition fieldD public List runValidation(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); + // output fields are special + if (validationEnvironment.getValidatedElement() == FIELD) { + return runValidationImpl(validationEnvironment); + } + // // 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 @@ -120,11 +125,6 @@ public List runValidation(ValidationEnvironment validationEnvironm return Collections.emptyList(); } - // output fields are special - if (validationEnvironment.getValidatedElement() == FIELD) { - return runValidationImpl(validationEnvironment); - } - GraphQLInputType inputType = Util.unwrapNonNull(validationEnvironment.getValidatedType()); validationEnvironment = validationEnvironment.transform(b -> b.validatedType(inputType)); diff --git a/src/test/groovy/graphql/validation/constraints/standard/ExpressionConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/ExpressionConstraintTest.groovy index 8cbcec7..835a7dc 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/ExpressionConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/ExpressionConstraintTest.groovy @@ -11,9 +11,6 @@ class ExpressionConstraintTest extends BaseConstraintTestSupport { @Unroll def "expression constraints on a field"() { - // TODO - is this useful? Those tests don't pass anymore because `validatedValue` is null. - // Those also validate the arguments, even it the directive is on the field. An alternative for the user could be to wrap the arguments in another abject and put the directive on that wrapped type. - DirectiveConstraint ruleUnderTest = new ExpressionConstraint() expect: @@ -31,7 +28,7 @@ class ExpressionConstraintTest extends BaseConstraintTestSupport { 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" + 'field( first : Int, last : Int) : ID ' + relayCheck | [first: 10, last: 20] | "Expression;path=/field;val:null;\t" } From 59787c5473f0eaab54abef5c9ca7b04f6f675a6c Mon Sep 17 00:00:00 2001 From: Sunny Pelletier Date: Sun, 25 Jul 2021 15:48:50 -0400 Subject: [PATCH 14/20] add more tests --- .../GraphQLListElementValidator.java | 3 +- .../AssertTrueFalseConstraintTest.groovy | 30 ++++--- .../DecimalMinMaxConstraintTest.groovy | 31 +++++--- .../standard/DigitsConstraintTest.groovy | 48 ++++++----- .../standard/MinMaxConstraintTest.groovy | 41 ++++++---- .../NegativePositiveConstraintTest.groovy | 79 ++++++++++++------- 6 files changed, 143 insertions(+), 89 deletions(-) diff --git a/src/main/java/graphql/validation/constraints/GraphQLListElementValidator.java b/src/main/java/graphql/validation/constraints/GraphQLListElementValidator.java index 3dbfd43..662e1bb 100644 --- a/src/main/java/graphql/validation/constraints/GraphQLListElementValidator.java +++ b/src/main/java/graphql/validation/constraints/GraphQLListElementValidator.java @@ -9,6 +9,7 @@ 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) { @@ -26,7 +27,7 @@ public List runConstraintOnListElements(ValidationEnvironment vali final AtomicInteger index = new AtomicInteger(0); return ((Collection) validatedValue) .stream() - .flatMap((item) -> runConstraintOnElement.apply(validationEnvironment.transform((environment) -> { + .flatMap((item) -> item == null ? Stream.empty() : runConstraintOnElement.apply(validationEnvironment.transform((environment) -> { environment .validatedValue(item) .validatedPath(validationEnvironment.getValidatedPath().segment(index.getAndIncrement())) 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/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 From d19b9ec94789a8c46fcabb8d10ec7ed904915538 Mon Sep 17 00:00:00 2001 From: Sunny Pelletier Date: Sun, 25 Jul 2021 15:57:37 -0400 Subject: [PATCH 15/20] fix all --- .../constraints/GraphQLScalars.java | 2 +- .../constraints/standard/RangeConstraint.java | 43 +++---------------- .../standard/PatternConstraintTest.groovy | 2 + .../standard/RangeConstraintTest.groovy | 5 +++ 4 files changed, 14 insertions(+), 38 deletions(-) diff --git a/src/main/java/graphql/validation/constraints/GraphQLScalars.java b/src/main/java/graphql/validation/constraints/GraphQLScalars.java index 027ac3a..b8dfb69 100644 --- a/src/main/java/graphql/validation/constraints/GraphQLScalars.java +++ b/src/main/java/graphql/validation/constraints/GraphQLScalars.java @@ -20,7 +20,7 @@ public class GraphQLScalars { ); public static final List GRAPHQL_NUMBER_AND_STRING_TYPES = Stream.concat( - Stream.of(Scalars.GraphQLString), + Stream.of(Scalars.GraphQLString, Scalars.GraphQLID), GraphQLScalars.GRAPHQL_NUMBER_TYPES.stream() ).collect(Collectors.toList()); } diff --git a/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java b/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java index 5afa90d..73a412c 100644 --- a/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java @@ -1,33 +1,21 @@ 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.constraints.GraphQLScalars; 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.Scalars.GraphQLString; +import static graphql.validation.constraints.GraphQLScalars.GRAPHQL_NUMBER_AND_STRING_TYPES; public class RangeConstraint extends AbstractDirectiveConstraint { - private static final List SUPPORTED_SCALARS = Stream.concat( - Stream.of(Scalars.GraphQLString), - GraphQLScalars.GRAPHQL_NUMBER_TYPES.stream() - ).collect(Collectors.toList()); - public RangeConstraint() { super("Range"); } - @Override public Documentation getDocumentation() { return Documentation.newDocumentation() @@ -35,7 +23,7 @@ public Documentation getDocumentation() { .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") - .applicableTypes(SUPPORTED_SCALARS) + .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()) @@ -44,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")); @@ -79,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(); @@ -92,10 +63,8 @@ private boolean isOK(BigDecimal argBD, BigDecimal min, BigDecimal max) { if (argBD.compareTo(max) > 0) { return false; } - if (argBD.compareTo(min) < 0) { - return false; - } - return true; + + return argBD.compareTo(min) >= 0; } @Override diff --git a/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy index 1c36fcc..3e932a4 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy @@ -24,6 +24,8 @@ 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[1];val:ABCd;\t' 'field( arg : [ID] @Pattern(regexp:"[A-Z]*") ) : ID' | ["ABC"] | '' 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 From 50424e8bdcd5bfbdab993cea502b8ea8d04f5776 Mon Sep 17 00:00:00 2001 From: Sunny Pelletier Date: Sun, 25 Jul 2021 16:09:02 -0400 Subject: [PATCH 16/20] update --- .../AbstractDirectiveConstraintTest.groovy | 19 ++++++++----------- .../DirectiveConstraintsTest.groovy | 8 ++++---- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/test/groovy/graphql/validation/constraints/AbstractDirectiveConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/AbstractDirectiveConstraintTest.groovy index f15f555..0b76ddb 100644 --- a/src/test/groovy/graphql/validation/constraints/AbstractDirectiveConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/AbstractDirectiveConstraintTest.groovy @@ -1,6 +1,6 @@ package graphql.validation.constraints -import graphql.validation.constraints.standard.ContainerSizeConstraint + import graphql.validation.constraints.standard.SizeConstraint import spock.lang.Unroll @@ -8,11 +8,8 @@ class AbstractDirectiveConstraintTest extends BaseConstraintTestSupport { @Unroll def "complex object argument constraints"() { - def sizeConstraint = new SizeConstraint() def extraSDL = """ - ${sizeConstraint.getDocumentation().getDirectiveSDL()} - input ProductItem { code : String @Size(max : 5) price : String @Size(max : 3) @@ -27,7 +24,7 @@ class AbstractDirectiveConstraintTest extends BaseConstraintTestSupport { // this tests that we can walk a complex tree of types via one specific implementation // but the same applies to all AbstractDirectiveConstraint classes - def constraintUnderTest = new ContainerSizeConstraint() + def constraintUnderTest = new SizeConstraint() expect: @@ -36,18 +33,18 @@ class AbstractDirectiveConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | eSize | expectedMessage + fieldDeclaration | argVal | eSize | expectedMessage // size can handle list elements - "field( testArg : [Product!] @ContainerSize(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!] @ContainerSize(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!] @ContainerSize(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!] @ContainerSize(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!] @ContainerSize(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 } } From 83ca52bba2697ad7be1e2429511138ac1830e455 Mon Sep 17 00:00:00 2001 From: Sunny Pelletier Date: Sun, 25 Jul 2021 16:15:31 -0400 Subject: [PATCH 17/20] update readme with new constraints --- README.md | 58 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 465d37e..fb9ad1e 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 element 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` - 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` + From 213406a6a21d8ec532661be4edef72cd447e3509 Mon Sep 17 00:00:00 2001 From: Sunny Pelletier Date: Sun, 25 Jul 2021 16:17:40 -0400 Subject: [PATCH 18/20] final reade update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fb9ad1e..a4fdcb3 100644 --- a/README.md +++ b/README.md @@ -413,7 +413,7 @@ The element must have a non-zero size. ### @ContainerNotEmpty -The element must have a non-zero size. +The list or input object must have a non-zero size. - Example : `updateAccident( accidentNotes : [Notes]! @ContainerNotEmpty) : DriverDetails` @@ -482,7 +482,7 @@ The string's size must be between the specified `min` and `max` boundaries (incl - Example : `updateDrivingNotes( drivingNote : String @Size( min : 1000, max : 100000)) : DriverDetails` -- Applies to : `String`, `ID` +- 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` From 753d4b9f7c583f97531ac79d6603cc2909f92513 Mon Sep 17 00:00:00 2001 From: Sunny Pelletier Date: Sat, 28 Aug 2021 07:06:47 -0400 Subject: [PATCH 19/20] Delete AbstractListElementValidatorConstraint.java --- ...bstractListElementValidatorConstraint.java | 28 ------------------- 1 file changed, 28 deletions(-) delete mode 100644 src/main/java/graphql/validation/constraints/AbstractListElementValidatorConstraint.java diff --git a/src/main/java/graphql/validation/constraints/AbstractListElementValidatorConstraint.java b/src/main/java/graphql/validation/constraints/AbstractListElementValidatorConstraint.java deleted file mode 100644 index 8635b79..0000000 --- a/src/main/java/graphql/validation/constraints/AbstractListElementValidatorConstraint.java +++ /dev/null @@ -1,28 +0,0 @@ -package graphql.validation.constraints; - -import graphql.GraphQLError; -import graphql.schema.GraphQLInputType; -import graphql.validation.rules.ValidationEnvironment; -import java.util.List; - -public abstract class AbstractListElementValidatorConstraint extends AbstractDirectiveConstraint { - private final GraphQLListElementValidator validator = new GraphQLListElementValidator(); - - public AbstractListElementValidatorConstraint(String name) { - super(name); - } - - @Override - final protected boolean appliesToType(GraphQLInputType inputType) { - return validator.appliesToType(inputType, this::appliesToTypeOrListElement); - } - - protected abstract boolean appliesToTypeOrListElement(GraphQLInputType inputType); - - @Override - final protected List runConstraint(ValidationEnvironment validationEnvironment) { - return validator.runConstraintOnListElements(validationEnvironment, this::runConstraintOnElement); - } - - protected abstract List runConstraintOnElement(ValidationEnvironment validationEnvironment); -} From c1c3459ee55e32fe4a427c3be7f3f3f062e05580 Mon Sep 17 00:00:00 2001 From: Sunny Pelletier Date: Sat, 30 Oct 2021 06:35:49 -0400 Subject: [PATCH 20/20] revert readme change --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b466ba9..a4fdcb3 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ This library provides extended validation of fields and field arguments for [gra ```xml com.graphql-java - graphql-java-extended-validation + graphql-java-extended-validation 17.0.0 pom