diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..10ef831 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index bc9d5f8..d7d199f 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -10,14 +10,17 @@ jobs: env: MAVEN_CENTRAL_USER: ${{ secrets.MAVEN_CENTRAL_USER }} MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + MAVEN_CENTRAL_USER_NEW: ${{ secrets.MAVEN_CENTRAL_USER_NEW }} + MAVEN_CENTRAL_PASSWORD_NEW: ${{ secrets.MAVEN_CENTRAL_PASSWORD_NEW }} MAVEN_CENTRAL_PGP_KEY: ${{ secrets.MAVEN_CENTRAL_PGP_KEY }} steps: - - uses: actions/checkout@v1 - - uses: gradle/wrapper-validation-action@v1 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - uses: actions/checkout@v5 + - uses: gradle/actions/wrapper-validation@v4 + - name: Set up JDK 11 + uses: actions/setup-java@v4 with: - java-version: '8.0.282' + java-version: '11' + distribution: 'corretto' - name: build test and publish run: ./gradlew assemble && ./gradlew check --info && ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository -x check --info --stacktrace diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 13a366a..b3ce092 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -11,11 +11,12 @@ jobs: buildAndTest: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: gradle/wrapper-validation-action@v1 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - uses: actions/checkout@v5 + - uses: gradle/actions/wrapper-validation@v4 + - name: Set up JDK 11 + uses: actions/setup-java@v4 with: - java-version: '8.0.282' + java-version: '11' + distribution: 'corretto' - name: build and test run: ./gradlew assemble && ./gradlew check --info --stacktrace diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b61d755..9466050 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,14 +14,17 @@ jobs: MAVEN_CENTRAL_PGP_KEY: ${{ secrets.MAVEN_CENTRAL_PGP_KEY }} MAVEN_CENTRAL_USER: ${{ secrets.MAVEN_CENTRAL_USER }} MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + MAVEN_CENTRAL_USER_NEW: ${{ secrets.MAVEN_CENTRAL_USER_NEW }} + MAVEN_CENTRAL_PASSWORD_NEW: ${{ secrets.MAVEN_CENTRAL_PASSWORD_NEW }} RELEASE_VERSION: ${{ github.event.inputs.version }} steps: - - uses: actions/checkout@v1 - - uses: gradle/wrapper-validation-action@v1 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - uses: actions/checkout@v5 + - uses: gradle/actions/wrapper-validation@v4 + - name: Set up JDK 11 + uses: actions/setup-java@v4 with: - java-version: '8.0.282' + java-version: '11' + distribution: 'corretto' - name: build test and publish run: ./gradlew assemble && ./gradlew check --info && ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository -x check --info --stacktrace diff --git a/README.md b/README.md index 8d38d69..1cc532b 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ [![Build Status](https://github.com/graphql-java/graphql-java-extended-validation/actions/workflows/master.yml/badge.svg)](https://github.com/graphql-java/graphql-java-extended-validation/actions/workflows/master.yml) -[![Latest Release](https://img.shields.io/maven-central/v/com.graphql-java/graphql-java-extended-validation?versionPrefix=17)](https://maven-badges.herokuapp.com/maven-central/com.graphql-java/graphql-java-extended-validation/) -[![Latest Snapshot](https://img.shields.io/maven-central/v/com.graphql-java/graphql-java-extended-validation?label=maven-central%20snapshot)](https://maven-badges.herokuapp.com/maven-central/com.graphql-java/graphql-java-extended-validation/) +[![Latest Release](https://img.shields.io/maven-central/v/com.graphql-java/graphql-java-extended-validation?versionPrefix=24.)](https://maven-badges.herokuapp.com/maven-central/com.graphql-java/graphql-java-extended-validation/) +[![Latest Snapshot](https://img.shields.io/maven-central/v/com.graphql-java/graphql-java-extended-validation?label=maven-central%20snapshot&versionPrefix=0.0.0)](https://maven-badges.herokuapp.com/maven-central/com.graphql-java/graphql-java-extended-validation/) [![MIT licensed](https://img.shields.io/badge/license-MIT-green)](https://github.com/graphql-java/graphql-java-extended-validation/blob/master/LICENSE.md) @@ -18,29 +18,27 @@ This library provides extended validation of fields and field arguments for [gra com.graphql-java graphql-java-extended-validation - 17.0 + 24.0 ``` ```groovy -compile 'com.graphql-java:graphql-java-extended-validation:17.0' +implementation 'com.graphql-java:graphql-java-extended-validation:24.0' ``` > Note: > -> use 0.0.3 or below for graphql-java 13.x and below +> use 24.0 or above for graphql-java 24.x and above > -> use 14.0.1 or above for graphql-java 14.x and above +> use 22.0 or above for graphql-java 22.x and above > -> use 15.0.1 or above for graphql-java 15.x and above -> -> use 16.0.0 or above for graphql-java 16.x and above -> -> use 17.0 or above for graphql-java 17.x and above -> -> use 17.0-hibernate-validator-6.2.0.Final for graphql-java 17.x and SpringBoot 2.x support +> use 21.0 or above for graphql-java 21.x and above +> +> use 20.0 for graphql-java 20.x and above +> +> use 20.0-hibernate-validator-6.2.0.Final for graphql-java 20.x and SpringBoot 2.x support -It's currently available from Maven central. +The library is currently available on Maven Central. # SDL @Directive constraints @@ -135,14 +133,13 @@ Like javax.validation, this library ships with some default error message templa # I18n Locale Support The validation library aims to offer Internationalisation (18N) of the error messages. When the validation rules -run they are passed in a `java.util.Locale`. A `ResourceBundleMessageInterpolator` can then be used to build up messages +run they are passed in a `java.util.Locale`. A `ResourceBundleMessageInterpolator` can then be used to build up messages that come from I18N bundles. -A `Locale` should be created per graphql execution. However at the time of writing graphql-java does not -pass in a `Locale` per request `ExecutionInput` . A PR exists to fix this and it will be released in v14.0. This -library will then be updated to to take advantage of this. +A `Locale` should be created per graphql execution, and can be passed to `ExecutionInput`. More i18n is being added to graphql-java +and later this library will then be updated to to take advantage of i18n. -In the mean time you can work around this by having the `context`, `source` or `root` implement `graphql.validation.locale.LocaleProvider` and +In the meantime you can work around this by having the `context`, `source` or `root` implement `graphql.validation.locale.LocaleProvider` and the library will extract a `Locale` from that. # Schema Directive Wiring @@ -316,7 +313,7 @@ The element must be a number inside the specified `integer` and `fraction` range - 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` +- SDL : `directive @Digits(integer : Int!, fraction : Int, message : String = "graphql.validation.Digits.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` - Message : `graphql.validation.Digits.message` diff --git a/build.gradle b/build.gradle index 8932608..e6c3fb2 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'java-library' id 'maven-publish' id 'signing' - id "io.github.gradle-nexus.publish-plugin" version "1.0.0" + id "io.github.gradle-nexus.publish-plugin" version "2.0.0" } def getDevelopmentVersion() { @@ -20,33 +20,33 @@ def getDevelopmentVersion() { println "git hash is empty: error: ${error.toString()}" throw new IllegalStateException("git hash could not be determined") } - new SimpleDateFormat('yyyy-MM-dd\'T\'HH-mm-ss').format(new Date()) + "-" + gitHash + "0.0.0-" + new SimpleDateFormat('yyyy-MM-dd\'T\'HH-mm-ss').format(new Date()) + "-" + gitHash } - def releaseVersion = System.env.RELEASE_VERSION version = releaseVersion ? releaseVersion : getDevelopmentVersion() println "Building version = " + version group = 'com.graphql-java' -sourceCompatibility = 1.8 -targetCompatibility = 1.8 +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} repositories { mavenCentral() mavenLocal() } - dependencies { - api "com.graphql-java:graphql-java:18.1" - api "com.graphql-java:graphql-java-extended-scalars:18.1" + api "com.graphql-java:graphql-java:24.2" + api "com.graphql-java:graphql-java-extended-scalars:24.0" api "org.hibernate.validator:hibernate-validator:7.0.1.Final" - api "org.glassfish:jakarta.el:4.0.0" + api "org.glassfish:jakarta.el:4.0.2" - 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' + testImplementation 'org.spockframework:spock-core:2.3-groovy-3.0' + testImplementation 'org.codehaus.groovy:groovy-all:3.0.25' } task sourcesJar(type: Jar, dependsOn: classes) { @@ -85,7 +85,7 @@ publishing { asNode().children().last() + { resolveStrategy = Closure.DELEGATE_FIRST name 'graphql-java-extended-validation' - description 'A library fo extended validation for graphql-java' + description 'A library of extended validation for graphql-java' url 'https://github.com/graphql-java/graphql-java-extended-validation' inceptionYear '2019' @@ -119,9 +119,12 @@ publishing { nexusPublishing { repositories { sonatype { - username = System.env.MAVEN_CENTRAL_USER - password = System.env.MAVEN_CENTRAL_PASSWORD - } + username = System.env.MAVEN_CENTRAL_USER_NEW + password = System.env.MAVEN_CENTRAL_PASSWORD_NEW + // https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/#configuration + nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) + // GraphQL Java does not publish snapshots, but adding this URL for completeness + snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) } } } @@ -131,9 +134,7 @@ signing { sign publishing.publications } - // all publish tasks depend on the build task tasks.withType(PublishToMavenRepository) { dependsOn build } - diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c..7454180 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 05679dc..db9a6b8 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-7.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0..744e882 100755 --- a/gradlew +++ b/gradlew @@ -72,7 +72,7 @@ case "`uname`" in Darwin* ) darwin=true ;; - MINGW* ) + MSYS* | MINGW* ) msys=true ;; NONSTOP* ) diff --git a/gradlew.bat b/gradlew.bat index 107acd3..ac1b06f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,89 +1,89 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java b/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java index 4cfc79b..3cfd0e9 100644 --- a/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java +++ b/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java @@ -4,8 +4,9 @@ import graphql.GraphQLError; import graphql.PublicSpi; import graphql.Scalars; +import graphql.schema.GraphQLAppliedDirective; +import graphql.schema.GraphQLAppliedDirectiveArgument; import graphql.schema.GraphQLArgument; -import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLFieldsContainer; import graphql.schema.GraphQLInputObjectType; @@ -16,6 +17,7 @@ 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; @@ -25,6 +27,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; + import static graphql.schema.GraphQLTypeUtil.isList; import static graphql.validation.rules.ValidationEnvironment.ValidatedElement.FIELD; import static graphql.validation.util.Util.mkMap; @@ -138,17 +142,17 @@ private List runValidationImpl(ValidationEnvironment validationEnv private List runConstraintOnDirectives(ValidationEnvironment validationEnvironment) { List errors = new ArrayList<>(); - List directives = validationEnvironment.getDirectives(); - directives = Util.sort(directives, GraphQLDirective::getName); + List directives = validationEnvironment.getDirectives(); + directives = Util.sort(directives, GraphQLAppliedDirective::getName); - for (GraphQLDirective directive : directives) { + for (GraphQLAppliedDirective directive : directives) { // we get called for arguments and input field and field types which can have multiple directive constraints on them and hence no just for this one boolean isOurDirective = directive.getName().equals(this.getName()); if (!isOurDirective) { continue; } - validationEnvironment = validationEnvironment.transform(b -> b.context(GraphQLDirective.class, directive)); + validationEnvironment = validationEnvironment.transform(b -> b.context(GraphQLAppliedDirective.class, directive)); // // now run the directive rule with this directive instance List ruleErrors = this.runConstrainOnPossibleListElements(validationEnvironment); @@ -195,26 +199,28 @@ protected boolean isOneOfTheseTypes(GraphQLInputType inputType, Collection assertExpectedArgType(argName, "Int")); + } - Number value = GraphQLArgument.getArgumentValue(argument); - if (value == null) { - value = GraphQLArgument.getArgumentDefaultValue(argument); - if (value == null) { - return assertExpectedArgType(argName, "Int"); - } - } - return value.intValue(); + /** + * Returns an optional integer argument from a directive (or its default), or empty Optional if the argument is null. + * + * @param directive the directive to check + * @param argName the argument name + * @return an optional null value + */ + protected Optional getIntArgOpt(GraphQLAppliedDirective directive, String argName) { + return Optional.ofNullable(directive.getArgument(argName)) + .map(GraphQLAppliedDirectiveArgument::getValue) + .map(Number::intValue); } /** @@ -224,17 +230,14 @@ protected int getIntArg(GraphQLDirective directive, String argName) { * @param argName the argument name * @return a non null value */ - protected String getStrArg(GraphQLDirective directive, String argName) { - GraphQLArgument argument = directive.getArgument(argName); + protected String getStrArg(GraphQLAppliedDirective directive, String argName) { + GraphQLAppliedDirectiveArgument argument = directive.getArgument(argName); if (argument == null) { return assertExpectedArgType(argName, "String"); } - String value = GraphQLArgument.getArgumentValue(argument); + String value = argument.getValue(); if (value == null) { - value = GraphQLArgument.getArgumentDefaultValue(argument); - if (value == null) { - return assertExpectedArgType(argName, "String"); - } + return assertExpectedArgType(argName, "String"); } return value; } @@ -246,17 +249,14 @@ protected String getStrArg(GraphQLDirective directive, String argName) { * @param argName the argument name * @return a non null value */ - protected boolean getBoolArg(GraphQLDirective directive, String argName) { - GraphQLArgument argument = directive.getArgument(argName); + protected boolean getBoolArg(GraphQLAppliedDirective directive, String argName) { + GraphQLAppliedDirectiveArgument argument = directive.getArgument(argName); if (argument == null) { return assertExpectedArgType(argName, "Boolean"); } - Object value = GraphQLArgument.getArgumentValue(argument); + Object value = argument.getValue(); if (value == null) { - value = GraphQLArgument.getArgumentDefaultValue(argument); - if (value == null) { - return assertExpectedArgType(argName, "Boolean"); - } + return assertExpectedArgType(argName, "Boolean"); } return Boolean.parseBoolean(String.valueOf(value)); } @@ -268,14 +268,11 @@ protected boolean getBoolArg(GraphQLDirective directive, String argName) { * @param directive the directive to check * @return a non null value */ - protected String getMessageTemplate(GraphQLDirective directive) { + protected String getMessageTemplate(GraphQLAppliedDirective directive) { String msg = null; - GraphQLArgument arg = directive.getArgument("message"); + GraphQLAppliedDirectiveArgument arg = directive.getArgument("message"); if (arg != null) { - msg = GraphQLArgument.getArgumentValue(arg); - if (msg == null) { - msg = GraphQLArgument.getArgumentDefaultValue(arg); - } + msg = arg.getValue(); } if (msg == null) { msg = "graphql.validation." + getName() + ".message"; @@ -310,14 +307,14 @@ protected Map mkMessageParams(Object validatedValue, ValidationE * @param msgParams the map of parameters * @return a list of a single error */ - protected List mkError(ValidationEnvironment validationEnvironment, GraphQLDirective directive, Map msgParams) { + protected List mkError(ValidationEnvironment validationEnvironment, GraphQLAppliedDirective directive, Map msgParams) { String messageTemplate = getMessageTemplate(directive); GraphQLError error = validationEnvironment.getInterpolator().interpolate(messageTemplate, msgParams, validationEnvironment); return singletonList(error); } protected List mkError(ValidationEnvironment validationEnvironment, Object... messageParameters) { - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); + GraphQLAppliedDirective directive = validationEnvironment.getContextObject(GraphQLAppliedDirective.class); String messageTemplate = getMessageTemplate(directive); Object validatedValue = validationEnvironment.getValidatedValue(); GraphQLError error = validationEnvironment.getInterpolator().interpolate(messageTemplate, mkMessageParams(validatedValue, validationEnvironment, messageParameters), validationEnvironment); diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java index 00f6da3..1bedd8b 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java @@ -1,14 +1,16 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.schema.GraphQLDirective; +import graphql.schema.GraphQLAppliedDirective; import graphql.schema.GraphQLInputType; import graphql.schema.GraphQLScalarType; import graphql.validation.constraints.AbstractDirectiveConstraint; import graphql.validation.rules.ValidationEnvironment; + import java.math.BigDecimal; import java.util.Collections; import java.util.List; + import static graphql.validation.constraints.GraphQLScalars.GRAPHQL_NUMBER_AND_STRING_TYPES; abstract class AbstractDecimalMinMaxConstraint extends AbstractDirectiveConstraint { @@ -29,7 +31,7 @@ protected boolean appliesToType(GraphQLInputType inputType) { protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); + GraphQLAppliedDirective directive = validationEnvironment.getContextObject(GraphQLAppliedDirective.class); String value = getStrArg(directive, "value"); boolean inclusive = getBoolArg(directive, "inclusive"); diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java index cdfb40b..e66cad5 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java @@ -1,12 +1,13 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.schema.GraphQLDirective; +import graphql.schema.GraphQLAppliedDirective; 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; @@ -29,7 +30,7 @@ protected boolean appliesToType(GraphQLInputType inputType) { @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); + GraphQLAppliedDirective directive = validationEnvironment.getContextObject(GraphQLAppliedDirective.class); int value = getIntArg(directive, "value"); boolean isOK; diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractSizeConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractSizeConstraint.java index 329ff95..7a61e0e 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractSizeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractSizeConstraint.java @@ -1,10 +1,11 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.schema.GraphQLDirective; +import graphql.schema.GraphQLAppliedDirective; import graphql.schema.GraphQLInputType; import graphql.validation.constraints.AbstractDirectiveConstraint; import graphql.validation.rules.ValidationEnvironment; + import java.util.Collections; import java.util.List; @@ -18,7 +19,7 @@ final protected List runConstraint(ValidationEnvironment validatio Object validatedValue = validationEnvironment.getValidatedValue(); GraphQLInputType argType = validationEnvironment.getValidatedType(); - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); + GraphQLAppliedDirective directive = validationEnvironment.getContextObject(GraphQLAppliedDirective.class); int min = getIntArg(directive, "min"); int max = getIntArg(directive, "max"); diff --git a/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java b/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java index 3a93513..a8f963e 100644 --- a/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java @@ -1,14 +1,17 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.schema.GraphQLDirective; +import graphql.schema.GraphQLAppliedDirective; import graphql.schema.GraphQLInputType; import graphql.validation.constraints.AbstractDirectiveConstraint; import graphql.validation.constraints.Documentation; import graphql.validation.rules.ValidationEnvironment; + import java.math.BigDecimal; import java.util.Collections; import java.util.List; +import java.util.Optional; + import static graphql.validation.constraints.GraphQLScalars.GRAPHQL_NUMBER_AND_STRING_TYPES; public class DigitsConstraint extends AbstractDirectiveConstraint { @@ -19,14 +22,14 @@ public DigitsConstraint() { @Override 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") - .applicableTypes(GRAPHQL_NUMBER_AND_STRING_TYPES) - .directiveSDL("directive @Digits(integer : Int!, fraction : Int!, message : String = \"%s\") " + - "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", - getMessageTemplate()) - .build(); + .messageTemplate(getMessageTemplate()) + .description("The element must be a number inside the specified `integer` and optionally inside `fraction` range.") + .example("buyCar( carCost : Float @Digits(integer : 5, fraction : 2) : DriverDetails") + .applicableTypes(GRAPHQL_NUMBER_AND_STRING_TYPES) + .directiveSDL("directive @Digits(integer : Int!, fraction : Int, message : String = \"%s\") " + + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", + getMessageTemplate()) + .build(); } @Override @@ -39,30 +42,42 @@ public boolean appliesToType(GraphQLInputType inputType) { protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); + GraphQLAppliedDirective directive = validationEnvironment.getContextObject(GraphQLAppliedDirective.class); int maxIntegerLength = getIntArg(directive, "integer"); - int maxFractionLength = getIntArg(directive, "fraction"); + Optional maxFractionLengthOpt = getIntArgOpt(directive, "fraction"); boolean isOk; try { BigDecimal bigNum = asBigDecimal(validatedValue); - isOk = isOk(bigNum, maxIntegerLength, maxFractionLength); + boolean isFractionPartOk = maxFractionLengthOpt + .map(maxFractionLength -> isFractionPartOk(bigNum, maxFractionLength)) + .orElse(true); + + isOk = isFractionPartOk && isIntegerPartOk(bigNum, maxIntegerLength); } catch (NumberFormatException e) { isOk = false; } if (!isOk) { - return mkError(validationEnvironment, "integer", maxIntegerLength, "fraction", maxFractionLength); + return mkError( + validationEnvironment, + "integer", + maxIntegerLength, "fraction", + maxFractionLengthOpt.map(Object::toString).orElse("unlimited") + ); } return Collections.emptyList(); } - private boolean isOk(BigDecimal bigNum, int maxIntegerLength, int maxFractionLength) { - int integerPartLength = bigNum.precision() - bigNum.scale(); - int fractionPartLength = Math.max(bigNum.scale(), 0); + private static boolean isIntegerPartOk(BigDecimal bigNum, int maxIntegerLength) { + final int integerPartLength = bigNum.precision() - bigNum.scale(); + return maxIntegerLength >= integerPartLength; + } - return maxIntegerLength >= integerPartLength && maxFractionLength >= fractionPartLength; + private static boolean isFractionPartOk(BigDecimal bigNum, int maxFractionLength) { + final int fractionPartLength = Math.max(bigNum.scale(), 0); + return maxFractionLength >= fractionPartLength; } @Override diff --git a/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java b/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java index f2d4d6c..3f003ff 100644 --- a/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java @@ -1,7 +1,7 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.schema.GraphQLDirective; +import graphql.schema.GraphQLAppliedDirective; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLFieldsContainer; import graphql.schema.GraphQLInputType; @@ -50,7 +50,7 @@ public boolean appliesTo(GraphQLFieldDefinition fieldDefinition, GraphQLFieldsCo @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); + GraphQLAppliedDirective directive = validationEnvironment.getContextObject(GraphQLAppliedDirective.class); String expression = helpWithCurlyBraces(getStrArg(directive, "value")); Map variables = StandardELVariables.standardELVars(validationEnvironment); diff --git a/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java b/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java index f377b83..b670bf8 100644 --- a/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java @@ -2,6 +2,7 @@ import graphql.GraphQLError; import graphql.Scalars; +import graphql.schema.GraphQLAppliedDirective; import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLInputType; import graphql.validation.constraints.AbstractDirectiveConstraint; @@ -47,7 +48,7 @@ protected List runConstraint(ValidationEnvironment validationEnvir String strValue = String.valueOf(validatedValue); - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); + GraphQLAppliedDirective directive = validationEnvironment.getContextObject(GraphQLAppliedDirective.class); String patternArg = getStrArg(directive, "regexp"); Pattern pattern = cachedPattern(patternArg); diff --git a/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java b/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java index 73a412c..1ad9ad6 100644 --- a/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java @@ -1,14 +1,16 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.schema.GraphQLDirective; +import graphql.schema.GraphQLAppliedDirective; import graphql.schema.GraphQLInputType; import graphql.validation.constraints.AbstractDirectiveConstraint; import graphql.validation.constraints.Documentation; import graphql.validation.rules.ValidationEnvironment; + import java.math.BigDecimal; import java.util.Collections; import java.util.List; + import static graphql.validation.constraints.GraphQLScalars.GRAPHQL_NUMBER_AND_STRING_TYPES; public class RangeConstraint extends AbstractDirectiveConstraint { @@ -40,7 +42,7 @@ public boolean appliesToType(GraphQLInputType inputType) { protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); + GraphQLAppliedDirective directive = validationEnvironment.getContextObject(GraphQLAppliedDirective.class); BigDecimal min = asBigDecimal(getIntArg(directive, "min")); BigDecimal max = asBigDecimal(getIntArg(directive, "max")); diff --git a/src/main/java/graphql/validation/interpolation/ResourceBundleMessageInterpolator.java b/src/main/java/graphql/validation/interpolation/ResourceBundleMessageInterpolator.java index 0896a8b..99df8c1 100644 --- a/src/main/java/graphql/validation/interpolation/ResourceBundleMessageInterpolator.java +++ b/src/main/java/graphql/validation/interpolation/ResourceBundleMessageInterpolator.java @@ -4,17 +4,9 @@ import graphql.GraphQLError; import graphql.GraphqlErrorBuilder; import graphql.execution.ResultPath; -import graphql.schema.GraphQLDirective; +import graphql.schema.GraphQLAppliedDirective; import graphql.validation.el.StandardELVariables; import graphql.validation.rules.ValidationEnvironment; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; -import java.util.LinkedHashMap; -import java.util.Locale; -import java.util.Map; -import java.util.MissingResourceException; -import java.util.Optional; -import java.util.ResourceBundle; import jakarta.validation.Constraint; import jakarta.validation.Path; import jakarta.validation.Payload; @@ -27,6 +19,16 @@ import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel; import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator; import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.Optional; +import java.util.ResourceBundle; + import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.FIELD; @@ -70,7 +72,7 @@ public class ResourceBundleMessageInterpolator implements MessageInterpolator { @SuppressWarnings("unused") protected ErrorClassification buildErrorClassification(String messageTemplate, Map messageParams, ValidationEnvironment validationEnvironment) { ResultPath fieldOrArgumentPath = validationEnvironment.getValidatedPath(); - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); + GraphQLAppliedDirective directive = validationEnvironment.getContextObject(GraphQLAppliedDirective.class); return new ValidationErrorType(fieldOrArgumentPath, directive); } @@ -183,9 +185,9 @@ private org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterp private static class ValidationErrorType implements ErrorClassification { private final ResultPath fieldOrArgumentPath; - private final GraphQLDirective directive; + private final GraphQLAppliedDirective directive; - ValidationErrorType(ResultPath fieldOrArgumentPath, GraphQLDirective directive) { + ValidationErrorType(ResultPath fieldOrArgumentPath, GraphQLAppliedDirective directive) { this.fieldOrArgumentPath = fieldOrArgumentPath; this.directive = directive; } diff --git a/src/main/java/graphql/validation/locale/LocaleUtil.java b/src/main/java/graphql/validation/locale/LocaleUtil.java index 13fffdb..11eabcd 100644 --- a/src/main/java/graphql/validation/locale/LocaleUtil.java +++ b/src/main/java/graphql/validation/locale/LocaleUtil.java @@ -23,9 +23,9 @@ public class LocaleUtil { */ public static Locale determineLocale(DataFetchingEnvironment environment, Locale defaultLocale) { // - // in a future version of graphql java the DFE will have the Locale but in the mean time - Locale locale; - locale = extractLocale(environment); + // The DFE has a locale now, but we retain the old look-ups for backwards compat reasons + // + Locale locale = environment.getLocale(); if (locale == null) { locale = extractLocale(environment.getContext()); if (locale == null) { diff --git a/src/main/java/graphql/validation/rules/TargetedValidationRules.java b/src/main/java/graphql/validation/rules/TargetedValidationRules.java index b12a8aa..f0a5b20 100644 --- a/src/main/java/graphql/validation/rules/TargetedValidationRules.java +++ b/src/main/java/graphql/validation/rules/TargetedValidationRules.java @@ -5,8 +5,8 @@ import graphql.PublicApi; import graphql.execution.ResultPath; import graphql.schema.DataFetchingEnvironment; +import graphql.schema.GraphQLAppliedDirective; import graphql.schema.GraphQLArgument; -import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLDirectiveContainer; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLInputObjectField; @@ -112,7 +112,7 @@ public List runValidationRules(DataFetchingEnvironment env, Messag .validatedType(inputType) .validatedValue(argValue) .validatedPath(fieldPath.segment(fieldArg.getName())) - .directives(fieldArg.getDirectives()) + .directives(fieldArg.getAppliedDirectives()) .messageInterpolator(interpolator) .locale(defaultLocale) .build(); @@ -173,7 +173,7 @@ private List walkObjectArg(ValidationRule rule, ValidationEnvironm .validatedPath(newPath) .validatedValue(validatedValue) .validatedType(fieldType) - .directives(inputField.getDirectives()) + .directives(inputField.getAppliedDirectives()) .validatedElement(INPUT_OBJECT_FIELD) ); @@ -187,11 +187,11 @@ private List walkListArg(ValidationRule rule, ValidationEnvironmen List errors = new ArrayList<>(); GraphQLInputType listItemType = Util.unwrapOneAndAllNonNull(argumentType); - List directives; + List directives; if (!(listItemType instanceof GraphQLDirectiveContainer)) { directives = Collections.emptyList(); } else { - directives = ((GraphQLDirectiveContainer) listItemType).getDirectives(); + directives = ((GraphQLDirectiveContainer) listItemType).getAppliedDirectives(); } int ix = 0; for (Object value : objectList) { diff --git a/src/main/java/graphql/validation/rules/ValidationEnvironment.java b/src/main/java/graphql/validation/rules/ValidationEnvironment.java index 088063b..ae3c4b1 100644 --- a/src/main/java/graphql/validation/rules/ValidationEnvironment.java +++ b/src/main/java/graphql/validation/rules/ValidationEnvironment.java @@ -1,11 +1,12 @@ package graphql.validation.rules; +import graphql.GraphQLContext; import graphql.PublicApi; import graphql.execution.ResultPath; import graphql.language.SourceLocation; import graphql.schema.DataFetchingEnvironment; +import graphql.schema.GraphQLAppliedDirective; import graphql.schema.GraphQLArgument; -import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLFieldsContainer; import graphql.schema.GraphQLInputType; @@ -55,7 +56,9 @@ public enum ValidatedElement { private final Object validatedValue; private final GraphQLInputType validatedType; private final ValidatedElement validatedElement; - private final List directives; + + private final GraphQLContext graphQLContext; + private final List directives; private ValidationEnvironment(Builder builder) { this.argument = builder.argument; @@ -71,6 +74,7 @@ private ValidationEnvironment(Builder builder) { this.location = builder.location; this.validatedValue = builder.validatedValue; this.validatedElement = builder.validatedElement; + this.graphQLContext = builder.graphQLContext; this.directives = builder.directives; } @@ -131,10 +135,14 @@ public ValidatedElement getValidatedElement() { return validatedElement; } - public List getDirectives() { + public List getDirectives() { return directives; } + public GraphQLContext getGraphQLContext() { + return graphQLContext; + } + public ValidationEnvironment transform(Consumer builderConsumer) { Builder builder = newValidationEnvironment().validationEnvironment(this); builderConsumer.accept(builder); @@ -155,7 +163,8 @@ public static class Builder { private Object validatedValue; private GraphQLInputType validatedType; private ValidatedElement validatedElement; - private List directives = Collections.emptyList(); + private List directives = Collections.emptyList(); + private GraphQLContext graphQLContext = GraphQLContext.getDefault(); public Builder validationEnvironment(ValidationEnvironment validationEnvironment) { this.argument = validationEnvironment.argument; @@ -172,18 +181,20 @@ public Builder validationEnvironment(ValidationEnvironment validationEnvironment this.validatedValue = validationEnvironment.validatedValue; this.validatedElement = validationEnvironment.validatedElement; this.directives = validationEnvironment.directives; + this.graphQLContext = validationEnvironment.graphQLContext; return this; } public Builder dataFetchingEnvironment(DataFetchingEnvironment dataFetchingEnvironment) { - fieldsContainer(dataFetchingEnvironment.getExecutionStepInfo().getFieldContainer()); + fieldsContainer(dataFetchingEnvironment.getExecutionStepInfo().getObjectType()); fieldDefinition(dataFetchingEnvironment.getFieldDefinition()); - directives(dataFetchingEnvironment.getFieldDefinition().getDirectives()); + directives(dataFetchingEnvironment.getFieldDefinition().getAppliedDirectives()); executionPath(dataFetchingEnvironment.getExecutionStepInfo().getPath()); validatedPath(dataFetchingEnvironment.getExecutionStepInfo().getPath()); location(dataFetchingEnvironment.getField().getSourceLocation()); argumentValues(dataFetchingEnvironment.getArguments()); validatedElement(ValidatedElement.FIELD); + graphQLContext(dataFetchingEnvironment.getGraphQlContext()); return this; } @@ -252,7 +263,12 @@ public Builder locale(Locale locale) { return this; } - public Builder directives(List directives) { + public Builder graphQLContext(GraphQLContext graphQLContext) { + this.graphQLContext = graphQLContext; + return this; + } + + public Builder directives(List directives) { this.directives = directives; return this; } diff --git a/src/main/java/graphql/validation/rules/ValidationRules.java b/src/main/java/graphql/validation/rules/ValidationRules.java index a7fdc43..b23f9fe 100644 --- a/src/main/java/graphql/validation/rules/ValidationRules.java +++ b/src/main/java/graphql/validation/rules/ValidationRules.java @@ -97,7 +97,7 @@ public List getRulesFor(GraphQLFieldDefinition fieldDefinition, * @return a list of zero or more input data validation errors */ public List runValidationRules(DataFetchingEnvironment env) { - GraphQLFieldsContainer fieldsContainer = env.getExecutionStepInfo().getFieldContainer(); + GraphQLFieldsContainer fieldsContainer = env.getExecutionStepInfo().getObjectType(); GraphQLFieldDefinition fieldDefinition = env.getFieldDefinition(); MessageInterpolator messageInterpolator = this.getMessageInterpolator(); diff --git a/src/main/java/graphql/validation/schemawiring/FieldValidatorDataFetcher.java b/src/main/java/graphql/validation/schemawiring/FieldValidatorDataFetcher.java index a02836a..58c1769 100644 --- a/src/main/java/graphql/validation/schemawiring/FieldValidatorDataFetcher.java +++ b/src/main/java/graphql/validation/schemawiring/FieldValidatorDataFetcher.java @@ -1,11 +1,14 @@ package graphql.validation.schemawiring; import graphql.GraphQLError; -import graphql.schema.*; +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLFieldsContainer; +import graphql.schema.GraphQLType; 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; diff --git a/src/main/java/graphql/validation/schemawiring/TrivialFieldValidatorDataFetcher.java b/src/main/java/graphql/validation/schemawiring/TrivialFieldValidatorDataFetcher.java new file mode 100644 index 0000000..1a6a98c --- /dev/null +++ b/src/main/java/graphql/validation/schemawiring/TrivialFieldValidatorDataFetcher.java @@ -0,0 +1,15 @@ +package graphql.validation.schemawiring; + +import graphql.TrivialDataFetcher; +import graphql.schema.DataFetcher; +import graphql.validation.interpolation.MessageInterpolator; +import graphql.validation.rules.OnValidationErrorStrategy; +import graphql.validation.rules.ValidationRules; + +import java.util.Locale; + +public class TrivialFieldValidatorDataFetcher extends FieldValidatorDataFetcher implements TrivialDataFetcher { + public TrivialFieldValidatorDataFetcher(OnValidationErrorStrategy errorStrategy, MessageInterpolator messageInterpolator, DataFetcher defaultDataFetcher, Locale defaultLocale, ValidationRules validationRules) { + super(errorStrategy, messageInterpolator, defaultDataFetcher, defaultLocale, validationRules); + } +} diff --git a/src/main/java/graphql/validation/schemawiring/ValidationSchemaWiring.java b/src/main/java/graphql/validation/schemawiring/ValidationSchemaWiring.java index 36cf6ba..65d0a23 100644 --- a/src/main/java/graphql/validation/schemawiring/ValidationSchemaWiring.java +++ b/src/main/java/graphql/validation/schemawiring/ValidationSchemaWiring.java @@ -1,9 +1,11 @@ package graphql.validation.schemawiring; import graphql.PublicApi; +import graphql.TrivialDataFetcher; import graphql.schema.DataFetcher; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLFieldsContainer; +import graphql.schema.GraphQLObjectType; import graphql.schema.idl.SchemaDirectiveWiring; import graphql.schema.idl.SchemaDirectiveWiringEnvironment; import graphql.validation.interpolation.MessageInterpolator; @@ -34,15 +36,23 @@ public ValidationSchemaWiring(ValidationRules ruleCandidates) { public GraphQLFieldDefinition onField(SchemaDirectiveWiringEnvironment env) { GraphQLFieldsContainer fieldsContainer = env.getFieldsContainer(); GraphQLFieldDefinition fieldDefinition = env.getFieldDefinition(); - + TargetedValidationRules rules = ruleCandidates.buildRulesFor(fieldDefinition, fieldsContainer); + if (rules.isEmpty()) { + return fieldDefinition; + } + if (! (fieldsContainer instanceof GraphQLObjectType)) { + // only object type fields can have data fetchers + return fieldDefinition; + } + GraphQLObjectType graphQLObjectType = (GraphQLObjectType) fieldsContainer; OnValidationErrorStrategy errorStrategy = ruleCandidates.getOnValidationErrorStrategy(); MessageInterpolator messageInterpolator = ruleCandidates.getMessageInterpolator(); Locale locale = ruleCandidates.getLocale(); - final DataFetcher currentDF = env.getCodeRegistry().getDataFetcher(fieldsContainer, fieldDefinition); + final DataFetcher currentDF = env.getCodeRegistry().getDataFetcher(graphQLObjectType, fieldDefinition); final DataFetcher newDF = buildValidatingDataFetcher(errorStrategy, messageInterpolator, currentDF, locale); - env.getCodeRegistry().dataFetcher(fieldsContainer, fieldDefinition, newDF); + env.getCodeRegistry().dataFetcher(graphQLObjectType, fieldDefinition, newDF); return fieldDefinition; } @@ -51,6 +61,16 @@ private DataFetcher buildValidatingDataFetcher(OnValidationErrorStrategy MessageInterpolator messageInterpolator, DataFetcher currentDF, final Locale defaultLocale) { + if (currentDF instanceof TrivialDataFetcher) { + return new TrivialFieldValidatorDataFetcher( + errorStrategy, + messageInterpolator, + currentDF, + defaultLocale, + ruleCandidates + ); + } + return new FieldValidatorDataFetcher( errorStrategy, messageInterpolator, diff --git a/src/main/java/graphql/validation/util/DirectivesAndTypeWalker.java b/src/main/java/graphql/validation/util/DirectivesAndTypeWalker.java index 53d7e07..ae46f3f 100644 --- a/src/main/java/graphql/validation/util/DirectivesAndTypeWalker.java +++ b/src/main/java/graphql/validation/util/DirectivesAndTypeWalker.java @@ -1,14 +1,7 @@ package graphql.validation.util; import graphql.Internal; -import graphql.schema.GraphQLArgument; -import graphql.schema.GraphQLDirective; -import graphql.schema.GraphQLDirectiveContainer; -import graphql.schema.GraphQLInputObjectField; -import graphql.schema.GraphQLInputObjectType; -import graphql.schema.GraphQLInputType; -import graphql.schema.GraphQLList; -import graphql.schema.GraphQLTypeUtil; +import graphql.schema.*; import java.util.HashMap; import java.util.List; @@ -20,16 +13,16 @@ public class DirectivesAndTypeWalker { private final Map seenTypes = new HashMap<>(); - public boolean isSuitable(GraphQLArgument argument, BiFunction isSuitable) { + public boolean isSuitable(GraphQLArgument argument, BiFunction isSuitable) { GraphQLInputType inputType = argument.getType(); - List directives = argument.getDirectives(); + List directives = argument.getAppliedDirectives(); return walkInputType(inputType, directives, isSuitable); } - private boolean walkInputType(GraphQLInputType inputType, List directives, BiFunction isSuitable) { + private boolean walkInputType(GraphQLInputType inputType, List directives, BiFunction isSuitable) { String typeName = GraphQLTypeUtil.unwrapAll(inputType).getName(); GraphQLInputType unwrappedInputType = Util.unwrapNonNull(inputType); - for (GraphQLDirective directive : directives) { + for (GraphQLAppliedDirective directive : directives) { if (isSuitable.apply(unwrappedInputType, directive)) { return seen(typeName,true); } @@ -43,7 +36,7 @@ private boolean walkInputType(GraphQLInputType inputType, List for (GraphQLInputObjectField inputField : inputObjType.getFieldDefinitions()) { inputType = inputField.getType(); - directives = inputField.getDirectives(); + directives = inputField.getAppliedDirectives(); if (walkInputType(inputType, directives, isSuitable)) { return seen(typeName,true); @@ -53,7 +46,7 @@ private boolean walkInputType(GraphQLInputType inputType, List if (unwrappedInputType instanceof GraphQLList) { GraphQLInputType innerListType = Util.unwrapOneAndAllNonNull(unwrappedInputType); if (innerListType instanceof GraphQLDirectiveContainer) { - directives = ((GraphQLDirectiveContainer) innerListType).getDirectives(); + directives = ((GraphQLDirectiveContainer) innerListType).getAppliedDirectives(); if (walkInputType(innerListType, directives, isSuitable)) { return seen(typeName,true); } diff --git a/src/test/groovy/ELDiscover.java b/src/test/groovy/ELDiscover.java index e09e511..7c280b1 100644 --- a/src/test/groovy/ELDiscover.java +++ b/src/test/groovy/ELDiscover.java @@ -3,9 +3,6 @@ import jakarta.el.ExpressionFactory; import jakarta.el.StandardELContext; import jakarta.el.ValueExpression; -import jakarta.validation.MessageInterpolator; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotNull; import org.hibernate.validator.constraints.Range; import org.hibernate.validator.internal.engine.MessageInterpolatorContext; import org.hibernate.validator.internal.engine.path.PathImpl; @@ -17,6 +14,9 @@ import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel; import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator; +import jakarta.validation.MessageInterpolator; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotNull; import java.lang.reflect.Method; import java.util.Collections; import java.util.HashMap; diff --git a/src/test/groovy/graphql/validation/TestUtil.groovy b/src/test/groovy/graphql/validation/TestUtil.groovy index 4fe940b..f642efb 100644 --- a/src/test/groovy/graphql/validation/TestUtil.groovy +++ b/src/test/groovy/graphql/validation/TestUtil.groovy @@ -15,6 +15,7 @@ import graphql.language.Type import graphql.parser.Parser import graphql.schema.Coercing import graphql.schema.DataFetcher +import graphql.schema.GraphQLAppliedDirective import graphql.schema.GraphQLArgument import graphql.schema.GraphQLDirective import graphql.schema.GraphQLFieldDefinition @@ -189,8 +190,8 @@ class TestUtil { definition) } - static GraphQLDirective mockDirective(String name) { - new GraphQLDirective(name, name, EnumSet.noneOf(DirectiveLocation.class), Collections.emptyList(), false, false, false) + static GraphQLAppliedDirective mockDirective(String name) { + new GraphQLAppliedDirective(name, name, EnumSet.noneOf(DirectiveLocation.class), Collections.emptyList(), false, false, false) } static TypeRuntimeWiring mockTypeRuntimeWiring(String typeName, boolean withResolver) { diff --git a/src/test/groovy/graphql/validation/constraints/BaseConstraintTestSupport.groovy b/src/test/groovy/graphql/validation/constraints/BaseConstraintTestSupport.groovy index 1c3606e..801c654 100644 --- a/src/test/groovy/graphql/validation/constraints/BaseConstraintTestSupport.groovy +++ b/src/test/groovy/graphql/validation/constraints/BaseConstraintTestSupport.groovy @@ -2,18 +2,12 @@ package graphql.validation.constraints import graphql.GraphQLError import graphql.GraphqlErrorBuilder -import graphql.execution.ResultPath import graphql.execution.ExecutionStepInfo import graphql.execution.MergedField +import graphql.execution.ResultPath import graphql.language.Field import graphql.language.SourceLocation -import graphql.schema.DataFetchingEnvironmentImpl -import graphql.schema.GraphQLArgument -import graphql.schema.GraphQLDirective -import graphql.schema.GraphQLFieldDefinition -import graphql.schema.GraphQLFieldsContainer -import graphql.schema.GraphQLObjectType -import graphql.schema.GraphQLSchema +import graphql.schema.* import graphql.validation.TestUtil import graphql.validation.interpolation.MessageInterpolator import graphql.validation.rules.TargetedValidationRules @@ -102,7 +96,7 @@ class BaseConstraintTestSupport extends Specification { .fieldsContainer(fieldsContainer) .executionPath(ResultPath.rootPath().segment(fieldDefinition.getName())) .validatedPath(ResultPath.rootPath().segment(argName)) - .context(GraphQLDirective.class, argUnderTest.getDirective(targetDirective)) + .context(GraphQLAppliedDirective.class, argUnderTest.getAppliedDirective(targetDirective)) .messageInterpolator(interpolator) .build() ruleEnvironment @@ -120,8 +114,8 @@ class BaseConstraintTestSupport extends Specification { .executionPath(path) .validatedElement(ValidationEnvironment.ValidatedElement.FIELD) .validatedPath(path) - .directives(fieldDefinition.getDirectives()) - .context(GraphQLDirective.class, fieldDefinition.getDirective(targetDirective.name)) + .directives(fieldDefinition.getAppliedDirectives()) + .context(GraphQLAppliedDirective.class, fieldDefinition.getAppliedDirective(targetDirective.name)) .messageInterpolator(interpolator) .build() ruleEnvironment diff --git a/src/test/groovy/graphql/validation/constraints/standard/DigitsConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/DigitsConstraintTest.groovy index 772ec36..701746c 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/DigitsConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/DigitsConstraintTest.groovy @@ -23,6 +23,7 @@ class DigitsConstraintTest extends BaseConstraintTestSupport { '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) ) : ID' | Double.valueOf("500.2345678") | '' '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' diff --git a/src/test/groovy/graphql/validation/locale/LocaleUtilTest.groovy b/src/test/groovy/graphql/validation/locale/LocaleUtilTest.groovy index b01b5eb..cda044b 100644 --- a/src/test/groovy/graphql/validation/locale/LocaleUtilTest.groovy +++ b/src/test/groovy/graphql/validation/locale/LocaleUtilTest.groovy @@ -1,7 +1,7 @@ package graphql.validation.locale +import graphql.GraphQLContext import graphql.GraphQLError -import graphql.GraphqlErrorBuilder import graphql.execution.ExecutionStepInfo import graphql.execution.MergedField import graphql.schema.DataFetchingEnvironment @@ -18,6 +18,8 @@ import graphql.validation.rules.ValidationEnvironment import graphql.validation.rules.ValidationRule import spock.lang.Specification +import static graphql.GraphqlErrorBuilder.newError + class LocaleUtilTest extends Specification { def directiveRules = DirectiveConstraints.newDirectiveConstraints().build() @@ -99,7 +101,10 @@ class LocaleUtilTest extends Specification { @Override List runValidation(ValidationEnvironment validationEnvironment) { - return [GraphqlErrorBuilder.newError().message("Locale=" + validationEnvironment.getLocale().getCountry()).build()] + return [ + newError().message("Locale=" + validationEnvironment.getLocale().getCountry()).build(), + newError().message("Context=" + (validationEnvironment.getGraphQLContext() != null)).build() + ] } } @@ -176,5 +181,31 @@ class LocaleUtilTest extends Specification { errors = targetedValidationRules.runValidationRules(dfe, new ResourceBundleMessageInterpolator(), Locale.CHINA) then: errors[0].message == "Locale=GB" + + // use DFE direct + when: + + dfe = DataFetchingEnvironmentImpl.newDataFetchingEnvironment(dfe) + .locale(Locale.UK) + .build() + + errors = targetedValidationRules.runValidationRules(dfe, new ResourceBundleMessageInterpolator(), Locale.CHINA) + then: + errors[0].message == "Locale=GB" + errors[1].message == "Context=false" + + // sneaking in a test that graphql context gets picked up here + // cheeky I know but the setup of a clean test in the exact right place is not worth it + when: + + dfe = DataFetchingEnvironmentImpl.newDataFetchingEnvironment(dfe) + .locale(Locale.UK) + .graphQLContext(GraphQLContext.of([x: "present"])) + .build() + + errors = targetedValidationRules.runValidationRules(dfe, new ResourceBundleMessageInterpolator(), Locale.CHINA) + then: + errors[0].message == "Locale=GB" + errors[1].message == "Context=true" } } diff --git a/src/test/resources/simplelogger.properties b/src/test/resources/simplelogger.properties deleted file mode 100644 index 21ed7fe..0000000 --- a/src/test/resources/simplelogger.properties +++ /dev/null @@ -1,35 +0,0 @@ -# SLF4J's SimpleLogger configuration file -# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. - -# Default logging detail level for all instances of SimpleLogger. -# Must be one of ("trace", "debug", "info", "warn", or "error"). -# If not specified, defaults to "info". - -org.slf4j.simpleLogger.defaultLogLevel=info - -# Logging detail level for a SimpleLogger instance named "xxxxx". -# Must be one of ("trace", "debug", "info", "warn", or "error"). -# If not specified, the default logging detail level is used. -org.slf4j.simpleLogger.log.graphql=info - -# Set to true if you want the current date and time to be included in output messages. -# Default is false, and will output the number of milliseconds elapsed since startup. -#org.slf4j.simpleLogger.showDateTime=false - -# The date and time format to be used in the output messages. -# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. -# If the format is not specified or is invalid, the default format is used. -# The default format is yyyy-MM-dd HH:mm:ss:SSS Z. -#org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z - -# Set to true if you want to output the current thread name. -# Defaults to true. -#org.slf4j.simpleLogger.showThreadName=true - -# Set to true if you want the Logger instance name to be included in output messages. -# Defaults to true. -#org.slf4j.simpleLogger.showLogName=true - -# Set to true if you want the last component of the name to be included in output messages. -# Defaults to false. -#org.slf4j.simpleLogger.showShortLogName=false \ No newline at end of file