diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..99000a4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,14 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Please provide a code example or even better a test to reproduce the bug. diff --git a/.github/ISSUE_TEMPLATE/other-issue.md b/.github/ISSUE_TEMPLATE/other-issue.md new file mode 100644 index 0000000..774bf32 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/other-issue.md @@ -0,0 +1,10 @@ +--- +name: Other issue +about: Generic issue +title: '' +labels: '' +assignees: '' + +--- + +If you have a general question or seeking advice how to use something please create a new topic in our GitHub discussions forum: https://github.com/graphql-java/graphql-java/discussions. Thanks. 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 new file mode 100644 index 0000000..9c8d3b9 --- /dev/null +++ b/.github/workflows/master.yml @@ -0,0 +1,26 @@ +name: Master Build and Publish +# For master push: Builds and publishes the development version to bintray/maven +on: + push: + branches: + - master +jobs: + buildAndPublish: + runs-on: ubuntu-latest + 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@v5 + - uses: gradle/actions/wrapper-validation@v5 + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + 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 new file mode 100644 index 0000000..a841559 --- /dev/null +++ b/.github/workflows/pull_request.yml @@ -0,0 +1,22 @@ +name: Pull Request Build +# For pull requests: builds and test +on: + push: + branches: + - '!master' + pull_request: + branches: + - master +jobs: + buildAndTest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: gradle/actions/wrapper-validation@v5 + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + 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 new file mode 100644 index 0000000..43f0035 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,30 @@ +name: Manual Release Build +# Release builds +on: + workflow_dispatch: + inputs: + version: + description: 'the version to be released' + required: true + +jobs: + buildAndPublish: + runs-on: ubuntu-latest + env: + 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@v5 + - uses: gradle/actions/wrapper-validation@v5 + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + 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/.travis.yml b/.travis.yml deleted file mode 100644 index ce92ecb..0000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -dist: trusty -language: java -sudo: false - -jdk: - - oraclejdk8 - -addons: - apt: - packages: - - oracle-java8-installer - -notifications: - email: false - -branches: - only: - - master - -script: -- ./gradlew clean test diff --git a/README.md b/README.md index 504ae12..1cc532b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # Extended Validation for graphql-java -[![Build Status](https://api.travis-ci.org/graphql-java/graphql-java-extended-validation.svg?branch=master)](https://api.travis-ci.org/graphql-java/graphql-java-extended-validation.svg?branch=master)   -[![Download](https://api.bintray.com/packages/graphql-java/graphql-java/graphql-java-extended-validation/images/download.svg) ](https://bintray.com/graphql-java/graphql-java/graphql-java-extended-validation/_latestVersion) -[![MIT licensed](https://img.shields.io/badge/license-MIT-green)](https://github.com/graphql-java/graphql-java-extended-validation/blob/master/LICENSE)   +[![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=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) @@ -17,26 +18,27 @@ This library provides extended validation of fields and field arguments for [gra com.graphql-java graphql-java-extended-validation - 16.0.0 - pom + 24.0 ``` ```groovy -compile 'com.graphql-java:graphql-java-extended-validation:16.0.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 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 -Its currently available from JCenter repo and Maven central. +The library is currently available on Maven Central. # SDL @Directive constraints @@ -131,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 @@ -258,7 +259,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` @@ -271,7 +272,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` @@ -284,7 +285,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` @@ -297,7 +298,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` @@ -310,9 +311,9 @@ 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` +- SDL : `directive @Digits(integer : Int!, fraction : Int, message : String = "graphql.validation.Digits.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` - Message : `graphql.validation.Digits.message` @@ -337,7 +338,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` @@ -350,7 +351,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` @@ -363,7 +364,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` @@ -376,7 +377,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` @@ -389,7 +390,7 @@ The String must contain at least one non-whitespace character, according to Java - Example : `updateAccident( accidentNotes : String @NotBlank) : DriverDetails` -- Applies to : `String`, `ID` +- Applies to : `String`, `ID`, `Lists` - SDL : `directive @NotBlank(message : String = "graphql.validation.NotBlank.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` @@ -398,16 +399,28 @@ The String must contain at least one non-whitespace character, according to Java ### @NotEmpty -The element must have a non zero size. +The element must have a non-zero size. -- Example : `updateAccident( accidentNotes : [Notes]! @NotEmpty) : DriverDetails` +- Example : `updateAccident( accidentNotes : String! @NotEmpty) : DriverDetails` -- Applies to : `String`, `ID`, `Lists`, `Input Objects` +- Applies to : `String`, `ID`, `Lists` - SDL : `directive @NotEmpty(message : String = "graphql.validation.NotEmpty.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` - Message : `graphql.validation.NotEmpty.message` +### @ContainerNotEmpty + +The list or input object must have a non-zero size. + +- Example : `updateAccident( accidentNotes : [Notes]! @ContainerNotEmpty) : DriverDetails` + +- Applies to : `Lists`, `Input Objects` + +- SDL : `directive @ContainerNotEmpty(message : String = "graphql.validation.ContainerNotEmpty.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` + +- Message : `graphql.validation.ContainerNotEmpty.message` + ### @Pattern @@ -428,7 +441,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` @@ -441,7 +454,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` @@ -454,7 +467,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` @@ -463,14 +476,26 @@ The element range must be between the specified `min` and `max` boundaries (incl ### @Size -The element size must be between the specified `min` and `max` boundaries (inclusive). +The string's size must be between the specified `min` and `max` boundaries (inclusive). - Example : `updateDrivingNotes( drivingNote : String @Size( min : 1000, max : 100000)) : DriverDetails` -- Applies to : `String`, `ID`, `Lists`, `Input Objects` +- Applies to : `String`, `ID`, `Lists` - SDL : `directive @Size(min : Int = 0, max : Int = 2147483647, message : String = "graphql.validation.Size.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` - Message : `graphql.validation.Size.message` +### @ContainerSize + +The list's or input object's size must be between the specified `min` and `max` boundaries (inclusive). + +- Example : `updateDrivingNotes( drivingNote : [String!]! @ContainerSize( min : 10, max : 20)) : DriverDetails` + +- Applies to : `Lists`, `Input Objects` + +- SDL : `directive @ContainerSize(min : Int = 0, max : Int = 2147483647, message : String = "graphql.validation.ContainerSize.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION` + +- Message : `graphql.validation.ContainerSize.message` + diff --git a/build.gradle b/build.gradle index 62b04c3..d1d94fb 100644 --- a/build.gradle +++ b/build.gradle @@ -2,14 +2,14 @@ import java.text.SimpleDateFormat plugins { - id "com.jfrog.bintray" version "1.8.5" + id 'java' + id 'groovy' + id 'java-library' + id 'maven-publish' + id 'signing' + id "io.github.gradle-nexus.publish-plugin" version "2.0.0" } -apply plugin: 'java' -apply plugin: 'groovy' -apply plugin: 'maven-publish' -apply plugin: 'com.jfrog.bintray' - def getDevelopmentVersion() { def output = new StringBuilder() def error = new StringBuilder() @@ -20,35 +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.properties.RELEASE_VERSION -println "Building version = " + releaseVersion +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() - maven { url "http://dl.bintray.com/andimarek/graphql-java" } + mavenLocal() } - dependencies { - compile "com.graphql-java:graphql-java:16.1" - compile 'org.slf4j:slf4j-api:1.7.30' - compile "jakarta.validation:jakarta.validation-api:2.0.2" - compile "org.hibernate.validator:hibernate-validator:6.1.6.Final" - compile "jakarta.el:jakarta.el-api:3.0.3" - compile "org.glassfish:jakarta.el:3.0.3" - - testCompile 'org.slf4j:slf4j-simple:1.7.30' - testCompile 'org.spockframework:spock-core:1.3-groovy-2.5' - testCompile 'org.codehaus.groovy:groovy-all:2.5.11' + api "com.graphql-java:graphql-java:24.3" + 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.2" + + 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) { @@ -66,6 +64,12 @@ artifacts { archives javadocJar } +test { + testLogging { + exceptionFormat = 'full' + } +} + publishing { publications { maven(MavenPublication) { @@ -81,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' @@ -112,24 +116,25 @@ publishing { } } -bintray { - user = System.getenv('BINTRAY_USER') - key = System.getenv('BINTRAY_API_KEY') - publications = ['maven'] - publish = true - pkg { - userOrg = 'graphql-java' - repo = 'graphql-java' - name = "graphql-java-extended-validation" - desc = 'A library fo extended validation for graphql-java' - licenses = ['MIT'] - vcsUrl = 'https://github.com/graphql-java/graphql-java-extended-validation.git' - version { - released = new Date() - vcsTag = project.version - gpg { - sign = true - } - } +nexusPublishing { + repositories { + sonatype { + 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/")) } } } + +signing { + def signingKey = System.env.MAVEN_CENTRAL_PGP_KEY + useInMemoryPgpKeys(signingKey, "") + 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 29953ea..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 ee69dd6..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-5.4.1-all.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 cccdd3d..744e882 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -56,7 +72,7 @@ case "`uname`" in Darwin* ) darwin=true ;; - MINGW* ) + MSYS* | MINGW* ) msys=true ;; NONSTOP* ) @@ -66,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -109,10 +126,11 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -138,19 +156,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -159,14 +177,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index f955316..ac1b06f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,84 +1,89 @@ -@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 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= - -@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 init - -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 init - -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 - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -: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 %CMD_LINE_ARGS% - -: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 698d589..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; @@ -20,11 +21,13 @@ import java.lang.reflect.Array; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import static graphql.schema.GraphQLTypeUtil.isList; import static graphql.validation.rules.ValidationEnvironment.ValidatedElement.FIELD; @@ -70,10 +73,18 @@ public boolean appliesTo(GraphQLArgument argument, GraphQLFieldDefinition fieldD boolean hasNamedDirective = directive.getName().equals(this.getName()); if (hasNamedDirective) { inputType = Util.unwrapNonNull(inputType); - boolean appliesToType = appliesToType(inputType); + + boolean appliesToType; + if (appliesToListElements()) { + appliesToType = appliesToType((GraphQLInputType) GraphQLTypeUtil.unwrapAll(inputType)); + } else { + appliesToType = appliesToType(inputType); + } + if (appliesToType) { return true; } + // if they have a @Directive on there BUT it can't handle that type // then is a really bad situation String argType = GraphQLTypeUtil.simplePrint(inputType); @@ -88,7 +99,6 @@ public boolean appliesTo(GraphQLArgument argument, GraphQLFieldDefinition fieldD * A derived class will be called to indicate whether this input type applies to the constraint * * @param inputType the input type - * * @return true if the constraint can handle that type */ abstract protected boolean appliesToType(GraphQLInputType inputType); @@ -97,22 +107,20 @@ public boolean appliesTo(GraphQLArgument argument, GraphQLFieldDefinition fieldD * This is called to perform the constraint validation * * @param validationEnvironment the validation environment - * * @return a list of errors or an empty one if there are no errors */ abstract protected List runConstraint(ValidationEnvironment validationEnvironment); - @SuppressWarnings("unchecked") @Override public List runValidation(ValidationEnvironment validationEnvironment) { + Object validatedValue = validationEnvironment.getValidatedValue(); // output fields are special if (validationEnvironment.getValidatedElement() == FIELD) { - return runFieldValidationImpl(validationEnvironment); + return runValidationImpl(validationEnvironment); } - Object validatedValue = validationEnvironment.getValidatedValue(); // // all the directives validation code does NOT care for NULL ness since the graphql engine covers that. // eg a @NonNull validation directive makes no sense in graphql like it might in Java @@ -124,50 +132,60 @@ public List runValidation(ValidationEnvironment validationEnvironm GraphQLInputType inputType = Util.unwrapNonNull(validationEnvironment.getValidatedType()); validationEnvironment = validationEnvironment.transform(b -> b.validatedType(inputType)); - return runValidationImpl(validationEnvironment, inputType, validatedValue); + return runValidationImpl(validationEnvironment); } - private List runFieldValidationImpl(ValidationEnvironment validationEnvironment) { - return runConstraintOnDirectives(validationEnvironment); - } - - @SuppressWarnings("unchecked") - private List runValidationImpl(ValidationEnvironment validationEnvironment, GraphQLInputType inputType, Object validatedValue) { + private List runValidationImpl(ValidationEnvironment validationEnvironment) { return runConstraintOnDirectives(validationEnvironment); } 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.runConstraint(validationEnvironment); + List ruleErrors = this.runConstrainOnPossibleListElements(validationEnvironment); errors.addAll(ruleErrors); } + return errors; } + private List runConstrainOnPossibleListElements(ValidationEnvironment validationEnvironment) { + if (appliesToListElements()) { + final GraphQLListElementValidator validator = new GraphQLListElementValidator(); + return validator.runConstraintOnListElements(validationEnvironment, this::runConstraint); + } + + return runConstraint(validationEnvironment); + } + + protected abstract boolean appliesToListElements(); + + + protected boolean isOneOfTheseTypes(GraphQLInputType inputType, GraphQLScalarType... scalarTypes) { + return isOneOfTheseTypes(inputType, Arrays.asList(scalarTypes)); + } /** * Returns true of the input type is one of the specified scalar types, regardless of non null ness * * @param inputType the type to check * @param scalarTypes the array of scalar types - * * @return true if its one of them */ - protected boolean isOneOfTheseTypes(GraphQLInputType inputType, GraphQLScalarType... scalarTypes) { + protected boolean isOneOfTheseTypes(GraphQLInputType inputType, Collection scalarTypes) { GraphQLInputType type = Util.unwrapNonNull(inputType); if (type instanceof GraphQLNamedInputType) { final GraphQLNamedInputType unwrappedType = (GraphQLNamedInputType) type; @@ -181,47 +199,45 @@ protected boolean isOneOfTheseTypes(GraphQLInputType inputType, GraphQLScalarTyp } /** - * Returns an integer argument from a directive (or its default) and throws an assertion of the argument is null + * Returns an integer argument from a directive (or its default) and throws an assertion if the argument is null * * @param directive the directive to check * @param argName the argument name - * * @return a non null value */ - protected int getIntArg(GraphQLDirective directive, String argName) { - GraphQLArgument argument = directive.getArgument(argName); - if (argument == null) { - return assertExpectedArgType(argName, "Int"); - } - Number value = (Number) argument.getValue(); - if (value == null) { - value = (Number) argument.getDefaultValue(); - if (value == null) { - return assertExpectedArgType(argName, "Int"); - } - } - return value.intValue(); + protected int getIntArg(GraphQLAppliedDirective directive, String argName) { + return getIntArgOpt(directive, argName) + .orElseGet(() -> assertExpectedArgType(argName, "Int")); } /** - * Returns an String argument from a directive (or its default) and throws an assertion of the argument is null + * 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); + } + + /** + * Returns an String argument from a directive (or its default) and throws an assertion of the argument is null * + * @param directive the directive to check + * @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 = (String) argument.getValue(); + String value = argument.getValue(); if (value == null) { - value = (String) argument.getDefaultValue(); - if (value == null) { - return assertExpectedArgType(argName, "String"); - } + return assertExpectedArgType(argName, "String"); } return value; } @@ -231,20 +247,16 @@ protected String getStrArg(GraphQLDirective directive, String argName) { * * @param directive the directive to check * @param argName the argument name - * * @return a non null value */ - protected boolean getBoolArg(GraphQLDirective directive, String argName) { - 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 = argument.getValue(); if (value == null) { - value = argument.getDefaultValue(); - if (value == null) { - return assertExpectedArgType(argName, "Boolean"); - } + return assertExpectedArgType(argName, "Boolean"); } return Boolean.parseBoolean(String.valueOf(value)); } @@ -254,17 +266,13 @@ protected boolean getBoolArg(GraphQLDirective directive, String argName) { * called "graphql.validation.{name}.message" * * @param directive the directive to check - * * @return a non null value */ - protected String getMessageTemplate(GraphQLDirective directive) { + protected String getMessageTemplate(GraphQLAppliedDirective directive) { String msg = null; - GraphQLArgument arg = directive.getArgument("message"); + GraphQLAppliedDirectiveArgument arg = directive.getArgument("message"); if (arg != null) { - msg = (String) arg.getValue(); - if (msg == null) { - msg = (String) arg.getDefaultValue(); - } + msg = arg.getValue(); } if (msg == null) { msg = "graphql.validation." + getName() + ".message"; @@ -278,7 +286,6 @@ protected String getMessageTemplate(GraphQLDirective directive) { * @param validatedValue the value being validated * @param validationEnvironment the validation environment * @param args must be an key / value array with String keys as the even params and values as then odd params - * * @return a map of message parameters */ protected Map mkMessageParams(Object validatedValue, ValidationEnvironment validationEnvironment, Object... args) { @@ -298,34 +305,54 @@ 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) { + 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) { + 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); + return singletonList(error); + } + /** - * Return true if the type is a String or ID or List type or {@link graphql.schema.GraphQLInputObjectType}, regardless of non null ness + * Return true if the type is a String or ID or List type, regardless of non null ness * * @param inputType the type to check + * @return true if one of the above + */ + protected boolean isStringOrIDOrList(GraphQLInputType inputType) { + return isStringOrID(inputType) || + isList(inputType); + } + + /** + * Return true if the type is a String or ID or List type or {@link graphql.schema.GraphQLInputObjectType}, regardless of non null ness * + * @param inputType the type to check * @return true if one of the above */ protected boolean isStringOrIDOrListOrMap(GraphQLInputType inputType) { - GraphQLInputType unwrappedType = Util.unwrapOneAndAllNonNull(inputType); return isStringOrID(inputType) || isList(inputType) || - (unwrappedType instanceof GraphQLInputObjectType); + isMap(inputType); + } + + protected boolean isMap(GraphQLInputType inputType) { + GraphQLInputType unwrappedType = Util.unwrapOneAndAllNonNull(inputType); + return (unwrappedType instanceof GraphQLInputObjectType); } /** * Return true if the type is a String or ID * * @param inputType the type to check - * * @return true if one of the above */ protected boolean isStringOrID(GraphQLInputType inputType) { @@ -337,7 +364,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") @@ -350,7 +376,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 { @@ -375,7 +400,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) { @@ -394,7 +418,6 @@ protected boolean asBoolean(Object value) { * * @param inputType the input type * @param value the value - * * @return the length of a String or Map or List */ protected int getStringOrIDOrObjectOrMapLength(GraphQLInputType inputType, Object value) { diff --git a/src/main/java/graphql/validation/constraints/DirectiveConstraints.java b/src/main/java/graphql/validation/constraints/DirectiveConstraints.java index 7c91471..a775d41 100644 --- a/src/main/java/graphql/validation/constraints/DirectiveConstraints.java +++ b/src/main/java/graphql/validation/constraints/DirectiveConstraints.java @@ -7,6 +7,8 @@ import graphql.schema.idl.TypeDefinitionRegistry; import graphql.validation.constraints.standard.AssertFalseConstraint; import graphql.validation.constraints.standard.AssertTrueConstraint; +import graphql.validation.constraints.standard.ContainerNotEmptyConstraint; +import graphql.validation.constraints.standard.ContainerSizeConstraint; import graphql.validation.constraints.standard.DecimalMaxConstraint; import graphql.validation.constraints.standard.DecimalMinConstraint; import graphql.validation.constraints.standard.DigitsConstraint; @@ -61,7 +63,10 @@ public class DirectiveConstraints { new PositiveOrZeroConstraint(), new PositiveConstraint(), new RangeConstraint(), - new SizeConstraint() + new SizeConstraint(), + new ContainerSizeConstraint(), + new ContainerNotEmptyConstraint() + ); private final Map constraints; diff --git a/src/main/java/graphql/validation/constraints/Documentation.java b/src/main/java/graphql/validation/constraints/Documentation.java index 355dba0..d1c746a 100644 --- a/src/main/java/graphql/validation/constraints/Documentation.java +++ b/src/main/java/graphql/validation/constraints/Documentation.java @@ -1,10 +1,11 @@ package graphql.validation.constraints; +import graphql.schema.GraphQLNamedType; import graphql.schema.idl.SchemaParser; import graphql.schema.idl.TypeDefinitionRegistry; - import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; public class Documentation { @@ -80,14 +81,23 @@ public Builder messageTemplate(String messageTemplate) { return this; } + public Builder applicableTypes(List applicableTypes) { + this.applicableTypeNames = applicableTypes.stream().map(GraphQLNamedType::getName).collect(Collectors.toList()); + return this; + } + + public Builder applicableTypes(GraphQLNamedType... applicableTypes) { + return applicableTypes(Arrays.asList(applicableTypes)); + } + + public Builder applicableTypeNames(List applicableTypeNames) { this.applicableTypeNames = applicableTypeNames; return this; } public Builder applicableTypeNames(String... applicableTypeNames) { - this.applicableTypeNames = Arrays.asList(applicableTypeNames); - return this; + return applicableTypeNames(Arrays.asList(applicableTypeNames)); } public Documentation build() { diff --git a/src/main/java/graphql/validation/constraints/GraphQLListElementValidator.java b/src/main/java/graphql/validation/constraints/GraphQLListElementValidator.java new file mode 100644 index 0000000..662e1bb --- /dev/null +++ b/src/main/java/graphql/validation/constraints/GraphQLListElementValidator.java @@ -0,0 +1,41 @@ +package graphql.validation.constraints; + +import graphql.GraphQLError; +import graphql.schema.GraphQLInputType; +import graphql.schema.GraphQLTypeUtil; +import graphql.validation.rules.ValidationEnvironment; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class GraphQLListElementValidator { + public boolean appliesToType(GraphQLInputType inputType, Function appliesToTypeOrListElement) { + if (GraphQLTypeUtil.isList(inputType)) { + return appliesToTypeOrListElement.apply((GraphQLInputType) GraphQLTypeUtil.unwrapAll(inputType)); + } + + return appliesToTypeOrListElement.apply(inputType); + } + + public List runConstraintOnListElements(ValidationEnvironment validationEnvironment, Function> runConstraintOnElement) { + Object validatedValue = validationEnvironment.getValidatedValue(); + + if (validatedValue instanceof Collection) { + final AtomicInteger index = new AtomicInteger(0); + return ((Collection) validatedValue) + .stream() + .flatMap((item) -> item == null ? Stream.empty() : runConstraintOnElement.apply(validationEnvironment.transform((environment) -> { + environment + .validatedValue(item) + .validatedPath(validationEnvironment.getValidatedPath().segment(index.getAndIncrement())) + .validatedType((GraphQLInputType) GraphQLTypeUtil.unwrapAll(validationEnvironment.getValidatedType())); + })).stream()) + .collect(Collectors.toList()); + } + + return runConstraintOnElement.apply(validationEnvironment); + } +} diff --git a/src/main/java/graphql/validation/constraints/GraphQLScalars.java b/src/main/java/graphql/validation/constraints/GraphQLScalars.java new file mode 100644 index 0000000..b8dfb69 --- /dev/null +++ b/src/main/java/graphql/validation/constraints/GraphQLScalars.java @@ -0,0 +1,26 @@ +package graphql.validation.constraints; + +import graphql.Scalars; +import graphql.scalars.ExtendedScalars; +import graphql.schema.GraphQLScalarType; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class GraphQLScalars { + public static final List GRAPHQL_NUMBER_TYPES = Arrays.asList( + Scalars.GraphQLInt, + Scalars.GraphQLFloat, + ExtendedScalars.GraphQLByte, + ExtendedScalars.GraphQLShort, + ExtendedScalars.GraphQLLong, + ExtendedScalars.GraphQLBigDecimal, + ExtendedScalars.GraphQLBigInteger + ); + + public static final List GRAPHQL_NUMBER_AND_STRING_TYPES = Stream.concat( + Stream.of(Scalars.GraphQLString, Scalars.GraphQLID), + GraphQLScalars.GRAPHQL_NUMBER_TYPES.stream() + ).collect(Collectors.toList()); +} diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractAssertConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractAssertConstraint.java index 9f5ddc7..fadd286 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractAssertConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractAssertConstraint.java @@ -1,49 +1,40 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLInputType; import graphql.validation.constraints.AbstractDirectiveConstraint; import graphql.validation.rules.ValidationEnvironment; - import java.util.Collections; import java.util.List; - import static graphql.Scalars.GraphQLBoolean; abstract class AbstractAssertConstraint extends AbstractDirectiveConstraint { - public AbstractAssertConstraint(String name) { super(name); } - @Override public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, - GraphQLBoolean - ); + return isOneOfTheseTypes(inputType, GraphQLBoolean); } @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - //null values are valid - if (validatedValue == null) { - return Collections.emptyList(); - } - - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); boolean isTrue = asBoolean(validatedValue); - if (!isOK(isTrue)) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment)); + if (!isOK(isTrue)) { + return mkError(validationEnvironment); } + return Collections.emptyList(); } protected abstract boolean isOK(boolean isTrue); - + @Override + protected boolean appliesToListElements() { + return true; + } } diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java index f02688d..1bedd8b 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractDecimalMinMaxConstraint.java @@ -1,8 +1,7 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.Scalars; -import graphql.schema.GraphQLDirective; +import graphql.schema.GraphQLAppliedDirective; import graphql.schema.GraphQLInputType; import graphql.schema.GraphQLScalarType; import graphql.validation.constraints.AbstractDirectiveConstraint; @@ -11,51 +10,28 @@ import java.math.BigDecimal; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; -abstract class AbstractDecimalMinMaxConstraint extends AbstractDirectiveConstraint { +import static graphql.validation.constraints.GraphQLScalars.GRAPHQL_NUMBER_AND_STRING_TYPES; +abstract class AbstractDecimalMinMaxConstraint extends AbstractDirectiveConstraint { public AbstractDecimalMinMaxConstraint(String name) { super(name); } - @Override - public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, - Scalars.GraphQLString, // note we allow strings - Scalars.GraphQLByte, - Scalars.GraphQLShort, - Scalars.GraphQLInt, - Scalars.GraphQLLong, - Scalars.GraphQLBigDecimal, - Scalars.GraphQLBigInteger, - Scalars.GraphQLFloat - ); + public List getApplicableTypes() { + return GRAPHQL_NUMBER_AND_STRING_TYPES; } - 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) - .map(GraphQLScalarType::getName) - .collect(Collectors.toList()); + @Override + protected boolean appliesToType(GraphQLInputType inputType) { + return isOneOfTheseTypes(inputType, GRAPHQL_NUMBER_AND_STRING_TYPES); } @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - //null values are valid - if (validatedValue == null) { - return Collections.emptyList(); - } - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); + GraphQLAppliedDirective directive = validationEnvironment.getContextObject(GraphQLAppliedDirective.class); String value = getStrArg(directive, "value"); boolean inclusive = getBoolArg(directive, "inclusive"); @@ -65,21 +41,21 @@ protected List runConstraint(ValidationEnvironment validationEnvir BigDecimal argBD = asBigDecimal(validatedValue); int comparisonResult = argBD.compareTo(directiveBD); isOK = isOK(inclusive, comparisonResult); - } catch (NumberFormatException nfe) { isOK = false; } if (!isOK) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment, - "value", validatedValue, - "inclusive", inclusive)); - + return mkError(validationEnvironment, "value", value, "inclusive", inclusive); } + return Collections.emptyList(); } abstract protected boolean isOK(boolean inclusive, int comparisonResult); - + @Override + protected boolean appliesToListElements() { + return true; + } } diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java index 1463e1c..e66cad5 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractMinMaxConstraint.java @@ -1,61 +1,36 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.Scalars; -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; -import java.util.stream.Stream; - -import static java.util.stream.Collectors.toList; abstract class AbstractMinMaxConstraint extends AbstractDirectiveConstraint { - public AbstractMinMaxConstraint(String name) { super(name); } - @Override - public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, - Scalars.GraphQLByte, - Scalars.GraphQLShort, - Scalars.GraphQLInt, - Scalars.GraphQLLong, - Scalars.GraphQLBigDecimal, - Scalars.GraphQLBigInteger, - Scalars.GraphQLFloat - ); + public List getApplicableTypes() { + return GraphQLScalars.GRAPHQL_NUMBER_TYPES; } - public List getApplicableTypeNames() { - return Stream.of(Scalars.GraphQLByte, - Scalars.GraphQLShort, - Scalars.GraphQLInt, - Scalars.GraphQLLong, - Scalars.GraphQLBigDecimal, - Scalars.GraphQLBigInteger, - Scalars.GraphQLFloat) - .map(GraphQLScalarType::getName) - .collect(toList()); - } + @Override + protected boolean appliesToType(GraphQLInputType inputType) { + return isOneOfTheseTypes(inputType, GraphQLScalars.GRAPHQL_NUMBER_TYPES); + } @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - //null values are valid - if (validatedValue == null) { - return Collections.emptyList(); - } - - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); + GraphQLAppliedDirective directive = validationEnvironment.getContextObject(GraphQLAppliedDirective.class); int value = getIntArg(directive, "value"); boolean isOK; @@ -71,12 +46,16 @@ protected List runConstraint(ValidationEnvironment validationEnvir if (!isOK) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment, - "value", value)); - + return mkError(validationEnvironment, "value", value); } + return Collections.emptyList(); } abstract protected boolean isOK(int comparisonResult); + + @Override + protected boolean appliesToListElements() { + return true; + } } diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractNotEmptyRule.java b/src/main/java/graphql/validation/constraints/standard/AbstractNotEmptyRule.java new file mode 100644 index 0000000..1ed3adf --- /dev/null +++ b/src/main/java/graphql/validation/constraints/standard/AbstractNotEmptyRule.java @@ -0,0 +1,28 @@ +package graphql.validation.constraints.standard; + +import graphql.GraphQLError; +import graphql.schema.GraphQLInputType; +import graphql.validation.constraints.AbstractDirectiveConstraint; +import graphql.validation.rules.ValidationEnvironment; +import java.util.Collections; +import java.util.List; + +public abstract class AbstractNotEmptyRule extends AbstractDirectiveConstraint { + public AbstractNotEmptyRule(String name) { + super(name); + } + + @Override + final protected List runConstraint(ValidationEnvironment validationEnvironment) { + Object validatedValue = validationEnvironment.getValidatedValue(); + GraphQLInputType argumentType = validationEnvironment.getValidatedType(); + + int size = getStringOrIDOrObjectOrMapLength(argumentType, validatedValue); + + if (size <= 0) { + return mkError(validationEnvironment, "size", size); + } + + return Collections.emptyList(); + } +} diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java index b2a89e3..b6090e8 100644 --- a/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AbstractPositiveNegativeConstraint.java @@ -1,19 +1,14 @@ 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,39 +18,17 @@ 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) - .map(GraphQLScalarType::getName) - .collect(toList()); + public List getApplicableTypes() { + return GraphQLScalars.GRAPHQL_NUMBER_TYPES; } @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - //null values are valid - if (validatedValue == null) { - return Collections.emptyList(); - } - - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); boolean isOK; try { @@ -66,11 +39,16 @@ protected List runConstraint(ValidationEnvironment validationEnvir } if (!isOK) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment)); + return mkError(validationEnvironment); } return Collections.emptyList(); } abstract protected boolean isOK(BigDecimal bigDecimal); + + @Override + protected boolean appliesToListElements() { + return true; + } } diff --git a/src/main/java/graphql/validation/constraints/standard/AbstractSizeConstraint.java b/src/main/java/graphql/validation/constraints/standard/AbstractSizeConstraint.java new file mode 100644 index 0000000..7a61e0e --- /dev/null +++ b/src/main/java/graphql/validation/constraints/standard/AbstractSizeConstraint.java @@ -0,0 +1,34 @@ +package graphql.validation.constraints.standard; + +import graphql.GraphQLError; +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; + +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(); + + GraphQLAppliedDirective directive = validationEnvironment.getContextObject(GraphQLAppliedDirective.class); + int min = getIntArg(directive, "min"); + int max = getIntArg(directive, "max"); + + int size = getStringOrIDOrObjectOrMapLength(argType, validatedValue); + + if (size < min || size > max) { + return mkError(validationEnvironment, "min", min, "max", max, "size", size); + } + + return Collections.emptyList(); + } +} diff --git a/src/main/java/graphql/validation/constraints/standard/AssertFalseConstraint.java b/src/main/java/graphql/validation/constraints/standard/AssertFalseConstraint.java index 5f5fb01..392b0ee 100644 --- a/src/main/java/graphql/validation/constraints/standard/AssertFalseConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AssertFalseConstraint.java @@ -14,13 +14,9 @@ public AssertFalseConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The boolean value must be false.") - .example("updateDriver( isDrunk : Boolean @AssertFalse) : DriverDetails") - - .applicableTypeNames(GraphQLBoolean.getName()) - + .applicableTypes(GraphQLBoolean) .directiveSDL("directive @AssertFalse(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) diff --git a/src/main/java/graphql/validation/constraints/standard/AssertTrueConstraint.java b/src/main/java/graphql/validation/constraints/standard/AssertTrueConstraint.java index 69a81fb..fa33d40 100644 --- a/src/main/java/graphql/validation/constraints/standard/AssertTrueConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/AssertTrueConstraint.java @@ -14,13 +14,9 @@ public AssertTrueConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The boolean value must be true.") - .example("driveCar( hasLicence : Boolean @AssertTrue) : DriverDetails") - - .applicableTypeNames(GraphQLBoolean.getName()) - + .applicableTypes(GraphQLBoolean) .directiveSDL("directive @AssertTrue(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) diff --git a/src/main/java/graphql/validation/constraints/standard/ContainerNotEmptyConstraint.java b/src/main/java/graphql/validation/constraints/standard/ContainerNotEmptyConstraint.java new file mode 100644 index 0000000..e5c4fa0 --- /dev/null +++ b/src/main/java/graphql/validation/constraints/standard/ContainerNotEmptyConstraint.java @@ -0,0 +1,34 @@ +package graphql.validation.constraints.standard; + +import graphql.schema.GraphQLInputType; +import graphql.validation.constraints.Documentation; +import static graphql.schema.GraphQLTypeUtil.isList; + +public class ContainerNotEmptyConstraint extends AbstractNotEmptyRule { + public ContainerNotEmptyConstraint() { + super("ContainerNotEmpty"); + } + + @Override + public Documentation getDocumentation() { + return Documentation.newDocumentation() + .messageTemplate(getMessageTemplate()) + .description("The container must have a non-zero size") + .example("updateAccident( accidentNotes : [Notes]! @ContainerNotEmpty) : DriverDetails") + .applicableTypeNames("Lists", "Input Objects") + .directiveSDL("directive @ContainerNotEmpty(message : String = \"%s\") " + + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", + getMessageTemplate()) + .build(); + } + + @Override + public boolean appliesToType(GraphQLInputType inputType) { + return isList(inputType) || isMap(inputType); + } + + @Override + protected boolean appliesToListElements() { + return false; + } +} diff --git a/src/main/java/graphql/validation/constraints/standard/ContainerSizeConstraint.java b/src/main/java/graphql/validation/constraints/standard/ContainerSizeConstraint.java new file mode 100644 index 0000000..775221b --- /dev/null +++ b/src/main/java/graphql/validation/constraints/standard/ContainerSizeConstraint.java @@ -0,0 +1,35 @@ +package graphql.validation.constraints.standard; + +import graphql.schema.GraphQLInputType; +import graphql.validation.constraints.Documentation; +import static graphql.schema.GraphQLTypeUtil.isList; + +public class ContainerSizeConstraint extends AbstractSizeConstraint { + public ContainerSizeConstraint() { + super("ContainerSize"); + } + + @Override + public Documentation getDocumentation() { + return Documentation.newDocumentation() + .messageTemplate(getMessageTemplate()) + .description("The element size must be between the specified `min` and `max` boundaries (inclusive).") + .example("updateDrivingNotes( drivingNote : String @ContainerSize( min : 1000, max : 100000)) : DriverDetails") + .applicableTypeNames("Lists", "Input Objects") + .directiveSDL("directive @ContainerSize(min : Int = 0, max : Int = %d, message : String = \"%s\") " + + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", + Integer.MAX_VALUE, getMessageTemplate()) + .build(); + } + + + @Override + public boolean appliesToType(GraphQLInputType inputType) { + return isList(inputType) || isMap(inputType); + } + + @Override + protected boolean appliesToListElements() { + return false; + } +} diff --git a/src/main/java/graphql/validation/constraints/standard/DecimalMaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/DecimalMaxConstraint.java index fc2cd58..e06440e 100644 --- a/src/main/java/graphql/validation/constraints/standard/DecimalMaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/DecimalMaxConstraint.java @@ -12,13 +12,9 @@ public DecimalMaxConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element must be a number whose value must be less than or equal to the specified maximum.") - .example("driveCar( bloodAlcoholLevel : Float @DecimalMax(value : \"0.05\") : DriverDetails") - - .applicableTypeNames(getApplicableTypeNames()) - + .applicableTypes(getApplicableTypes()) .directiveSDL("directive @DecimalMax(value : String!, inclusive : Boolean! = true, message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) diff --git a/src/main/java/graphql/validation/constraints/standard/DecimalMinConstraint.java b/src/main/java/graphql/validation/constraints/standard/DecimalMinConstraint.java index aeb6a00..895f1cf 100644 --- a/src/main/java/graphql/validation/constraints/standard/DecimalMinConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/DecimalMinConstraint.java @@ -12,13 +12,9 @@ public DecimalMinConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element must be a number whose value must be greater than or equal to the specified minimum.") - .example("driveCar( carHorsePower : Float @DecimalMin(value : \"300.50\") : DriverDetails") - - .applicableTypeNames(getApplicableTypeNames()) - + .applicableTypes(getApplicableTypes()) .directiveSDL("directive @DecimalMin(value : String!, inclusive : Boolean! = true, message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) diff --git a/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java b/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java index 11da22b..a8f963e 100644 --- a/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/DigitsConstraint.java @@ -1,10 +1,8 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.Scalars; -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.Documentation; import graphql.validation.rules.ValidationEnvironment; @@ -12,12 +10,11 @@ import java.math.BigDecimal; import java.util.Collections; import java.util.List; -import java.util.stream.Stream; +import java.util.Optional; -import static java.util.stream.Collectors.toList; +import static graphql.validation.constraints.GraphQLScalars.GRAPHQL_NUMBER_AND_STRING_TYPES; public class DigitsConstraint extends AbstractDirectiveConstraint { - public DigitsConstraint() { super("Digits"); } @@ -25,75 +22,66 @@ 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") - - .applicableTypeNames(Stream.of(Scalars.GraphQLString, - Scalars.GraphQLByte, - Scalars.GraphQLShort, - Scalars.GraphQLInt, - Scalars.GraphQLLong, - Scalars.GraphQLBigDecimal, - Scalars.GraphQLBigInteger, - Scalars.GraphQLFloat) - .map(GraphQLScalarType::getName) - .collect(toList())) - - .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 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, GRAPHQL_NUMBER_AND_STRING_TYPES); } @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - if (validatedValue == null) { - return Collections.emptyList(); - } - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); + 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, directive, mkMessageParams(validatedValue, 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 = bigNum.scale() < 0 ? 0 : bigNum.scale(); + private static boolean isIntegerPartOk(BigDecimal bigNum, int maxIntegerLength) { + final int integerPartLength = bigNum.precision() - bigNum.scale(); + return maxIntegerLength >= integerPartLength; + } + + private static boolean isFractionPartOk(BigDecimal bigNum, int maxFractionLength) { + final int fractionPartLength = Math.max(bigNum.scale(), 0); + return maxFractionLength >= fractionPartLength; + } - return maxIntegerLength >= integerPartLength && maxFractionLength >= fractionPartLength; + @Override + protected boolean appliesToListElements() { + return true; } } diff --git a/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java b/src/main/java/graphql/validation/constraints/standard/ExpressionConstraint.java index 484cdfb..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; @@ -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()) @@ -54,21 +50,18 @@ 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")); - Object validatedValue = validationEnvironment.getValidatedValue(); - Map variables = StandardELVariables.standardELVars(validationEnvironment); ELSupport elSupport = new ELSupport(validationEnvironment.getLocale()); boolean isOK = elSupport.evaluateBoolean(expression, variables); if (!isOK) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment, - "value", expression)); - + return mkError(validationEnvironment,"value", expression); } + return Collections.emptyList(); } @@ -82,4 +75,9 @@ private String helpWithCurlyBraces(String expression) { } return expression; } + + @Override + protected boolean appliesToListElements() { + return false; + } } diff --git a/src/main/java/graphql/validation/constraints/standard/MaxConstraint.java b/src/main/java/graphql/validation/constraints/standard/MaxConstraint.java index bc21d33..899c70d 100644 --- a/src/main/java/graphql/validation/constraints/standard/MaxConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/MaxConstraint.java @@ -12,13 +12,9 @@ public MaxConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element must be a number whose value must be less than or equal to the specified maximum.") - .example("driveCar( horsePower : Float @Max(value : 1000) : DriverDetails") - - .applicableTypeNames(getApplicableTypeNames()) - + .applicableTypes(getApplicableTypes()) .directiveSDL("directive @Max(value : Int! = %d, message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", Integer.MAX_VALUE, getMessageTemplate()) diff --git a/src/main/java/graphql/validation/constraints/standard/MinConstraint.java b/src/main/java/graphql/validation/constraints/standard/MinConstraint.java index 421abf6..ff16edb 100644 --- a/src/main/java/graphql/validation/constraints/standard/MinConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/MinConstraint.java @@ -12,13 +12,9 @@ public MinConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element must be a number whose value must be greater than or equal to the specified minimum.") - .example("driveCar( age : Int @Min(value : 18) : DriverDetails") - - .applicableTypeNames(getApplicableTypeNames()) - + .applicableTypes(getApplicableTypes()) .directiveSDL("directive @Min(value : Int! = 0, message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) diff --git a/src/main/java/graphql/validation/constraints/standard/NegativeConstraint.java b/src/main/java/graphql/validation/constraints/standard/NegativeConstraint.java index f9acd29..e074771 100644 --- a/src/main/java/graphql/validation/constraints/standard/NegativeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/NegativeConstraint.java @@ -14,13 +14,9 @@ public NegativeConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element must be a negative number.") - .example("driveCar( lostLicencePoints : Int @Negative) : DriverDetails") - - .applicableTypeNames(getApplicableTypeNames()) - + .applicableTypes(getApplicableTypes()) .directiveSDL("directive @Negative(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) diff --git a/src/main/java/graphql/validation/constraints/standard/NegativeOrZeroConstraint.java b/src/main/java/graphql/validation/constraints/standard/NegativeOrZeroConstraint.java index f61f1c1..4806470 100644 --- a/src/main/java/graphql/validation/constraints/standard/NegativeOrZeroConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/NegativeOrZeroConstraint.java @@ -14,23 +14,17 @@ public NegativeOrZeroConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element must be a negative number or zero.") - .example("driveCar( lostLicencePoints : Int @NegativeOrZero) : DriverDetails") - - .applicableTypeNames(getApplicableTypeNames()) - + .applicableTypes(getApplicableTypes()) .directiveSDL("directive @NegativeOrZero(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) .build(); } - @Override protected boolean isOK(BigDecimal bigDecimal) { return bigDecimal.compareTo(BigDecimal.ZERO) <= 0; } - } diff --git a/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java b/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java index d63da23..3af990d 100644 --- a/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java +++ b/src/main/java/graphql/validation/constraints/standard/NotBlankRule.java @@ -2,12 +2,10 @@ 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; @@ -21,13 +19,9 @@ public NotBlankRule() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The String must contain at least one non-whitespace character, according to Java's Character.isWhitespace().") - .example("updateAccident( accidentNotes : String @NotBlank) : DriverDetails") - - .applicableTypeNames(Scalars.GraphQLString.getName(), Scalars.GraphQLID.getName()) - + .applicableTypeNames(Scalars.GraphQLString.getName(), Scalars.GraphQLID.getName(), "Lists") .directiveSDL("directive @NotBlank(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) @@ -36,29 +30,22 @@ public Documentation getDocumentation() { @Override public boolean appliesToType(GraphQLInputType inputType) { - return isStringOrID(inputType); + return isStringOrIDOrList(inputType); } @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); - - if (validatedValue == null || isBlank(validatedValue)) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment)); - + if (validatedValue.toString().trim().isEmpty()) { + return mkError(validationEnvironment); } + return Collections.emptyList(); } - private boolean isBlank(Object value) { - char[] chars = value.toString().toCharArray(); - for (char c : chars) { - if (!Character.isWhitespace(c)) { - return false; - } - } + @Override + protected boolean appliesToListElements() { return true; } } diff --git a/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java b/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java index 06fe5c1..bdb3d0d 100644 --- a/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java +++ b/src/main/java/graphql/validation/constraints/standard/NotEmptyRule.java @@ -1,16 +1,10 @@ package graphql.validation.constraints.standard; -import graphql.GraphQLError; import graphql.Scalars; -import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLInputType; -import graphql.validation.constraints.AbstractDirectiveConstraint; import graphql.validation.constraints.Documentation; -import graphql.validation.rules.ValidationEnvironment; -import java.util.Collections; -import java.util.List; -public class NotEmptyRule extends AbstractDirectiveConstraint { +public class NotEmptyRule extends AbstractNotEmptyRule { public NotEmptyRule() { super("NotEmpty"); @@ -20,13 +14,9 @@ public NotEmptyRule() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - - .description("The element must have a non zero size.") - - .example("updateAccident( accidentNotes : [Notes]! @NotEmpty) : DriverDetails") - - .applicableTypeNames(Scalars.GraphQLString.getName(), Scalars.GraphQLID.getName(), "Lists", "Input Objects") - + .description("The element must have a non zero size") + .example("updateAccident( accidentNotes : [String!]! @NotEmpty) : DriverDetails") + .applicableTypeNames(Scalars.GraphQLString.getName(), Scalars.GraphQLID.getName()) .directiveSDL("directive @NotEmpty(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) @@ -35,24 +25,11 @@ public Documentation getDocumentation() { @Override public boolean appliesToType(GraphQLInputType inputType) { - return isStringOrIDOrListOrMap(inputType); + return isStringOrID(inputType); } @Override - protected List runConstraint(ValidationEnvironment validationEnvironment) { - Object validatedValue = validationEnvironment.getValidatedValue(); - GraphQLInputType argumentType = validationEnvironment.getValidatedType(); - - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); - int size = getStringOrIDOrObjectOrMapLength(argumentType, validatedValue); - - if (size <= 0) { - return mkError(validationEnvironment, directive, mkMessageParams(validatedValue, validationEnvironment, - "size", size - )); - } - return Collections.emptyList(); + protected boolean appliesToListElements() { + return true; } - - } diff --git a/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java b/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java index 6a73c18..b670bf8 100644 --- a/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/PatternConstraint.java @@ -2,25 +2,23 @@ import graphql.GraphQLError; import graphql.Scalars; +import graphql.schema.GraphQLAppliedDirective; import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLInputType; import graphql.validation.constraints.AbstractDirectiveConstraint; import graphql.validation.constraints.Documentation; import graphql.validation.rules.ValidationEnvironment; - -import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.regex.Matcher; import java.util.regex.Pattern; - import static graphql.schema.GraphQLTypeUtil.isList; import static java.util.Collections.emptyList; public class PatternConstraint extends AbstractDirectiveConstraint { - private final static Map SEEN_PATTERNS = new HashMap<>(); + private final static ConcurrentMap SEEN_PATTERNS = new ConcurrentHashMap<>(); public PatternConstraint() { super("Pattern"); @@ -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()) @@ -51,34 +45,19 @@ public boolean appliesToType(GraphQLInputType inputType) { @Override protected List runConstraint(ValidationEnvironment validationEnvironment) { Object validatedValue = validationEnvironment.getValidatedValue(); - GraphQLInputType argumentType = validationEnvironment.getValidatedType(); - if (validatedValue == null) { - return emptyList(); - } + String strValue = String.valueOf(validatedValue); - List validatedValues; + GraphQLAppliedDirective directive = validationEnvironment.getContextObject(GraphQLAppliedDirective.class); - if (isList(argumentType)) { - validatedValues = (List)validatedValue; - } else { - validatedValues = Arrays.asList(validatedValue); - } + String patternArg = getStrArg(directive, "regexp"); + Pattern pattern = cachedPattern(patternArg); - for (Object value : validatedValues) { - String strValue = String.valueOf(value); - - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); - - String patternArg = getStrArg(directive, "regexp"); - Pattern pattern = cachedPattern(patternArg); - - Matcher matcher = pattern.matcher(strValue); - if (!matcher.matches()) { - return mkError(validationEnvironment, directive, - mkMessageParams(validatedValue, validationEnvironment, "regexp", patternArg)); - } + Matcher matcher = pattern.matcher(strValue); + if (!matcher.matches()) { + return mkError(validationEnvironment, "regexp", patternArg); } + return emptyList(); } @@ -86,5 +65,8 @@ private Pattern cachedPattern(String patternArg) { return SEEN_PATTERNS.computeIfAbsent(patternArg, Pattern::compile); } - + @Override + protected boolean appliesToListElements() { + return true; + } } diff --git a/src/main/java/graphql/validation/constraints/standard/PositiveConstraint.java b/src/main/java/graphql/validation/constraints/standard/PositiveConstraint.java index d48e689..93d164f 100644 --- a/src/main/java/graphql/validation/constraints/standard/PositiveConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/PositiveConstraint.java @@ -5,7 +5,6 @@ import java.math.BigDecimal; public class PositiveConstraint extends AbstractPositiveNegativeConstraint { - public PositiveConstraint() { super("Positive"); } @@ -14,13 +13,9 @@ public PositiveConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element must be a positive number.") - .example("driver( licencePoints : Int @Positive) : DriverDetails") - - .applicableTypeNames(getApplicableTypeNames()) - + .applicableTypes(getApplicableTypes()) .directiveSDL("directive @Positive(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) @@ -31,5 +26,4 @@ public Documentation getDocumentation() { protected boolean isOK(BigDecimal bigDecimal) { return bigDecimal.compareTo(BigDecimal.ZERO) > 0; } - } diff --git a/src/main/java/graphql/validation/constraints/standard/PositiveOrZeroConstraint.java b/src/main/java/graphql/validation/constraints/standard/PositiveOrZeroConstraint.java index 3f1abf7..c8961c4 100644 --- a/src/main/java/graphql/validation/constraints/standard/PositiveOrZeroConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/PositiveOrZeroConstraint.java @@ -14,13 +14,9 @@ public PositiveOrZeroConstraint() { public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element must be a positive number or zero.") - .example("driver( licencePoints : Int @PositiveOrZero) : DriverDetails") - - .applicableTypeNames(getApplicableTypeNames()) - + .applicableTypes(getApplicableTypes()) .directiveSDL("directive @PositiveOrZero(message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", getMessageTemplate()) @@ -31,5 +27,4 @@ public Documentation getDocumentation() { protected boolean isOK(BigDecimal bigDecimal) { return bigDecimal.compareTo(BigDecimal.ZERO) >= 0; } - } diff --git a/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java b/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java index 292320b..1ad9ad6 100644 --- a/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/RangeConstraint.java @@ -1,10 +1,8 @@ package graphql.validation.constraints.standard; import graphql.GraphQLError; -import graphql.Scalars; -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.Documentation; import graphql.validation.rules.ValidationEnvironment; @@ -12,39 +10,22 @@ import java.math.BigDecimal; import java.util.Collections; import java.util.List; -import java.util.stream.Stream; -import static graphql.Scalars.GraphQLString; -import static java.util.stream.Collectors.toList; +import static graphql.validation.constraints.GraphQLScalars.GRAPHQL_NUMBER_AND_STRING_TYPES; public class RangeConstraint extends AbstractDirectiveConstraint { - public RangeConstraint() { super("Range"); } - @Override public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element range must be between the specified `min` and `max` boundaries (inclusive). It " + "accepts numbers and strings that represent numerical values.") - .example("driver( milesTravelled : Int @Range( min : 1000, max : 100000)) : DriverDetails") - - .applicableTypeNames(Stream.of(GraphQLString, - Scalars.GraphQLByte, - Scalars.GraphQLShort, - Scalars.GraphQLInt, - Scalars.GraphQLLong, - Scalars.GraphQLBigDecimal, - Scalars.GraphQLBigInteger, - Scalars.GraphQLFloat) - .map(GraphQLScalarType::getName) - .collect(toList())) - + .applicableTypes(GRAPHQL_NUMBER_AND_STRING_TYPES) .directiveSDL("directive @Range(min : Int = 0, max : Int = %d, message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", Integer.MAX_VALUE, getMessageTemplate()) @@ -53,29 +34,15 @@ Integer.MAX_VALUE, getMessageTemplate()) @Override public boolean appliesToType(GraphQLInputType inputType) { - return isOneOfTheseTypes(inputType, - GraphQLString, - Scalars.GraphQLByte, - Scalars.GraphQLShort, - Scalars.GraphQLInt, - Scalars.GraphQLLong, - Scalars.GraphQLBigDecimal, - Scalars.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); + GraphQLAppliedDirective directive = validationEnvironment.getContextObject(GraphQLAppliedDirective.class); BigDecimal min = asBigDecimal(getIntArg(directive, "min")); BigDecimal max = asBigDecimal(getIntArg(directive, "max")); @@ -88,10 +55,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(); @@ -101,9 +65,12 @@ private boolean isOK(BigDecimal argBD, BigDecimal min, BigDecimal max) { if (argBD.compareTo(max) > 0) { return false; } - if (argBD.compareTo(min) < 0) { - return false; - } + + return argBD.compareTo(min) >= 0; + } + + @Override + protected boolean appliesToListElements() { return true; } } diff --git a/src/main/java/graphql/validation/constraints/standard/SizeConstraint.java b/src/main/java/graphql/validation/constraints/standard/SizeConstraint.java index 5027f12..876205e 100644 --- a/src/main/java/graphql/validation/constraints/standard/SizeConstraint.java +++ b/src/main/java/graphql/validation/constraints/standard/SizeConstraint.java @@ -1,35 +1,21 @@ package graphql.validation.constraints.standard; -import graphql.GraphQLError; import graphql.Scalars; -import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLInputType; -import graphql.validation.constraints.AbstractDirectiveConstraint; import graphql.validation.constraints.Documentation; -import graphql.validation.rules.ValidationEnvironment; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -public class SizeConstraint extends AbstractDirectiveConstraint { +public class SizeConstraint extends AbstractSizeConstraint { public SizeConstraint() { super("Size"); } - @Override public Documentation getDocumentation() { return Documentation.newDocumentation() .messageTemplate(getMessageTemplate()) - .description("The element size must be between the specified `min` and `max` boundaries (inclusive).") - .example("updateDrivingNotes( drivingNote : String @Size( min : 1000, max : 100000)) : DriverDetails") - - .applicableTypeNames(Scalars.GraphQLString.getName(), Scalars.GraphQLID.getName(), "Lists", "Input Objects") - + .applicableTypes(Scalars.GraphQLString, Scalars.GraphQLID) .directiveSDL("directive @Size(min : Int = 0, max : Int = %d, message : String = \"%s\") " + "on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION", Integer.MAX_VALUE, getMessageTemplate()) @@ -39,35 +25,11 @@ Integer.MAX_VALUE, getMessageTemplate()) @Override public boolean appliesToType(GraphQLInputType inputType) { - return isStringOrIDOrListOrMap(inputType); + return isStringOrID(inputType); } @Override - protected List runConstraint(ValidationEnvironment validationEnvironment) { - Object validatedValue = validationEnvironment.getValidatedValue(); - GraphQLInputType argType = validationEnvironment.getValidatedType(); - - GraphQLDirective directive = validationEnvironment.getContextObject(GraphQLDirective.class); - int min = getIntArg(directive, "min"); - int max = getIntArg(directive, "max"); - - - int size = getStringOrIDOrObjectOrMapLength(argType, validatedValue); - - if (size < min) { - return mkError(validationEnvironment, directive, mkParams(validatedValue, validationEnvironment, min, max, size)); - } - if (size > max) { - return mkError(validationEnvironment, directive, mkParams(validatedValue, validationEnvironment, min, max, size)); - } - return Collections.emptyList(); - } - - private Map mkParams(Object validatedValue, ValidationEnvironment validationEnvironment, int min, int max, int size) { - return mkMessageParams(validatedValue, validationEnvironment, - "min", min, - "max", max, - "size", size - ); + protected boolean appliesToListElements() { + return true; } } diff --git a/src/main/java/graphql/validation/el/BetterMapELResolver.java b/src/main/java/graphql/validation/el/BetterMapELResolver.java index eb10b2e..59d6b88 100644 --- a/src/main/java/graphql/validation/el/BetterMapELResolver.java +++ b/src/main/java/graphql/validation/el/BetterMapELResolver.java @@ -1,9 +1,9 @@ package graphql.validation.el; import graphql.Internal; +import jakarta.el.ELContext; +import jakarta.el.MapELResolver; -import javax.el.ELContext; -import javax.el.MapELResolver; import java.util.Arrays; import java.util.List; import java.util.Map; diff --git a/src/main/java/graphql/validation/el/ELSupport.java b/src/main/java/graphql/validation/el/ELSupport.java index c11d5cf..1571bae 100644 --- a/src/main/java/graphql/validation/el/ELSupport.java +++ b/src/main/java/graphql/validation/el/ELSupport.java @@ -1,16 +1,15 @@ package graphql.validation.el; import graphql.Internal; -import org.hibernate.validator.internal.engine.messageinterpolation.FormatterWrapper; - -import javax.el.ELContext; -import javax.el.ELManager; -import javax.el.ExpressionFactory; -import javax.el.StandardELContext; -import javax.el.ValueExpression; +import jakarta.el.ELContext; +import jakarta.el.ELManager; +import jakarta.el.ExpressionFactory; +import jakarta.el.StandardELContext; +import jakarta.el.ValueExpression; import java.lang.reflect.Method; import java.util.Locale; import java.util.Map; +import org.hibernate.validator.internal.engine.messageinterpolation.FormatterWrapper; @Internal @SuppressWarnings("unused") diff --git a/src/main/java/graphql/validation/interpolation/ResourceBundleMessageInterpolator.java b/src/main/java/graphql/validation/interpolation/ResourceBundleMessageInterpolator.java index 76d83d3..99df8c1 100644 --- a/src/main/java/graphql/validation/interpolation/ResourceBundleMessageInterpolator.java +++ b/src/main/java/graphql/validation/interpolation/ResourceBundleMessageInterpolator.java @@ -4,21 +4,22 @@ 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 javax.validation.Path; +import jakarta.validation.Constraint; +import jakarta.validation.Path; +import jakarta.validation.Payload; import org.hibernate.validator.internal.engine.MessageInterpolatorContext; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl; import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl.ConstraintType; import org.hibernate.validator.internal.metadata.location.ConstraintLocation.ConstraintLocationKind; import org.hibernate.validator.internal.util.annotation.ConstraintAnnotationDescriptor; +import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel; import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator; import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator; -import javax.validation.Constraint; -import javax.validation.Payload; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.util.LinkedHashMap; @@ -71,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); } @@ -161,7 +162,7 @@ private MessageInterpolatorContext buildHibernateContext(Map mes return new MessageInterpolatorContext( constraintDescriptor, validatedValue, rootBeanType, - propertyPath, messageParams, expressionVariables); + propertyPath, messageParams, expressionVariables, ExpressionLanguageFeatureLevel.BEAN_PROPERTIES, true); } private org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator hibernateInterpolator() { @@ -184,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 2ef7207..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; @@ -69,7 +69,7 @@ public List runValidationRules(DataFetchingEnvironment env, Messag List errors = new ArrayList<>(); - GraphQLObjectType fieldContainer = env.getExecutionStepInfo().getFieldContainer(); + GraphQLObjectType fieldContainer = env.getExecutionStepInfo().getObjectType(); GraphQLFieldDefinition fieldDefinition = env.getFieldDefinition(); ResultPath fieldPath = env.getExecutionStepInfo().getPath(); // @@ -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(); @@ -162,8 +162,7 @@ private List walkObjectArg(ValidationRule rule, ValidationEnvironm for (GraphQLInputObjectField inputField : fieldDefinitions) { GraphQLInputType fieldType = inputField.getType(); - List directives = inputField.getDirectives(); - Object validatedValue = objectMap.getOrDefault(inputField.getName(), inputField.getDefaultValue()); + Object validatedValue = objectMap.getOrDefault(inputField.getName(), GraphQLInputObjectField.getInputFieldDefaultValue(inputField)); if (validatedValue == null) { continue; } @@ -174,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) ); @@ -188,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/main/resources/graphql/validation/ValidationMessages.properties b/src/main/resources/graphql/validation/ValidationMessages.properties index 0ba4dca..e5759ac 100644 --- a/src/main/resources/graphql/validation/ValidationMessages.properties +++ b/src/main/resources/graphql/validation/ValidationMessages.properties @@ -13,9 +13,11 @@ graphql.validation.Negative.message={path} must be less than 0 graphql.validation.NegativeOrZero.message={path} must be less than or equal to 0 graphql.validation.NotBlank.message={path} must not be blank graphql.validation.NotEmpty.message={path} must not be empty +graphql.validation.ContainerNotEmpty.message={path} must contain at least one element graphql.validation.Pattern.message={path} must match "{regexp}" graphql.validation.Positive.message={path} must be greater than 0 graphql.validation.PositiveOrZero.message={path} must be greater than or equal to 0 graphql.validation.Range.message={path} range must be between {min} and {max} graphql.validation.Size.message={path} size must be between {min} and {max} +graphql.validation.ContainerSize.message={path} size must be between {min} and {max} diff --git a/src/test/groovy/ELDiscover.java b/src/test/groovy/ELDiscover.java index b03459d..7c280b1 100644 --- a/src/test/groovy/ELDiscover.java +++ b/src/test/groovy/ELDiscover.java @@ -1,3 +1,8 @@ +import jakarta.el.ELContext; +import jakarta.el.ELManager; +import jakarta.el.ExpressionFactory; +import jakarta.el.StandardELContext; +import jakarta.el.ValueExpression; import org.hibernate.validator.constraints.Range; import org.hibernate.validator.internal.engine.MessageInterpolatorContext; import org.hibernate.validator.internal.engine.path.PathImpl; @@ -6,16 +11,12 @@ import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl.ConstraintType; import org.hibernate.validator.internal.metadata.location.ConstraintLocation.ConstraintLocationKind; import org.hibernate.validator.internal.util.annotation.ConstraintAnnotationDescriptor; +import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel; import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator; -import javax.el.ELContext; -import javax.el.ELManager; -import javax.el.ExpressionFactory; -import javax.el.StandardELContext; -import javax.el.ValueExpression; -import javax.validation.MessageInterpolator; -import javax.validation.constraints.Email; -import javax.validation.constraints.NotNull; +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; @@ -104,8 +105,13 @@ public static void main(String[] args) throws Exception { PathImpl rootPath = PathImpl.createRootPath(); + ExpressionLanguageFeatureLevel level = ExpressionLanguageFeatureLevel.BEAN_METHODS; MessageInterpolator.Context context = new MessageInterpolatorContext( - constraintDescriptor, user, null, rootPath, Collections.emptyMap(), Collections.emptyMap()); + constraintDescriptor, user, null, rootPath, + Collections.emptyMap(), Collections.emptyMap(), + level, + true); + print("${validatedValue.age}", messageInterpolator, context); print("${validatedValue}", messageInterpolator, context); 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/AbstractDirectiveConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/AbstractDirectiveConstraintTest.groovy index ff17de8..0b76ddb 100644 --- a/src/test/groovy/graphql/validation/constraints/AbstractDirectiveConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/AbstractDirectiveConstraintTest.groovy @@ -1,5 +1,6 @@ package graphql.validation.constraints + import graphql.validation.constraints.standard.SizeConstraint import spock.lang.Unroll @@ -8,7 +9,7 @@ class AbstractDirectiveConstraintTest extends BaseConstraintTestSupport { @Unroll def "complex object argument constraints"() { - def extraSDL = ''' + def extraSDL = """ input ProductItem { code : String @Size(max : 5) price : String @Size(max : 3) @@ -18,9 +19,8 @@ class AbstractDirectiveConstraintTest extends BaseConstraintTestSupport { name : String @Size(max : 7) items : [ProductItem!]! # crazy nulls crazyItems : [[[ProductItem!]!]] # nuts but can we handle it - } - - ''' + } + """ // this tests that we can walk a complex tree of types via one specific implementation // but the same applies to all AbstractDirectiveConstraint classes @@ -33,18 +33,18 @@ class AbstractDirectiveConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | eSize | expectedMessage + fieldDeclaration | argVal | eSize | expectedMessage // size can handle list elements - "field( testArg : [Product!] @Size(max : 2) ) : ID" | [[:], [:], [:]] | 1 | "graphql.validation.Size.message;path=/testArg;val:[[:], [:], [:]];\t" + "field( testArg : [Product!] ) : ID" | [[:], [:], [:]] | 0 | "" // goes down into input types - "field( testArg : [Product!] @Size(max : 2) ) : ID" | [[name: "morethan7"], [:]] | 1 | "graphql.validation.Size.message;path=/testArg[0]/name;val:morethan7;\t" + "field( testArg : [Product!] ) : ID" | [[name: "morethan7"], [:]] | 1 | "graphql.validation.Size.message;path=/testArg[0]/name;val:morethan7;\t" // shows that it traverses down lists - "field( testArg : [Product!] @Size(max : 2) ) : ID" | [[name: "ok"], [name: "notOkHere"]] | 1 | "graphql.validation.Size.message;path=/testArg[1]/name;val:notOkHere;\t" + "field( testArg : [Product!] ) : ID" | [[name: "ok"], [name: "notOkHere"]] | 1 | "graphql.validation.Size.message;path=/testArg[1]/name;val:notOkHere;\t" // shows that it traverses down lists and objects - "field( testArg : [Product!] @Size(max : 2) ) : ID" | [[items: [[code: "morethan5", price: "morethan3"]]]] | 2 | "graphql.validation.Size.message;path=/testArg[0]/items[0]/code;val:morethan5;\tgraphql.validation.Size.message;path=/testArg[0]/items[0]/price;val:morethan3;\t" + "field( testArg : [Product!] ) : ID" | [[items: [[code: "morethan5", price: "morethan3"]]]] | 2 | "graphql.validation.Size.message;path=/testArg[0]/items[0]/code;val:morethan5;\tgraphql.validation.Size.message;path=/testArg[0]/items[0]/price;val:morethan3;\t" // shows that it traverses down crazy lists and objects - "field( testArg : [Product!] @Size(max : 2) ) : ID" | [[crazyItems: [[[[code: "morethan5"]]]]]] | 1 | "graphql.validation.Size.message;path=/testArg[0]/crazyItems[0][0][0]/code;val:morethan5;\t" + "field( testArg : [Product!] ) : ID" | [[crazyItems: [[[[code: "morethan5"]]]]]] | 1 | "graphql.validation.Size.message;path=/testArg[0]/crazyItems[0][0][0]/code;val:morethan5;\t" } } diff --git a/src/test/groovy/graphql/validation/constraints/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/DirectiveConstraintsTest.groovy b/src/test/groovy/graphql/validation/constraints/DirectiveConstraintsTest.groovy index 0b4a7d4..a8225f7 100644 --- a/src/test/groovy/graphql/validation/constraints/DirectiveConstraintsTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/DirectiveConstraintsTest.groovy @@ -12,7 +12,7 @@ class DirectiveConstraintsTest extends BaseConstraintTestSupport { def rules = DirectiveConstraints.newDirectiveConstraints().build() then: - rules.getConstraints().size() == 17 + rules.getConstraints().size() == 19 when: rules = DirectiveConstraints.newDirectiveConstraints().clearRules().build() @@ -69,8 +69,8 @@ class DirectiveConstraintsTest extends BaseConstraintTestSupport { "field( testArg : NoSizeDirectives ) : ID" | "Range" - "field( testArg : [Product!] @Size(max : 2) ) : ID" | "Size" - "field( testArg : Product! @Size(max : 2) ) : ID" | "Size" + "field( testArg : [Product!] @ContainerSize(max : 2) ) : ID" | "ContainerSize,Size" + "field( testArg : Product! @ContainerSize(max : 2) ) : ID" | "ContainerSize,Size" "field( testArg : [Product!] ) : ID" | "Size" } @@ -116,7 +116,7 @@ class DirectiveConstraintsTest extends BaseConstraintTestSupport { def declaration = directiveValidationRules.getDirectivesDeclaration() then: declaration != null - declaration.getDirectiveDefinitions().size() == 17 + declaration.getDirectiveDefinitions().size() == 19 } } diff --git a/src/test/groovy/graphql/validation/constraints/standard/AssertTrueFalseConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/AssertTrueFalseConstraintTest.groovy index 41c2166..f386f1c 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/AssertTrueFalseConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/AssertTrueFalseConstraintTest.groovy @@ -19,14 +19,19 @@ class AssertTrueFalseConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage - 'field( arg : Boolean @AssertTrue ) : ID' | false | 'AssertTrue;path=/arg;val:false;\t' - 'field( arg : Boolean @AssertTrue ): ID' | true | '' + fieldDeclaration | argVal | expectedMessage + 'field( arg : Boolean @AssertTrue ) : ID' | false | 'AssertTrue;path=/arg;val:false;\t' + 'field( arg : Boolean @AssertTrue ): ID' | true | '' - 'field( arg : Boolean @AssertTrue(message : "custom")) : ID' | false | 'custom;path=/arg;val:false;\t' + 'field( arg : Boolean @AssertTrue(message : "custom")) : ID' | false | 'custom;path=/arg;val:false;\t' // nulls are valid - 'field( arg : Boolean @AssertTrue ) : ID' | null | '' + 'field( arg : Boolean @AssertTrue ) : ID' | null | '' + + // Lists + 'field( arg : [Boolean] @AssertTrue ) : ID' | [true, true] | '' + 'field( arg : [Boolean] @AssertTrue ) : ID' | [true, false] | 'AssertTrue;path=/arg[1];val:false;\t' + 'field( arg : [Boolean] @AssertTrue ) : ID' | [null] | '' } @Unroll @@ -41,13 +46,18 @@ class AssertTrueFalseConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage - 'field( arg : Boolean @AssertFalse ) : ID' | true | 'AssertFalse;path=/arg;val:true;\t' - 'field( arg : Boolean @AssertFalse ): ID' | false | '' + fieldDeclaration | argVal | expectedMessage + 'field( arg : Boolean @AssertFalse ) : ID' | true | 'AssertFalse;path=/arg;val:true;\t' + 'field( arg : Boolean @AssertFalse ): ID' | false | '' - 'field( arg : Boolean @AssertFalse(message : "custom")) : ID' | true | 'custom;path=/arg;val:true;\t' + 'field( arg : Boolean @AssertFalse(message : "custom")) : ID' | true | 'custom;path=/arg;val:true;\t' // nulls are valid - 'field( arg : Boolean @AssertFalse ) : ID' | null | '' + 'field( arg : Boolean @AssertFalse ) : ID' | null | '' + + // Lists + 'field( arg : [Boolean] @AssertFalse ) : ID' | [false, false] | '' + 'field( arg : [Boolean] @AssertFalse ) : ID' | [false, true] | 'AssertFalse;path=/arg[1];val:true;\t' + 'field( arg : [Boolean] @AssertFalse ) : ID' | [null] | '' } } \ No newline at end of file diff --git a/src/test/groovy/graphql/validation/constraints/standard/DecimalMinMaxConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/DecimalMinMaxConstraintTest.groovy index b7cd944..31755a4 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/DecimalMinMaxConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/DecimalMinMaxConstraintTest.groovy @@ -54,6 +54,11 @@ class DecimalMinMaxConstraintTest extends BaseConstraintTestSupport { // nulls are valid 'field( arg : Int @DecimalMin(value : "50" inclusive:false) ) : ID' | null | '' + + // Lists + 'field( arg : [Int] @DecimalMin( value: "50", inclusive: false ) ) : ID' | [50] | 'DecimalMin;path=/arg[0];val:50;\t' + 'field( arg : [Int] @DecimalMin( value: "50", inclusive: false ) ) : ID' | [51, 52] | '' + 'field( arg : [Int] @DecimalMin( value: "50", inclusive: false ) ) : ID' | [null] | '' } @Unroll @@ -68,24 +73,28 @@ class DecimalMinMaxConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage - 'field( arg : Int @DecimalMax(value : "100") ) : ID' | 150 | 'DecimalMax;path=/arg;val:150;\t' - 'field( arg : Int @DecimalMax(value : "100") ) : ID' | 50 | '' + fieldDeclaration | argVal | expectedMessage + 'field( arg : Int @DecimalMax(value : "100") ) : ID' | 150 | 'DecimalMax;path=/arg;val:150;\t' + 'field( arg : Int @DecimalMax(value : "100") ) : ID' | 50 | '' - 'field( arg : Int @DecimalMax(value : "100", message : "custom") ) : ID' | 150 | 'custom;path=/arg;val:150;\t' + 'field( arg : Int @DecimalMax(value : "100", message : "custom") ) : ID' | 150 | 'custom;path=/arg;val:150;\t' // edge case - 'field( arg : Int @DecimalMax(value : "50") ) : ID' | 51 | 'DecimalMax;path=/arg;val:51;\t' - 'field( arg : Int @DecimalMax(value : "50") ) : ID' | 50 | '' - 'field( arg : Int @DecimalMax(value : "50") ) : ID' | 49 | '' + 'field( arg : Int @DecimalMax(value : "50") ) : ID' | 51 | 'DecimalMax;path=/arg;val:51;\t' + 'field( arg : Int @DecimalMax(value : "50") ) : ID' | 50 | '' + 'field( arg : Int @DecimalMax(value : "50") ) : ID' | 49 | '' // exclusive - 'field( arg : Int @DecimalMax(value : "50" inclusive:false) ) : ID' | 50 | 'DecimalMax;path=/arg;val:50;\t' - 'field( arg : Int @DecimalMax(value : "50" inclusive:false) ) : ID' | 51 | 'DecimalMax;path=/arg;val:51;\t' - 'field( arg : Int @DecimalMax(value : "50" inclusive:false) ) : ID' | 49 | '' + 'field( arg : Int @DecimalMax(value : "50" inclusive:false) ) : ID' | 50 | 'DecimalMax;path=/arg;val:50;\t' + 'field( arg : Int @DecimalMax(value : "50" inclusive:false) ) : ID' | 51 | 'DecimalMax;path=/arg;val:51;\t' + 'field( arg : Int @DecimalMax(value : "50" inclusive:false) ) : ID' | 49 | '' // nulls are valid - 'field( arg : Int @DecimalMax(value : "50" inclusive:false) ) : ID' | null | '' + 'field( arg : Int @DecimalMax(value : "50" inclusive:false) ) : ID' | null | '' + // Lists + 'field( arg : [Int] @DecimalMax( value: "50", inclusive: false ) ) : ID' | [50] | 'DecimalMax;path=/arg[0];val:50;\t' + 'field( arg : [Int] @DecimalMax( value: "50", inclusive: false ) ) : ID' | [48, 49] | '' + 'field( arg : [Int] @DecimalMax( value: "50", inclusive: false ) ) : ID' | [null] | '' } } \ No newline at end of file diff --git a/src/test/groovy/graphql/validation/constraints/standard/DigitsConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/DigitsConstraintTest.groovy index 173a142..701746c 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/DigitsConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/DigitsConstraintTest.groovy @@ -19,33 +19,40 @@ 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) ) : 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' - 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | new BigDecimal("-123456.123") | 'Digits;path=/arg;val:-123456.123;\t' - 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | new BigDecimal("-12345.123") | 'Digits;path=/arg;val:-12345.123;\t' - 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | new BigDecimal("12345.123") | 'Digits;path=/arg;val:12345.123;\t' + 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | new BigDecimal("-12345.12") | '' + 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | new BigDecimal("-123456.12") | 'Digits;path=/arg;val:-123456.12;\t' + 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | new BigDecimal("-123456.123") | 'Digits;path=/arg;val:-123456.123;\t' + 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | new BigDecimal("-12345.123") | 'Digits;path=/arg;val:-12345.123;\t' + 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | new BigDecimal("12345.123") | 'Digits;path=/arg;val:12345.123;\t' - 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | Float.valueOf("-000000000.22") | '' + 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | Float.valueOf("-000000000.22") | '' - 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | Integer.valueOf("256874") | 'Digits;path=/arg;val:256874;\t' - 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | Double.valueOf("12.0001") | 'Digits;path=/arg;val:12.0001;\t' + 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | Integer.valueOf("256874") | 'Digits;path=/arg;val:256874;\t' + 'field( arg : String @Digits(integer : 5, fraction : 2) ) : ID' | Double.valueOf("12.0001") | 'Digits;path=/arg;val:12.0001;\t' // zero length - 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | null | '' - 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | Byte.valueOf("0") | 'Digits;path=/arg;val:0;\t' - 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | Double.valueOf("0") | 'Digits;path=/arg;val:0.0;\t' - 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | new BigDecimal(0) | 'Digits;path=/arg;val:0;\t' - 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | 0 | 'Digits;path=/arg;val:0;\t' - 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | 0L | 'Digits;path=/arg;val:0;\t' + 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | null | '' + 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | Byte.valueOf("0") | 'Digits;path=/arg;val:0;\t' + 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | Double.valueOf("0") | 'Digits;path=/arg;val:0.0;\t' + 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | new BigDecimal(0) | 'Digits;path=/arg;val:0;\t' + 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | 0 | 'Digits;path=/arg;val:0;\t' + 'field( arg : String @Digits(integer : 0, fraction : 0) ) : ID' | 0L | 'Digits;path=/arg;val:0;\t' // zeroes are trimmed - 'field( arg : String @Digits(integer : 12, fraction : 3) ) : ID' | 0.001d | '' - 'field( arg : String @Digits(integer : 12, fraction : 3) ) : ID' | 0.00100d | '' - 'field( arg : String @Digits(integer : 12, fraction : 3) ) : ID' | 0.0001d | 'Digits;path=/arg;val:1.0E-4;\t' + 'field( arg : String @Digits(integer : 12, fraction : 3) ) : ID' | 0.001d | '' + 'field( arg : String @Digits(integer : 12, fraction : 3) ) : ID' | 0.00100d | '' + 'field( arg : String @Digits(integer : 12, fraction : 3) ) : ID' | 0.0001d | 'Digits;path=/arg;val:1.0E-4;\t' + + + // Lists + 'field( arg : [String] @Digits( integer : 5, fraction : 2 ) ) : ID' | ["500.2", "343.2343"] | 'Digits;path=/arg[1];val:343.2343;\t' + 'field( arg : [String] @Digits( integer : 5, fraction : 2 ) ) : ID' | ["500.2", "343.2"] | '' + 'field( arg : [String] @Digits( integer : 5, fraction : 2 ) ) : ID' | [null] | '' } } \ No newline at end of file diff --git a/src/test/groovy/graphql/validation/constraints/standard/ExpressionConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/ExpressionConstraintTest.groovy index 30475df..835a7dc 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/ExpressionConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/ExpressionConstraintTest.groovy @@ -11,7 +11,6 @@ class ExpressionConstraintTest extends BaseConstraintTestSupport { @Unroll def "expression constraints on a field"() { - DirectiveConstraint ruleUnderTest = new ExpressionConstraint() expect: @@ -27,8 +26,8 @@ class ExpressionConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | args | expectedMessage - 'field( first : Int, last : Int) : ID ' + relayCheck | [first: 10] | "" + fieldDeclaration | args | expectedMessage + 'field( first : Int, last : Int) : ID ' + relayCheck | [first: 10] | "" 'field( first : Int, last : Int) : ID ' + relayCheck | [first: 10, last: 20] | "Expression;path=/field;val:null;\t" } diff --git a/src/test/groovy/graphql/validation/constraints/standard/MinMaxConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/MinMaxConstraintTest.groovy index eda68a3..fd6be57 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/MinMaxConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/MinMaxConstraintTest.groovy @@ -19,19 +19,23 @@ class MinMaxConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage - "field( arg : Int @Min(value : 100) ) : ID" | 50 | "Min;path=/arg;val:50;\t" - "field( arg : Int @Min(value : 10) ) : ID" | 50 | "" + fieldDeclaration | argVal | expectedMessage + "field( arg : Int @Min(value : 100) ) : ID" | 50 | "Min;path=/arg;val:50;\t" + "field( arg : Int @Min(value : 10) ) : ID" | 50 | "" - 'field( arg : Int @Min(value : 100, message : "custom") ) : ID' | 50 | "custom;path=/arg;val:50;\t" + 'field( arg : Int @Min(value : 100, message : "custom") ) : ID' | 50 | "custom;path=/arg;val:50;\t" // edge case - "field( arg : Int @Min(value : 50) ) : ID" | 49 | "Min;path=/arg;val:49;\t" - "field( arg : Int @Min(value : 50) ) : ID" | 50 | "" - "field( arg : Int @Min(value : 50) ) : ID" | 51 | "" + "field( arg : Int @Min(value : 50) ) : ID" | 49 | "Min;path=/arg;val:49;\t" + "field( arg : Int @Min(value : 50) ) : ID" | 50 | "" + "field( arg : Int @Min(value : 50) ) : ID" | 51 | "" // nulls are valid - 'field( arg : Int @Min(value : 50) ) : ID' | null | '' + 'field( arg : Int @Min(value : 50) ) : ID' | null | '' + // Lists + 'field( arg : [Int] @Min( value : 50 ) ) : ID' | [50, 49] | 'Min;path=/arg[1];val:49;\t' + 'field( arg : [Int] @Min( value : 50 ) ) : ID' | [50, 51] | '' + 'field( arg : [Int] @Min( value : 50 ) ) : ID' | [null] | '' } @Unroll @@ -46,17 +50,22 @@ class MinMaxConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage - "field( arg : Int @Max(value : 100) ) : ID" | 150 | "Max;path=/arg;val:150;\t" - "field( arg : Int @Max(value : 100) ) : ID" | 50 | "" + fieldDeclaration | argVal | expectedMessage + "field( arg : Int @Max(value : 100) ) : ID" | 150 | "Max;path=/arg;val:150;\t" + "field( arg : Int @Max(value : 100) ) : ID" | 50 | "" - 'field( arg : Int @Max(value : 100, message : "custom") ) : ID' | 150 | "custom;path=/arg;val:150;\t" + 'field( arg : Int @Max(value : 100, message : "custom") ) : ID' | 150 | "custom;path=/arg;val:150;\t" // edge case - "field( arg : Int @Max(value : 50) ) : ID" | 51 | "Max;path=/arg;val:51;\t" - "field( arg : Int @Max(value : 50) ) : ID" | 50 | "" - "field( arg : Int @Max(value : 50) ) : ID" | 49 | "" + "field( arg : Int @Max(value : 50) ) : ID" | 51 | "Max;path=/arg;val:51;\t" + "field( arg : Int @Max(value : 50) ) : ID" | 50 | "" + "field( arg : Int @Max(value : 50) ) : ID" | 49 | "" // nulls are valid - 'field( arg : Int @Max(value : 50) ) : ID' | null | '' + 'field( arg : Int @Max(value : 50) ) : ID' | null | '' + + // Lists + 'field( arg : [Int] @Max( value : 50 ) ) : ID' | [50, 51] | 'Max;path=/arg[1];val:51;\t' + 'field( arg : [Int] @Max( value : 50 ) ) : ID' | [50, 49] | '' + 'field( arg : [Int] @Max( value : 50 ) ) : ID' | [null] | '' } } \ No newline at end of file diff --git a/src/test/groovy/graphql/validation/constraints/standard/NegativePositiveConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/NegativePositiveConstraintTest.groovy index 81a0309..823853c 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/NegativePositiveConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/NegativePositiveConstraintTest.groovy @@ -19,19 +19,23 @@ class NegativePositiveConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage - "field( arg : Int @Positive) : ID" | -50 | "Positive;path=/arg;val:-50;\t" - "field( arg : Int @Positive) : ID" | 50 | "" + fieldDeclaration | argVal | expectedMessage + "field( arg : Int @Positive) : ID" | -50 | "Positive;path=/arg;val:-50;\t" + "field( arg : Int @Positive) : ID" | 50 | "" - 'field( arg : Int @Positive(message : "custom") ) : ID' | -50 | "custom;path=/arg;val:-50;\t" + 'field( arg : Int @Positive(message : "custom") ) : ID' | -50 | "custom;path=/arg;val:-50;\t" // edge case - "field( arg : Int @Positive) : ID" | 0 | "Positive;path=/arg;val:0;\t" - "field( arg : Int @Positive) : ID" | 1 | "" + "field( arg : Int @Positive) : ID" | 0 | "Positive;path=/arg;val:0;\t" + "field( arg : Int @Positive) : ID" | 1 | "" // nulls are valid - 'field( arg : Int @Positive ) : ID' | null | '' + 'field( arg : Int @Positive ) : ID' | null | '' + // Lists + 'field( arg : [Int] @Positive ) : ID' | [50, 0] | 'Positive;path=/arg[1];val:0;\t' + 'field( arg : [Int] @Positive ) : ID' | [50, 51] | '' + 'field( arg : [Int] @Positive ) : ID' | [null] | '' } @Unroll @@ -46,19 +50,24 @@ class NegativePositiveConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage - "field( arg : Int @PositiveOrZero) : ID" | -50 | "PositiveOrZero;path=/arg;val:-50;\t" - "field( arg : Int @PositiveOrZero) : ID" | 50 | "" + fieldDeclaration | argVal | expectedMessage + "field( arg : Int @PositiveOrZero) : ID" | -50 | "PositiveOrZero;path=/arg;val:-50;\t" + "field( arg : Int @PositiveOrZero) : ID" | 50 | "" - 'field( arg : Int @PositiveOrZero(message : "custom") ) : ID' | -50 | "custom;path=/arg;val:-50;\t" + 'field( arg : Int @PositiveOrZero(message : "custom") ) : ID' | -50 | "custom;path=/arg;val:-50;\t" // edge case - "field( arg : Int @PositiveOrZero) : ID" | -1 | "PositiveOrZero;path=/arg;val:-1;\t" - "field( arg : Int @PositiveOrZero) : ID" | 0 | "" - "field( arg : Int @PositiveOrZero) : ID" | 1 | "" + "field( arg : Int @PositiveOrZero) : ID" | -1 | "PositiveOrZero;path=/arg;val:-1;\t" + "field( arg : Int @PositiveOrZero) : ID" | 0 | "" + "field( arg : Int @PositiveOrZero) : ID" | 1 | "" // nulls are valid - 'field( arg : Int @PositiveOrZero ) : ID' | null | '' + 'field( arg : Int @PositiveOrZero ) : ID' | null | '' + + // Lists + 'field( arg : [Int] @PositiveOrZero ) : ID' | [50, -1] | 'PositiveOrZero;path=/arg[1];val:-1;\t' + 'field( arg : [Int] @PositiveOrZero ) : ID' | [50, 0] | '' + 'field( arg : [Int] @PositiveOrZero ) : ID' | [null] | '' } @Unroll @@ -73,18 +82,23 @@ class NegativePositiveConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage - "field( arg : Int @Negative) : ID" | 50 | "Negative;path=/arg;val:50;\t" - "field( arg : Int @Negative) : ID" | -50 | "" + fieldDeclaration | argVal | expectedMessage + "field( arg : Int @Negative) : ID" | 50 | "Negative;path=/arg;val:50;\t" + "field( arg : Int @Negative) : ID" | -50 | "" - 'field( arg : Int @Negative(message : "custom") ) : ID' | 50 | "custom;path=/arg;val:50;\t" + 'field( arg : Int @Negative(message : "custom") ) : ID' | 50 | "custom;path=/arg;val:50;\t" // edge case - "field( arg : Int @Negative) : ID" | 0 | "Negative;path=/arg;val:0;\t" - "field( arg : Int @Negative) : ID" | -1 | "" + "field( arg : Int @Negative) : ID" | 0 | "Negative;path=/arg;val:0;\t" + "field( arg : Int @Negative) : ID" | -1 | "" // nulls are valid - 'field( arg : Int @Negative ) : ID' | null | '' + 'field( arg : Int @Negative ) : ID' | null | '' + + // Lists + 'field( arg : [Int] @Negative ) : ID' | [0, -1] | 'Negative;path=/arg[0];val:0;\t' + 'field( arg : [Int] @Negative ) : ID' | [-50, -1] | '' + 'field( arg : [Int] @Negative ) : ID' | [null] | '' } @Unroll @@ -99,18 +113,23 @@ class NegativePositiveConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage - 'field( arg : Int @NegativeOrZero) : ID' | 50 | 'NegativeOrZero;path=/arg;val:50;\t' - "field( arg : Int @NegativeOrZero) : ID" | -50 | '' + fieldDeclaration | argVal | expectedMessage + 'field( arg : Int @NegativeOrZero) : ID' | 50 | 'NegativeOrZero;path=/arg;val:50;\t' + "field( arg : Int @NegativeOrZero) : ID" | -50 | '' - 'field( arg : Int @NegativeOrZero(message : "custom") ) : ID' | 50 | 'custom;path=/arg;val:50;\t' + 'field( arg : Int @NegativeOrZero(message : "custom") ) : ID' | 50 | 'custom;path=/arg;val:50;\t' // edge case - 'field( arg : Int @NegativeOrZero) : ID' | 1 | 'NegativeOrZero;path=/arg;val:1;\t' - 'field( arg : Int @NegativeOrZero) : ID' | 0 | '' - 'field( arg : Int @NegativeOrZero) : ID' | -1 | '' + 'field( arg : Int @NegativeOrZero) : ID' | 1 | 'NegativeOrZero;path=/arg;val:1;\t' + 'field( arg : Int @NegativeOrZero) : ID' | 0 | '' + 'field( arg : Int @NegativeOrZero) : ID' | -1 | '' // nulls are valid - 'field( arg : Int @NegativeOrZero ) : ID' | null | '' + 'field( arg : Int @NegativeOrZero ) : ID' | null | '' + + // Lists + 'field( arg : [Int] @NegativeOrZero ) : ID' | [1, -1] | 'NegativeOrZero;path=/arg[0];val:1;\t' + 'field( arg : [Int] @NegativeOrZero ) : ID' | [0, -1] | '' + 'field( arg : [Int] @NegativeOrZero ) : ID' | [null] | '' } } \ No newline at end of file diff --git a/src/test/groovy/graphql/validation/constraints/standard/NoEmptyBlankConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/NoEmptyBlankConstraintTest.groovy index e5a82cc..ce2c668 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/NoEmptyBlankConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/NoEmptyBlankConstraintTest.groovy @@ -9,7 +9,6 @@ class NoEmptyBlankConstraintTest extends BaseConstraintTestSupport { @Unroll def "not blank rule constraints"() { - DirectiveConstraint ruleUnderTest = new NotBlankRule() expect: @@ -19,23 +18,34 @@ class NoEmptyBlankConstraintTest extends BaseConstraintTestSupport { where: - fieldDeclaration | argVal | expectedMessage + fieldDeclaration | argVal | expectedMessage // strings - 'field( arg : String @NotBlank ) : ID' | "\t\n\r " | 'NotBlank;path=/arg;val:\t\n\r ;\t' - 'field( arg : String @NotBlank ) : ID' | "" | 'NotBlank;path=/arg;val:;\t' - 'field( arg : String @NotBlank ) : ID' | "\t\n\r X" | '' - 'field( arg : String @NotBlank ) : ID' | null | '' + 'field( arg : String @NotBlank ) : ID' | "\t\n\r " | 'NotBlank;path=/arg;val:\t\n\r ;\t' + 'field( arg : String @NotBlank ) : ID' | "" | 'NotBlank;path=/arg;val:;\t' + 'field( arg : String @NotBlank ) : ID' | "\t\n\r X" | '' + 'field( arg : String @NotBlank ) : ID' | null | '' // IDs - 'field( arg : ID @NotBlank ) : ID' | "\t\n\r " | 'NotBlank;path=/arg;val:\t\n\r ;\t' - 'field( arg : ID @NotBlank ) : ID' | "" | 'NotBlank;path=/arg;val:;\t' - 'field( arg : ID @NotBlank ) : ID' | "\t\n\r X" | '' - 'field( arg : ID @NotBlank ) : ID' | null | '' + 'field( arg : ID @NotBlank ) : ID' | "\t\n\r " | 'NotBlank;path=/arg;val:\t\n\r ;\t' + 'field( arg : ID @NotBlank ) : ID' | "" | 'NotBlank;path=/arg;val:;\t' + 'field( arg : ID @NotBlank ) : ID' | "\t\n\r X" | '' + 'field( arg : ID @NotBlank ) : ID' | null | '' + + // Lists + 'field( arg : [String] @NotBlank ) : ID' | [] | '' + 'field( arg : [String] @NotBlank ) : ID' | null | '' + 'field( arg : [String] @NotBlank ) : ID' | ["x"] | '' + 'field( arg : [String] @NotBlank ) : ID' | ["x", "y"] | '' + 'field( arg : [String] @NotBlank ) : ID' | ["x", " "] | 'NotBlank;path=/arg[1];val: ;\t' + 'field( arg : [ID] @NotBlank ) : ID' | [] | '' + 'field( arg : [ID] @NotBlank ) : ID' | null | '' + 'field( arg : [ID] @NotBlank ) : ID' | ["x"] | '' + 'field( arg : [ID] @NotBlank ) : ID' | ["x", "y"] | '' + 'field( arg : [String] @NotBlank ) : ID' | ["x", " "] | 'NotBlank;path=/arg[1];val: ;\t' } @Unroll def "not empty rule constraints"() { - DirectiveConstraint ruleUnderTest = new NotEmptyRule() expect: @@ -53,10 +63,10 @@ class NoEmptyBlankConstraintTest extends BaseConstraintTestSupport { 'field( arg : String @NotEmpty ) : ID' | "ABC" | '' // IDs - 'field( arg : ID @NotEmpty ) : ID' | "" | 'NotEmpty;path=/arg;val:;\t' - 'field( arg : ID @NotEmpty ) : ID' | null | '' - 'field( arg : ID @NotEmpty ) : ID' | "\t\n\r" | '' - 'field( arg : ID @NotEmpty ) : ID' | "ABC" | '' + 'field( arg : ID @NotEmpty ) : ID' | "" | 'NotEmpty;path=/arg;val:;\t' + 'field( arg : ID @NotEmpty ) : ID' | null | '' + 'field( arg : ID @NotEmpty ) : ID' | "\t\n\r" | '' + 'field( arg : ID @NotEmpty ) : ID' | "ABC" | '' // objects @@ -65,13 +75,42 @@ class NoEmptyBlankConstraintTest extends BaseConstraintTestSupport { 'field( arg : InputObject @NotEmpty ) : ID' | [name: "x"] | '' // lists - 'field( arg : [String] @NotEmpty ) : ID' | [] | 'NotEmpty;path=/arg;val:[];\t' + 'field( arg : [String] @NotEmpty ) : ID' | [] | '' // Validated by @ContainerNotEmpty 'field( arg : [String] @NotEmpty ) : ID' | null | '' 'field( arg : [String] @NotEmpty ) : ID' | ["x"] | '' - 'field( arg : [ID] @NotEmpty ) : ID' | [] | 'NotEmpty;path=/arg;val:[];\t' - 'field( arg : [ID] @NotEmpty ) : ID' | null | '' - 'field( arg : [ID] @NotEmpty ) : ID' | ["x"] | '' + 'field( arg : [String] @NotEmpty ) : ID' | ["\t"] | '' + 'field( arg : [String] @NotEmpty ) : ID' | [""] | 'NotEmpty;path=/arg[0];val:;\t' + 'field( arg : [ID] @NotEmpty ) : ID' | [] | '' + 'field( arg : [ID] @NotEmpty ) : ID' | null | '' + 'field( arg : [ID] @NotEmpty ) : ID' | ["x"] | '' + 'field( arg : [ID] @NotEmpty ) : ID' | ["\t"] | '' + 'field( arg : [ID] @NotEmpty ) : ID' | [""] | 'NotEmpty;path=/arg[0];val:;\t' + } + + @Unroll + def "container not empty rule constraints"() { + + DirectiveConstraint ruleUnderTest = new ContainerNotEmptyConstraint() + + expect: + def errors = runValidation(ruleUnderTest, fieldDeclaration, "arg", argVal) + assertErrors(errors, expectedMessage) + + where: + + fieldDeclaration | argVal | expectedMessage + // lists + 'field( arg : [String] @ContainerNotEmpty ) : ID' | [] | 'ContainerNotEmpty;path=/arg;val:[];\t' + 'field( arg : [String] @ContainerNotEmpty ) : ID' | null | '' + 'field( arg : [String] @ContainerNotEmpty ) : ID' | ["x"] | '' + 'field( arg : [String] @ContainerNotEmpty ) : ID' | ["\t"] | '' + 'field( arg : [String] @ContainerNotEmpty ) : ID' | [""] | '' + 'field( arg : [ID] @ContainerNotEmpty ) : ID' | [] | 'ContainerNotEmpty;path=/arg;val:[];\t' + 'field( arg : [ID] @ContainerNotEmpty ) : ID' | null | '' + 'field( arg : [ID] @ContainerNotEmpty ) : ID' | ["x"] | '' + 'field( arg : [ID] @ContainerNotEmpty ) : ID' | ["\t"] | '' + 'field( arg : [ID] @ContainerNotEmpty ) : ID' | [""] | '' } } \ No newline at end of file diff --git a/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy index 56bae51..3e932a4 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/PatternConstraintTest.groovy @@ -24,10 +24,12 @@ class PatternConstraintTest extends BaseConstraintTestSupport { 'field( arg : String @Pattern(regexp:"[A-Z]*") ) : ID' | "ABC" | '' 'field( arg : ID @Pattern(regexp:"[A-Z]*") ) : ID' | "ABCd" | 'Pattern;path=/arg;val:ABCd;\t' 'field( arg : ID @Pattern(regexp:"[A-Z]*") ) : ID' | "ABC" | '' + + // Lists 'field( arg : [String] @Pattern(regexp:"[A-Z]*") ) : ID' | ["ABC"] | '' - 'field( arg : [String] @Pattern(regexp:"[A-Z]*") ) : ID' | ["ABC", "ABCd"] | 'Pattern;path=/arg;val:[ABC, ABCd];\t' + 'field( arg : [String] @Pattern(regexp:"[A-Z]*") ) : ID' | ["ABC", "ABCd"] | 'Pattern;path=/arg[1];val:ABCd;\t' 'field( arg : [ID] @Pattern(regexp:"[A-Z]*") ) : ID' | ["ABC"] | '' - 'field( arg : [ID] @Pattern(regexp:"[A-Z]*") ) : ID' | ["ABC", "ABCd"] | 'Pattern;path=/arg;val:[ABC, ABCd];\t' + 'field( arg : [ID] @Pattern(regexp:"[A-Z]*") ) : ID' | ["ABC", "ABCd"] | 'Pattern;path=/arg[1];val:ABCd;\t' // nulls are valid 'field( arg : String @Pattern(regexp:"[A-Z]*") ) : ID' | null | '' diff --git a/src/test/groovy/graphql/validation/constraints/standard/RangeConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/RangeConstraintTest.groovy index 3c618f0..e47791d 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/RangeConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/RangeConstraintTest.groovy @@ -33,5 +33,10 @@ class RangeConstraintTest extends BaseConstraintTestSupport { "field( arg : String @Range(max : 10) ) : ID" | Long.valueOf("12") | "Range;path=/arg;val:12;\t" "field( arg : String @Range(max : 10) ) : ID" | Integer.valueOf("12") | "Range;path=/arg;val:12;\t" "field( arg : String @Range(max : 10) ) : ID" | Short.valueOf("12") | "Range;path=/arg;val:12;\t" + + // Lists + 'field( arg : [String] @Range(max : 10) ) : ID' | [50, 0] | 'Range;path=/arg[0];val:50;\t' + 'field( arg : [String] @Range(max : 10) ) : ID' | [9, 8] | '' + 'field( arg : [String] @Range(max : 10) ) : ID' | [null] | '' } } \ No newline at end of file diff --git a/src/test/groovy/graphql/validation/constraints/standard/SizeConstraintTest.groovy b/src/test/groovy/graphql/validation/constraints/standard/SizeConstraintTest.groovy index b34f24a..a67ae2e 100644 --- a/src/test/groovy/graphql/validation/constraints/standard/SizeConstraintTest.groovy +++ b/src/test/groovy/graphql/validation/constraints/standard/SizeConstraintTest.groovy @@ -5,11 +5,8 @@ import graphql.validation.constraints.DirectiveConstraint import spock.lang.Unroll class SizeConstraintTest extends BaseConstraintTestSupport { - - @Unroll def "size rule constraints"() { - DirectiveConstraint ruleUnderTest = new SizeConstraint() expect: @@ -28,12 +25,36 @@ class SizeConstraintTest extends BaseConstraintTestSupport { 'field( arg : String @Size(min : 5, message : "custom") ) : ID' | "123" | "custom;path=/arg;val:123;\t" "field( arg : String @Size(min : 5) ) : ID" | null | "" - //IDs - "field( arg : ID @Size(max : 10) ) : ID" | "1234567891011" | "Size;path=/arg;val:1234567891011;\t" - "field( arg : ID @Size(max : 100) ) : ID" | "1234567891011" | "" - "field( arg : ID @Size(max : 10, min : 5) ) : ID" | "123" | "Size;path=/arg;val:123;\t" + // IDs + "field( arg : ID @Size(max : 10) ) : ID" | "1234567891011" | "Size;path=/arg;val:1234567891011;\t" + "field( arg : ID @Size(max : 100) ) : ID" | "1234567891011" | "" + "field( arg : ID @Size(max : 10, min : 5) ) : ID" | "123" | "Size;path=/arg;val:123;\t" + + 'field( arg : ID @Size(min : 5, message : "custom") ) : ID' | "123" | "custom;path=/arg;val:123;\t" + "field( arg : ID @Size(min : 5) ) : ID" | null | "" + + // Lists + 'field (arg : [String] @Size(min: 5)) : ID' | [] | "" // Validated by @ContainerSize + } + + @Unroll + def "container size rule constraints"() { + DirectiveConstraint ruleUnderTest = new ContainerSizeConstraint() + + expect: + + def errors = runValidation(ruleUnderTest, fieldDeclaration, "arg", argVal) + assertErrors(errors, expectedMessage) + + where: - 'field( arg : ID @Size(min : 5, message : "custom") ) : ID' | "123" | "custom;path=/arg;val:123;\t" - "field( arg : ID @Size(min : 5) ) : ID" | null | "" + fieldDeclaration | argVal | expectedMessage + // Lists + 'field (arg : [String] @ContainerSize(min: 2)) : ID' | [] | "ContainerSize;path=/arg;val:[];\t" + 'field (arg : [String] @ContainerSize(max: 1)) : ID' | ["asd", "sdf"] | "ContainerSize;path=/arg;val:[asd, sdf];\t" + 'field (arg : [String] @ContainerSize(min: 2)) : ID' | ["a", "b"] | "" + 'field (arg : [String] @ContainerSize(max: 2)) : ID' | ["a", "b"] | "" + 'field (arg : [String] @ContainerSize(max: 5)) : ID' | [] | "" + 'field (arg : [String] @ContainerSize(min: 2)) : ID' | null | "" } } \ No newline at end of file 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 diff --git a/travis-build.sh b/travis-build.sh deleted file mode 100755 index c5e1815..0000000 --- a/travis-build.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -set -ev -echo "current git hash:" -git rev-parse --short HEAD -BUILD_COMMAND="./gradlew assemble && ./gradlew check --info" -if [ "${TRAVIS_PULL_REQUEST}" = "false" ] && [ "${TRAVIS_BRANCH}" = "master" ]; then - echo "Building on master" - BUILD_COMMAND="./gradlew clean assemble && ./gradlew check --info && ./gradlew bintrayUpload -x check --info" -fi -docker run -it --rm -v `pwd .`:/build -e RELEASE_VERSION=$RELEASE_VERSION -e BINTRAY_USER=$BINTRAY_USER -e BINTRAY_API_KEY=$BINTRAY_API_KEY -e MAVEN_CENTRAL_USER=$MAVEN_CENTRAL_USER -e MAVEN_CENTRAL_PASSWORD=$MAVEN_CENTRAL_PASSWORD -w /build openjdk:8u131-jdk bash -c "${BUILD_COMMAND}"