diff --git a/README.adoc b/README.adoc index f5d8f46c18..43c5b4f045 100644 --- a/README.adoc +++ b/README.adoc @@ -1644,6 +1644,7 @@ $ touch .springformat ==== Intellij IDEA In order to setup Intellij you should import our coding conventions, inspection profiles and set up the checkstyle plugin. +The following files can be found in the https://github.com/spring-cloud/spring-cloud-build/tree/master/spring-cloud-build-tools[Spring Cloud Build] project. .spring-cloud-build-tools/ ---- @@ -1711,10 +1712,12 @@ Here you can find the Spring Cloud Contract folder structure ├── docker ├── samples ├── scripts +├── specs ├── spring-cloud-contract-dependencies -├── spring-cloud-contract-spec +├── spring-cloud-contract-shade ├── spring-cloud-contract-starters ├── spring-cloud-contract-stub-runner +├── spring-cloud-contract-stub-runner-boot ├── spring-cloud-contract-tools ├── spring-cloud-contract-verifier ├── spring-cloud-contract-wiremock @@ -1725,7 +1728,9 @@ Here you can find the Spring Cloud Contract folder structure - `docker` - folder contains docker images - `samples` - folder contains test samples together with standalone ones used also to build documentation - `scripts` - contains scripts to build and test `Spring Cloud Contract` with Maven, Gradle and standalone projects + - `specs` - contains specifications for the Contract DSL. - `spring-cloud-contract-dependencies` - contains Spring Cloud Contract BOM + - `spring-cloud-contract-shade` - shaded dependencies used by the plugins - `spring-cloud-contract-starters` - contains Spring Cloud Contract Starters - `spring-cloud-contract-spec` - contains specification modules (contains concept of a Contract) - `spring-cloud-contract-stub-runner` - contains Stub Runner related modules diff --git a/docker/spring-cloud-contract-docker/Dockerfile b/docker/spring-cloud-contract-docker/Dockerfile index 0434059b75..2f73f0e38f 100644 --- a/docker/spring-cloud-contract-docker/Dockerfile +++ b/docker/spring-cloud-contract-docker/Dockerfile @@ -26,5 +26,5 @@ WORKDIR /spring-cloud-contract/ COPY target/maven_dependencies /root/.m2/repository/ COPY target/gradle_dependencies /root/.gradle/ # Let's download gradle wrapper if for some reason it hasn't been downloaded -RUN ./gradlew clean build --stacktrace -x copyOutput || echo "Expected to fail" +RUN ./gradlew clean resolveDependencies build --stacktrace -x copyOutput || echo "Expected to fail" CMD ["./build.sh"] diff --git a/docker/spring-cloud-contract-docker/get_dependencies.sh b/docker/spring-cloud-contract-docker/get_dependencies.sh index 7b36ff0947..a56cbc7d3a 100755 --- a/docker/spring-cloud-contract-docker/get_dependencies.sh +++ b/docker/spring-cloud-contract-docker/get_dependencies.sh @@ -10,7 +10,7 @@ GRADLE_OUTPUT_DIR="${CURRENT_DIR}/target/gradle_dependencies/" pushd project rm -rf .gradle ./gradlew wrapper --gradle-version "${WRAPPER_VERSION}" -./gradlew clean build -g "${GRADLE_OUTPUT_DIR}" -x copyOutput || echo "Expected to fail the build" +./gradlew clean resolveDependencies build -g "${GRADLE_OUTPUT_DIR}" -x copyOutput || echo "Expected to fail the build" if [ -d "${GRADLE_WRAPPER_DIR}" ]; then echo "Copying Gradle Wrapper version [${WRAPPER_VERSION}]" mkdir -p "${GRADLE_OUTPUT_DIR}/wrapper/dists/" diff --git a/docker/spring-cloud-contract-docker/project/build.gradle b/docker/spring-cloud-contract-docker/project/build.gradle index 2af7253562..bd24ec7da7 100644 --- a/docker/spring-cloud-contract-docker/project/build.gradle +++ b/docker/spring-cloud-contract-docker/project/build.gradle @@ -144,3 +144,17 @@ String getProp(String propName) { (getProperty(propName) ?: System.properties[propName]) : System.properties[propName] ?: System.getenv(propName) } + +task resolveDependencies { + description "Pre-downloads *most* dependencies" + doLast { + configurations.getAsMap().each { name, config -> + println "Retrieving dependencies for $name" + try { + config.files + } catch (e) { + project.logger.info e.message // some cannot be resolved, silentlyish skip them + } + } + } +} \ No newline at end of file diff --git a/docker/spring-cloud-contract-docker/project/gradle/wrapper/gradle-wrapper.jar b/docker/spring-cloud-contract-docker/project/gradle/wrapper/gradle-wrapper.jar index 29953ea141..5c2d1cf016 100644 Binary files a/docker/spring-cloud-contract-docker/project/gradle/wrapper/gradle-wrapper.jar and b/docker/spring-cloud-contract-docker/project/gradle/wrapper/gradle-wrapper.jar differ diff --git a/docker/spring-cloud-contract-docker/project/gradlew b/docker/spring-cloud-contract-docker/project/gradlew index cccdd3d517..8e25e6c19d 100755 --- a/docker/spring-cloud-contract-docker/project/gradlew +++ b/docker/spring-cloud-contract-docker/project/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" diff --git a/docker/spring-cloud-contract-docker/project/gradlew.bat b/docker/spring-cloud-contract-docker/project/gradlew.bat index e95643d6a2..24467a141f 100644 --- a/docker/spring-cloud-contract-docker/project/gradlew.bat +++ b/docker/spring-cloud-contract-docker/project/gradlew.bat @@ -1,3 +1,19 @@ +@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 @@ -14,7 +30,7 @@ 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= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/docs/src/main/asciidoc/building.adoc b/docs/src/main/asciidoc/building.adoc index 0244dc1194..79fe0f349a 100644 --- a/docs/src/main/asciidoc/building.adoc +++ b/docs/src/main/asciidoc/building.adoc @@ -19,10 +19,12 @@ Here you can find the Spring Cloud Contract folder structure ├── docker ├── samples ├── scripts +├── specs ├── spring-cloud-contract-dependencies -├── spring-cloud-contract-spec +├── spring-cloud-contract-shade ├── spring-cloud-contract-starters ├── spring-cloud-contract-stub-runner +├── spring-cloud-contract-stub-runner-boot ├── spring-cloud-contract-tools ├── spring-cloud-contract-verifier ├── spring-cloud-contract-wiremock @@ -33,7 +35,9 @@ Here you can find the Spring Cloud Contract folder structure - `docker` - folder contains docker images - `samples` - folder contains test samples together with standalone ones used also to build documentation - `scripts` - contains scripts to build and test `Spring Cloud Contract` with Maven, Gradle and standalone projects + - `specs` - contains specifications for the Contract DSL. - `spring-cloud-contract-dependencies` - contains Spring Cloud Contract BOM + - `spring-cloud-contract-shade` - shaded dependencies used by the plugins - `spring-cloud-contract-starters` - contains Spring Cloud Contract Starters - `spring-cloud-contract-spec` - contains specification modules (contains concept of a Contract) - `spring-cloud-contract-stub-runner` - contains Stub Runner related modules diff --git a/docs/src/main/asciidoc/spring-cloud-contract-verifier.adoc b/docs/src/main/asciidoc/spring-cloud-contract-verifier.adoc index f8d4c512e7..1dd900ca53 100644 --- a/docs/src/main/asciidoc/spring-cloud-contract-verifier.adoc +++ b/docs/src/main/asciidoc/spring-cloud-contract-verifier.adoc @@ -2,7 +2,8 @@ :plugins_path: ../../../spring-cloud-contract-tools :converters_path: {plugins_path}/spring-cloud-contract-converters :verifier_root_path: {core_path}/spring-cloud-contract-verifier -:contract_spec_path: {core_path}/spring-cloud-contract-spec +:contract_spec_path: {core_path}/specs/spring-cloud-contract-spec-java +:contract_spec_tests_path: {core_path}/specs/spring-cloud-contract-spec :samples_path: {core_path}/samples :verifier_core_path: {verifier_root_path} :stubrunner_core_path: {core_path}/spring-cloud-contract-stub-runner diff --git a/docs/src/main/asciidoc/verifier_contract.adoc b/docs/src/main/asciidoc/verifier_contract.adoc index 991bf3b09f..ee2eaa7fb2 100644 --- a/docs/src/main/asciidoc/verifier_contract.adoc +++ b/docs/src/main/asciidoc/verifier_contract.adoc @@ -67,7 +67,7 @@ following code shows an example: .Groovy DSL [source,groovy,indent=0] ---- -include::{contract_spec_path}/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy[tags=description,indent=0] +include::{contract_spec_tests_path}/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy[tags=description,indent=0] ---- .YAML @@ -92,7 +92,7 @@ override each other. .Groovy DSL [source,groovy,indent=0] ---- -include::{contract_spec_path}/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy[tags=name,indent=0] +include::{contract_spec_tests_path}/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy[tags=name,indent=0] ---- .YAML @@ -110,7 +110,7 @@ plugin configuration or set the `ignored` property on the contract itself: .Groovy DSL [source,groovy,indent=0] ---- -include::{contract_spec_path}/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy[tags=ignored,indent=0] +include::{contract_spec_tests_path}/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy[tags=ignored,indent=0] ---- .YAML @@ -480,9 +480,9 @@ generated for request and response. Spring Cloud Contract comes with a series of predefined regular expressions that you can use in your contracts, as shown in the following example: -[source,groovy,indent=0] +[source,java,indent=0] ---- -include::{contract_spec_path}/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexPatterns.groovy[tags=regexps,indent=0] +include::{contract_spec_path}/src/main/java/org/springframework/cloud/contract/spec/internal/RegexPatterns.java[tags=regexps,indent=0] ---- In your contract, you can use it as shown in the following example: @@ -495,9 +495,9 @@ include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract To make matters even simpler you can use a set of predefined objects that will automatically assume that you want a regular expression to be passed. All of those methods start with `any` prefix: -[source,groovy,indent=0] +[source,java,indent=0] ---- -include::{contract_spec_path}/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexCreatingProperty.groovy[tags=regex_creating_props,indent=0] +include::{contract_spec_path}/src/main/java/org/springframework/cloud/contract/spec/internal/RegexCreatingProperty.java[tags=regex_creating_props,indent=0] ---- and this is an example of how you can reference those methods: @@ -1644,9 +1644,9 @@ can generate stubs for other HTTP server implementations). The `ContractConverter` interface lets you register your own implementation of a contract structure converter. The following code listing shows the `ContractConverter` interface: -[source,groovy] +[source,java] ---- -include::{contract_spec_path}/src/main/groovy/org/springframework/cloud/contract/spec/ContractConverter.groovy[indent=0,lines=17..-1] +include::{contract_spec_path}/src/main/java/org/springframework/cloud/contract/spec/ContractConverter.java[indent=0,lines=17..-1] ---- Your implementation must define the condition on which it should start the diff --git a/pom.xml b/pom.xml index ecdd64b80f..bcd7af7b62 100644 --- a/pom.xml +++ b/pom.xml @@ -65,12 +65,11 @@ spring-cloud-contract-dependencies docs - guides docker spring-cloud-contract-shade spring-cloud-contract-wiremock spring-cloud-contract-verifier - spring-cloud-contract-spec + specs spring-cloud-contract-stub-runner spring-cloud-contract-starters spring-cloud-contract-tools @@ -612,6 +611,25 @@ + + guides + + spring-cloud-contract-dependencies + docs + guides + docker + spring-cloud-contract-shade + spring-cloud-contract-wiremock + spring-cloud-contract-verifier + specs + spring-cloud-contract-stub-runner + spring-cloud-contract-starters + spring-cloud-contract-tools + spring-cloud-contract-stub-runner-boot + tests + samples + + sonar diff --git a/spring-cloud-contract-spec/README.adoc b/specs/README.adoc similarity index 100% rename from spring-cloud-contract-spec/README.adoc rename to specs/README.adoc diff --git a/specs/pom.xml b/specs/pom.xml new file mode 100644 index 0000000000..4156895c61 --- /dev/null +++ b/specs/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + + org.springframework.cloud + spring-cloud-contract-parent + 2.2.0.BUILD-SNAPSHOT + .. + + + spring-cloud-contract-specs + pom + 2.2.0.BUILD-SNAPSHOT + Spring Cloud Contract Specs + Spring Cloud Contract Specs + + + ${basedir}/.. + + + + spring-cloud-contract-spec-java + spring-cloud-contract-spec-groovy + spring-cloud-contract-spec + + + + + + maven-deploy-plugin + 2.8.2 + + true + + + + + + diff --git a/specs/spring-cloud-contract-spec-groovy/README.adoc b/specs/spring-cloud-contract-spec-groovy/README.adoc new file mode 100644 index 0000000000..e1aa472960 --- /dev/null +++ b/specs/spring-cloud-contract-spec-groovy/README.adoc @@ -0,0 +1,3 @@ +=== Spring Cloud Contract Specification Groovy + +This module contains the Contract specification written in Groovy. It extends the Java spec with Groovy methods. \ No newline at end of file diff --git a/specs/spring-cloud-contract-spec-groovy/pom.xml b/specs/spring-cloud-contract-spec-groovy/pom.xml new file mode 100644 index 0000000000..9a6de06bfb --- /dev/null +++ b/specs/spring-cloud-contract-spec-groovy/pom.xml @@ -0,0 +1,82 @@ + + + 4.0.0 + + org.springframework.cloud + spring-cloud-contract-parent + 2.2.0.BUILD-SNAPSHOT + ../.. + + spring-cloud-contract-spec-groovy + jar + Spring Cloud Contract Spec Groovy + Spring Cloud Contract Spec Groovy + + + org.springframework.cloud + spring-cloud-contract-spec-java + + + org.codehaus.groovy + groovy + + + org.codehaus.groovy + groovy-nio + + + org.codehaus.groovy + groovy-json + + + org.codehaus.groovy + groovy-xml + + + org.apache.commons + commons-text + + + org.springframework + spring-jcl + + + org.spockframework + spock-core + test + + + + + + org.codehaus.gmavenplus + gmavenplus-plugin + + + + + addSources + addTestSources + generateStubs + compile + generateTestStubs + compileTests + removeStubs + removeTestStubs + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + + diff --git a/specs/spring-cloud-contract-spec-groovy/src/main/groovy/org/springframework/cloud/contract/spec/internal/GroovyDslPropertyConverter.java b/specs/spring-cloud-contract-spec-groovy/src/main/groovy/org/springframework/cloud/contract/spec/internal/GroovyDslPropertyConverter.java new file mode 100644 index 0000000000..5d84ac1e3c --- /dev/null +++ b/specs/spring-cloud-contract-spec-groovy/src/main/groovy/org/springframework/cloud/contract/spec/internal/GroovyDslPropertyConverter.java @@ -0,0 +1,55 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import groovy.lang.GString; +import org.codehaus.groovy.runtime.GStringImpl; + +/** + * Groovy converter that understand types like {@link GString}. + * + * @author Marcin Grzejszczak + * @since 2.2.0 + */ +public class GroovyDslPropertyConverter implements DslPropertyConverter { + + @Override + public Object testSide(Object object) { + if (object instanceof GString) { + boolean anyPattern = Arrays.stream(((GString) object).getValues()) + .anyMatch(it -> it instanceof RegexProperty); + if (!anyPattern) { + return object; + } + List generatedValues = Arrays.stream(((GString) object).getValues()) + .map(it -> it instanceof RegexProperty + ? ((RegexProperty) it).generate() : it) + .collect(Collectors.toList()); + Object[] arrayOfObjects = generatedValues.toArray(); + String[] strings = Arrays.copyOf(((GString) object).getStrings(), + ((GString) object).getStrings().length, String[].class); + String newUrl = new GStringImpl(arrayOfObjects, strings).toString(); + return new Url(newUrl); + } + return object; + } + +} diff --git a/specs/spring-cloud-contract-spec-groovy/src/main/resources/META-INF/spring.factories b/specs/spring-cloud-contract-spec-groovy/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..d040f4a478 --- /dev/null +++ b/specs/spring-cloud-contract-spec-groovy/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.cloud.contract.spec.internal.DslPropertyConverter=\ +org.springframework.cloud.contract.spec.internal.GroovyDslPropertyConverter diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MessagingHeaders.groovy b/specs/spring-cloud-contract-spec-groovy/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy similarity index 57% rename from spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MessagingHeaders.groovy rename to specs/spring-cloud-contract-spec-groovy/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy index 41196cbb7f..8a71a61182 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MessagingHeaders.groovy +++ b/specs/spring-cloud-contract-spec-groovy/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy @@ -16,26 +16,43 @@ package org.springframework.cloud.contract.spec.internal -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString +import spock.lang.Specification + +import org.springframework.cloud.contract.spec.Contract /** - * Contains most commonly used messaging headers - * * @author Marcin Grzejszczak - * @since 1.1.2 */ -@CompileStatic -@EqualsAndHashCode -@ToString(includePackage = false) -class MessagingHeaders { +class ContractSpec extends Specification { - /** - * The Content Type of a message - * @return - */ - String messagingContentType() { - return "contentType" + def 'should work for http'() { + when: + Contract.make { + request { + url("/foo/${regex('[0]')}") + method('PUT') + headers { + header([ + foo: 'bar' + ]) + } + body([ + foo: 'bar' + ]) + } + response { + status 200 + headers { + header([ + foo2: 'bar' + ]) + } + body([ + foo2: 'bar' + ]) + } + } + then: + noExceptionThrown() } } diff --git a/specs/spring-cloud-contract-spec-java/README.adoc b/specs/spring-cloud-contract-spec-java/README.adoc new file mode 100644 index 0000000000..ec24646c4e --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/README.adoc @@ -0,0 +1,3 @@ +=== Spring Cloud Contract Specification Java + +This module contains the Contract specification written in Java. \ No newline at end of file diff --git a/spring-cloud-contract-spec/pom.xml b/specs/spring-cloud-contract-spec-java/pom.xml similarity index 71% rename from spring-cloud-contract-spec/pom.xml rename to specs/spring-cloud-contract-spec-java/pom.xml index 410d11c945..db8d979e98 100644 --- a/spring-cloud-contract-spec/pom.xml +++ b/specs/spring-cloud-contract-spec-java/pom.xml @@ -12,28 +12,21 @@ org.springframework.cloud spring-cloud-contract-parent 2.2.0.BUILD-SNAPSHOT - .. + ../.. - spring-cloud-contract-spec + spring-cloud-contract-spec-java jar - Spring Cloud Contract Spec - Spring Cloud Contract Spec + Spring Cloud Contract Spec Java + Spring Cloud Contract Spec Java + org.codehaus.groovy groovy - org.codehaus.groovy - groovy-nio - - - org.codehaus.groovy - groovy-json - - - org.codehaus.groovy - groovy-xml + org.springframework + spring-core dk.brics.automaton @@ -48,6 +41,21 @@ org.springframework spring-jcl + + org.codehaus.groovy + groovy-nio + test + + + org.codehaus.groovy + groovy-json + test + + + org.codehaus.groovy + groovy-xml + test + org.spockframework spock-core @@ -69,6 +77,21 @@ org.codehaus.gmavenplus gmavenplus-plugin + + + + + addTestSources + generateTestStubs + compileTests + removeTestStubs + + + + + + org.apache.maven.plugins + maven-compiler-plugin org.apache.maven.plugins diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/Contract.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/Contract.java new file mode 100644 index 0000000000..8f7abe57d1 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/Contract.java @@ -0,0 +1,372 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec; + +import java.util.Objects; +import java.util.function.Consumer; + +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; + +import org.springframework.cloud.contract.spec.internal.Input; +import org.springframework.cloud.contract.spec.internal.OutputMessage; +import org.springframework.cloud.contract.spec.internal.Request; +import org.springframework.cloud.contract.spec.internal.Response; + +/** + * The definition of a Contract. Contains helper methods in Groovy left for backward + * compatibility reasons. + * + * @since 1.0.0 + */ +public class Contract { + + /** + * You can set the level of priority of this contract. If there are two contracts + * mapped for example to the same endpoint, then the one with greater priority should + * take precedence. A priority of 1 is highest and takes precedence over a priority of + * 2. + */ + private Integer priority; + + /** + * The HTTP request part of the contract. + */ + private Request request; + + /** + * The HTTP response part of the contract. + */ + private Response response; + + /** + * The label by which you'll reference the contract on the message consumer side. + */ + private String label; + + /** + * Description of a contract. May be used in the documentation generation. + */ + private String description; + + /** + * Name of the generated test / stub. If not provided then the file name will be used. + * If you have multiple contracts in a single file and you don't provide this value + * then a prefix will be added to the file with the index number while iterating over + * the collection of contracts. + * + * Remember to have a unique name for every single contract. Otherwise you might + * generate tests that have two identical methods or you will override the stubs. + */ + private String name; + + /** + * The input side of a messaging contract. + */ + private Input input; + + /** + * The output side of a messaging contract. + */ + private OutputMessage outputMessage; + + /** + * Whether the contract should be ignored or not. + */ + private boolean ignored; + + public Contract() { + + } + + /** + * You can set the level of priority of this contract. If there are two contracts + * mapped for example to the same endpoint, then the one with greater priority should + * take precedence. A priority of 1 is highest and takes precedence over a priority of + * 2. + * @param priority the higher the value the lower the priority + */ + public void priority(int priority) { + this.priority = priority; + } + + /** + * Name of the generated test / stub. If not provided then the file name will be used. + * If you have multiple contracts in a single file and you don't provide this value + * then a prefix will be added to the file with the index number while iterating over + * the collection of contracts. + * + * Remember to have a unique name for every single contract. Otherwise you might + * generate tests that have two identical methods or you will override the stubs. + * @param name name of the contract + */ + public void name(String name) { + this.name = name; + } + + /** + * Label used by the messaging contracts to trigger a message on the consumer side. + * @param label - name of the label of a messaging contract to trigger + */ + public void label(String label) { + this.label = label; + } + + /** + * Description text. Might be used to describe the usage scenario. + * @param description - value of the description + */ + public void description(String description) { + this.description = description; + } + + public static void assertContract(Contract dsl) { + if (dsl.getRequest() != null) { + if (dsl.request.getUrl() == null && dsl.request.getUrlPath() == null) { + throw new IllegalStateException( + "URL is missing for HTTP contract(exclude = { TraceWebClientAutoConfiguration.class })"); + } + if (dsl.request.getMethod() == null) { + throw new IllegalStateException("Method is missing for HTTP contract"); + } + } + if (dsl.response != null) { + if (dsl.response.getStatus() == null) { + throw new IllegalStateException("Status is missing for HTTP contract"); + } + } + // Can't assert messaging part cause Pact doesn't require destinations it seems + } + + /** + * Point of entry to build a contract. + * @param consumer function to manipulate the contract + * @return manipulated contract + */ + public static Contract make(Consumer consumer) { + Contract contract = new Contract(); + consumer.accept(contract); + return contract; + } + + /** + * Groovy point of entry to build a contract. Left for backward compatibility reasons. + * @param closure function to manipulate the contract + * @return manipulated contract + */ + public static Contract make(@DelegatesTo(Contract.class) Closure closure) { + Contract dsl = new Contract(); + closure.setDelegate(dsl); + closure.call(); + Contract.assertContract(dsl); + return dsl; + } + + /** + * The HTTP request part of the contract. + * @param consumer function to manipulate the request + */ + public void request(Consumer consumer) { + this.request = new Request(); + consumer.accept(this.request); + } + + /** + * The HTTP response part of the contract. + * @param consumer function to manipulate the response + */ + public void response(Consumer consumer) { + this.response = new Response(); + consumer.accept(this.response); + } + + /** + * The input part of the contract. + * @param consumer function to manipulate the input + */ + public void input(Consumer consumer) { + this.input = new Input(); + consumer.accept(this.input); + } + + /** + * The output part of the contract. + * @param consumer function to manipulate the output message + */ + public void outputMessage(Consumer consumer) { + this.outputMessage = new OutputMessage(); + consumer.accept(this.outputMessage); + } + + /** + * The HTTP request part of the contract. + * @param consumer function to manipulate the request + */ + public void request(@DelegatesTo(Request.class) Closure consumer) { + this.request = new Request(); + consumer.setDelegate(this.request); + consumer.call(); + } + + /** + * The HTTP response part of the contract. + * @param consumer function to manipulate the response + */ + public void response(@DelegatesTo(Response.class) Closure consumer) { + this.response = new Response(); + consumer.setDelegate(this.response); + consumer.call(); + } + + /** + * The input part of the contract. + * @param consumer function to manipulate the input + */ + public void input(@DelegatesTo(Input.class) Closure consumer) { + this.input = new Input(); + consumer.setDelegate(this.input); + consumer.call(); + } + + /** + * The output part of the contract. + * @param consumer function to manipulate the output message + */ + public void outputMessage(@DelegatesTo(OutputMessage.class) Closure consumer) { + this.outputMessage = new OutputMessage(); + consumer.setDelegate(this.outputMessage); + consumer.call(); + } + + /** + * Whether the contract should be ignored or not. + */ + public void ignored() { + this.ignored = true; + } + + public Integer getPriority() { + return priority; + } + + public void setPriority(Integer priority) { + this.priority = priority; + } + + public Request getRequest() { + return request; + } + + public void setRequest(Request request) { + this.request = request; + } + + public Response getResponse() { + return response; + } + + public void setResponse(Response response) { + this.response = response; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Input getInput() { + return input; + } + + public void setInput(Input input) { + this.input = input; + } + + public OutputMessage getOutputMessage() { + return outputMessage; + } + + public void setOutputMessage(OutputMessage outputMessage) { + this.outputMessage = outputMessage; + } + + public boolean getIgnored() { + return ignored; + } + + public boolean isIgnored() { + return ignored; + } + + public void setIgnored(boolean ignored) { + this.ignored = ignored; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Contract contract = (Contract) o; + return ignored == contract.ignored && Objects.equals(priority, contract.priority) + && Objects.equals(request, contract.request) + && Objects.equals(response, contract.response) + && Objects.equals(label, contract.label) + && Objects.equals(description, contract.description) + && Objects.equals(name, contract.name) + && Objects.equals(input, contract.input) + && Objects.equals(outputMessage, contract.outputMessage); + } + + @Override + public int hashCode() { + return Objects.hash(priority, request, response, label, description, name, input, + outputMessage, ignored); + } + + @Override + public String toString() { + return "Contract{" + "\npriority=" + priority + ", \n\trequest=" + request + + ", \n\tresponse=" + response + ", \n\tlabel='" + label + '\'' + + ", \n\tdescription='" + description + '\'' + ", \n\tname='" + name + + '\'' + ", \n\tinput=" + input + ", \n\toutputMessage=" + outputMessage + + ", \n\tignored=" + ignored + '}'; + } + +} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/ContractConverter.groovy b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/ContractConverter.java similarity index 72% rename from spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/ContractConverter.groovy rename to specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/ContractConverter.java index 13b02e8c4c..a36401c160 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/ContractConverter.groovy +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/ContractConverter.java @@ -14,41 +14,41 @@ * limitations under the License. */ -package org.springframework.cloud.contract.spec +package org.springframework.cloud.contract.spec; + +import java.io.File; +import java.util.Collection; /** - * Converter to be used to convert FROM {@link File} TO {@link Contract} - * and from {@link Contract} to {@code T} - * - * @param - type to which we want to convert the contract + * Converter to be used to convert FROM {@link File} TO {@link Contract} and from + * {@link Contract} to {@code T}. * + * @param - type to which we want to convert the contract * @author Marcin Grzejszczak * @since 1.1.0 */ -interface ContractConverter extends ContractStorer { +public interface ContractConverter extends ContractStorer { /** - * Should this file be accepted by the converter. Can use the file extension - * to check if the conversion is possible. - * + * Should this file be accepted by the converter. Can use the file extension to check + * if the conversion is possible. * @param file - file to be considered for conversion * @return - {@code true} if the given implementation can convert the file */ - boolean isAccepted(File file) + boolean isAccepted(File file); /** - * Converts the given {@link File} to its {@link Contract} representation - * + * Converts the given {@link File} to its {@link Contract} representation. * @param file - file to convert * @return - {@link Contract} representation of the file */ - Collection convertFrom(File file) + Collection convertFrom(File file); /** - * Converts the given {@link Contract} to a {@link T} representation - * + * Converts the given {@link Contract} to a {@link T} representation. * @param contract - the parsed contract * @return - {@link T} the type to which we do the conversion */ - T convertTo(Collection contract) + T convertTo(Collection contract); + } diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/ContractStorer.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/ContractStorer.java similarity index 100% rename from spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/ContractStorer.java rename to specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/ContractStorer.java diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/ContractTemplate.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/ContractTemplate.java similarity index 100% rename from spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/ContractTemplate.java rename to specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/ContractTemplate.java diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/ContractVerifierException.groovy b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/ContractVerifierException.java similarity index 68% rename from spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/ContractVerifierException.groovy rename to specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/ContractVerifierException.java index 063a78681c..48327abc1b 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/ContractVerifierException.groovy +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/ContractVerifierException.java @@ -14,21 +14,19 @@ * limitations under the License. */ -package org.springframework.cloud.contract.spec - -import groovy.transform.CompileStatic +package org.springframework.cloud.contract.spec; /** * @author Jakub Kubrynski, codearte.io */ -@CompileStatic -class ContractVerifierException extends RuntimeException { +public class ContractVerifierException extends RuntimeException { - ContractVerifierException(String message) { - super(message) + public ContractVerifierException(String message) { + super(message); } - ContractVerifierException(String message, Throwable cause) { - super(message, cause) + public ContractVerifierException(String message, Throwable cause) { + super(message, cause); } + } diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Body.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Body.java new file mode 100644 index 0000000000..4585976a48 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Body.java @@ -0,0 +1,81 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Represents a body of a request / response or a message. + * + * @since 1.0.0 + */ +public class Body extends DslProperty { + + public Body(Map body) { + super(extractValue(body, ContractUtils.CLIENT_VALUE), + extractValue(body, ContractUtils.SERVER_VALUE)); + } + + public Body(List bodyAsList) { + super(bodyAsList.stream().map(DslProperty::getClientValue) + .collect(Collectors.toList()), + bodyAsList.stream().map(DslProperty::getServerValue) + .collect(Collectors.toList())); + } + + public Body(Object value) { + super(ContractUtils.CLIENT_VALUE.apply(value), + ContractUtils.SERVER_VALUE.apply(value)); + } + + public Body(Byte[] bodyAsValue) { + super(bodyAsValue); + } + + public Body(Number bodyAsValue) { + super(bodyAsValue); + } + + public Body(String bodyAsValue) { + super(bodyAsValue, bodyAsValue); + } + + public Body(DslProperty bodyAsValue) { + super(bodyAsValue.getClientValue(), bodyAsValue.getServerValue()); + } + + public Body(Serializable serializable) { + super(serializable, serializable); + } + + public Body(MatchingStrategy matchingStrategy) { + super(matchingStrategy, matchingStrategy); + } + + private static Map extractValue(Map body, + final Function valueProvider) { + final Map map = new LinkedHashMap(); + body.forEach((key, value) -> map.put(key, valueProvider.apply(value))); + return map; + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/BodyMatcher.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/BodyMatcher.java new file mode 100644 index 0000000000..90300a1b75 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/BodyMatcher.java @@ -0,0 +1,58 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +/** + * Matchers for the given path. + * + * @author Marcin Grzejszczak + * @since 1.0.3 + */ +public interface BodyMatcher { + + /** + * @return What kind of matching are we dealing with. + */ + MatchingType matchingType(); + + /** + * @return Path to the path. Example for JSON it will be JSON Path. + */ + String path(); + + /** + * @return Optional value that the given path should be checked against. If there is + * no value then presence will be checked only together with type check. Example if we + * expect a JSON Path path {@code $.a} to be matched. by type, the defined response + * body contained an integer but the actual one contained a string then the assertion + * should fail. + */ + Object value(); + + /** + * @return Min no of occurrence when matching by type. In all other cases it will be + * ignored. + */ + Integer minTypeOccurrence(); + + /** + * @return Max no of occurrence when matching by type. In all other cases it will be + * ignored. + */ + Integer maxTypeOccurrence(); + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/BodyMatchers.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/BodyMatchers.java new file mode 100644 index 0000000000..1035394a17 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/BodyMatchers.java @@ -0,0 +1,156 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.regex.Pattern; + +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; + +/** + * Matching strategy of dynamic parts of the body. + * + * @author Marcin Grzejszczak + * @author Olga Maciaszek-Sharma + * @since 1.0.3 + */ +public class BodyMatchers { + + protected final List matchers = new LinkedList<>(); + + private final RegexPatterns regexPatterns = new RegexPatterns(); + + public void jsonPath(String path, MatchingTypeValue matchingType) { + this.matchers.add(new PathBodyMatcher(path, matchingType)); + } + + /** + * Adds xPath matcher; even though same implementation as in + * {@link BodyMatchers#jsonPath(java.lang.String, org.springframework.cloud.contract.spec.internal.MatchingTypeValue)}, + * added for logical coherence in xml. + * @param xPath the xPath used to find the element to match + * @param matchingTypeValue to match the element found by the xPath against + */ + public void xPath(String xPath, MatchingTypeValue matchingTypeValue) { + this.matchers.add(new PathBodyMatcher(xPath, matchingTypeValue)); + } + + /** + * @deprecated use{@link #matchers()} + * @return json path matchers + */ + @Deprecated + public List jsonPathMatchers() { + return matchers(); + } + + public boolean hasMatchers() { + return !this.matchers.isEmpty(); + } + + public List matchers() { + return this.matchers; + } + + public MatchingTypeValue byDate() { + return new MatchingTypeValue(MatchingType.DATE, this.regexPatterns.isoDate()); + } + + public MatchingTypeValue byTime() { + return new MatchingTypeValue(MatchingType.TIME, this.regexPatterns.isoTime()); + } + + public MatchingTypeValue byTimestamp() { + return new MatchingTypeValue(MatchingType.TIMESTAMP, + this.regexPatterns.isoDateTime()); + } + + public RegexMatchingTypeValue byRegex(String regex) { + return byRegex(Pattern.compile(regex)); + } + + public RegexMatchingTypeValue byRegex(RegexProperty regex) { + assertNotNull(regex); + return new RegexMatchingTypeValue(MatchingType.REGEX, regex); + } + + public RegexMatchingTypeValue byRegex(Pattern regex) { + assertNotNull(regex); + return new RegexMatchingTypeValue(MatchingType.REGEX, new RegexProperty(regex)); + } + + public MatchingTypeValue byEquality() { + return new MatchingTypeValue(MatchingType.EQUALITY); + } + + private void assertNotNull(Object object) { + if (object == null) { + throw new IllegalStateException("Object can't be null!"); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BodyMatchers that = (BodyMatchers) o; + return Objects.equals(matchers, that.matchers); + } + + @Override + public int hashCode() { + return Objects.hash(matchers); + } + + @Override + public String toString() { + return "BodyMatchers{" + "\nmatchers=" + matchers + '}'; + } + + /** + * The output part of the contract. + * @param consumer function to manipulate the output message + * @return matching type + */ + public MatchingTypeValue byType(Consumer consumer) { + MatchingTypeValueHolder matchingTypeValue = new MatchingTypeValueHolder(); + consumer.accept(matchingTypeValue); + return matchingTypeValue.matchingTypeValue; + } + + /** + * The output part of the contract. + * @param consumer function to manipulate the output message + * @return matching type + */ + public MatchingTypeValue byType( + @DelegatesTo(MatchingTypeValueHolder.class) Closure consumer) { + MatchingTypeValueHolder matchingTypeValue = new MatchingTypeValueHolder(); + consumer.setDelegate(matchingTypeValue); + consumer.call(); + return matchingTypeValue.matchingTypeValue; + } + +} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/CanBeDynamic.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/CanBeDynamic.java similarity index 95% rename from spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/CanBeDynamic.java rename to specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/CanBeDynamic.java index e64502c648..0d23321be9 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/CanBeDynamic.java +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/CanBeDynamic.java @@ -26,7 +26,7 @@ package org.springframework.cloud.contract.spec.internal; public interface CanBeDynamic { /** - * @return a generated, concrete value + * @return a generated, concrete value. */ Object generateConcreteValue(); diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ClientDslProperty.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ClientDslProperty.java new file mode 100644 index 0000000000..8c5c577577 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ClientDslProperty.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +/** + * Represents a client side {@link DslProperty}. + * + * @since 1.0.0 + */ +public class ClientDslProperty extends DslProperty { + + public ClientDslProperty(Object singleValue) { + super(singleValue); + } + + public ClientDslProperty(Object client, Object server) { + super(client, server); + } + +} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/GroovyClass.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ClientInput.java similarity index 79% rename from spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/GroovyClass.java rename to specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ClientInput.java index 3dc244fa90..2a37ef0b8a 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/GroovyClass.java +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ClientInput.java @@ -16,12 +16,10 @@ package org.springframework.cloud.contract.spec.internal; -final class GroovyClass { +class ClientInput extends Input { - private GroovyClass() { - throw new IllegalStateException("Can't instantiate a utility class"); + ClientInput(Input request) { + super(request); } - public static Class BYTE_CLASS = byte[].class; - } diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/JSONCompareMode.groovy b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ClientOutputMessage.java similarity index 76% rename from spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/JSONCompareMode.groovy rename to specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ClientOutputMessage.java index ff7432f1a6..1963800589 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/JSONCompareMode.groovy +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ClientOutputMessage.java @@ -14,13 +14,12 @@ * limitations under the License. */ -package org.springframework.cloud.contract.spec.internal +package org.springframework.cloud.contract.spec.internal; + +class ClientOutputMessage extends OutputMessage { + + ClientOutputMessage(OutputMessage request) { + super(request); + } -/** - * Represents JSON comparison modes - * - * @since 1.0.0 - */ -enum JSONCompareMode { - STRICT, LENIENT, NON_EXTENSIBLE, STRICT_ORDER } diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Common.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Common.java new file mode 100644 index 0000000000..32a9acc6a1 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Common.java @@ -0,0 +1,402 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * Contains useful common methods for the DSL. + * + * @since 1.0.0 + */ +public class Common { + + private final RegexPatterns regexPatterns = new RegexPatterns(); + + public Map convertObjectsToDslProperties( + Map body) { + return body.entrySet().stream().collect(Collectors.toMap( + (Function) t -> t.getKey().toString(), + (Function) t -> toDslProperty(t.getValue()), + throwingMerger(), LinkedHashMap::new)); + } + + private static BinaryOperator throwingMerger() { + return (u, v) -> { + throw new IllegalStateException(String.format("Duplicate key %s", u)); + }; + } + + public Collection convertObjectsToDslProperties(List body) { + return body.stream().map(this::toDslProperty).collect(Collectors.toList()); + } + + public DslProperty toDslProperty(Object property) { + return new DslProperty(property); + } + + public DslProperty toDslProperty(Map property) { + return new DslProperty(convertObjectsToDslProperties(property)); + } + + public DslProperty toDslProperty(List property) { + return new DslProperty(convertObjectsToDslProperties(property)); + } + + public DslProperty toDslProperty(DslProperty property) { + return property; + } + + public NamedProperty named(DslProperty name, DslProperty value) { + return new NamedProperty(name, value); + } + + public NamedProperty named(DslProperty name, DslProperty value, + DslProperty contentType) { + return new NamedProperty(name, value, contentType); + } + + public NamedProperty named(Map namedMap) { + return new NamedProperty(namedMap); + } + + public DslProperty value(DslProperty value) { + return value; + } + + public DslProperty $(DslProperty value) { + return value; + } + + public DslProperty value(Object value) { + return new DslProperty(value); + } + + public DslProperty $(Object value) { + return new DslProperty(value); + } + + public DslProperty value(ClientDslProperty client, ServerDslProperty server) { + assertThatSidesMatch(client.getClientValue(), server.getServerValue()); + return new DslProperty(client.getClientValue(), server.getServerValue()); + } + + public DslProperty $(ClientDslProperty client, ServerDslProperty server) { + return value(client, server); + } + + public DslProperty value(ServerDslProperty server, ClientDslProperty client) { + assertThatSidesMatch(client.getClientValue(), server.getServerValue()); + return new DslProperty(client.getClientValue(), server.getServerValue()); + } + + public DslProperty $(ServerDslProperty server, ClientDslProperty client) { + return value(server, client); + } + + public RegexProperty regex(String regex) { + return regexProperty(Pattern.compile(regex)); + } + + public RegexProperty regex(RegexProperty regex) { + return regex; + } + + public RegexProperty regex(Pattern regex) { + return regexProperty(regex); + } + + public OptionalProperty optional(Object object) { + return new OptionalProperty(object); + } + + public RegexProperty regexProperty(Object object) { + return new RegexProperty(object); + } + + public ExecutionProperty execute(String commandToExecute) { + return new ExecutionProperty(commandToExecute); + } + + /** + * Helper method to provide a better name for the consumer side. + * @param clientValue client value + * @return client dsl property + */ + public ClientDslProperty client(Object clientValue) { + return new ClientDslProperty(clientValue); + } + + /** + * Helper method to provide a better name for the consumer side. + * @param clientValue client value + * @return client dsl property + */ + public ClientDslProperty stub(Object clientValue) { + return new ClientDslProperty(clientValue); + } + + /** + * Helper method to provide a better name for the consumer side. + * @param clientValue client value + * @return client dsl property + */ + public ClientDslProperty consumer(Object clientValue) { + return new ClientDslProperty(clientValue); + } + + /** + * Helper method to provide a better name for the server side. + * @param serverValue server value + * @return server dsl property + */ + public ServerDslProperty server(Object serverValue) { + return new ServerDslProperty(serverValue); + } + + /** + * Helper method to provide a better name for the consumer side. + * @param clientValue client value + * @return client dsl property + */ + public ClientDslProperty c(Object clientValue) { + return new ClientDslProperty(clientValue); + } + + /** + * Helper method to provide a better name for the server side. + * @param serverValue server value + * @return server dsl property + */ + public ServerDslProperty p(Object serverValue) { + return new ServerDslProperty(serverValue); + } + + /** + * Helper method to provide a better name for the server side. + * @param serverValue server value + * @return server dsl property + */ + public ServerDslProperty test(Object serverValue) { + return new ServerDslProperty(serverValue); + } + + /** + * Read file contents as String. + * @param relativePath of the file to read + * @return String file contents + */ + public FromFileProperty file(String relativePath) { + return file(relativePath, Charset.defaultCharset()); + } + + /** + * Read file contents as bytes[]. + * @param relativePath of the file to read + * @return String file contents + */ + public FromFileProperty fileAsBytes(String relativePath) { + return new FromFileProperty(fileLocation(relativePath), byte[].class); + } + + /** + * Read file contents as String with the given Charset. + * @param relativePath of the file to read + * @param charset to use for converting the bytes to String + * @return String file contents + */ + public FromFileProperty file(String relativePath, Charset charset) { + return new FromFileProperty(fileLocation(relativePath), String.class, charset); + } + + /** + * Read file contents as array of bytes. + * @param relativePath of the file to read + * @return file contents as an array of bytes + */ + private File fileLocation(String relativePath) { + URL resource = Thread.currentThread().getContextClassLoader() + .getResource(relativePath); + if (resource == null) { + throw new IllegalStateException("File [" + relativePath + "] is not present"); + } + try { + return new File(resource.toURI()); + } + catch (URISyntaxException ex) { + throw new IllegalStateException(ex); + } + } + + /** + * Helper method to provide a better name for the server side. + * @param serverValue server value + * @return server dsl property + */ + public ServerDslProperty producer(Object serverValue) { + return new ServerDslProperty(serverValue); + } + + private void throwAbsentError() { + throw new IllegalStateException("Absent cannot only be used only on one side"); + } + + private void assertThat(boolean condition, String msg) { + if (!condition) { + throw new IllegalStateException(msg); + } + } + + public void assertThatSidesMatch(Object firstSide, Object secondSide) { + if (firstSide instanceof OptionalProperty) { + assertThat( + secondSide.toString() + .matches(((OptionalProperty) firstSide).optionalPattern()), + "Pattern [" + ((OptionalProperty) firstSide).optionalPattern() + + "] is not matched by [" + secondSide.toString() + "]"); + } + else if ((firstSide instanceof Pattern || firstSide instanceof RegexProperty) + && secondSide instanceof String) { + Pattern pattern = firstSide instanceof Pattern ? (Pattern) firstSide + : ((RegexProperty) firstSide).getPattern(); + assertThat(((String) secondSide).toString().matches(pattern.pattern()), + "Pattern [" + pattern.pattern() + "] is not matched by [" + + secondSide.toString() + "]"); + } + else if ((secondSide instanceof Pattern || secondSide instanceof RegexProperty) + && firstSide instanceof String) { + Pattern pattern = secondSide instanceof Pattern ? (Pattern) secondSide + : ((RegexProperty) secondSide).getPattern(); + assertThat(((String) firstSide).matches(pattern.pattern()), + "Pattern [" + pattern.pattern() + "] is not matched by [" + + firstSide.toString() + "]"); + } + else if (firstSide instanceof MatchingStrategy + && secondSide instanceof MatchingStrategy) { + if (((MatchingStrategy) firstSide).getType() + .equals(MatchingStrategy.Type.ABSENT) + && !((MatchingStrategy) secondSide).getType() + .equals(MatchingStrategy.Type.ABSENT)) { + throwAbsentError(); + } + + } + else if (firstSide instanceof MatchingStrategy) { + if (((MatchingStrategy) firstSide).getType() + .equals(MatchingStrategy.Type.ABSENT)) { + throwAbsentError(); + } + + } + else if (secondSide instanceof MatchingStrategy) { + if (((MatchingStrategy) secondSide).getType() + .equals(MatchingStrategy.Type.ABSENT)) { + throwAbsentError(); + } + + } + + } + + public RegexProperty onlyAlphaUnicode() { + return regexPatterns.onlyAlphaUnicode(); + } + + public RegexProperty alphaNumeric() { + return regexPatterns.alphaNumeric(); + } + + public RegexProperty number() { + return regexPatterns.number(); + } + + public RegexProperty positiveInt() { + return regexPatterns.positiveInt(); + } + + public RegexProperty anyBoolean() { + return regexPatterns.anyBoolean(); + } + + public RegexProperty anInteger() { + return regexPatterns.anInteger(); + } + + public RegexProperty aDouble() { + return regexPatterns.aDouble(); + } + + public RegexProperty ipAddress() { + return regexPatterns.ipAddress(); + } + + public RegexProperty hostname() { + return regexPatterns.hostname(); + } + + public RegexProperty email() { + return regexPatterns.email(); + } + + public RegexProperty url() { + return regexPatterns.url(); + } + + public RegexProperty httpsUrl() { + return regexPatterns.httpsUrl(); + } + + public RegexProperty uuid() { + return regexPatterns.uuid(); + } + + public RegexProperty isoDate() { + return regexPatterns.isoDate(); + } + + public RegexProperty isoDateTime() { + return regexPatterns.isoDateTime(); + } + + public RegexProperty isoTime() { + return regexPatterns.isoTime(); + } + + public RegexProperty iso8601WithOffset() { + return regexPatterns.iso8601WithOffset(); + } + + public RegexProperty nonEmpty() { + return regexPatterns.nonEmpty(); + } + + public RegexProperty nonBlank() { + return regexPatterns.nonBlank(); + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/CompositeContractTemplate.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/CompositeContractTemplate.java new file mode 100644 index 0000000000..4bcc53145c --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/CompositeContractTemplate.java @@ -0,0 +1,169 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import org.springframework.cloud.contract.spec.ContractTemplate; + +/** + * For backward compatibility when assertions take place, first checks the custom setup. + * Writes in a new format, can read the old format. + * + * @author Marcin Grzejszczak + * @since 2.1.0 + */ +public class CompositeContractTemplate implements ContractTemplate { + + private final CustomHandlebarsContractTemplate custom = new CustomHandlebarsContractTemplate(); + + private final HandlebarsContractTemplate template = new HandlebarsContractTemplate(); + + @Override + public boolean startsWithTemplate(String text) { + if (this.custom.startsWithTemplate(text)) { + return true; + } + return template.startsWithTemplate(text); + } + + @Override + public boolean startsWithEscapedTemplate(String text) { + return this.template.startsWithEscapedTemplate(text); + } + + @Override + public String openingTemplate() { + return this.template.openingTemplate(); + } + + @Override + public String closingTemplate() { + return this.template.closingTemplate(); + } + + @Override + public String escapedOpeningTemplate() { + return this.template.escapedOpeningTemplate(); + } + + @Override + public String escapedClosingTemplate() { + return this.template.escapedClosingTemplate(); + } + + @Override + public String url() { + return this.template.url(); + } + + @Override + public String query(String key) { + return this.template.query(key); + } + + @Override + public String query(String key, int index) { + return this.template.query(key, index); + } + + @Override + public String path() { + return this.template.path(); + } + + @Override + public String path(int index) { + return this.template.path(index); + } + + @Override + public String header(String key) { + return this.template.header(key); + } + + @Override + public String header(String key, int index) { + return this.template.header(key, index); + } + + @Override + public String cookie(String key) { + return this.template.cookie(key); + } + + @Override + public String body() { + return this.template.body(); + } + + @Override + public String escapedBody() { + // WireMock doesn't support proper escaping of JSON body + // that's why we need to use our custom handlebars extension + return this.custom.escapedBody(); + } + + @Override + public String escapedBody(String jsonPath) { + return this.template.escapedBody(jsonPath); + } + + @Override + public String body(String jsonPath) { + return this.template.body(jsonPath); + } + + @Override + public String escapedUrl() { + return this.template.escapedUrl(); + } + + @Override + public String escapedQuery(String key) { + return this.template.escapedQuery(key); + } + + @Override + public String escapedQuery(String key, int index) { + return this.template.escapedQuery(key, index); + } + + @Override + public String escapedPath() { + return this.template.escapedPath(); + } + + @Override + public String escapedPath(int index) { + return this.template.escapedPath(index); + } + + @Override + public String escapedHeader(String key) { + return this.template.escapedHeader(key); + } + + @Override + public String escapedHeader(String key, int index) { + return this.template.escapedHeader(key, index); + } + + @Override + public String escapedCookie(String key) { + return this.template.escapedCookie(key); + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ContractUtils.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ContractUtils.java new file mode 100644 index 0000000000..244964e163 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ContractUtils.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.function.Function; + +final class ContractUtils { + + static final Function CLIENT_VALUE = o -> o instanceof DslProperty + ? ((DslProperty) o).getClientValue() : o; + static final Function SERVER_VALUE = o -> o instanceof DslProperty + ? ((DslProperty) o).getServerValue() : o; + + private ContractUtils() { + throw new IllegalStateException("Can't instantiate an utility class"); + } + + static Object convertStubSideRecursively(Object object) { + return convertRecursively(object, CLIENT_VALUE); + } + + static Object convertTestSideRecursively(Object object) { + return convertRecursively(object, SERVER_VALUE); + } + + private static Object convertRecursively(Object object, Function function) { + if (object instanceof DslProperty) { + return convertRecursively(function.apply(object), function); + } + return object; + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Cookie.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Cookie.java new file mode 100644 index 0000000000..437e88525e --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Cookie.java @@ -0,0 +1,87 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.Objects; + +/** + * Represents a http cookie. + * + * @author Alex Xandra Albert Sim + * @since 1.2.5 + */ +public class Cookie extends DslProperty { + + private String key; + + public Cookie(String key, DslProperty dslProperty) { + super(dslProperty.getClientValue(), dslProperty.getServerValue()); + this.key = key; + } + + public Cookie(String key, MatchingStrategy value) { + super(value); + this.key = key; + } + + public Cookie(String key, Object value) { + super(ContractUtils.CLIENT_VALUE.apply(value), + ContractUtils.SERVER_VALUE.apply(value)); + this.key = key; + } + + public static Cookie build(String key, Object value) { + if (value instanceof MatchingStrategy) { + return new Cookie(key, (MatchingStrategy) value); + } + return new Cookie(key, value); + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + Cookie cookie = (Cookie) o; + return Objects.equals(key, cookie.key); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), key); + } + + @Override + public String toString() { + return "Cookie{" + "key='" + key + '\'' + "}, value=" + super.toString(); + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Cookies.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Cookies.java new file mode 100644 index 0000000000..c1d0e40e98 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Cookies.java @@ -0,0 +1,114 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; + +/** + * Represents a set of http cookies. + * + * @author Alex Xandra Albert Sim + * @since 1.2.5 + */ +public class Cookies { + + private Set entries = new HashSet<>(); + + public void cookie(Map singleCookie) { + Iterator> iterator = singleCookie.entrySet().iterator(); + if (iterator.hasNext()) { + Map.Entry first = iterator.next(); + if (first != null) { + entries.add(Cookie.build(first.getKey(), first.getValue())); + } + } + } + + public void cookie(String cookieKey, Object cookieValue) { + entries.add(Cookie.build(cookieKey, cookieValue)); + } + + public void executeForEachCookie(final Consumer consumer) { + entries.forEach(consumer::accept); + } + + public DslProperty matching(String value) { + return new DslProperty(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Cookies cookies = (Cookies) o; + return Objects.equals(entries, cookies.entries); + } + + @Override + public int hashCode() { + return Objects.hash(entries); + } + + @Override + public String toString() { + return "Cookies{" + "entries=" + entries + '}'; + } + + /** + * Converts the headers into their stub side representations and returns as a map of + * String key => Object value. + * @return converted map + */ + public Map asStubSideMap() { + final Map map = new LinkedHashMap<>(); + entries.forEach(cookie -> map.put(cookie.getKey(), + ContractUtils.convertStubSideRecursively(cookie))); + return map; + } + + /** + * Converts the headers into their stub side representations and returns as a map of + * String key => Object value. + * @return converted map + */ + public Map asTestSideMap() { + final Map map = new HashMap(); + entries.forEach(cookie -> map.put(cookie.getKey(), + ContractUtils.convertTestSideRecursively(cookie))); + return map; + } + + public Set getEntries() { + return entries; + } + + public void setEntries(Set entries) { + this.entries = entries; + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/CustomHandlebarsContractTemplate.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/CustomHandlebarsContractTemplate.java new file mode 100644 index 0000000000..2eb08ad51a --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/CustomHandlebarsContractTemplate.java @@ -0,0 +1,105 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import org.springframework.cloud.contract.spec.ContractTemplate; + +/** + * Represents the structure of templates using Handlebars compatible with WireMock + * template model requirements. + * + * @author Marcin Grzejszczak + * @since 1.1.0* + * @deprecated use {@link HandlebarsContractTemplate} + */ +class CustomHandlebarsContractTemplate implements ContractTemplate { + + @Override + public String openingTemplate() { + return "{{{"; + } + + @Override + public String closingTemplate() { + return "}}}"; + } + + @Override + public String url() { + return wrapped("request.url"); + } + + @Override + public String query(String key) { + return query(key, 0); + } + + @Override + public String query(String key, int index) { + return wrapped("request.query." + key + ".[" + index + "]"); + } + + @Override + public String path() { + return wrapped("request.path"); + } + + @Override + public String path(int index) { + return wrapped("request.path.[" + index + "]"); + } + + @Override + public String header(String key) { + return header(key, 0); + } + + @Override + public String header(String key, int index) { + return wrapped("request.headers." + key + ".[" + index + "]"); + } + + @Override + public String cookie(String key) { + return wrapped("request.cookies." + key); + } + + @Override + public String body() { + return wrapped("request.body"); + } + + @Override + public String escapedBody() { + return wrapped("escapejsonbody"); + } + + @Override + public String escapedBody(String jsonPath) { + return body(jsonPath); + } + + @Override + public String body(String jsonPath) { + return wrapped("jsonpath this \'" + jsonPath + "\'"); + } + + private String wrapped(String text) { + return openingTemplate() + text + closingTemplate(); + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/DslProperty.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/DslProperty.java new file mode 100644 index 0000000000..279cb387c0 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/DslProperty.java @@ -0,0 +1,91 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.io.Serializable; +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * Represents an element of a DSL that can contain client or sever side values. + * + * @since 1.0.0 + */ +public class DslProperty implements Serializable { + + private final T clientValue; + + private final T serverValue; + + public DslProperty(T clientValue, T serverValue) { + this.clientValue = clientValue; + this.serverValue = serverValue; + } + + public DslProperty(T singleValue) { + this.clientValue = singleValue; + this.serverValue = singleValue; + } + + public boolean isSingleValue() { + return this.clientValue.equals(this.serverValue) + || (this.clientValue != null && this.serverValue == null) + || (this.serverValue != null && this.clientValue == null); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DslProperty that = (DslProperty) o; + Object thisClientValue = stringPatternIfPattern(clientValue); + Object thatClientValue = stringPatternIfPattern(that.clientValue); + Object thisServerValue = stringPatternIfPattern(serverValue); + Object thatServerValue = stringPatternIfPattern(that.serverValue); + return Objects.equals(thisClientValue, thatClientValue) + && Objects.equals(thisServerValue, thatServerValue); + } + + private Object stringPatternIfPattern(Object value) { + return value instanceof Pattern ? ((Pattern) value).pattern() : value; + } + + @Override + public int hashCode() { + return Objects.hash(stringPatternIfPattern(clientValue), + stringPatternIfPattern(serverValue)); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + "\nclientValue=" + clientValue + + ", \n\tserverValue=" + serverValue + '}'; + } + + public final T getClientValue() { + return clientValue; + } + + public final T getServerValue() { + return serverValue; + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/DslPropertyConverter.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/DslPropertyConverter.java new file mode 100644 index 0000000000..5f5731e639 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/DslPropertyConverter.java @@ -0,0 +1,95 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.List; + +import org.springframework.core.io.support.SpringFactoriesLoader; + +/** + * A hook mechanism to allow external languages and frameworks to convert types that are + * not in Java for Spring Cloud Contract to understand. + * + * @author Marcin Grzejszczak + * @since 2.2.0 + */ +public interface DslPropertyConverter { + + /** + * Default no op implementation. + */ + DslPropertyConverter DEFAULT = new DslPropertyConverter() { + }; + + /** + * A composite over available instances or default if none is present. + */ + DslPropertyConverter INSTANCE = instance(); + + /** + * @return a composite {@link DslPropertyConverter} around a list of + * {@link DslPropertyConverter} or a default no op instance + */ + static DslPropertyConverter instance() { + if (INSTANCE != null) { + return INSTANCE; + } + List converters = SpringFactoriesLoader + .loadFactories(DslPropertyConverter.class, null); + if (converters.isEmpty()) { + return DEFAULT; + } + return new DslPropertyConverter() { + @Override + public Object testSide(Object object) { + Object convertedObject = object; + for (DslPropertyConverter converter : converters) { + convertedObject = converter.testSide(convertedObject); + } + return convertedObject; + } + + @Override + public Object stubSide(Object object) { + Object convertedObject = object; + for (DslPropertyConverter converter : converters) { + convertedObject = converter.stubSide(convertedObject); + } + return convertedObject; + } + }; + } + + /** + * Conversion mechanism for the test side manipulation. + * @param object object to manipulate + * @return converted object for the test side + */ + default Object testSide(Object object) { + return object; + } + + /** + * Conversion mechanism for the stub side manipulation. + * @param object object to manipulate + * @return converted object for the stub side + */ + default Object stubSide(Object object) { + return object; + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ExecutionProperty.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ExecutionProperty.java new file mode 100644 index 0000000000..1b24eef842 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ExecutionProperty.java @@ -0,0 +1,73 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Represents a property that will become an executable method in the generated tests. + * + * @since 1.0.0 + */ +public class ExecutionProperty implements Serializable { + + private static final String PLACEHOLDER_VALUE = "$it"; + + private final String executionCommand; + + public ExecutionProperty(String executionCommand) { + this.executionCommand = executionCommand; + } + + /** + * Inserts the provided code as a parameter to the method and returns the code that + * represents that method execution. + * @param valueToInsert value to insert into the string + * @return string with inserted value + */ + public String insertValue(String valueToInsert) { + return executionCommand.replace(PLACEHOLDER_VALUE, valueToInsert); + } + + @Override + public String toString() { + return executionCommand; + } + + public final String getExecutionCommand() { + return executionCommand; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ExecutionProperty that = (ExecutionProperty) o; + return Objects.equals(executionCommand, that.executionCommand); + } + + @Override + public int hashCode() { + return Objects.hash(executionCommand); + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/FromFileProperty.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/FromFileProperty.java new file mode 100644 index 0000000000..40fd297815 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/FromFileProperty.java @@ -0,0 +1,96 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.nio.file.Files; + +/** + * Represents a property that will become a File content. + * + * @since 2.1.0 + */ +public class FromFileProperty implements Serializable { + + private final File file; + + private final String charset; + + private final Class type; + + public FromFileProperty(File file, Class type) { + this(file, type, Charset.defaultCharset()); + } + + public FromFileProperty(File file, Class type, Charset charset) { + this.file = file; + this.type = type; + this.charset = charset.toString(); + } + + public boolean isString() { + return String.class.equals(this.type); + } + + public boolean isByte() { + return byte[].class.equals(this.type) || Byte[].class.equals(this.type); + } + + public String asString() { + try { + return new String(asBytes(), this.charset); + } + catch (UnsupportedEncodingException ex) { + throw new IllegalStateException(ex); + } + } + + public String fileName() { + return this.file.getName(); + } + + public byte[] asBytes() { + try { + return Files.readAllBytes(this.file.toPath()); + } + catch (IOException ex) { + throw new IllegalStateException(ex); + } + } + + @Override + public String toString() { + return asString(); + } + + public final File getFile() { + return file; + } + + public final String getCharset() { + return charset; + } + + public final Class getType() { + return type; + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/FromRequest.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/FromRequest.java new file mode 100644 index 0000000000..9e61284373 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/FromRequest.java @@ -0,0 +1,219 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import org.springframework.cloud.contract.spec.ContractTemplate; + +/** + * Helper class to reference the request body parameters. + * + * @author Marcin Grzejszczak + * @since 1.1.0 + */ +public class FromRequest { + + private final ContractTemplate template; + + public FromRequest() { + this.template = template(); + } + + private ContractTemplate template() { + return new CompositeContractTemplate(); + } + + /** + * @return URL path and query. + */ + public DslProperty url() { + return new DslProperty(template.escapedUrl()); + } + + /** + * First value of a query parameter e.g. request.query.search. + * @param key key for the query param + * @return dsl property + */ + public DslProperty query(String key) { + return new DslProperty(template.escapedQuery(key)); + } + + /** + * nth value of a query parameter (zero indexed) e.g. request.query.search.[5]. + * @param key key for the query param + * @param index index of the query param + * @return dsl property + */ + public DslProperty query(String key, int index) { + return new DslProperty(template.escapedQuery(key, index)); + } + + /** + * URL path. + * @return dsl property + */ + public DslProperty path() { + return new DslProperty(template.escapedPath()); + } + + /** + * nth value of a URL path (zero indexed) e.g. {{{ request.path.[2] }}}* @param index. + * @param index path index + * @return dsl property + */ + public DslProperty path(int index) { + return new DslProperty(template.escapedPath(index)); + } + + /** + * First value of a request header e.g. request.headers.X-Request-Id. + * @param key header key + * @return dsl property + */ + public DslProperty header(String key) { + return new DslProperty(template.escapedHeader(key)); + } + + /** + * nth value of a request header (zero indexed) e.g. request.headers.X-Request-Id. + * @param key header key + * @param index header index + * @return dsl property + */ + public DslProperty header(String key, int index) { + return new DslProperty(template.escapedHeader(key, index)); + } + + /** + * Retruns the tempalte for retrieving the first value of a cookie with certain key. + * @param key cookie key + * @return dsl property + */ + public DslProperty cookie(String key) { + return new DslProperty(template.escapedCookie(key)); + } + + /** + * Request body text (avoid for non-text bodies). + * @return dsl property + */ + public DslProperty body() { + return new DslProperty(template.escapedBody()); + } + + /** + * Request body text for the given JsonPath. + * @param jsonPath json path body + * @return dsl property + */ + public DslProperty body(String jsonPath) { + return new DslProperty(template.escapedBody(jsonPath)); + } + + /** + * Unescaped URL path and query. + * @return dsl property + */ + public DslProperty rawUrl() { + return new DslProperty(template.url()); + } + + /** + * Unescaped First value of a query parameter e.g. request.query.search. + * @param key query key + * @return dsl property + */ + public DslProperty rawQuery(String key) { + return new DslProperty(template.query(key)); + } + + /** + * Unescaped nth value of a query parameter (zero indexed) e.g. + * request.query.search.[5]. + * @param key query key + * @param index query index + * @return dsl property + */ + public DslProperty rawQuery(String key, int index) { + return new DslProperty(template.query(key, index)); + } + + /** + * Unescaped URL path. + * @return dsl property + */ + public DslProperty rawPath() { + return new DslProperty(template.path()); + } + + /** + * Unescaped nth value of a URL path (zero indexed) e.g. {{{ request.path.[2]. }}}* + * @param index path index + * @return dsl property + */ + public DslProperty rawPath(int index) { + return new DslProperty(template.path(index)); + } + + /** + * Unescaped First value of a request header e.g. request.headers.X-Request-Id. + * @param key header key + * @return dsl property + */ + public DslProperty rawHeader(String key) { + return new DslProperty(template.header(key)); + } + + /** + * Unescaped nth value of a request header (zero indexed) e.g. + * request.headers.X-Request-Id. + * @param key header key + * @param index header index + * @return dsl property + */ + public DslProperty rawHeader(String key, int index) { + return new DslProperty(template.header(key, index)); + } + + /** + * Unescaped Returns the template for retrieving the first value of a cookie with + * certain key. + * @param key cookie key + * @return dsl property + */ + public DslProperty rawCookie(String key) { + return new DslProperty(template.cookie(key)); + } + + /** + * Unescaped Request body text (avoid for non-text bodies). + * @return dsl property + */ + public DslProperty rawBody() { + return new DslProperty(template.body()); + } + + /** + * Unescaped Request body text for the given JsonPath. + * @param jsonPath json path body + * @return dsl property + */ + public DslProperty rawBody(String jsonPath) { + return new DslProperty(template.body(jsonPath)); + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/HandlebarsContractTemplate.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/HandlebarsContractTemplate.java new file mode 100644 index 0000000000..5a2965cc04 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/HandlebarsContractTemplate.java @@ -0,0 +1,160 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import org.springframework.cloud.contract.spec.ContractTemplate; + +/** + * Represents the structure of templates using Handlebars compatible with WireMock + * template model requirements. + * + * @author Marcin Grzejszczak + * @since 1.1.0 + */ +public class HandlebarsContractTemplate implements ContractTemplate { + + @Override + public String openingTemplate() { + return "{{"; + } + + @Override + public String closingTemplate() { + return "}}"; + } + + @Override + public String escapedOpeningTemplate() { + return "{{{"; + } + + @Override + public String escapedClosingTemplate() { + return "}}}"; + } + + @Override + public String url() { + return wrapped("request.url"); + } + + @Override + public String query(String key) { + return query(key, 0); + } + + @Override + public String query(String key, int index) { + return wrapped("request.query." + key + ".[" + String.valueOf(index) + "]"); + } + + @Override + public String path() { + return wrapped("request.path"); + } + + @Override + public String path(int index) { + return wrapped("request.path.[" + String.valueOf(index) + "]"); + } + + @Override + public String header(String key) { + return header(key, 0); + } + + @Override + public String header(String key, int index) { + return wrapped("request.headers." + key + ".[" + String.valueOf(index) + "]"); + } + + @Override + public String cookie(String key) { + return wrapped("request.cookies." + key); + } + + @Override + public String body() { + return wrapped("request.body"); + } + + @Override + public String escapedBody() { + return escapedWrapped("request.body"); + } + + @Override + public String escapedBody(String jsonPath) { + return escapedWrapped("jsonPath request.body \'" + jsonPath + "\'"); + } + + @Override + public String body(String jsonPath) { + return wrapped("jsonPath request.body \'" + jsonPath + "\'"); + } + + @Override + public String escapedUrl() { + return escapedWrapped("request.url"); + } + + @Override + public String escapedQuery(String key) { + return escapedQuery(key, 0); + } + + @Override + public String escapedQuery(String key, int index) { + return escapedWrapped( + "request.query." + key + ".[" + String.valueOf(index) + "]"); + } + + @Override + public String escapedPath() { + return escapedWrapped("request.path"); + } + + @Override + public String escapedPath(int index) { + return escapedWrapped("request.path.[" + String.valueOf(index) + "]"); + } + + @Override + public String escapedHeader(String key) { + return escapedHeader(key, 0); + } + + @Override + public String escapedHeader(String key, int index) { + return escapedWrapped( + "request.headers." + key + ".[" + String.valueOf(index) + "]"); + } + + @Override + public String escapedCookie(String key) { + return escapedWrapped("request.cookies." + key); + } + + private String wrapped(String text) { + return openingTemplate() + text + closingTemplate(); + } + + private String escapedWrapped(String text) { + return escapedOpeningTemplate() + text + escapedClosingTemplate(); + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Header.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Header.java new file mode 100644 index 0000000000..e34c3f5d46 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Header.java @@ -0,0 +1,86 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.Objects; + +/** + * Represents a header of a request / response or a message. + * + * @since 1.0.0 + */ +public class Header extends DslProperty { + + private String name; + + public Header(String name, DslProperty dslProperty) { + super(dslProperty.getClientValue(), dslProperty.getServerValue()); + this.name = name; + } + + public Header(String name, MatchingStrategy value) { + super(value); + this.name = name; + } + + public Header(String name, Object value) { + super(ContractUtils.CLIENT_VALUE.apply(value), + ContractUtils.SERVER_VALUE.apply(value)); + this.name = name; + } + + public static Header build(String key, Object value) { + if (value instanceof MatchingStrategy) { + return new Header(key, (MatchingStrategy) value); + } + return new Header(key, value); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + Header header = (Header) o; + return Objects.equals(name, header.name); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), name); + } + + @Override + public String toString() { + return "Header{" + "\nname='" + name + '\'' + "} \n" + super.toString(); + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Headers.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Headers.java new file mode 100644 index 0000000000..53bb457f66 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Headers.java @@ -0,0 +1,490 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.regex.Pattern; + +/** + * Represents a set of headers of a request / response or a message. + * + * @since 1.0.0 + */ +public class Headers { + + private static final BiFunction CLIENT_SIDE = (s, + header) -> ContractUtils.convertStubSideRecursively(header); + + private static final BiFunction SERVER_SIDE = (s, + header) -> ContractUtils.convertTestSideRecursively(header); + + private MediaTypes mediaTypes = new MediaTypes(); + + private HttpHeaders httpHeaders = new HttpHeaders(); + + private MessagingHeaders messagingHeaders = new MessagingHeaders(); + + private Set
entries = new LinkedHashSet<>(); + + public void header(Map singleHeader) { + Iterator> iterator = singleHeader.entrySet().iterator(); + if (iterator.hasNext()) { + Map.Entry first = iterator.next(); + if (first != null) { + entries.add(Header.build(first.getKey(), first.getValue())); + } + } + } + + public void header(String headerKey, Object headerValue) { + entries.add(Header.build(headerKey, headerValue)); + } + + public void executeForEachHeader(final Consumer
consumer) { + entries.forEach(consumer); + } + + public void headers(Set
headers) { + entries.addAll(headers); + } + + public void accept(String contentType) { + header(accept(), matching(contentType)); + } + + public void contentType(String contentType) { + header(httpHeaders.contentType(), matching(contentType)); + } + + public void messagingContentType(String contentType) { + header(messagingHeaders.messagingContentType(), matching(contentType)); + } + + /** + * If for the consumer / producer you want to match exactly only the root of content + * type. I.e. {@code application/json;charset=UTF8} you care only about + * {@code application/json} then you should use this method + * @param value regex as String + * @return dsl property + */ + public DslProperty matching(String value) { + return new DslProperty(value); + } + + protected NotToEscapePattern notEscaped(Pattern pattern) { + return new NotToEscapePattern(pattern); + } + + public Map asMap(final BiFunction consumer) { + final Map map = new LinkedHashMap<>(); + entries.forEach(header -> map.put(header.getName(), + consumer.apply(header.getName(), header))); + return map; + } + + /** + * Converts the headers into their stub side representations and returns as a map of + * String key => Object value. + * @return converted map + */ + public Map asStubSideMap() { + return asMap(CLIENT_SIDE); + } + + /** + * Converts the headers into their stub side representations and returns as a map of + * String key => Object value. + * @return converted map + */ + public Map asTestSideMap() { + return asMap(SERVER_SIDE); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Headers headers = (Headers) o; + return Objects.equals(entries, headers.entries); + } + + @Override + public int hashCode() { + return Objects.hash(entries); + } + + @Override + public String toString() { + return "Headers{" + "\nentries=" + entries + '}'; + } + + public MediaTypes getMediaTypes() { + return mediaTypes; + } + + public void setMediaTypes(MediaTypes mediaTypes) { + this.mediaTypes = mediaTypes; + } + + public HttpHeaders getHttpHeaders() { + return httpHeaders; + } + + public void setHttpHeaders(HttpHeaders httpHeaders) { + this.httpHeaders = httpHeaders; + } + + public MessagingHeaders getMessagingHeaders() { + return messagingHeaders; + } + + public void setMessagingHeaders(MessagingHeaders messagingHeaders) { + this.messagingHeaders = messagingHeaders; + } + + public Set
getEntries() { + return entries; + } + + public void setEntries(Set
entries) { + this.entries = entries; + } + + public String messagingContentType() { + return messagingHeaders.messagingContentType(); + } + + public String accept() { + return httpHeaders.accept(); + } + + public String acceptCharset() { + return httpHeaders.acceptCharset(); + } + + public String acceptEncoding() { + return httpHeaders.acceptEncoding(); + } + + public String acceptLanguage() { + return httpHeaders.acceptLanguage(); + } + + public String acceptRanges() { + return httpHeaders.acceptRanges(); + } + + public String accessControlAllowCredentials() { + return httpHeaders.accessControlAllowCredentials(); + } + + public String accessControlAllowHeaders() { + return httpHeaders.accessControlAllowHeaders(); + } + + public String accessControlAllowMethods() { + return httpHeaders.accessControlAllowMethods(); + } + + public String accessControlAllowOrigin() { + return httpHeaders.accessControlAllowOrigin(); + } + + public String accessControlExposeHeaders() { + return httpHeaders.accessControlExposeHeaders(); + } + + public String accessControlMaxAge() { + return httpHeaders.accessControlMaxAge(); + } + + public String accessControlRequestHeaders() { + return httpHeaders.accessControlRequestHeaders(); + } + + public String accessControlRequestMethod() { + return httpHeaders.accessControlRequestMethod(); + } + + public String age() { + return httpHeaders.age(); + } + + public String allow() { + return httpHeaders.allow(); + } + + public String authorization() { + return httpHeaders.authorization(); + } + + public String cacheControl() { + return httpHeaders.cacheControl(); + } + + public String connection() { + return httpHeaders.connection(); + } + + public String contentEncoding() { + return httpHeaders.contentEncoding(); + } + + public String contentDisposition() { + return httpHeaders.contentDisposition(); + } + + public String contentLanguage() { + return httpHeaders.contentLanguage(); + } + + public String contentLength() { + return httpHeaders.contentLength(); + } + + public String contentLocation() { + return httpHeaders.contentLocation(); + } + + public String contentRange() { + return httpHeaders.contentRange(); + } + + public String contentType() { + return httpHeaders.contentType(); + } + + public String cookie() { + return httpHeaders.cookie(); + } + + public String date() { + return httpHeaders.date(); + } + + public String etag() { + return httpHeaders.etag(); + } + + public String expect() { + return httpHeaders.expect(); + } + + public String expires() { + return httpHeaders.expires(); + } + + public String from() { + return httpHeaders.from(); + } + + public String host() { + return httpHeaders.host(); + } + + public String ifMatch() { + return httpHeaders.ifMatch(); + } + + public String ifModifiedSince() { + return httpHeaders.ifModifiedSince(); + } + + public String ifNoneMatch() { + return httpHeaders.ifNoneMatch(); + } + + public String ifRange() { + return httpHeaders.ifRange(); + } + + public String ifUnmodifiedSince() { + return httpHeaders.ifUnmodifiedSince(); + } + + public String lastModified() { + return httpHeaders.lastModified(); + } + + public String link() { + return httpHeaders.link(); + } + + public String location() { + return httpHeaders.location(); + } + + public String max_forwards() { + return httpHeaders.max_forwards(); + } + + public String origin() { + return httpHeaders.origin(); + } + + public String pragma() { + return httpHeaders.pragma(); + } + + public String proxyAuthenticate() { + return httpHeaders.proxyAuthenticate(); + } + + public String proxyAuthorization() { + return httpHeaders.proxyAuthorization(); + } + + public String range() { + return httpHeaders.range(); + } + + public String referer() { + return httpHeaders.referer(); + } + + public String retryAfter() { + return httpHeaders.retryAfter(); + } + + public String server() { + return httpHeaders.server(); + } + + public String setCookie() { + return httpHeaders.setCookie(); + } + + public String setCookie2() { + return httpHeaders.setCookie2(); + } + + public String te() { + return httpHeaders.te(); + } + + public String trailer() { + return httpHeaders.trailer(); + } + + public String transferEncoding() { + return httpHeaders.transferEncoding(); + } + + public String upgrade() { + return httpHeaders.upgrade(); + } + + public String user_agent() { + return httpHeaders.user_agent(); + } + + public String vary() { + return httpHeaders.vary(); + } + + public String via() { + return httpHeaders.via(); + } + + public String warning() { + return httpHeaders.warning(); + } + + public String wwwAuthenticate() { + return httpHeaders.wwwAuthenticate(); + } + + public String allValue() { + return mediaTypes.allValue(); + } + + public String applicationAtomXml() { + return mediaTypes.applicationAtomXml(); + } + + public String applicationFormUrlencoded() { + return mediaTypes.applicationFormUrlencoded(); + } + + public String applicationJson() { + return mediaTypes.applicationJson(); + } + + public String applicationJsonUtf8() { + return mediaTypes.applicationJsonUtf8(); + } + + public String applicationOctetStream() { + return mediaTypes.applicationOctetStream(); + } + + public String applicationPdf() { + return mediaTypes.applicationPdf(); + } + + public String applicationXhtmlXml() { + return mediaTypes.applicationXhtmlXml(); + } + + public String applicationXml() { + return mediaTypes.applicationXml(); + } + + public String imageGif() { + return mediaTypes.imageGif(); + } + + public String imageJpeg() { + return mediaTypes.imageJpeg(); + } + + public String imagePng() { + return mediaTypes.imagePng(); + } + + public String multipartFormData() { + return mediaTypes.multipartFormData(); + } + + public String textHtml() { + return mediaTypes.textHtml(); + } + + public String textMarkdown() { + return mediaTypes.textMarkdown(); + } + + public String textPlain() { + return mediaTypes.textPlain(); + } + + public String textXml() { + return mediaTypes.textXml(); + } + +} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/HttpHeaders.groovy b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/HttpHeaders.java similarity index 55% rename from spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/HttpHeaders.groovy rename to specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/HttpHeaders.java index 6845486a8e..1a866789c6 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/HttpHeaders.groovy +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/HttpHeaders.java @@ -14,483 +14,542 @@ * limitations under the License. */ -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString +package org.springframework.cloud.contract.spec.internal; /** - * Contains most commonly used http headers + * Contains most commonly used http headers. * * @author Marcin Grzejszczak * @since 1.0.2 */ -@CompileStatic -@EqualsAndHashCode -@ToString(includePackage = false) -class HttpHeaders { +public class HttpHeaders { /** * The HTTP {@code Accept} header field name. - * @see Section 5.3.2 of RFC 7231 + * @see Section 5.3.2 of + * RFC 7231 */ - String accept() { - return "Accept" + public String accept() { + return "Accept"; } + /** * The HTTP {@code Accept-Charset} header field name. - * @see Section 5.3.3 of RFC 7231 + * @see Section 5.3.3 of + * RFC 7231 */ - String acceptCharset() { - return "Accept-Charset" + public String acceptCharset() { + return "Accept-Charset"; } + /** * The HTTP {@code Accept-Encoding} header field name. - * @see Section 5.3.4 of RFC 7231 + * @see Section 5.3.4 of + * RFC 7231 */ - String acceptEncoding() { - return "Accept-Encoding" + public String acceptEncoding() { + return "Accept-Encoding"; } + /** * The HTTP {@code Accept-Language} header field name. - * @see Section 5.3.5 of RFC 7231 + * @see Section 5.3.5 of + * RFC 7231 */ - String acceptLanguage() { - return "Accept-Language" + public String acceptLanguage() { + return "Accept-Language"; } + /** * The HTTP {@code Accept-Ranges} header field name. - * @see Section 5.3.5 of RFC 7233 + * @see Section 5.3.5 of + * RFC 7233 */ - String acceptRanges() { - return "Accept-Ranges" + public String acceptRanges() { + return "Accept-Ranges"; } + /** * The CORS {@code Access-Control-Allow-Credentials} response header field name. - * @see CORS W3C recommendation + * @see CORS W3C recommendation */ - String accessControlAllowCredentials() { - return "Access-Control-Allow-Credentials" + public String accessControlAllowCredentials() { + return "Access-Control-Allow-Credentials"; } + /** * The CORS {@code Access-Control-Allow-Headers} response header field name. - * @see CORS W3C recommendation + * @see CORS W3C recommendation */ - String accessControlAllowHeaders() { - return "Access-Control-Allow-Headers" + public String accessControlAllowHeaders() { + return "Access-Control-Allow-Headers"; } + /** * The CORS {@code Access-Control-Allow-Methods} response header field name. - * @see CORS W3C recommendation + * @see CORS W3C recommendation */ - String accessControlAllowMethods() { - return "Access-Control-Allow-Methods" + public String accessControlAllowMethods() { + return "Access-Control-Allow-Methods"; } + /** * The CORS {@code Access-Control-Allow-Origin} response header field name. - * @see CORS W3C recommendation + * @see CORS W3C recommendation */ - String accessControlAllowOrigin() { - return "Access-Control-Allow-Origin" + public String accessControlAllowOrigin() { + return "Access-Control-Allow-Origin"; } + /** * The CORS {@code Access-Control-Expose-Headers} response header field name. - * @see CORS W3C recommendation + * @see CORS W3C recommendation */ - String accessControlExposeHeaders() { - return "Access-Control-Expose-Headers" + public String accessControlExposeHeaders() { + return "Access-Control-Expose-Headers"; } + /** * The CORS {@code Access-Control-Max-Age} response header field name. - * @see CORS W3C recommendation + * @see CORS W3C recommendation */ - String accessControlMaxAge() { - return "Access-Control-Max-Age" + public String accessControlMaxAge() { + return "Access-Control-Max-Age"; } + /** * The CORS {@code Access-Control-Request-Headers} request header field name. - * @see CORS W3C recommendation + * @see CORS W3C recommendation */ - String accessControlRequestHeaders() { - return "Access-Control-Request-Headers" + public String accessControlRequestHeaders() { + return "Access-Control-Request-Headers"; } + /** * The CORS {@code Access-Control-Request-Method} request header field name. - * @see CORS W3C recommendation + * @see CORS W3C recommendation */ - String accessControlRequestMethod() { - return "Access-Control-Request-Method" + public String accessControlRequestMethod() { + return "Access-Control-Request-Method"; } + /** * The HTTP {@code Age} header field name. - * @see Section 5.1 of RFC 7234 + * @see Section 5.1 of RFC + * 7234 */ - String age() { - return "Age" + public String age() { + return "Age"; } + /** * The HTTP {@code Allow} header field name. - * @see Section 7.4.1 of RFC 7231 + * @see Section 7.4.1 of + * RFC 7231 */ - String allow() { - return "Allow" + public String allow() { + return "Allow"; } + /** * The HTTP {@code Authorization} header field name. - * @see Section 4.2 of RFC 7235 + * @see Section 4.2 of RFC + * 7235 */ - String authorization() { - return "Authorization" + public String authorization() { + return "Authorization"; } + /** * The HTTP {@code Cache-Control} header field name. - * @see Section 5.2 of RFC 7234 + * @see Section 5.2 of RFC + * 7234 */ - String cacheControl() { - return "Cache-Control" + public String cacheControl() { + return "Cache-Control"; } /** * The HTTP {@code Connection} header field name. - * @see Section 6.1 of RFC 7230 + * @see Section 6.1 of RFC + * 7230 */ - String connection() { - return "Connection" + public String connection() { + return "Connection"; } /** * The HTTP {@code Content-Encoding} header field name. - * @see Section 3.1.2.2 of RFC 7231 + * @see Section 3.1.2.2 + * of RFC 7231 */ - String contentEncoding() { - return "Content-Encoding" + public String contentEncoding() { + return "Content-Encoding"; } /** * The HTTP {@code Content-Disposition} header field name - * @see RFC 6266 + * @see RFC 6266 */ - String contentDisposition() { - return "Content-Disposition" + public String contentDisposition() { + return "Content-Disposition"; } /** * The HTTP {@code Content-Language} header field name. - * @see Section 3.1.3.2 of RFC 7231 + * @see Section 3.1.3.2 + * of RFC 7231 */ - String contentLanguage() { - return "Content-Language" + public String contentLanguage() { + return "Content-Language"; } /** * The HTTP {@code Content-Length} header field name. - * @see Section 3.3.2 of RFC 7230 + * @see Section 3.3.2 of + * RFC 7230 */ - String contentLength() { - return "Content-Length" + public String contentLength() { + return "Content-Length"; } /** * The HTTP {@code Content-Location} header field name. - * @see Section 3.1.4.2 of RFC 7231 + * @see Section 3.1.4.2 + * of RFC 7231 */ - String contentLocation() { - return "Content-Location" + public String contentLocation() { + return "Content-Location"; } /** * The HTTP {@code Content-Range} header field name. - * @see Section 4.2 of RFC 7233 + * @see Section 4.2 of RFC + * 7233 */ - String contentRange() { - return "Content-Range" + public String contentRange() { + return "Content-Range"; } /** * The HTTP {@code Content-Type} header field name. - * @see Section 3.1.1.5 of RFC 7231 + * @see Section 3.1.1.5 + * of RFC 7231 */ - String contentType() { - return "Content-Type" + public String contentType() { + return "Content-Type"; } /** * The HTTP {@code Cookie} header field name. - * @see Section 4.3.4 of RFC 2109 + * @see Section 4.3.4 of + * RFC 2109 */ - String cookie() { - return "Cookie" + public String cookie() { + return "Cookie"; } /** * The HTTP {@code Date} header field name. - * @see Section 7.1.1.2 of RFC 7231 + * @see Section 7.1.1.2 + * of RFC 7231 */ - String date() { - return "Date" + public String date() { + return "Date"; } /** * The HTTP {@code ETag} header field name. - * @see Section 2.3 of RFC 7232 + * @see Section 2.3 of RFC + * 7232 */ - String etag() { - return "ETag" + public String etag() { + return "ETag"; } /** * The HTTP {@code Expect} header field name. - * @see Section 5.1.1 of RFC 7231 + * @see Section 5.1.1 of + * RFC 7231 */ - String expect() { - return "Expect" + public String expect() { + return "Expect"; } /** * The HTTP {@code Expires} header field name. - * @see Section 5.3 of RFC 7234 + * @see Section 5.3 of RFC + * 7234 */ - String expires() { - return "Expires" + public String expires() { + return "Expires"; } /** * The HTTP {@code From} header field name. - * @see Section 5.5.1 of RFC 7231 + * @see Section 5.5.1 of + * RFC 7231 */ - String from() { - return "From" + public String from() { + return "From"; } /** * The HTTP {@code Host} header field name. - * @see Section 5.4 of RFC 7230 + * @see Section 5.4 of RFC + * 7230 */ - String host() { - return "Host" + public String host() { + return "Host"; } /** * The HTTP {@code If-Match} header field name. - * @see Section 3.1 of RFC 7232 + * @see Section 3.1 of RFC + * 7232 */ - String ifMatch() { - return "If-Match" + public String ifMatch() { + return "If-Match"; } /** * The HTTP {@code If-Modified-Since} header field name. - * @see Section 3.3 of RFC 7232 + * @see Section 3.3 of RFC + * 7232 */ - String ifModifiedSince() { - return "If-Modified-Since" + public String ifModifiedSince() { + return "If-Modified-Since"; } /** * The HTTP {@code If-None-Match} header field name. - * @see Section 3.2 of RFC 7232 + * @see Section 3.2 of RFC + * 7232 */ - String ifNoneMatch() { - return "If-None-Match" + public String ifNoneMatch() { + return "If-None-Match"; } /** * The HTTP {@code If-Range} header field name. - * @see Section 3.2 of RFC 7233 + * @see Section 3.2 of RFC + * 7233 */ - String ifRange() { - return "If-Range" + public String ifRange() { + return "If-Range"; } /** * The HTTP {@code If-Unmodified-Since} header field name. - * @see Section 3.4 of RFC 7232 + * @see Section 3.4 of RFC + * 7232 */ - String ifUnmodifiedSince() { - return "If-Unmodified-Since" + public String ifUnmodifiedSince() { + return "If-Unmodified-Since"; } /** * The HTTP {@code Last-Modified} header field name. - * @see Section 2.2 of RFC 7232 + * @see Section 2.2 of RFC + * 7232 */ - String lastModified() { - return "Last-Modified" + public String lastModified() { + return "Last-Modified"; } /** * The HTTP {@code Link} header field name. - * @see RFC 5988 + * @see RFC 5988 */ - String link() { - return "Link" + public String link() { + return "Link"; } /** * The HTTP {@code Location} header field name. - * @see Section 7.1.2 of RFC 7231 + * @see Section 7.1.2 of + * RFC 7231 */ - String location() { - return "Location" + public String location() { + return "Location"; } /** * The HTTP {@code Max-Forwards} header field name. - * @see Section 5.1.2 of RFC 7231 + * @see Section 5.1.2 of + * RFC 7231 */ - String max_forwards() { - return "Max-Forwards" + public String max_forwards() { + return "Max-Forwards"; } + /** * The HTTP {@code Origin} header field name. - * @see RFC 6454 + * @see RFC 6454 */ - String origin() { - return "Origin" + public String origin() { + return "Origin"; } /** * The HTTP {@code Pragma} header field name. - * @see Section 5.4 of RFC 7234 + * @see Section 5.4 of RFC + * 7234 */ - String pragma() { - return "Pragma" + public String pragma() { + return "Pragma"; } /** * The HTTP {@code Proxy-Authenticate} header field name. - * @see Section 4.3 of RFC 7235 + * @see Section 4.3 of RFC + * 7235 */ - String proxyAuthenticate() { - return "Proxy-Authenticate" + public String proxyAuthenticate() { + return "Proxy-Authenticate"; } /** * The HTTP {@code Proxy-Authorization} header field name. - * @see Section 4.4 of RFC 7235 + * @see Section 4.4 of RFC + * 7235 */ - String proxyAuthorization() { - return "Proxy-Authorization" + public String proxyAuthorization() { + return "Proxy-Authorization"; } /** * The HTTP {@code Range} header field name. - * @see Section 3.1 of RFC 7233 + * @see Section 3.1 of RFC + * 7233 */ - String range() { - return "Range" + public String range() { + return "Range"; } /** * The HTTP {@code Referer} header field name. - * @see Section 5.5.2 of RFC 7231 + * @see Section 5.5.2 of + * RFC 7231 */ - String referer() { - return "Referer" + public String referer() { + return "Referer"; } /** * The HTTP {@code Retry-After} header field name. - * @see Section 7.1.3 of RFC 7231 + * @see Section 7.1.3 of + * RFC 7231 */ - String retryAfter() { - return "Retry-After" + public String retryAfter() { + return "Retry-After"; } /** * The HTTP {@code Server} header field name. - * @see Section 7.4.2 of RFC 7231 + * @see Section 7.4.2 of + * RFC 7231 */ - String server() { - return "Server" + public String server() { + return "Server"; } /** * The HTTP {@code Set-Cookie} header field name. - * @see Section 4.2.2 of RFC 2109 + * @see Section 4.2.2 of + * RFC 2109 */ - String setCookie() { - return "Set-Cookie" + public String setCookie() { + return "Set-Cookie"; } /** * The HTTP {@code Set-Cookie2} header field name. - * @see RFC 2965 + * @see RFC 2965 */ - String setCookie2() { - return "Set-Cookie2" + public String setCookie2() { + return "Set-Cookie2"; } /** * The HTTP {@code TE} header field name. - * @see Section 4.3 of RFC 7230 + * @see Section 4.3 of RFC + * 7230 */ - String te() { - return "TE" + public String te() { + return "TE"; } /** * The HTTP {@code Trailer} header field name. - * @see Section 4.4 of RFC 7230 + * @see Section 4.4 of RFC + * 7230 */ - String trailer() { - return "Trailer" + public String trailer() { + return "Trailer"; } /** * The HTTP {@code Transfer-Encoding} header field name. - * @see Section 3.3.1 of RFC 7230 + * @see Section 3.3.1 of + * RFC 7230 */ - String transferEncoding() { - return "Transfer-Encoding" + public String transferEncoding() { + return "Transfer-Encoding"; } /** * The HTTP {@code Upgrade} header field name. - * @see Section 6.7 of RFC 7230 + * @see Section 6.7 of RFC + * 7230 */ - String upgrade() { - return "Upgrade" + public String upgrade() { + return "Upgrade"; } /** * The HTTP {@code User-Agent} header field name. - * @see Section 5.5.3 of RFC 7231 + * @see Section 5.5.3 of + * RFC 7231 */ - String user_agent() { - return "User-Agent" + public String user_agent() { + return "User-Agent"; } /** * The HTTP {@code Vary} header field name. - * @see Section 7.1.4 of RFC 7231 + * @see Section 7.1.4 of + * RFC 7231 */ - String vary() { - return "Vary" + public String vary() { + return "Vary"; } /** * The HTTP {@code Via} header field name. - * @see Section 5.7.1 of RFC 7230 + * @see Section 5.7.1 of + * RFC 7230 */ - String via() { - return "Via" + public String via() { + return "Via"; } /** * The HTTP {@code Warning} header field name. - * @see Section 5.5 of RFC 7234 + * @see Section 5.5 of RFC + * 7234 */ - String warning() { - return "Warning" + public String warning() { + return "Warning"; } /** * The HTTP {@code WWW-Authenticate} header field name. - * @see Section 4.1 of RFC 7235 + * @see Section 4.1 of RFC + * 7235 */ - String wwwAuthenticate() { - return "WWW-Authenticate" + public String wwwAuthenticate() { + return "WWW-Authenticate"; } + } diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/HttpMethods.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/HttpMethods.java new file mode 100644 index 0000000000..86b3ac1361 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/HttpMethods.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +/** + * Contains Http Methods. + * + * @author Marcin Grzejszczak + * @since 1.0.2 + */ +public class HttpMethods { + + public HttpMethod GET() { + return HttpMethod.GET; + } + + public HttpMethod HEAD() { + return HttpMethod.HEAD; + } + + public HttpMethod POST() { + return HttpMethod.POST; + } + + public HttpMethod PUT() { + return HttpMethod.PUT; + } + + public HttpMethod PATCH() { + return HttpMethod.PATCH; + } + + public HttpMethod DELETE() { + return HttpMethod.DELETE; + } + + public HttpMethod OPTIONS() { + return HttpMethod.OPTIONS; + } + + public HttpMethod TRACE() { + return HttpMethod.TRACE; + } + + public enum HttpMethod { + + GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; + + } + +} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/HttpStatus.groovy b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/HttpStatus.java similarity index 51% rename from spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/HttpStatus.groovy rename to specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/HttpStatus.java index 80144c777b..2ccdb3aae5 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/HttpStatus.groovy +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/HttpStatus.java @@ -14,383 +14,622 @@ * limitations under the License. */ -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode +package org.springframework.cloud.contract.spec.internal; /** - * Helper functions for HTTP statuses + * Helper functions for HTTP statuses. * * @author Marcin Grzejszczak * @since 2.0.0 */ -@CompileStatic -@EqualsAndHashCode -class HttpStatus { - - // 1xx Informational +public class HttpStatus { /** * {@code 100 Continue}. - * @see HTTP/1.1: Semantics and Content, section 6.2.1 + * @see HTTP/1.1: + * Semantics and Content, section 6.2.1 */ - int CONTINUE() { - return 100 + public int CONTINUE() { + return 100; } /** * {@code 101 Switching Protocols}. - * @see HTTP/1.1: Semantics and Content, section 6.2.2 + * @see HTTP/1.1: + * Semantics and Content, section 6.2.2 */ - int SWITCHING_PROTOCOLS() { - return 101 + public int SWITCHING_PROTOCOLS() { + return 101; } + /** * {@code 102 Processing}. - * @see WebDAV + * @see WebDAV */ - int PROCESSING() { return 102 } + public int PROCESSING() { + return 102; + } + /** * {@code 103 Checkpoint}. - * @see A proposal for supporting - * resumable POST/PUT HTTP requests in HTTP/1.0 + * @see + * A proposal for supporting resumable POST/PUT HTTP requests in HTTP/1.0 */ - int CHECKPOINT() { return 103 } - - // 2xx Success + public int CHECKPOINT() { + return 103; + } /** * {@code 200 OK}. - * @see HTTP/1.1: Semantics and Content, section 6.3.1 + * @see HTTP/1.1: + * Semantics and Content, section 6.3.1 */ - int OK() { return 200 } + public int OK() { + return 200; + } + /** * {@code 201 Created}. - * @see HTTP/1.1: Semantics and Content, section 6.3.2 + * @see HTTP/1.1: + * Semantics and Content, section 6.3.2 */ - int CREATED() { return 201 } + public int CREATED() { + return 201; + } + /** * {@code 202 Accepted}. - * @see HTTP/1.1: Semantics and Content, section 6.3.3 + * @see HTTP/1.1: + * Semantics and Content, section 6.3.3 */ - int ACCEPTED() { return 202 } + public int ACCEPTED() { + return 202; + } + /** * {@code 203 Non-Authoritative Information}. - * @see HTTP/1.1: Semantics and Content, section 6.3.4 + * @see HTTP/1.1: + * Semantics and Content, section 6.3.4 */ - int NON_AUTHORITATIVE_INFORMATION() { return 203 } + public int NON_AUTHORITATIVE_INFORMATION() { + return 203; + } + /** * {@code 204 No Content}. - * @see HTTP/1.1: Semantics and Content, section 6.3.5 + * @see HTTP/1.1: + * Semantics and Content, section 6.3.5 */ - int NO_CONTENT() { return 204 } + public int NO_CONTENT() { + return 204; + } + /** * {@code 205 Reset Content}. - * @see HTTP/1.1: Semantics and Content, section 6.3.6 + * @see HTTP/1.1: + * Semantics and Content, section 6.3.6 */ - int RESET_CONTENT() { return 205 } + public int RESET_CONTENT() { + return 205; + } + /** * {@code 206 Partial Content}. - * @see HTTP/1.1: Range Requests, section 4.1 + * @see HTTP/1.1: Range + * Requests, section 4.1 */ - int PARTIAL_CONTENT() { return 206 } + public int PARTIAL_CONTENT() { + return 206; + } + /** * {@code 207 Multi-Status}. - * @see WebDAV + * @see WebDAV */ - int MULTI_STATUS() { return 207 } + public int MULTI_STATUS() { + return 207; + } + /** * {@code 208 Already Reported}. - * @see WebDAV Binding Extensions + * @see WebDAV Binding + * Extensions */ - int ALREADY_REPORTED() { return 208 } + public int ALREADY_REPORTED() { + return 208; + } + /** * {@code 226 IM Used}. - * @see Delta encoding in HTTP + * @see Delta encoding + * in HTTP */ - int IM_USED() { return 226 } - - // 3xx Redirection + public int IM_USED() { + return 226; + } /** * {@code 300 Multiple Choices}. - * @see HTTP/1.1: Semantics and Content, section 6.4.1 + * @see HTTP/1.1: + * Semantics and Content, section 6.4.1 */ - int MULTIPLE_CHOICES() { return 300 } + public int MULTIPLE_CHOICES() { + return 300; + } + /** * {@code 301 Moved Permanently}. - * @see HTTP/1.1: Semantics and Content, section 6.4.2 + * @see HTTP/1.1: + * Semantics and Content, section 6.4.2 */ - int MOVED_PERMANENTLY() { return 301 } + public int MOVED_PERMANENTLY() { + return 301; + } + /** * {@code 302 Found}. - * @see HTTP/1.1: Semantics and Content, section 6.4.3 + * @see HTTP/1.1: + * Semantics and Content, section 6.4.3 */ - int FOUND() { return 302 } + public int FOUND() { + return 302; + } + /** * {@code 302 Moved Temporarily}. - * @see HTTP/1.0, section 9.3 - * @deprecated in favor of {@link #FOUND} which will be returned from {@code HttpStatus.valueOf(302)} + * @see HTTP/1.0, section + * 9.3 + * @deprecated in favor of {@link #FOUND} which will be returned from + * {@code HttpStatus.valueOf(302)} */ @Deprecated - int MOVED_TEMPORARILY() { return 302 } + public int MOVED_TEMPORARILY() { + return 302; + } + /** * {@code 303 See Other}. - * @see HTTP/1.1: Semantics and Content, section 6.4.4 + * @see HTTP/1.1: + * Semantics and Content, section 6.4.4 */ - int SEE_OTHER() { return 303 } + public int SEE_OTHER() { + return 303; + } + /** * {@code 304 Not Modified}. - * @see HTTP/1.1: Conditional Requests, section 4.1 + * @see HTTP/1.1: + * Conditional Requests, section 4.1 */ - int NOT_MODIFIED() { return 304 } + public int NOT_MODIFIED() { + return 304; + } + /** * {@code 305 Use Proxy}. - * @see HTTP/1.1: Semantics and Content, section 6.4.5 + * @see HTTP/1.1: + * Semantics and Content, section 6.4.5 * @deprecated due to security concerns regarding in-band configuration of a proxy */ @Deprecated - int USE_PROXY() { return 305 } + public int USE_PROXY() { + return 305; + } + /** * {@code 307 Temporary Redirect}. - * @see HTTP/1.1: Semantics and Content, section 6.4.7 + * @see HTTP/1.1: + * Semantics and Content, section 6.4.7 */ - int TEMPORARY_REDIRECT() { return 307 } + public int TEMPORARY_REDIRECT() { + return 307; + } + /** * {@code 308 Permanent Redirect}. - * @see RFC 7238 + * @see RFC 7238 */ - int PERMANENT_REDIRECT() { return 308 } - - // --- 4xx Client Error --- + public int PERMANENT_REDIRECT() { + return 308; + } /** * {@code 400 Bad Request}. - * @see HTTP/1.1: Semantics and Content, section 6.5.1 + * @see HTTP/1.1: + * Semantics and Content, section 6.5.1 */ - int BAD_REQUEST() { return 400 } + public int BAD_REQUEST() { + return 400; + } + /** * {@code 401 Unauthorized}. - * @see HTTP/1.1: Authentication, section 3.1 + * @see HTTP/1.1: + * Authentication, section 3.1 */ - int UNAUTHORIZED() { return 401 } + public int UNAUTHORIZED() { + return 401; + } + /** * {@code 402 Payment Required}. - * @see HTTP/1.1: Semantics and Content, section 6.5.2 + * @see HTTP/1.1: + * Semantics and Content, section 6.5.2 */ - int PAYMENT_REQUIRED() { return 402 } + public int PAYMENT_REQUIRED() { + return 402; + } + /** * {@code 403 Forbidden}. - * @see HTTP/1.1: Semantics and Content, section 6.5.3 + * @see HTTP/1.1: + * Semantics and Content, section 6.5.3 */ - int FORBIDDEN() { return 403 } + public int FORBIDDEN() { + return 403; + } + /** * {@code 404 Not Found}. - * @see HTTP/1.1: Semantics and Content, section 6.5.4 + * @see HTTP/1.1: + * Semantics and Content, section 6.5.4 */ - int NOT_FOUND() { return 404 } + public int NOT_FOUND() { + return 404; + } + /** * {@code 405 Method Not Allowed}. - * @see HTTP/1.1: Semantics and Content, section 6.5.5 + * @see HTTP/1.1: + * Semantics and Content, section 6.5.5 */ - int METHOD_NOT_ALLOWED() { return 405 } + public int METHOD_NOT_ALLOWED() { + return 405; + } + /** * {@code 406 Not Acceptable}. - * @see HTTP/1.1: Semantics and Content, section 6.5.6 + * @see HTTP/1.1: + * Semantics and Content, section 6.5.6 */ - int NOT_ACCEPTABLE() { return 406 } + public int NOT_ACCEPTABLE() { + return 406; + } + /** * {@code 407 Proxy Authentication Required}. - * @see HTTP/1.1: Authentication, section 3.2 + * @see HTTP/1.1: + * Authentication, section 3.2 */ - int PROXY_AUTHENTICATION_REQUIRED() { return 407 } + public int PROXY_AUTHENTICATION_REQUIRED() { + return 407; + } + /** * {@code 408 Request Timeout}. - * @see HTTP/1.1: Semantics and Content, section 6.5.7 + * @see HTTP/1.1: + * Semantics and Content, section 6.5.7 */ - int REQUEST_TIMEOUT() { return 408 } + public int REQUEST_TIMEOUT() { + return 408; + } + /** * {@code 409 Conflict}. - * @see HTTP/1.1: Semantics and Content, section 6.5.8 + * @see HTTP/1.1: + * Semantics and Content, section 6.5.8 */ - int CONFLICT() { return 409 } + public int CONFLICT() { + return 409; + } + /** * {@code 410 Gone}. - * @see HTTP/1.1: Semantics and Content, section 6.5.9 + * @see HTTP/1.1: + * Semantics and Content, section 6.5.9 */ - int GONE() { return 410 } + public int GONE() { + return 410; + } + /** * {@code 411 Length Required}. - * @see HTTP/1.1: Semantics and Content, section 6.5.10 + * @see HTTP/1.1: + * Semantics and Content, section 6.5.10 */ - int LENGTH_REQUIRED() { return 411 } + public int LENGTH_REQUIRED() { + return 411; + } + /** * {@code 412 Precondition failed}. - * @see HTTP/1.1: Conditional Requests, section 4.2 + * @see HTTP/1.1: + * Conditional Requests, section 4.2 */ - int PRECONDITION_FAILED() { return 412 } + public int PRECONDITION_FAILED() { + return 412; + } + /** * {@code 413 Payload Too Large}. - * @since 4.1* @see HTTP/1.1: Semantics and Content, section 6.5.11 + * @since 4.1* @see + * HTTP/1.1: Semantics and Content, section 6.5.11 */ - int PAYLOAD_TOO_LARGE() { return 413 } + public int PAYLOAD_TOO_LARGE() { + return 413; + } + /** * {@code 413 Request Entity Too Large}. - * @see HTTP/1.1, section 10.4.14 - * @deprecated in favor of {@link #PAYLOAD_TOO_LARGE} which will be returned from {@code HttpStatus.valueOf(413)} + * @see HTTP/1.1, + * section 10.4.14 + * @deprecated in favor of {@link #PAYLOAD_TOO_LARGE} which will be returned from + * {@code HttpStatus.valueOf(413)} */ @Deprecated - int REQUEST_ENTITY_TOO_LARGE() { return 413 } + public int REQUEST_ENTITY_TOO_LARGE() { + return 413; + } + /** * {@code 414 URI Too Long}. - * @since 4.1* @see HTTP/1.1: Semantics and Content, section 6.5.12 + * @since 4.1* @see + * HTTP/1.1: Semantics and Content, section 6.5.12 */ - int URI_TOO_LONG() { return 414 } + public int URI_TOO_LONG() { + return 414; + } + /** * {@code 414 Request-URI Too Long}. - * @see HTTP/1.1, section 10.4.15 - * @deprecated in favor of {@link #URI_TOO_LONG} which will be returned from {@code HttpStatus.valueOf(414)} + * @see HTTP/1.1, + * section 10.4.15 + * @deprecated in favor of {@link #URI_TOO_LONG} which will be returned from + * {@code HttpStatus.valueOf(414)} */ @Deprecated - int REQUEST_URI_TOO_LONG() { return 414 } + public int REQUEST_URI_TOO_LONG() { + return 414; + } + /** * {@code 415 Unsupported Media Type}. - * @see HTTP/1.1: Semantics and Content, section 6.5.13 + * @see HTTP/1.1: + * Semantics and Content, section 6.5.13 */ - int UNSUPPORTED_MEDIA_TYPE() { return 415 } + public int UNSUPPORTED_MEDIA_TYPE() { + return 415; + } + /** * {@code 416 Requested Range Not Satisfiable}. - * @see HTTP/1.1: Range Requests, section 4.4 + * @see HTTP/1.1: Range + * Requests, section 4.4 */ - int REQUESTED_RANGE_NOT_SATISFIABLE() { return 416 } + public int REQUESTED_RANGE_NOT_SATISFIABLE() { + return 416; + } + /** * {@code 417 Expectation Failed}. - * @see HTTP/1.1: Semantics and Content, section 6.5.14 + * @see HTTP/1.1: + * Semantics and Content, section 6.5.14 */ - int EXPECTATION_FAILED() { return 417 } + public int EXPECTATION_FAILED() { + return 417; + } + /** * {@code 418 I'm a teapot}. - * @see HTCPCP/1.0 + * @see HTCPCP/1.0 */ - int I_AM_A_TEAPOT() { return 418 } + public int I_AM_A_TEAPOT() { + return 418; + } + /** - * @deprecated See WebDAV Draft Changes + * @deprecated See WebDAV + * Draft Changes */ @Deprecated - int INSUFFICIENT_SPACE_ON_RESOURCE() { return 419 } + public int INSUFFICIENT_SPACE_ON_RESOURCE() { + return 419; + } + /** - * @deprecated See WebDAV Draft Changes + * @deprecated See WebDAV + * Draft Changes */ @Deprecated - int METHOD_FAILURE() { return 420 } + public int METHOD_FAILURE() { + return 420; + } + /** - * @deprecated See WebDAV Draft Changes + * @deprecated See WebDAV + * Draft Changes */ @Deprecated - int DESTINATION_LOCKED() { return 421 } + public int DESTINATION_LOCKED() { + return 421; + } + /** * {@code 422 Unprocessable Entity}. - * @see WebDAV + * @see WebDAV */ - int UNPROCESSABLE_ENTITY() { return 422 } + public int UNPROCESSABLE_ENTITY() { + return 422; + } + /** * {@code 423 Locked}. - * @see WebDAV + * @see WebDAV */ - int LOCKED() { return 423 } + public int LOCKED() { + return 423; + } + /** * {@code 424 Failed Dependency}. - * @see WebDAV + * @see WebDAV */ - int FAILED_DEPENDENCY() { return 424 } + public int FAILED_DEPENDENCY() { + return 424; + } + /** * {@code 426 Upgrade Required}. - * @see Upgrading to TLS Within HTTP/1.1 + * @see Upgrading to TLS + * Within HTTP/1.1 */ - int UPGRADE_REQUIRED() { return 426 } + public int UPGRADE_REQUIRED() { + return 426; + } + /** * {@code 428 Precondition Required}. - * @see Additional HTTP Status Codes + * @see Additional HTTP + * Status Codes */ - int PRECONDITION_REQUIRED() { return 428 } + public int PRECONDITION_REQUIRED() { + return 428; + } + /** * {@code 429 Too Many Requests}. - * @see Additional HTTP Status Codes + * @see Additional HTTP + * Status Codes */ - int TOO_MANY_REQUESTS() { return 429 } + public int TOO_MANY_REQUESTS() { + return 429; + } + /** * {@code 431 Request Header Fields Too Large}. - * @see Additional HTTP Status Codes + * @see Additional HTTP + * Status Codes */ - int REQUEST_HEADER_FIELDS_TOO_LARGE() { return 431 } + public int REQUEST_HEADER_FIELDS_TOO_LARGE() { + return 431; + } + /** * {@code 451 Unavailable For Legal Reasons}. - * @see - * An HTTP Status Code to Report Legal Obstacles + * @see An HTTP Status Code to Report Legal Obstacles * @since 4.3 */ - int UNAVAILABLE_FOR_LEGAL_REASONS() { return 451 } - - // --- 5xx Server Error --- + public int UNAVAILABLE_FOR_LEGAL_REASONS() { + return 451; + } /** * {@code 500 Internal Server Error}. - * @see HTTP/1.1: Semantics and Content, section 6.6.1 + * @see HTTP/1.1: + * Semantics and Content, section 6.6.1 */ - int INTERNAL_SERVER_ERROR() { return 500 } + public int INTERNAL_SERVER_ERROR() { + return 500; + } + /** * {@code 501 Not Implemented}. - * @see HTTP/1.1: Semantics and Content, section 6.6.2 + * @see HTTP/1.1: + * Semantics and Content, section 6.6.2 */ - int NOT_IMPLEMENTED() { return 501 } + public int NOT_IMPLEMENTED() { + return 501; + } + /** * {@code 502 Bad Gateway}. - * @see HTTP/1.1: Semantics and Content, section 6.6.3 + * @see HTTP/1.1: + * Semantics and Content, section 6.6.3 */ - int BAD_GATEWAY() { return 502 } + public int BAD_GATEWAY() { + return 502; + } + /** * {@code 503 Service Unavailable}. - * @see HTTP/1.1: Semantics and Content, section 6.6.4 + * @see HTTP/1.1: + * Semantics and Content, section 6.6.4 */ - int SERVICE_UNAVAILABLE() { return 503 } + public int SERVICE_UNAVAILABLE() { + return 503; + } + /** * {@code 504 Gateway Timeout}. - * @see HTTP/1.1: Semantics and Content, section 6.6.5 + * @see HTTP/1.1: + * Semantics and Content, section 6.6.5 */ - int GATEWAY_TIMEOUT() { return 504 } + public int GATEWAY_TIMEOUT() { + return 504; + } + /** * {@code 505 HTTP Version Not Supported}. - * @see HTTP/1.1: Semantics and Content, section 6.6.6 + * @see HTTP/1.1: + * Semantics and Content, section 6.6.6 */ - int HTTP_VERSION_NOT_SUPPORTED() { return 505 } + public int HTTP_VERSION_NOT_SUPPORTED() { + return 505; + } + /** * {@code 506 Variant Also Negotiates} - * @see Transparent Content Negotiation + * @see Transparent Content + * Negotiation */ - int VARIANT_ALSO_NEGOTIATES() { return 506 } + public int VARIANT_ALSO_NEGOTIATES() { + return 506; + } + /** * {@code 507 Insufficient Storage} - * @see WebDAV + * @see WebDAV */ - int INSUFFICIENT_STORAGE() { return 507 } + public int INSUFFICIENT_STORAGE() { + return 507; + } + /** * {@code 508 Loop Detected} - * @see WebDAV Binding Extensions + * @see WebDAV Binding + * Extensions */ - int LOOP_DETECTED() { return 508 } + public int LOOP_DETECTED() { + return 508; + } + /** * {@code 509 Bandwidth Limit Exceeded} */ - int BANDWIDTH_LIMIT_EXCEEDED() { return 509 } + public int BANDWIDTH_LIMIT_EXCEEDED() { + return 509; + } + /** * {@code 510 Not Extended} - * @see HTTP Extension Framework + * @see HTTP Extension + * Framework */ - int NOT_EXTENDED() { return 510 } + public int NOT_EXTENDED() { + return 510; + } + /** * {@code 511 Network Authentication Required}. - * @see Additional HTTP Status Codes + * @see Additional HTTP + * Status Codes */ - int NETWORK_AUTHENTICATION_REQUIRED() { return 511 } + public int NETWORK_AUTHENTICATION_REQUIRED() { + return 511; + } + } diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Input.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Input.java new file mode 100644 index 0000000000..99235a8d44 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Input.java @@ -0,0 +1,405 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.Objects; +import java.util.function.Consumer; +import java.util.regex.Pattern; + +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Represents an input for messaging. The input can be a message or some action inside the + * application. + * + * @author Marcin Grzejszczak + * @author Tim Ysewyn + * @since 1.0.0 + */ +public class Input extends Common implements RegexCreatingProperty { + + private static final Log log = LogFactory.getLog(Input.class); + + private ClientPatternValueDslProperty property = new ClientPatternValueDslProperty(); + + private DslProperty messageFrom; + + private ExecutionProperty triggeredBy; + + private Headers messageHeaders = new Headers(); + + private BodyType messageBody; + + private ExecutionProperty assertThat; + + private BodyMatchers bodyMatchers; + + public Input() { + } + + public Input(Input input) { + this.messageFrom = input.getMessageFrom(); + this.messageHeaders = input.getMessageHeaders(); + this.messageBody = input.getMessageBody(); + } + + /** + * Name of a destination from which message would come to trigger action in the + * system. + * @param messageFrom message destination + */ + public void messageFrom(String messageFrom) { + this.messageFrom = new DslProperty<>(messageFrom); + } + + /** + * Name of a destination from which message would come to trigger action in the + * system. + * @param messageFrom message destination + */ + public void messageFrom(DslProperty messageFrom) { + this.messageFrom = messageFrom; + } + + /** + * Function that needs to be executed to trigger action in the system. + * @param triggeredBy method name that triggers the message + */ + public void triggeredBy(String triggeredBy) { + this.triggeredBy = new ExecutionProperty(triggeredBy); + } + + public BodyType messageBody(Object bodyAsValue) { + this.messageBody = new BodyType(bodyAsValue); + return this.messageBody; + } + + public DslProperty value(ClientDslProperty client) { + Object dynamicValue = client.getClientValue(); + Object concreteValue = client.getServerValue(); + if (dynamicValue instanceof RegexProperty) { + return ((RegexProperty) dynamicValue).dynamicClientConcreteProducer(); + } + return new DslProperty(dynamicValue, concreteValue); + } + + public DslProperty value(RegexProperty prop) { + return value(client(prop)); + } + + public DslProperty $(RegexProperty prop) { + return value(client(prop)); + } + + public DslProperty $(ClientDslProperty client) { + return value(client); + } + + @Override + public RegexProperty regexProperty(Object object) { + return new RegexProperty(object).dynamicClientConcreteProducer(); + } + + public void assertThat(String assertThat) { + this.assertThat = new ExecutionProperty(assertThat); + } + + public ClientPatternValueDslProperty getProperty() { + return property; + } + + public void setProperty(ClientPatternValueDslProperty property) { + this.property = property; + } + + public DslProperty getMessageFrom() { + return messageFrom; + } + + public void setMessageFrom(DslProperty messageFrom) { + this.messageFrom = messageFrom; + } + + public ExecutionProperty getTriggeredBy() { + return triggeredBy; + } + + public void setTriggeredBy(ExecutionProperty triggeredBy) { + this.triggeredBy = triggeredBy; + } + + public Headers getMessageHeaders() { + return messageHeaders; + } + + public void setMessageHeaders(Headers messageHeaders) { + this.messageHeaders = messageHeaders; + } + + public BodyType getMessageBody() { + return messageBody; + } + + public void setMessageBody(BodyType messageBody) { + this.messageBody = messageBody; + } + + public ExecutionProperty getAssertThat() { + return assertThat; + } + + public void setAssertThat(ExecutionProperty assertThat) { + this.assertThat = assertThat; + } + + public BodyMatchers getBodyMatchers() { + return bodyMatchers; + } + + public void setBodyMatchers(BodyMatchers bodyMatchers) { + this.bodyMatchers = bodyMatchers; + } + + @Override + public ClientDslProperty anyAlphaUnicode() { + return property.anyAlphaUnicode(); + } + + @Override + public ClientDslProperty anyAlphaNumeric() { + return property.anyAlphaNumeric(); + } + + @Override + public ClientDslProperty anyNumber() { + return property.anyNumber(); + } + + @Override + public ClientDslProperty anyInteger() { + return property.anyInteger(); + } + + @Override + public ClientDslProperty anyPositiveInt() { + return property.anyPositiveInt(); + } + + @Override + public ClientDslProperty anyDouble() { + return property.anyDouble(); + } + + @Override + public ClientDslProperty anyHex() { + return property.anyHex(); + } + + @Override + public ClientDslProperty aBoolean() { + return property.aBoolean(); + } + + @Override + public ClientDslProperty anyIpAddress() { + return property.anyIpAddress(); + } + + @Override + public ClientDslProperty anyHostname() { + return property.anyHostname(); + } + + @Override + public ClientDslProperty anyEmail() { + return property.anyEmail(); + } + + @Override + public ClientDslProperty anyUrl() { + return property.anyUrl(); + } + + @Override + public ClientDslProperty anyHttpsUrl() { + return property.anyHttpsUrl(); + } + + @Override + public ClientDslProperty anyUuid() { + return property.anyUuid(); + } + + @Override + public ClientDslProperty anyDate() { + return property.anyDate(); + } + + @Override + public ClientDslProperty anyDateTime() { + return property.anyDateTime(); + } + + @Override + public ClientDslProperty anyTime() { + return property.anyTime(); + } + + @Override + public ClientDslProperty anyIso8601WithOffset() { + return property.anyIso8601WithOffset(); + } + + @Override + public ClientDslProperty anyNonBlankString() { + return property.anyNonBlankString(); + } + + @Override + public ClientDslProperty anyNonEmptyString() { + return property.anyNonEmptyString(); + } + + @Override + public ClientDslProperty anyOf(String... values) { + return property.anyOf(values); + } + + /** + * The message headers part of the contract. + * @param consumer function to manipulate the message headers + */ + public void messageHeaders(Consumer consumer) { + this.messageHeaders = new Headers(); + consumer.accept(this.messageHeaders); + } + + /** + * The stub matchers part of the contract. + * @param consumer function to manipulate the body headers + * @deprecated Deprecated in favor of bodyMatchers to support other future + * bodyMatchers too + */ + @Deprecated + public void stubMatchers(Consumer consumer) { + log.warn("stubMatchers method is deprecated. Please use bodyMatchers instead"); + bodyMatchers(consumer); + } + + /** + * The stub matchers part of the contract. + * @param consumer function to manipulate the message headers + * @deprecated Deprecated in favor of bodyMatchers to support other future + * bodyMatchers too + */ + public void bodyMatchers(Consumer consumer) { + this.bodyMatchers = new BodyMatchers(); + consumer.accept(this.bodyMatchers); + } + + /** + * The message headers part of the contract. + * @param consumer function to manipulate the message headers + */ + public void messageHeaders(@DelegatesTo(Headers.class) Closure consumer) { + this.messageHeaders = new Headers(); + consumer.setDelegate(this.messageHeaders); + consumer.call(); + } + + /** + * The stub matchers part of the contract. + * @param consumer function to manipulate the body headers + * @deprecated Deprecated in favor of bodyMatchers to support other future + * bodyMatchers too + */ + @Deprecated + public void stubMatchers(@DelegatesTo(BodyMatchers.class) Closure consumer) { + log.warn("stubMatchers method is deprecated. Please use bodyMatchers instead"); + bodyMatchers(consumer); + } + + /** + * The stub matchers part of the contract. + * @param consumer function to manipulate the message headers + * @deprecated Deprecated in favor of bodyMatchers to support other future + * bodyMatchers too + */ + public void bodyMatchers(@DelegatesTo(BodyMatchers.class) Closure consumer) { + this.bodyMatchers = new BodyMatchers(); + consumer.setDelegate(this.bodyMatchers); + consumer.call(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Input input = (Input) o; + return Objects.equals(messageFrom, input.messageFrom) + && Objects.equals(triggeredBy, input.triggeredBy) + && Objects.equals(messageHeaders, input.messageHeaders) + && Objects.equals(messageBody, input.messageBody) + && Objects.equals(assertThat, input.assertThat) + && Objects.equals(bodyMatchers, input.bodyMatchers); + } + + @Override + public int hashCode() { + return Objects.hash(messageFrom, triggeredBy, messageHeaders, messageBody, + assertThat, bodyMatchers); + } + + @Override + public String toString() { + return "Input{\n\tmessageFrom=" + messageFrom + ", \n\ttriggeredBy=" + triggeredBy + + ", \n\tmessageHeaders=" + messageHeaders + ", \n\tmessageBody=" + + messageBody + ", \n\tassertThat=" + assertThat + ", \n\tbodyMatchers=" + + bodyMatchers + "} \n\t" + super.toString(); + } + + public static class BodyType extends DslProperty { + + public BodyType(Object clientValue, Object serverValue) { + super(clientValue, serverValue); + } + + public BodyType(Object singleValue) { + super(singleValue); + } + + } + + private class ClientPatternValueDslProperty + extends PatternValueDslProperty { + + @Override + protected ClientDslProperty createProperty(Pattern pattern, + Object generatedValue) { + return new ClientDslProperty(pattern, generatedValue); + } + + } + +} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ClientDslProperty.groovy b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/JSONCompareMode.java similarity index 57% rename from spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ClientDslProperty.groovy rename to specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/JSONCompareMode.java index 83d69781f4..c939664960 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ClientDslProperty.groovy +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/JSONCompareMode.java @@ -14,27 +14,30 @@ * limitations under the License. */ -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString +package org.springframework.cloud.contract.spec.internal; /** - * Represents a client side {@link DslProperty} + * Represents JSON comparison modes. * * @since 1.0.0 */ -@CompileStatic -@EqualsAndHashCode(callSuper = true) -@ToString(includeSuper = true) -class ClientDslProperty extends DslProperty { +public enum JSONCompareMode { - ClientDslProperty(Object singleValue) { - super(singleValue) - } + /** + * Strict checking. Not extensible, and strict array ordering. + */ + STRICT, + /** + * Lenient checking. Extensible, and non-strict array ordering. + */ + LENIENT, + /** + * Non-extensible checking. Not extensible, and non-strict array ordering. + */ + NON_EXTENSIBLE, + /** + * Strict order checking. Extensible, and strict array ordering. + */ + STRICT_ORDER; - ClientDslProperty(Object client, Object server) { - super(client, server) - } } diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MatchingStrategy.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MatchingStrategy.java new file mode 100644 index 0000000000..31a72e9497 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MatchingStrategy.java @@ -0,0 +1,149 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.Objects; + +/** + * Represents a matching strategy for a JSON. + * + * @since 1.0.0 + */ +public class MatchingStrategy extends DslProperty { + + private Type type; + + private JSONCompareMode jsonCompareMode; + + public MatchingStrategy(Object value, Type type) { + this(value, type, null); + } + + public MatchingStrategy(Object value, Type type, JSONCompareMode jsonCompareMode) { + super(value); + this.type = type; + this.jsonCompareMode = jsonCompareMode; + } + + public MatchingStrategy(DslProperty value, Type type) { + this(value, type, null); + } + + public MatchingStrategy(DslProperty value, Type type, + JSONCompareMode jsonCompareMode) { + super(value.getClientValue(), value.getServerValue()); + this.type = type; + this.jsonCompareMode = jsonCompareMode; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public JSONCompareMode getJsonCompareMode() { + return jsonCompareMode; + } + + public void setJsonCompareMode(JSONCompareMode jsonCompareMode) { + this.jsonCompareMode = jsonCompareMode; + } + + @Override + public String toString() { + return "MatchingStrategy{" + "type=" + type + ", jsonCompareMode=" + + jsonCompareMode + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + MatchingStrategy that = (MatchingStrategy) o; + return type == that.type && jsonCompareMode == that.jsonCompareMode; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), type, jsonCompareMode); + } + + public enum Type { + + /** + * Equality check. + */ + EQUAL_TO("equalTo"), + + /** + * Contains check. + */ + CONTAINS("containing"), + + /** + * Matching check. + */ + MATCHING("matching"), + + /** + * Not matching check. + */ + NOT_MATCHING("notMatching"), + + /** + * Equal to JSON check. + */ + EQUAL_TO_JSON("equalToJson"), + + /** + * Equal to XML check. + */ + EQUAL_TO_XML("equalToXml"), + + /** + * Absence check. + */ + ABSENT("absent"), + + /** + * Binary equality check. + */ + BINARY_EQUAL_TO("binaryEqualTo"); + + private final String name; + + Type(String name) { + this.name = name; + } + + public final String getName() { + return name; + } + + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MatchingType.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MatchingType.java new file mode 100644 index 0000000000..c5ef782bc1 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MatchingType.java @@ -0,0 +1,69 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +/** + * Represents the type of matching the should be done against the body of the request or + * response. + * + * @author Marcin Grzejszczak + * @author Tim Ysewyn + * @author Olga Maciaszek-Sharma + * @since 1.0.3 + */ +public enum MatchingType { + + /** + * Verification by equality. + */ + EQUALITY, + /** + * Verification by type - is the type received in a body for the given path is of the + * same type. If it's a collection you can verify number of occurrences. + */ + TYPE, + /** + * Special version of regex for date check. + */ + DATE, + /** + * Special version of regex for time check. + */ + TIME, + /** + * Special version of regex for timestamp check. + */ + TIMESTAMP, + /** + * Verification if the value for the given path matches the provided regex. + */ + REGEX, + /** + * The user can provide custom command to execute. + */ + COMMAND, + /** + * Verification if the value for the given path is null. + */ + NULL; + + public static boolean regexRelated(MatchingType type) { + return !type.equals(EQUALITY) && !type.equals(TYPE) && !type.equals(COMMAND) + && !type.equals(NULL); + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MatchingTypeValue.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MatchingTypeValue.java new file mode 100644 index 0000000000..2aada387d5 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MatchingTypeValue.java @@ -0,0 +1,127 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.Objects; + +/** + * Matching type with corresponding values. + */ +public class MatchingTypeValue { + + private MatchingType type; + + /** + * Value to check. + */ + private Object value; + + /** + * Min occurrence when matching by type. + */ + private Integer minTypeOccurrence; + + /** + * Max occurrence when matching by type. + */ + private Integer maxTypeOccurrence; + + MatchingTypeValue() { + } + + MatchingTypeValue(MatchingType type) { + this.type = type; + } + + MatchingTypeValue(MatchingType type, Object value) { + this.type = type; + this.value = value; + } + + MatchingTypeValue(MatchingType type, Object value, Integer minTypeOccurrence) { + this.type = type; + this.value = value; + this.minTypeOccurrence = minTypeOccurrence; + } + + MatchingTypeValue(MatchingType type, Object value, Integer minTypeOccurrence, + Integer maxTypeOccurrence) { + this.type = type; + this.value = value; + this.minTypeOccurrence = minTypeOccurrence; + this.maxTypeOccurrence = maxTypeOccurrence; + } + + public MatchingType getType() { + return type; + } + + public void setType(MatchingType type) { + this.type = type; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + public Integer getMinTypeOccurrence() { + return minTypeOccurrence; + } + + public void setMinTypeOccurrence(Integer minTypeOccurrence) { + this.minTypeOccurrence = minTypeOccurrence; + } + + public Integer getMaxTypeOccurrence() { + return maxTypeOccurrence; + } + + public void setMaxTypeOccurrence(Integer maxTypeOccurrence) { + this.maxTypeOccurrence = maxTypeOccurrence; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MatchingTypeValue value1 = (MatchingTypeValue) o; + return type == value1.type && Objects.equals(value, value1.value) + && Objects.equals(minTypeOccurrence, value1.minTypeOccurrence) + && Objects.equals(maxTypeOccurrence, value1.maxTypeOccurrence); + } + + @Override + public int hashCode() { + return Objects.hash(type, value, minTypeOccurrence, maxTypeOccurrence); + } + + @Override + public String toString() { + return "MatchingTypeValue{" + "type=" + type + ", value=" + value + + ", minTypeOccurrence=" + minTypeOccurrence + ", maxTypeOccurrence=" + + maxTypeOccurrence + '}'; + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MatchingTypeValueHolder.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MatchingTypeValueHolder.java new file mode 100644 index 0000000000..8de09aca8b --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MatchingTypeValueHolder.java @@ -0,0 +1,39 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +public class MatchingTypeValueHolder { + + MatchingTypeValue matchingTypeValue = new MatchingTypeValue(MatchingType.TYPE); + + public MatchingTypeValue minOccurrence(int number) { + this.matchingTypeValue.setMinTypeOccurrence(number); + return this.matchingTypeValue; + } + + public MatchingTypeValue maxOccurrence(int number) { + this.matchingTypeValue.setMaxTypeOccurrence(number); + return this.matchingTypeValue; + } + + public MatchingTypeValue occurrence(int number) { + this.matchingTypeValue.setMinTypeOccurrence(number); + this.matchingTypeValue.setMaxTypeOccurrence(number); + return this.matchingTypeValue; + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MediaTypes.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MediaTypes.java new file mode 100644 index 0000000000..75443daba7 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MediaTypes.java @@ -0,0 +1,95 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +/** + * Contains most commonly used media types. + * + * @author Marcin Grzejszczak + * @since 1.0.2 + */ +public class MediaTypes { + + public String allValue() { + return "*/*"; + } + + public String applicationAtomXml() { + return "application/atom+xml"; + } + + public String applicationFormUrlencoded() { + return "application/x-www-form-urlencoded"; + } + + public String applicationJson() { + return "application/json"; + } + + public String applicationJsonUtf8() { + return applicationJson() + ";charset=UTF-8"; + } + + public String applicationOctetStream() { + return "application/octet-stream"; + } + + public String applicationPdf() { + return "application/pdf"; + } + + public String applicationXhtmlXml() { + return "application/xhtml+xml"; + } + + public String applicationXml() { + return "application/xml"; + } + + public String imageGif() { + return "image/gif"; + } + + public String imageJpeg() { + return "image/jpeg"; + } + + public String imagePng() { + return "image/png"; + } + + public String multipartFormData() { + return "multipart/form-data"; + } + + public String textHtml() { + return "text/html"; + } + + public String textMarkdown() { + return "text/markdown"; + } + + public String textPlain() { + return "text/plain"; + } + + public String textXml() { + return "text/xml"; + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MessagingHeaders.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MessagingHeaders.java new file mode 100644 index 0000000000..c0d9fe9456 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MessagingHeaders.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +/** + * Contains most commonly used messaging headers. + * + * @author Marcin Grzejszczak + * @since 1.1.2 + */ +public class MessagingHeaders { + + /** + * The Content Type of a message. + * @return messaging content type + */ + public String messagingContentType() { + return "contentType"; + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Multipart.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Multipart.java new file mode 100644 index 0000000000..ef77eb6efe --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Multipart.java @@ -0,0 +1,67 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class Multipart extends DslProperty { + + public Multipart(Map multipart) { + super(extractValue(multipart, ContractUtils.CLIENT_VALUE), + extractValue(multipart, ContractUtils.SERVER_VALUE)); + } + + public Multipart(List multipartAsList) { + super(multipartAsList.stream().map(DslProperty::getClientValue) + .collect(Collectors.toList()), + multipartAsList.stream().map(DslProperty::getServerValue) + .collect(Collectors.toList())); + } + + public Multipart(Object value) { + super(ContractUtils.CLIENT_VALUE.apply(value), + ContractUtils.SERVER_VALUE.apply(value)); + } + + public Multipart(DslProperty multipartAsValue) { + super(multipartAsValue.getClientValue(), multipartAsValue.getServerValue()); + } + + public Multipart(MatchingStrategy matchingStrategy) { + super(matchingStrategy); + } + + public static Multipart build(Object value) { + if (value instanceof MatchingStrategy) { + return new Multipart((MatchingStrategy) value); + } + return new Multipart(value); + } + + private static Map extractValue(Map multipart, + final Function valueProvider) { + final Map map = new LinkedHashMap(); + multipart.forEach( + (s, dslProperty) -> map.put(s, valueProvider.apply(dslProperty))); + return map; + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/NamedProperty.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/NamedProperty.java new file mode 100644 index 0000000000..1b5dd841e4 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/NamedProperty.java @@ -0,0 +1,127 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.Map; +import java.util.Objects; + +/** + * Represents a property that has name and content. Used together with multipart requests. + * + * @since 1.0.0 + */ +public class NamedProperty { + + private static final String NAME = "name"; + + private static final String CONTENT = "content"; + + private static final String CONTENT_TYPE = "contentType"; + + private DslProperty name; + + private DslProperty value; + + private DslProperty contentType; + + public NamedProperty(DslProperty name, DslProperty value) { + this.name = name; + this.value = value; + this.contentType = null; + } + + public NamedProperty(DslProperty name, DslProperty value, DslProperty contentType) { + this.name = name; + this.value = value; + this.contentType = contentType; + } + + public NamedProperty(Map namedMap) { + this(asDslProperty(value(namedMap, NAME)), + asDslProperty(value(namedMap, CONTENT)), + asDslProperty(value(namedMap, CONTENT_TYPE))); + } + + private static DslProperty value(Map namedMap, String key) { + if (namedMap == null) { + return null; + } + + return namedMap.get(key); + } + + public static DslProperty asDslProperty(Object o) { + if (o == null) { + return null; + } + + if (o instanceof DslProperty) { + return ((DslProperty) (o)); + } + + return new DslProperty(o); + } + + public DslProperty getName() { + return name; + } + + public void setName(DslProperty name) { + this.name = name; + } + + public DslProperty getValue() { + return value; + } + + public void setValue(DslProperty value) { + this.value = value; + } + + public DslProperty getContentType() { + return contentType; + } + + public void setContentType(DslProperty contentType) { + this.contentType = contentType; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NamedProperty that = (NamedProperty) o; + return Objects.equals(name, that.name) && Objects.equals(value, that.value) + && Objects.equals(contentType, that.contentType); + } + + @Override + public int hashCode() { + return Objects.hash(name, value, contentType); + } + + @Override + public String toString() { + return "NamedProperty{" + "name=" + name + ", value=" + value + ", contentType=" + + contentType + '}'; + } + +} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/NotToEscapePattern.groovy b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/NotToEscapePattern.java similarity index 64% rename from spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/NotToEscapePattern.groovy rename to specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/NotToEscapePattern.java index 01103b5276..518e279919 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/NotToEscapePattern.groovy +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/NotToEscapePattern.java @@ -14,24 +14,24 @@ * limitations under the License. */ -package org.springframework.cloud.contract.spec.internal - -import java.util.regex.Pattern +package org.springframework.cloud.contract.spec.internal; +import java.util.regex.Pattern; /** - * Special case of Patterns that we don't want to escape + * Special case of Patterns that we don't want to escape. * * @author Marcin Grzejszczak * @since 1.0.3 */ -class NotToEscapePattern extends DslProperty { +public class NotToEscapePattern extends DslProperty { - NotToEscapePattern(Pattern clientValue, Pattern serverValue) { - super(clientValue, serverValue) + public NotToEscapePattern(Pattern clientValue, Pattern serverValue) { + super(clientValue, serverValue); } - NotToEscapePattern(Pattern singleValue) { - super(singleValue) + public NotToEscapePattern(Pattern singleValue) { + super(singleValue); } + } diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/OptionalProperty.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/OptionalProperty.java new file mode 100644 index 0000000000..6e19b1482c --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/OptionalProperty.java @@ -0,0 +1,69 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.io.Serializable; +import java.util.regex.Pattern; + +import repackaged.nl.flotsam.xeger.Xeger; + +/** + * Represents a property that may or may not be there. + * + * @since 1.0.0 + */ +public class OptionalProperty implements Serializable, CanBeDynamic { + + private final Object value; + + public OptionalProperty(Object value) { + this.value = value; + } + + /** + * String version of a regular expression that wraps the provided value in an optional + * function. + * @return pattern wrapped in optional regular expresion + */ + public String optionalPattern() { + return "(" + value() + ")?"; + } + + public String value() { + return this.value instanceof RegexProperty + ? ((RegexProperty) this.value).pattern() : this.value.toString(); + } + + protected Pattern optionalPatternValue() { + return Pattern.compile(optionalPattern()); + } + + @Override + public String toString() { + return optionalPattern(); + } + + @Override + public Object generateConcreteValue() { + return new Xeger(optionalPattern()).generate(); + } + + public final Object getValue() { + return value; + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/OutputMessage.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/OutputMessage.java new file mode 100644 index 0000000000..dad2cde38a --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/OutputMessage.java @@ -0,0 +1,400 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.Objects; +import java.util.function.Consumer; +import java.util.regex.Pattern; + +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Represents an output for messaging. Used for verifying the body and headers that are + * sent. + * + * @author Marcin Grzejszczak + * @author Tim Ysewyn + * @since 1.0.0 + */ +public class OutputMessage extends Common + implements RegexCreatingProperty { + + private static final Log log = LogFactory.getLog(OutputMessage.class); + + private DslProperty sentTo; + + private Headers headers; + + private DslProperty body; + + private ExecutionProperty assertThat; + + private ResponseBodyMatchers bodyMatchers; + + private ServerPatternValueDslProperty property = new ServerPatternValueDslProperty(); + + public OutputMessage() { + } + + public OutputMessage(OutputMessage outputMessage) { + this.sentTo = outputMessage.getSentTo(); + this.headers = outputMessage.getHeaders(); + this.body = outputMessage.getBody(); + } + + public void sentTo(String sentTo) { + this.sentTo = new DslProperty(sentTo); + } + + public void sentTo(DslProperty sentTo) { + this.sentTo = sentTo; + } + + public void body(Object bodyAsValue) { + this.body = new DslProperty(bodyAsValue); + } + + public void body(DslProperty bodyAsValue) { + this.body = bodyAsValue; + } + + public void assertThat(String assertThat) { + this.assertThat = new ExecutionProperty(assertThat); + } + + /** + * @deprecated - use the server dsl property + * @param clientDslProperty client property + * @return dsl property + */ + @Deprecated + public DslProperty value(ClientDslProperty clientDslProperty) { + return value(new ServerDslProperty(clientDslProperty.getServerValue(), + clientDslProperty.getClientValue())); + } + + public DslProperty value(ServerDslProperty serverDslProperty) { + Object concreteValue = serverDslProperty.getClientValue(); + Object dynamicValue = serverDslProperty.getServerValue(); + // for the output messages ran via stub runner, + // entries have to have fixed values + if (dynamicValue instanceof RegexProperty) { + return ((RegexProperty) dynamicValue).concreteClientEscapedDynamicProducer(); + } + + return new DslProperty(concreteValue, dynamicValue); + } + + /** + * @deprecated - use the server dsl property + * @param client client value + * @return dsl proprty + */ + @Deprecated + public DslProperty $(ClientDslProperty client) { + return value(client); + } + + public DslProperty $(ServerDslProperty property) { + return value(property); + } + + public DslProperty $(Pattern pattern) { + return value(new RegexProperty(pattern)); + } + + public DslProperty $(RegexProperty pattern) { + return value(pattern); + } + + public DslProperty value(RegexProperty pattern) { + return value(producer(pattern)); + } + + public DslProperty $(OptionalProperty property) { + return value(producer(property.optionalPatternValue())); + } + + @Override + public RegexProperty regexProperty(Object object) { + return new RegexProperty(object).concreteClientDynamicProducer(); + } + + public ServerPatternValueDslProperty getProperty() { + return property; + } + + public void setProperty(ServerPatternValueDslProperty property) { + this.property = property; + } + + public DslProperty getSentTo() { + return sentTo; + } + + public void setSentTo(DslProperty sentTo) { + this.sentTo = sentTo; + } + + public Headers getHeaders() { + return headers; + } + + public void setHeaders(Headers headers) { + this.headers = headers; + } + + public DslProperty getBody() { + return body; + } + + public void setBody(DslProperty body) { + this.body = body; + } + + public ExecutionProperty getAssertThat() { + return assertThat; + } + + public void setAssertThat(ExecutionProperty assertThat) { + this.assertThat = assertThat; + } + + public ResponseBodyMatchers getBodyMatchers() { + return bodyMatchers; + } + + public void setBodyMatchers(ResponseBodyMatchers bodyMatchers) { + this.bodyMatchers = bodyMatchers; + } + + @Override + public ServerDslProperty anyAlphaUnicode() { + return property.anyAlphaUnicode(); + } + + @Override + public ServerDslProperty anyAlphaNumeric() { + return property.anyAlphaNumeric(); + } + + @Override + public ServerDslProperty anyNumber() { + return property.anyNumber(); + } + + @Override + public ServerDslProperty anyInteger() { + return property.anyInteger(); + } + + @Override + public ServerDslProperty anyPositiveInt() { + return property.anyPositiveInt(); + } + + @Override + public ServerDslProperty anyDouble() { + return property.anyDouble(); + } + + @Override + public ServerDslProperty anyHex() { + return property.anyHex(); + } + + @Override + public ServerDslProperty aBoolean() { + return property.aBoolean(); + } + + @Override + public ServerDslProperty anyIpAddress() { + return property.anyIpAddress(); + } + + @Override + public ServerDslProperty anyHostname() { + return property.anyHostname(); + } + + @Override + public ServerDslProperty anyEmail() { + return property.anyEmail(); + } + + @Override + public ServerDslProperty anyUrl() { + return property.anyUrl(); + } + + @Override + public ServerDslProperty anyHttpsUrl() { + return property.anyHttpsUrl(); + } + + @Override + public ServerDslProperty anyUuid() { + return property.anyUuid(); + } + + @Override + public ServerDslProperty anyDate() { + return property.anyDate(); + } + + @Override + public ServerDslProperty anyDateTime() { + return property.anyDateTime(); + } + + @Override + public ServerDslProperty anyTime() { + return property.anyTime(); + } + + @Override + public ServerDslProperty anyIso8601WithOffset() { + return property.anyIso8601WithOffset(); + } + + @Override + public ServerDslProperty anyNonBlankString() { + return property.anyNonBlankString(); + } + + @Override + public ServerDslProperty anyNonEmptyString() { + return property.anyNonEmptyString(); + } + + @Override + public ServerDslProperty anyOf(String... values) { + return property.anyOf(values); + } + + /** + * The message headers part of the contract. + * @param consumer function to manipulate the message headers + */ + public void headers(Consumer consumer) { + this.headers = new Headers(); + consumer.accept(this.headers); + } + + /** + * The message headers part of the contract. + * @param consumer function to manipulate the message headers + * @deprecated Deprecated in favor of bodyMatchers to support other future + * bodyMatchers too + */ + @Deprecated + public void testMatchers(Consumer consumer) { + log.warn("testMatchers method is deprecated. Please use bodyMatchers instead"); + bodyMatchers(consumer); + } + + /** + * The stub matchers part of the contract. + * @param consumer function to manipulate the body matchers + * @deprecated Deprecated in favor of bodyMatchers to support other future + * bodyMatchers too + */ + @Deprecated + public void bodyMatchers(Consumer consumer) { + this.bodyMatchers = new ResponseBodyMatchers(); + consumer.accept(this.bodyMatchers); + } + + /** + * The message headers part of the contract. + * @param consumer function to manipulate the message headers + */ + public void headers(@DelegatesTo(Headers.class) Closure consumer) { + this.headers = new Headers(); + consumer.setDelegate(this.headers); + consumer.call(); + } + + /** + * The message headers part of the contract. + * @param consumer function to manipulate the message headers + * @deprecated Deprecated in favor of bodyMatchers to support other future + * bodyMatchers too + */ + @Deprecated + public void testMatchers(@DelegatesTo(ResponseBodyMatchers.class) Closure consumer) { + log.warn("testMatchers method is deprecated. Please use bodyMatchers instead"); + bodyMatchers(consumer); + } + + /** + * The stub matchers part of the contract. + * @param consumer function to manipulate the body matchers + * @deprecated Deprecated in favor of bodyMatchers to support other future + * bodyMatchers too + */ + @Deprecated + public void bodyMatchers(@DelegatesTo(ResponseBodyMatchers.class) Closure consumer) { + this.bodyMatchers = new ResponseBodyMatchers(); + consumer.setDelegate(this.bodyMatchers); + consumer.call(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + OutputMessage that = (OutputMessage) o; + return Objects.equals(sentTo, that.sentTo) + && Objects.equals(headers, that.headers) + && Objects.equals(body, that.body) + && Objects.equals(assertThat, that.assertThat) + && Objects.equals(bodyMatchers, that.bodyMatchers); + } + + @Override + public int hashCode() { + return Objects.hash(sentTo, headers, body, assertThat, bodyMatchers); + } + + @Override + public String toString() { + return "OutputMessage{" + "\n\tsentTo=" + sentTo + ", \n\theaders=" + headers + + ", \n\tbody=" + body + ", \n\tassertThat=" + assertThat + + ", \n\tbodyMatchers=" + bodyMatchers + "} \n\t" + super.toString(); + } + + private class ServerPatternValueDslProperty + extends PatternValueDslProperty { + + @Override + protected ServerDslProperty createProperty(Pattern pattern, + Object generatedValue) { + return new ServerDslProperty(pattern, generatedValue); + } + + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/PathBodyMatcher.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/PathBodyMatcher.java new file mode 100644 index 0000000000..0f880c5dbb --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/PathBodyMatcher.java @@ -0,0 +1,84 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.Objects; + +/** + * @author Marcin Grzejszczak + */ +public class PathBodyMatcher implements BodyMatcher { + + private String path; + + private MatchingTypeValue matchingTypeValue; + + PathBodyMatcher(String path, MatchingTypeValue matchingTypeValue) { + this.path = path; + this.matchingTypeValue = matchingTypeValue; + } + + @Override + public MatchingType matchingType() { + return this.matchingTypeValue.getType(); + } + + @Override + public String path() { + return this.path; + } + + @Override + public Object value() { + return this.matchingTypeValue.getValue(); + } + + @Override + public Integer minTypeOccurrence() { + return this.matchingTypeValue.getMinTypeOccurrence(); + } + + @Override + public Integer maxTypeOccurrence() { + return this.matchingTypeValue.getMaxTypeOccurrence(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PathBodyMatcher that = (PathBodyMatcher) o; + return Objects.equals(path, that.path) + && Objects.equals(matchingTypeValue, that.matchingTypeValue); + } + + @Override + public int hashCode() { + return Objects.hash(path, matchingTypeValue); + } + + @Override + public String toString() { + return "PathBodyMatcher{" + "path='" + path + '\'' + ", matchingTypeValue=" + + matchingTypeValue + '}'; + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/PatternValueDslProperty.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/PatternValueDslProperty.java new file mode 100644 index 0000000000..960fa274ab --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/PatternValueDslProperty.java @@ -0,0 +1,190 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.Random; +import java.util.UUID; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.RandomStringUtils; + +/** + * @author Marcin Grzejszczak + * @author Tim Ysewyn + */ +abstract class PatternValueDslProperty + implements RegexCreatingProperty { + + private final Random random = new Random(); + + protected T createAndValidateProperty(Pattern pattern, Object object) { + if (object != null) { + String generatedValue = object.toString(); + boolean matches = pattern.matcher(generatedValue).matches(); + if (!matches) { + throw new IllegalStateException("The generated value [" + generatedValue + + "] doesn\'t match the pattern [" + pattern.pattern() + "]"); + } + + return createProperty(pattern, object); + } + + return createProperty(pattern, object); + } + + protected T createAndValidateProperty(Pattern pattern) { + return createAndValidateProperty(pattern, null); + } + + /** + * Method to generate the PatternValue. The resulting implementation will create + * either a Client or a Server side impl. + * @param pattern - pattern for which the value will be generated or reused + * @param generatedValue - Nullable - potential generated value to be reused + * @return {@link DslProperty} wrapping a pattern and generated value + */ + protected abstract T createProperty(Pattern pattern, Object generatedValue); + + @Override + public T anyAlphaUnicode() { + return createAndValidateProperty(RegexPatterns.ONLY_ALPHA_UNICODE, + RandomStringGenerator.randomString(20)); + } + + @Override + public T anyAlphaNumeric() { + return createAndValidateProperty(RegexPatterns.ALPHA_NUMERIC, + RandomStringUtils.randomAlphanumeric(20)); + } + + @Override + public T anyNumber() { + return createAndValidateProperty(RegexPatterns.NUMBER, this.random.nextInt()); + } + + @Override + public T anyInteger() { + return createAndValidateProperty(RegexPatterns.INTEGER, this.random.nextInt()); + } + + @Override + public T anyPositiveInt() { + return createAndValidateProperty(RegexPatterns.POSITIVE_INT, + Math.abs(this.random.nextInt() + 1)); + } + + @Override + public T anyDouble() { + return createAndValidateProperty(RegexPatterns.DOUBLE, + this.random.nextInt(100) + this.random.nextDouble()); + } + + @Override + public T anyHex() { + return createAndValidateProperty(RegexPatterns.HEX, + RandomStringUtils.random(10, "0123456789abcdef")); + } + + @Override + public T aBoolean() { + return createAndValidateProperty(RegexPatterns.TRUE_OR_FALSE); + } + + @Override + public T anyIpAddress() { + return createAndValidateProperty(RegexPatterns.IP_ADDRESS, + "192.168.0." + this.random.nextInt(10)); + } + + @Override + public T anyHostname() { + return createAndValidateProperty(RegexPatterns.HOSTNAME_PATTERN, + "https://foo" + this.random.nextInt() + ".com"); + } + + @Override + public T anyEmail() { + return createAndValidateProperty(RegexPatterns.EMAIL, + "foo@bar" + this.random.nextInt() + ".com"); + } + + @Override + public T anyUrl() { + return createAndValidateProperty(RegexPatterns.URL, + "https://foo" + this.random.nextInt() + ".com"); + } + + @Override + public T anyHttpsUrl() { + return createAndValidateProperty(RegexPatterns.HTTPS_URL, + "https://baz" + this.random.nextInt() + ".com"); + } + + @Override + public T anyUuid() { + return createAndValidateProperty(RegexPatterns.UUID, + UUID.randomUUID().toString()); + } + + @Override + public T anyDate() { + int d = this.random.nextInt(8) + 1; + return createAndValidateProperty(RegexPatterns.ANY_DATE, "201" + String.valueOf(d) + + "-0" + String.valueOf(d) + "-1" + String.valueOf(d)); + } + + @Override + public T anyDateTime() { + final int d = this.random.nextInt(8) + 1; + return createAndValidateProperty(RegexPatterns.ANY_DATE_TIME, + "201" + String.valueOf(d) + "-0" + String.valueOf(d) + "-1" + + String.valueOf(d) + "T12:23:34"); + } + + @Override + public T anyTime() { + int d = this.random.nextInt(9); + return createAndValidateProperty(RegexPatterns.ANY_TIME, + "12:2" + String.valueOf(d) + ":3" + String.valueOf(d)); + } + + @Override + public T anyIso8601WithOffset() { + final int d = this.random.nextInt(8) + 1; + return createAndValidateProperty(RegexPatterns.ISO8601_WITH_OFFSET, + "201" + String.valueOf(d) + "-0" + String.valueOf(d) + "-1" + + String.valueOf(d) + "T12:23:34.123Z"); + } + + @Override + public T anyNonBlankString() { + return createAndValidateProperty(RegexPatterns.NON_BLANK, + RandomStringGenerator.randomString(20)); + } + + @Override + public T anyNonEmptyString() { + return createAndValidateProperty(RegexPatterns.NON_EMPTY, + RandomStringGenerator.randomString(20)); + } + + @Override + public T anyOf(String... values) { + return createAndValidateProperty(RegexPatterns.anyOf(values), values[0]); + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/QueryParameter.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/QueryParameter.java new file mode 100644 index 0000000000..2b73be7c6d --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/QueryParameter.java @@ -0,0 +1,99 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.Objects; + +import org.springframework.cloud.contract.spec.util.ValidateUtils; + +/** + * Represents a single HTTP query parameter. + * + * @since 1.0.0 + */ +public class QueryParameter extends DslProperty { + + private String name; + + public QueryParameter(String name, DslProperty dslProperty) { + super(dslProperty.getClientValue(), dslProperty.getServerValue()); + ValidateUtils.validateServerValueIsAvailable(dslProperty.getServerValue(), + "Query parameter \'" + name + "\'"); + this.name = name; + } + + public QueryParameter(String name, MatchingStrategy matchingStrategy) { + super(matchingStrategy); + ValidateUtils.validateServerValueIsAvailable(matchingStrategy, + "Query parameter \'" + name + "\'"); + this.name = name; + } + + public QueryParameter(String name, Object value) { + super(ContractUtils.CLIENT_VALUE.apply(value), + ContractUtils.SERVER_VALUE.apply(value)); + ValidateUtils.validateServerValueIsAvailable(value, + "Query parameter \'" + name + "\'"); + this.name = name; + } + + public static QueryParameter build(String key, Object value) { + if (value instanceof MatchingStrategy) { + return new QueryParameter(key, (MatchingStrategy) value); + } + else if (value instanceof RegexProperty) { + return new QueryParameter(key, + ((RegexProperty) value).dynamicClientEscapedConcreteProducer()); + } + return new QueryParameter(key, value); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + QueryParameter that = (QueryParameter) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), name); + } + + @Override + public String toString() { + return "QueryParameter{" + "name='" + name + '\'' + ", value=" + super.toString() + + '}'; + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/QueryParameters.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/QueryParameters.java new file mode 100644 index 0000000000..31bac82a6b --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/QueryParameters.java @@ -0,0 +1,74 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class QueryParameters { + + private List parameters = new LinkedList(); + + public void parameter(Map singleParameter) { + Iterator> iterator = singleParameter.entrySet() + .iterator(); + if (iterator.hasNext()) { + Map.Entry first = iterator.next(); + if (first != null) { + parameters.add(QueryParameter.build(first.getKey(), first.getValue())); + } + } + } + + public void parameter(String parameterName, Object parameterValue) { + parameters.add(QueryParameter.build(parameterName, parameterValue)); + } + + public List getParameters() { + return parameters; + } + + public void setParameters(List parameters) { + this.parameters = parameters; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + QueryParameters that = (QueryParameters) o; + return Objects.equals(parameters, that.parameters); + } + + @Override + public int hashCode() { + return Objects.hash(parameters); + } + + @Override + public String toString() { + return "QueryParameters{" + "\nparameters=" + parameters + '}'; + } + +} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/RandomStringGenerator.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RandomStringGenerator.java similarity index 100% rename from spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/RandomStringGenerator.java rename to specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RandomStringGenerator.java diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexCreatingProperty.groovy b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexCreatingProperty.java similarity index 55% rename from spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexCreatingProperty.groovy rename to specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexCreatingProperty.java index 791531cf72..ad098c697d 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexCreatingProperty.groovy +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexCreatingProperty.java @@ -14,60 +14,53 @@ * limitations under the License. */ -package org.springframework.cloud.contract.spec.internal - - -import groovy.transform.CompileStatic -import groovy.transform.PackageScope +package org.springframework.cloud.contract.spec.internal; /** * @author Marcin Grzejszczak */ -@PackageScope -@CompileStatic interface RegexCreatingProperty { - //tag::regex_creating_props[] - T anyAlphaUnicode() + T anyAlphaUnicode(); - T anyAlphaNumeric() + T anyAlphaNumeric(); - T anyNumber() + T anyNumber(); - T anyInteger() + T anyInteger(); - T anyPositiveInt() + T anyPositiveInt(); - T anyDouble() + T anyDouble(); - T anyHex() + T anyHex(); - T aBoolean() + T aBoolean(); - T anyIpAddress() + T anyIpAddress(); - T anyHostname() + T anyHostname(); - T anyEmail() + T anyEmail(); - T anyUrl() + T anyUrl(); - T anyHttpsUrl() + T anyHttpsUrl(); - T anyUuid() + T anyUuid(); - T anyDate() + T anyDate(); - T anyDateTime() + T anyDateTime(); - T anyTime() + T anyTime(); - T anyIso8601WithOffset() + T anyIso8601WithOffset(); - T anyNonBlankString() + T anyNonBlankString(); - T anyNonEmptyString() + T anyNonEmptyString(); + + T anyOf(String... values); - T anyOf(String... values) - //end::regex_creating_props[] } diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexMatchingTypeValue.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexMatchingTypeValue.java new file mode 100644 index 0000000000..f70207422a --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexMatchingTypeValue.java @@ -0,0 +1,72 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +/** + * Matching type with corresponding values. + */ +public class RegexMatchingTypeValue extends MatchingTypeValue { + + RegexMatchingTypeValue(MatchingType type, Object value, Integer minTypeOccurrence, + Integer maxTypeOccurrence) { + super(type, value, minTypeOccurrence, maxTypeOccurrence); + } + + RegexMatchingTypeValue(MatchingType type, Object value) { + super(type, value); + } + + public RegexMatchingTypeValue asInteger() { + return typed(Integer.class); + } + + private RegexMatchingTypeValue typed(Class clazz) { + if (!(this.getValue() instanceof RegexProperty)) { + throw new IllegalStateException("Value has to be a regex"); + } + RegexProperty regexProperty = (RegexProperty) this.getValue(); + return new RegexMatchingTypeValue(this.getType(), + new RegexProperty(regexProperty.getClientValue(), + regexProperty.getServerValue(), clazz), + this.getMinTypeOccurrence(), this.getMaxTypeOccurrence()); + } + + public RegexMatchingTypeValue asDouble() { + return typed(Double.class); + } + + public RegexMatchingTypeValue asFloat() { + return typed(Float.class); + } + + public RegexMatchingTypeValue asLong() { + return typed(Long.class); + } + + public RegexMatchingTypeValue asShort() { + return typed(Short.class); + } + + public RegexMatchingTypeValue asString() { + return typed(String.class); + } + + public RegexMatchingTypeValue asBooleanType() { + return typed(Boolean.class); + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexPatterns.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexPatterns.java new file mode 100644 index 0000000000..1e6d3d8285 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexPatterns.java @@ -0,0 +1,189 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.Arrays; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * Contains most common regular expression patterns. + * + * @author Marcin Grzejszczak + * @author Tim Ysewyn + * @since 1.0.0 + */ +public class RegexPatterns { + + // tag::regexps[] + protected static final Pattern TRUE_OR_FALSE = Pattern.compile("(true|false)"); + + protected static final Pattern ALPHA_NUMERIC = Pattern.compile("[a-zA-Z0-9]+"); + + protected static final Pattern ONLY_ALPHA_UNICODE = Pattern.compile("[\\p{L}]*"); + + protected static final Pattern NUMBER = Pattern.compile("-?(\\d*\\.\\d+|\\d+)"); + + protected static final Pattern INTEGER = Pattern.compile("-?(\\d+)"); + + protected static final Pattern POSITIVE_INT = Pattern.compile("([1-9]\\d*)"); + + protected static final Pattern DOUBLE = Pattern.compile("-?(\\d*\\.\\d+)"); + + protected static final Pattern HEX = Pattern.compile("[a-fA-F0-9]+"); + + protected static final Pattern IP_ADDRESS = Pattern.compile( + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])"); + + protected static final Pattern HOSTNAME_PATTERN = Pattern + .compile("((http[s]?|ftp):/)/?([^:/\\s]+)(:[0-9]{1,5})?"); + + protected static final Pattern EMAIL = Pattern + .compile("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}"); + + protected static final Pattern URL = UrlHelper.URL; + + protected static final Pattern HTTPS_URL = UrlHelper.HTTPS_URL; + + protected static final Pattern UUID = Pattern + .compile("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"); + + protected static final Pattern ANY_DATE = Pattern + .compile("(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])"); + + protected static final Pattern ANY_DATE_TIME = Pattern.compile( + "([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])"); + + protected static final Pattern ANY_TIME = Pattern + .compile("(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])"); + + protected static final Pattern NON_EMPTY = Pattern.compile("[\\S\\s]+"); + + protected static final Pattern NON_BLANK = Pattern.compile("^\\s*\\S[\\S\\s]*"); + + protected static final Pattern ISO8601_WITH_OFFSET = Pattern.compile( + "([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\\.\\d{3})?(Z|[+-][01]\\d:[0-5]\\d)"); + + protected static Pattern anyOf(String... values) { + return Pattern.compile(Arrays.stream(values).map(it -> '^' + it + '$') + .collect(Collectors.joining("|"))); + } + + public static String multipartParam(Object name, Object value) { + return ".*--(.*)\r\nContent-Disposition: form-data; name=\"" + name + + "\"\r\n(Content-Type: .*\r\n)?(Content-Transfer-Encoding: .*\r\n)?(Content-Length: \\d+\r\n)?\r\n" + + value + "\r\n--\\1.*"; + } + + public static String multipartFile(Object name, Object filename, Object content, + Object contentType) { + return ".*--(.*)\r\nContent-Disposition: form-data; name=\"" + name + + "\"; filename=\"" + filename + "\"\r\n(Content-Type: " + + toContentType(contentType) + + "\r\n)?(Content-Transfer-Encoding: .*\r\n)?(Content-Length: \\d+\r\n)?\r\n" + + content + "\r\n--\\1.*"; + } + + private static String toContentType(Object contentType) { + if (contentType == null) { + return ".*"; + } + if (contentType instanceof RegexProperty) { + return ((RegexProperty) contentType).pattern(); + } + return contentType.toString(); + } + + public RegexProperty onlyAlphaUnicode() { + return new RegexProperty(ONLY_ALPHA_UNICODE).asString(); + } + + public RegexProperty alphaNumeric() { + return new RegexProperty(ALPHA_NUMERIC).asString(); + } + + public RegexProperty number() { + return new RegexProperty(NUMBER).asDouble(); + } + + public RegexProperty positiveInt() { + return new RegexProperty(POSITIVE_INT).asInteger(); + } + + public RegexProperty anyBoolean() { + return new RegexProperty(TRUE_OR_FALSE).asBooleanType(); + } + + public RegexProperty anInteger() { + return new RegexProperty(INTEGER).asInteger(); + } + + public RegexProperty aDouble() { + return new RegexProperty(DOUBLE).asDouble(); + } + + public RegexProperty ipAddress() { + return new RegexProperty(IP_ADDRESS).asString(); + } + + public RegexProperty hostname() { + return new RegexProperty(HOSTNAME_PATTERN).asString(); + } + + public RegexProperty email() { + return new RegexProperty(EMAIL).asString(); + } + + public RegexProperty url() { + return new RegexProperty(URL).asString(); + } + + public RegexProperty httpsUrl() { + return new RegexProperty(HTTPS_URL).asString(); + } + + public RegexProperty uuid() { + return new RegexProperty(UUID).asString(); + } + + public RegexProperty isoDate() { + return new RegexProperty(ANY_DATE).asString(); + } + + public RegexProperty isoDateTime() { + return new RegexProperty(ANY_DATE_TIME).asString(); + } + + public RegexProperty isoTime() { + return new RegexProperty(ANY_TIME).asString(); + } + + // end::regexps[] + + public RegexProperty iso8601WithOffset() { + return new RegexProperty(ISO8601_WITH_OFFSET).asString(); + } + + public RegexProperty nonEmpty() { + return new RegexProperty(NON_EMPTY).asString(); + } + + public RegexProperty nonBlank() { + return new RegexProperty(NON_BLANK).asString(); + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexProperty.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexProperty.java new file mode 100644 index 0000000000..00020532ce --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexProperty.java @@ -0,0 +1,224 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.text.StringEscapeUtils; +import repackaged.nl.flotsam.xeger.Xeger; + +/** + * Represents a regular expression property. + * + * @since 2.1.0 + */ +public class RegexProperty extends DslProperty implements CanBeDynamic { + + final Pattern pattern; + + private final Class clazz; + + public RegexProperty(Object value) { + this(value, value, null); + } + + public RegexProperty(Object client, Object server) { + this(client, server, null); + } + + public RegexProperty(Object client, Object server, Class clazz) { + super(client, server); + boolean clientDynamic = client instanceof Pattern + || client instanceof RegexProperty; + boolean serverDynamic = server instanceof Pattern + || server instanceof RegexProperty; + if (!clientDynamic && !serverDynamic) { + throw new IllegalStateException("Neither client not server side is dynamic"); + } + Object dynamicValue = clientDynamic ? client : server; + if (dynamicValue instanceof Pattern) { + this.pattern = (Pattern) dynamicValue; + this.clazz = clazz != null ? clazz : String.class; + } + else if (dynamicValue instanceof RegexProperty) { + RegexProperty regexProperty = ((RegexProperty) dynamicValue); + this.pattern = regexProperty.pattern; + this.clazz = clazz != null ? clazz : regexProperty.clazz; + } + else { + this.clazz = clazz; + this.pattern = null; + } + } + + public Matcher matcher(CharSequence input) { + return this.pattern.matcher(input); + } + + public String pattern() { + return this.pattern.pattern(); + } + + public Class clazz() { + return this.getClass(); + } + + public RegexProperty asInteger() { + return new RegexProperty(this.getClientValue(), this.getServerValue(), + Integer.class); + } + + public RegexProperty asDouble() { + return new RegexProperty(this.getClientValue(), this.getServerValue(), + Double.class); + } + + public RegexProperty asFloat() { + return new RegexProperty(this.getClientValue(), this.getServerValue(), + Float.class); + } + + public RegexProperty asLong() { + return new RegexProperty(this.getClientValue(), this.getServerValue(), + Long.class); + } + + public RegexProperty asShort() { + return new RegexProperty(this.getClientValue(), this.getServerValue(), + Short.class); + } + + public RegexProperty asString() { + return new RegexProperty(this.getClientValue(), this.getServerValue(), + String.class); + } + + public RegexProperty asBooleanType() { + return new RegexProperty(this.getClientValue(), this.getServerValue(), + Boolean.class); + } + + public Object generate() { + return doGenerate(3); + } + + private Object doGenerate(int retries) { + try { + String generatedValue = new Xeger(this.pattern.pattern()).generate(); + if (Integer.class.equals(this.clazz)) { + return Integer.parseInt(generatedValue); + } + else if (Double.class.equals(this.clazz)) { + return Double.parseDouble(generatedValue); + } + else if (Float.class.equals(this.clazz)) { + return Float.parseFloat(generatedValue); + } + else if (Long.class.equals(this.clazz)) { + return Long.parseLong(generatedValue); + } + else if (Short.class.equals(this.clazz)) { + return Short.parseShort(generatedValue); + } + else if (Boolean.class.equals(this.clazz)) { + return Boolean.parseBoolean(generatedValue); + } + return generatedValue; + } + catch (NumberFormatException ex) { + if (retries > 0) { + retries = retries - 1; + return doGenerate(retries); + } + throw ex; + } + } + + public Object generateAndEscapeJavaStringIfNeeded() { + Object generated = generate(); + if (isNumber()) { + return generated; + } + return StringEscapeUtils.escapeJava(String.valueOf(generated)); + } + + private boolean isNumber() { + return Number.class.isAssignableFrom(this.clazz); + } + + public RegexProperty dynamicClientConcreteProducer() { + return new RegexProperty(this.pattern, generate(), this.clazz); + } + + public RegexProperty concreteClientDynamicProducer() { + return new RegexProperty(generate(), this.pattern, this.clazz); + } + + public RegexProperty concreteClientEscapedDynamicProducer() { + return new RegexProperty(generateAndEscapeJavaStringIfNeeded(), this.pattern, + this.clazz); + } + + public RegexProperty dynamicClientEscapedConcreteProducer() { + return new RegexProperty(this.pattern, generateAndEscapeJavaStringIfNeeded(), + this.clazz); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RegexProperty that = (RegexProperty) o; + return Objects.equals(stringPatternIfPresent(pattern), + stringPatternIfPresent(that.pattern)) + && Objects.equals(clazz, that.clazz); + } + + private Object stringPatternIfPresent(Pattern value) { + return value != null ? value.pattern() : null; + } + + @Override + public int hashCode() { + return Objects.hash(stringPatternIfPresent(pattern), clazz); + } + + @Override + public String toString() { + return this.pattern(); + } + + @Override + public Object generateConcreteValue() { + return generate(); + } + + public Pattern getPattern() { + return pattern; + } + + public Class getClazz() { + return clazz; + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Request.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Request.java new file mode 100644 index 0000000000..db1750c747 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Request.java @@ -0,0 +1,848 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.regex.Pattern; + +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.cloud.contract.spec.util.RegexpUtils; + +/** + * Represents the request side of the HTTP communication. + * + * @author Marcin Grzejszczak + * @author Tim Ysewyn + * @since 1.0.0 + */ +public class Request extends Common implements RegexCreatingProperty { + + private static final Log log = LogFactory.getLog(Request.class); + + private ClientPatternValueDslProperty property = new ClientPatternValueDslProperty(); + + private HttpMethods httpMethods = new HttpMethods(); + + private DslProperty method; + + private Url url; + + private UrlPath urlPath; + + private Headers headers; + + private Cookies cookies; + + private Body body; + + private Multipart multipart; + + private BodyMatchers bodyMatchers; + + public Request() { + } + + public Request(Request request) { + this.method = request.getMethod(); + this.url = request.getUrl(); + this.urlPath = request.getUrlPath(); + this.headers = request.getHeaders(); + this.cookies = request.getCookies(); + this.body = request.getBody(); + this.multipart = request.getMultipart(); + } + + /** + * Name of the HTTP method. + * @param method HTTP method name + */ + public void method(String method) { + this.method = toDslProperty(method); + } + + /** + * Name of the HTTP method. + * @param httpMethod HTTP method name + */ + public void method(HttpMethods.HttpMethod httpMethod) { + this.method = toDslProperty(httpMethod.toString()); + } + + /** + * Name of the HTTP method. + * @param method HTTP method name + */ + public void method(DslProperty method) { + this.method = toDslProperty(method); + } + + /** + * @param url URL to which the request will be sent + */ + public void url(Object url) { + this.url = new Url(url); + } + + /** + * @param url URL to which the request will be sent + */ + public void url(DslProperty url) { + this.url = new Url(url); + } + + /** + * @param path URL to which the request will be sent + */ + public void urlPath(String path) { + this.urlPath = new UrlPath(path); + } + + /** + * @param path URL to which the request will be sent + */ + public void urlPath(Object path) { + this.urlPath = new UrlPath(path); + } + + /** + * @param path URL to which the request will be sent + */ + public void urlPath(DslProperty path) { + this.urlPath = new UrlPath(path); + } + + /** + * Allows set an HTTP body. + * @param body body to set + */ + public void body(Map body) { + this.body = new Body(convertObjectsToDslProperties(body)); + } + + /** + * Allows set an HTTP body. + * @param body body to set + */ + public void body(List body) { + this.body = new Body(convertObjectsToDslProperties(body)); + } + + /** + * Allows set an HTTP body. + * @param matchingStrategy body to set + */ + public void body(MatchingStrategy matchingStrategy) { + this.body = new Body(matchingStrategy); + } + + /** + * Allows set an HTTP body. + * @param dslProperty body to set + */ + public void body(DslProperty dslProperty) { + this.body = new Body(dslProperty); + } + + /** + * Allows set an HTTP body. + * @param bodyAsValue body to set + */ + public void body(Object bodyAsValue) { + this.body = new Body(bodyAsValue); + } + + public Body getBody() { + return body; + } + + public void setBody(Body body) { + this.body = body; + } + + /** + * Allows to set multipart via the map notation. + * @param body multipart in a map notation + */ + public void multipart(Map body) { + this.multipart = new Multipart(convertObjectsToDslProperties(body)); + } + + /** + * Allows to set multipart via lists. + * @param multipartAsList multipart in a list notation + */ + public void multipart(List multipartAsList) { + this.multipart = Multipart.build(convertObjectsToDslProperties(multipartAsList)); + } + + /** + * Allows to set multipart value. + * @param dslProperty multipart + */ + public void multipart(DslProperty dslProperty) { + this.multipart = new Multipart(dslProperty); + } + + /** + * Allows to set multipart value. + * @param multipartAsValue multipart + */ + public void multipart(Object multipartAsValue) { + this.multipart = Multipart.build(multipartAsValue); + } + + /** + * Sets the equality check to the given query parameter. + * @param value value to check against + * @return matching strategy + */ + public MatchingStrategy equalTo(Object value) { + return new MatchingStrategy(value, MatchingStrategy.Type.EQUAL_TO); + } + + /** + * Sets the containing check to the given query parameter. + * @param value value to check against + * @return matching strategy + */ + public MatchingStrategy containing(Object value) { + return new MatchingStrategy(value, MatchingStrategy.Type.CONTAINS); + } + + /** + * Sets the matching check to the given query parameter. + * @param value value to check against + * @return matching strategy + */ + public MatchingStrategy matching(Object value) { + return new MatchingStrategy(value, MatchingStrategy.Type.MATCHING); + } + + /** + * Sets the not matching check to the given query parameter. + * @param value value to check against + * @return matching strategy + */ + public MatchingStrategy notMatching(Object value) { + return new MatchingStrategy(value, MatchingStrategy.Type.NOT_MATCHING); + } + + /** + * Sets the XML equality check to the body. + * @param value value to check against + * @return matching strategy + */ + public MatchingStrategy equalToXml(Object value) { + return new MatchingStrategy(value, MatchingStrategy.Type.EQUAL_TO_XML); + } + + /** + * Sets the JSON equality check to the body. + * @param value value to check against + * @return matching strategy + */ + public MatchingStrategy equalToJson(Object value) { + return new MatchingStrategy(value, MatchingStrategy.Type.EQUAL_TO_JSON); + } + + /** + * Sets absence check to the given query parameter. + * @return matching strategy + */ + public MatchingStrategy absent() { + return new MatchingStrategy(true, MatchingStrategy.Type.ABSENT); + } + + @Override + public void assertThatSidesMatch(Object stubSide, Object testSide) { + if (testSide instanceof OptionalProperty) { + throw new IllegalStateException( + "Optional can be used only for the stub side of the request!"); + } + super.assertThatSidesMatch(stubSide, testSide); + } + + /** + * Allows to set a dynamic value for the given element. + * @param client client value + * @return dsl property + */ + public DslProperty value(ClientDslProperty client) { + Object concreteValue = client.getServerValue(); + Object dynamicValue = client.getClientValue(); + if (dynamicValue instanceof RegexProperty && client.isSingleValue()) { + return ((RegexProperty) dynamicValue).dynamicClientEscapedConcreteProducer(); + } + else if (concreteValue instanceof RegexProperty && !client.isSingleValue()) { + concreteValue = dynamicValue; + } + + return new DslProperty(dynamicValue, concreteValue); + } + + /** + * Allows to set a dynamic value for the given regular expression element. + * @param property property to set + * @return dsl property + */ + public DslProperty $(RegexProperty property) { + return value(property); + } + + /** + * Allows to set a dynamic value for the given regular expression element. + * @param property property to set + * @return dsl property + */ + public DslProperty value(RegexProperty property) { + return value(client(property)); + } + + /** + * Allows to set a dynamic value for the given element. + * @param client property to set + * @return dsl property + */ + public DslProperty $(ClientDslProperty client) { + return value(client); + } + + /** + * Allows to set a dynamic value for the Pattern element. + * @param client property to set + * @return dsl property + */ + public DslProperty value(Pattern client) { + return value(new RegexProperty(client)); + } + + /** + * Allows to set a dynamic value for the given Pattern element. + * @param client property to set + * @return dsl property + */ + public DslProperty $(Pattern client) { + return value(client); + } + + @Override + public RegexProperty regexProperty(Object object) { + return new RegexProperty(object).dynamicClientConcreteProducer(); + } + + /** + * Allows to set a dynamic value for client and server side. + * @param client client value + * @param server server value + * @return dsl property + */ + @Override + public DslProperty value(ClientDslProperty client, ServerDslProperty server) { + if (server.getClientValue() instanceof RegexProperty) { + throw new IllegalStateException( + "You can't have a regular expression for the request on the server side"); + } + return super.value(client, server); + } + + /** + * Allows to set a dynamic value for client and server side. + * @param client client value + * @param server server value + * @return dsl property + */ + @Override + public DslProperty value(ServerDslProperty server, ClientDslProperty client) { + if (server.getClientValue() instanceof RegexProperty) { + throw new IllegalStateException( + "You can't have a regular expression for the request on the server side"); + } + + return super.value(server, client); + } + + public ClientPatternValueDslProperty getProperty() { + return property; + } + + public void setProperty(ClientPatternValueDslProperty property) { + this.property = property; + } + + public HttpMethods getHttpMethods() { + return httpMethods; + } + + public void setHttpMethods(HttpMethods httpMethods) { + this.httpMethods = httpMethods; + } + + public DslProperty getMethod() { + return method; + } + + public void setMethod(DslProperty method) { + this.method = method; + } + + public Url getUrl() { + return url; + } + + public void setUrl(Url url) { + this.url = url; + } + + public UrlPath getUrlPath() { + return urlPath; + } + + public void setUrlPath(UrlPath urlPath) { + this.urlPath = urlPath; + } + + public Headers getHeaders() { + return headers; + } + + public void setHeaders(Headers headers) { + this.headers = headers; + } + + public Cookies getCookies() { + return cookies; + } + + public void setCookies(Cookies cookies) { + this.cookies = cookies; + } + + public Multipart getMultipart() { + return multipart; + } + + public void setMultipart(Multipart multipart) { + this.multipart = multipart; + } + + public BodyMatchers getBodyMatchers() { + return bodyMatchers; + } + + public void setBodyMatchers(BodyMatchers bodyMatchers) { + this.bodyMatchers = bodyMatchers; + } + + @Override + public ClientDslProperty anyAlphaUnicode() { + return property.anyAlphaUnicode(); + } + + @Override + public ClientDslProperty anyAlphaNumeric() { + return property.anyAlphaNumeric(); + } + + @Override + public ClientDslProperty anyNumber() { + return property.anyNumber(); + } + + @Override + public ClientDslProperty anyInteger() { + return property.anyInteger(); + } + + @Override + public ClientDslProperty anyPositiveInt() { + return property.anyPositiveInt(); + } + + @Override + public ClientDslProperty anyDouble() { + return property.anyDouble(); + } + + @Override + public ClientDslProperty anyHex() { + return property.anyHex(); + } + + @Override + public ClientDslProperty aBoolean() { + return property.aBoolean(); + } + + @Override + public ClientDslProperty anyIpAddress() { + return property.anyIpAddress(); + } + + @Override + public ClientDslProperty anyHostname() { + return property.anyHostname(); + } + + @Override + public ClientDslProperty anyEmail() { + return property.anyEmail(); + } + + @Override + public ClientDslProperty anyUrl() { + return property.anyUrl(); + } + + @Override + public ClientDslProperty anyHttpsUrl() { + return property.anyHttpsUrl(); + } + + @Override + public ClientDslProperty anyUuid() { + return property.anyUuid(); + } + + @Override + public ClientDslProperty anyDate() { + return property.anyDate(); + } + + @Override + public ClientDslProperty anyDateTime() { + return property.anyDateTime(); + } + + @Override + public ClientDslProperty anyTime() { + return property.anyTime(); + } + + @Override + public ClientDslProperty anyIso8601WithOffset() { + return property.anyIso8601WithOffset(); + } + + @Override + public ClientDslProperty anyNonBlankString() { + return property.anyNonBlankString(); + } + + @Override + public ClientDslProperty anyNonEmptyString() { + return property.anyNonEmptyString(); + } + + @Override + public ClientDslProperty anyOf(String... values) { + return property.anyOf(values); + } + + public HttpMethods.HttpMethod GET() { + return httpMethods.GET(); + } + + public HttpMethods.HttpMethod HEAD() { + return httpMethods.HEAD(); + } + + public HttpMethods.HttpMethod POST() { + return httpMethods.POST(); + } + + public HttpMethods.HttpMethod PUT() { + return httpMethods.PUT(); + } + + public HttpMethods.HttpMethod PATCH() { + return httpMethods.PATCH(); + } + + public HttpMethods.HttpMethod DELETE() { + return httpMethods.DELETE(); + } + + public HttpMethods.HttpMethod OPTIONS() { + return httpMethods.OPTIONS(); + } + + public HttpMethods.HttpMethod TRACE() { + return httpMethods.TRACE(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Request request = (Request) o; + return Objects.equals(method, request.method) && Objects.equals(url, request.url) + && Objects.equals(urlPath, request.urlPath) + && Objects.equals(headers, request.headers) + && Objects.equals(cookies, request.cookies) + && Objects.equals(body, request.body) + && Objects.equals(multipart, request.multipart) + && Objects.equals(bodyMatchers, request.bodyMatchers); + } + + @Override + public int hashCode() { + return Objects.hash(method, url, urlPath, headers, cookies, body, multipart, + bodyMatchers); + } + + @Override + public String toString() { + return "Request{" + "\nmethod=" + method + ", \n\turl=" + url + ", \n\turlPath=" + + urlPath + ", \n\theaders=" + headers + ", \n\tcookies=" + cookies + + ", \n\tbody=" + body + ", \n\tmultipart=" + multipart + + ", \n\tbodyMatchers=" + bodyMatchers + '}'; + } + + /** + * The URL of the contract. + * @param url url value + * @param consumer function to manipulate the URL + */ + public void url(Object url, Consumer consumer) { + this.url = new Url(url); + consumer.accept(this.url); + } + + /** + * URL to which the request will be sent. Allows to customize additional query + * parameters if needed + * @param url url value + * @param consumer function to manipulate the URL + */ + public void url(DslProperty url, Consumer consumer) { + this.url = new Url(url); + consumer.accept(this.url); + } + + /** + * URL to which the request will be sent. Allows to customize additional query. + * parameters if needed + * @param path url value + * @param consumer function to manipulate the URL + */ + public void urlPath(String path, Consumer consumer) { + this.urlPath = new UrlPath(path); + consumer.accept(this.urlPath); + } + + /** + * URL to which the request will be sent. Allows to customize additional query. + * parameters if needed + * @param path url value + * @param consumer function to manipulate the URL + */ + public void urlPath(DslProperty path, Consumer consumer) { + this.urlPath = new UrlPath(path); + consumer.accept(this.urlPath); + } + + /** + * Allows to configure HTTP headers. + * @param consumer function to manipulate the headers + */ + public void headers(Consumer consumer) { + this.headers = new Request.RequestHeaders(); + consumer.accept((RequestHeaders) this.headers); + } + + /** + * Allows to configure HTTP cookies. + * @param consumer function to manipulate the cookies + */ + public void cookies(Consumer consumer) { + this.cookies = new Request.RequestCookies(); + consumer.accept((RequestCookies) this.cookies); + } + + /** + * @param consumer function to manipulate the body matchers + * @deprecated Deprecated in favor of bodyMatchers to support other future + * bodyMatchers too + */ + @Deprecated + public void stubMatchers(Consumer consumer) { + log.warn("stubMatchers method is deprecated. Please use bodyMatchers instead"); + bodyMatchers(consumer); + } + + /** + * Allows to set matchers for the body. + * @param consumer function to manipulate the URL + */ + public void bodyMatchers(Consumer consumer) { + this.bodyMatchers = new BodyMatchers(); + consumer.accept(this.bodyMatchers); + } + + /** + * The URL of the contract. + * @param url url value + * @param consumer function to manipulate the URL + */ + public void url(Object url, @DelegatesTo(Url.class) Closure consumer) { + this.url = new Url(url); + consumer.setDelegate(this.url); + consumer.call(); + } + + /** + * URL to which the request will be sent. Allows to customize additional query + * parameters if needed + * @param url url value + * @param consumer function to manipulate the URL + */ + public void url(DslProperty url, @DelegatesTo(Url.class) Closure consumer) { + this.url = new Url(url); + consumer.setDelegate(this.url); + consumer.call(); + } + + /** + * URL to which the request will be sent. Allows to customize additional query. + * parameters if needed + * @param path url value + * @param consumer function to manipulate the URL + */ + public void urlPath(String path, @DelegatesTo(UrlPath.class) Closure consumer) { + this.urlPath = new UrlPath(path); + consumer.setDelegate(this.urlPath); + consumer.call(); + } + + /** + * URL to which the request will be sent. Allows to customize additional query. + * parameters if needed + * @param path url value + * @param consumer function to manipulate the URL + */ + public void urlPath(DslProperty path, @DelegatesTo(UrlPath.class) Closure consumer) { + this.urlPath = new UrlPath(path); + consumer.setDelegate(this.urlPath); + consumer.call(); + } + + /** + * Allows to configure HTTP headers. + * @param consumer function to manipulate the headers + */ + public void headers(@DelegatesTo(Request.RequestHeaders.class) Closure consumer) { + this.headers = new Request.RequestHeaders(); + consumer.setDelegate(this.headers); + consumer.call(); + } + + /** + * Allows to configure HTTP cookies. + * @param consumer function to manipulate the cookies + */ + public void cookies(@DelegatesTo(Request.RequestCookies.class) Closure consumer) { + this.cookies = new Request.RequestCookies(); + consumer.setDelegate(this.cookies); + consumer.call(); + } + + /** + * @param consumer function to manipulate the body matchers + * @deprecated Deprecated in favor of bodyMatchers to support other future + * bodyMatchers too + */ + @Deprecated + public void stubMatchers(@DelegatesTo(BodyMatchers.class) Closure consumer) { + log.warn("stubMatchers method is deprecated. Please use bodyMatchers instead"); + bodyMatchers(consumer); + } + + /** + * Allows to set matchers for the body. + * @param consumer function to manipulate the URL + */ + public void bodyMatchers(@DelegatesTo(BodyMatchers.class) Closure consumer) { + this.bodyMatchers = new BodyMatchers(); + consumer.setDelegate(this.bodyMatchers); + consumer.call(); + } + + static class RequestHeaders extends Headers { + + private final Common common = new Common(); + + @Override + public DslProperty matching(String value) { + return this.common.$(this.common.c(this.common + .regex(RegexpUtils.escapeSpecialRegexWithSingleEscape(value) + ".*")), + this.common.p(value)); + } + + } + + static class RequestCookies extends Cookies { + + private final Common common = new Common(); + + @Override + public DslProperty matching(String value) { + return this.common.$(this.common.c(this.common + .regex(RegexpUtils.escapeSpecialRegexWithSingleEscape(value) + ".*")), + this.common.p(value)); + } + + } + + private class ServerRequest extends Request { + + ServerRequest(Request enclosing, Request request) { + super(request); + } + + } + + private class ClientRequest extends Request { + + ClientRequest(Request enclosing, Request request) { + super(request); + } + + } + + private class ClientPatternValueDslProperty + extends PatternValueDslProperty { + + @Override + protected ClientDslProperty createProperty(Pattern pattern, + Object generatedValue) { + return new ClientDslProperty(pattern, generatedValue); + } + + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Response.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Response.java new file mode 100644 index 0000000000..3506218865 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Response.java @@ -0,0 +1,864 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.regex.Pattern; + +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.cloud.contract.spec.util.RegexpUtils; + +/** + * Represents the response side of the HTTP communication. + * + * @author Marcin Grzejszczak + * @author Tim Ysewyn + * @since 1.0.0 + */ +public class Response extends Common implements RegexCreatingProperty { + + private static final Log log = LogFactory.getLog(Response.class); + + private DslProperty status; + + private DslProperty delay; + + private Headers headers; + + private Cookies cookies; + + private Body body; + + private boolean async; + + private ResponseBodyMatchers bodyMatchers; + + private ServerPatternValueDslProperty property = new ServerPatternValueDslProperty(); + + private HttpStatus httpStatus = new HttpStatus(); + + public Response() { + } + + public Response(Response response) { + this.status = response.getStatus(); + this.headers = response.getHeaders(); + this.cookies = response.getCookies(); + this.body = response.getBody(); + } + + /** + * Allows to set the HTTP status. + * @param status HTTP status + */ + public void status(int status) { + this.status = toDslProperty(status); + } + + /** + * Allows to set the HTTP status. + * @param status HTTP status + */ + public void status(DslProperty status) { + this.status = toDslProperty(status); + } + + /** + * Allows set an HTTP body. + * @param body body to set + */ + public void body(Map body) { + this.body = new Body(convertObjectsToDslProperties(body)); + } + + /** + * Allows set an HTTP body. + * @param body body to set + */ + public void body(List body) { + this.body = new Body(convertObjectsToDslProperties(body)); + } + + /** + * Allows set an HTTP body. + * @param bodyAsValue body to set + */ + public void body(Object bodyAsValue) { + if (bodyAsValue instanceof List) { + body((List) bodyAsValue); + } + else { + this.body = new Body(bodyAsValue); + } + + } + + /** + * Allows to set a fixed delay of the response in milliseconds. + * @param timeInMilliseconds delay in millis + */ + public void fixedDelayMilliseconds(int timeInMilliseconds) { + this.delay = toDslProperty(timeInMilliseconds); + } + + /** + * Turns on the asynchronous mode for this contract. Used with MockMvc and the Servlet + * 3.0 features. + */ + public void async() { + this.async = true; + } + + @Override + public void assertThatSidesMatch(Object stubSide, Object testSide) { + if (stubSide instanceof OptionalProperty) { + throw new IllegalStateException( + "Optional can be used only in the test side of the response!"); + } + super.assertThatSidesMatch(stubSide, testSide); + } + + /** + * Allows to set a dynamic value for the given element. + * @param server server value + * @return dsl property + */ + public DslProperty value(ServerDslProperty server) { + Object dynamicValue = server.getServerValue(); + Object concreteValue = server.getClientValue(); + if (dynamicValue instanceof RegexProperty && server.isSingleValue()) { + return ((RegexProperty) dynamicValue).concreteClientDynamicProducer(); + } + return new DslProperty(concreteValue, dynamicValue); + } + + /** + * Allows to set a dynamic value for the given element. + * @param server server value + * @return dsl property + */ + public DslProperty $(ServerDslProperty server) { + return value(server); + } + + /** + * Allows to set a dynamic value for the given element. + * @param server server value + * @return dsl property + */ + public DslProperty value(Pattern server) { + return value(new RegexProperty(server)); + } + + /** + * Allows to set a dynamic value for the given element. + * @param server server value + * @return dsl property + */ + public DslProperty value(RegexProperty server) { + return value(new ServerDslProperty(server)); + } + + /** + * Allows to set a dynamic value for the given element. + * @param server server value + * @return dsl property + */ + public DslProperty $(RegexProperty server) { + return value(server); + } + + /** + * Allows to set a dynamic value for the given element. + * @param server server value + * @return dsl property + */ + public DslProperty $(Pattern server) { + return value(new RegexProperty(server)); + } + + @Override + public RegexProperty regexProperty(Object object) { + return new RegexProperty(object).concreteClientDynamicProducer(); + } + + /** + * Allows to reference entries from the request. + * @return from request object + */ + public FromRequest fromRequest() { + return new FromRequest(); + } + + /** + * Allows to set a dynamic value for the given element. + * @param client client value + * @param server server value + * @return dsl property + */ + @Override + public DslProperty value(ClientDslProperty client, ServerDslProperty server) { + if (client.getClientValue() instanceof RegexProperty) { + throw new IllegalStateException( + "You can't have a regular expression for the response on the client side"); + } + + return super.value(client, server); + } + + /** + * Allows to set a dynamic value for the given element. + * @param server server value + * @param client client value + * @return dsl property + */ + @Override + public DslProperty value(ServerDslProperty server, ClientDslProperty client) { + if (client.getClientValue() instanceof RegexProperty) { + throw new IllegalStateException( + "You can't have a regular expression for the response on the client side"); + } + + return super.value(server, client); + } + + public ServerPatternValueDslProperty getProperty() { + return property; + } + + public void setProperty(ServerPatternValueDslProperty property) { + this.property = property; + } + + public HttpStatus getHttpStatus() { + return httpStatus; + } + + public void setHttpStatus(HttpStatus httpStatus) { + this.httpStatus = httpStatus; + } + + public DslProperty getStatus() { + return status; + } + + public void setStatus(DslProperty status) { + this.status = status; + } + + public DslProperty getDelay() { + return delay; + } + + public void setDelay(DslProperty delay) { + this.delay = delay; + } + + public Headers getHeaders() { + return headers; + } + + public void setHeaders(Headers headers) { + this.headers = headers; + } + + public Cookies getCookies() { + return cookies; + } + + public void setCookies(Cookies cookies) { + this.cookies = cookies; + } + + public Body getBody() { + return body; + } + + public void setBody(Body body) { + this.body = body; + } + + public boolean getAsync() { + return async; + } + + public boolean isAsync() { + return async; + } + + public void setAsync(boolean async) { + this.async = async; + } + + public ResponseBodyMatchers getBodyMatchers() { + return bodyMatchers; + } + + public void setBodyMatchers(ResponseBodyMatchers bodyMatchers) { + this.bodyMatchers = bodyMatchers; + } + + @Override + public ServerDslProperty anyAlphaUnicode() { + return property.anyAlphaUnicode(); + } + + @Override + public ServerDslProperty anyAlphaNumeric() { + return property.anyAlphaNumeric(); + } + + @Override + public ServerDslProperty anyNumber() { + return property.anyNumber(); + } + + @Override + public ServerDslProperty anyInteger() { + return property.anyInteger(); + } + + @Override + public ServerDslProperty anyPositiveInt() { + return property.anyPositiveInt(); + } + + @Override + public ServerDslProperty anyDouble() { + return property.anyDouble(); + } + + @Override + public ServerDslProperty anyHex() { + return property.anyHex(); + } + + @Override + public ServerDslProperty aBoolean() { + return property.aBoolean(); + } + + @Override + public ServerDslProperty anyIpAddress() { + return property.anyIpAddress(); + } + + @Override + public ServerDslProperty anyHostname() { + return property.anyHostname(); + } + + @Override + public ServerDslProperty anyEmail() { + return property.anyEmail(); + } + + @Override + public ServerDslProperty anyUrl() { + return property.anyUrl(); + } + + @Override + public ServerDslProperty anyHttpsUrl() { + return property.anyHttpsUrl(); + } + + @Override + public ServerDslProperty anyUuid() { + return property.anyUuid(); + } + + @Override + public ServerDslProperty anyDate() { + return property.anyDate(); + } + + @Override + public ServerDslProperty anyDateTime() { + return property.anyDateTime(); + } + + @Override + public ServerDslProperty anyTime() { + return property.anyTime(); + } + + @Override + public ServerDslProperty anyIso8601WithOffset() { + return property.anyIso8601WithOffset(); + } + + @Override + public ServerDslProperty anyNonBlankString() { + return property.anyNonBlankString(); + } + + @Override + public ServerDslProperty anyNonEmptyString() { + return property.anyNonEmptyString(); + } + + @Override + public ServerDslProperty anyOf(String... values) { + return property.anyOf(values); + } + + public int CONTINUE() { + return httpStatus.CONTINUE(); + } + + public int SWITCHING_PROTOCOLS() { + return httpStatus.SWITCHING_PROTOCOLS(); + } + + public int PROCESSING() { + return httpStatus.PROCESSING(); + } + + public int CHECKPOINT() { + return httpStatus.CHECKPOINT(); + } + + public int OK() { + return httpStatus.OK(); + } + + public int CREATED() { + return httpStatus.CREATED(); + } + + public int ACCEPTED() { + return httpStatus.ACCEPTED(); + } + + public int NON_AUTHORITATIVE_INFORMATION() { + return httpStatus.NON_AUTHORITATIVE_INFORMATION(); + } + + public int NO_CONTENT() { + return httpStatus.NO_CONTENT(); + } + + public int RESET_CONTENT() { + return httpStatus.RESET_CONTENT(); + } + + public int PARTIAL_CONTENT() { + return httpStatus.PARTIAL_CONTENT(); + } + + public int MULTI_STATUS() { + return httpStatus.MULTI_STATUS(); + } + + public int ALREADY_REPORTED() { + return httpStatus.ALREADY_REPORTED(); + } + + public int IM_USED() { + return httpStatus.IM_USED(); + } + + public int MULTIPLE_CHOICES() { + return httpStatus.MULTIPLE_CHOICES(); + } + + public int MOVED_PERMANENTLY() { + return httpStatus.MOVED_PERMANENTLY(); + } + + public int FOUND() { + return httpStatus.FOUND(); + } + + @Deprecated + public int MOVED_TEMPORARILY() { + return httpStatus.MOVED_TEMPORARILY(); + } + + public int SEE_OTHER() { + return httpStatus.SEE_OTHER(); + } + + public int NOT_MODIFIED() { + return httpStatus.NOT_MODIFIED(); + } + + @Deprecated + public int USE_PROXY() { + return httpStatus.USE_PROXY(); + } + + public int TEMPORARY_REDIRECT() { + return httpStatus.TEMPORARY_REDIRECT(); + } + + public int PERMANENT_REDIRECT() { + return httpStatus.PERMANENT_REDIRECT(); + } + + public int BAD_REQUEST() { + return httpStatus.BAD_REQUEST(); + } + + public int UNAUTHORIZED() { + return httpStatus.UNAUTHORIZED(); + } + + public int PAYMENT_REQUIRED() { + return httpStatus.PAYMENT_REQUIRED(); + } + + public int FORBIDDEN() { + return httpStatus.FORBIDDEN(); + } + + public int NOT_FOUND() { + return httpStatus.NOT_FOUND(); + } + + public int METHOD_NOT_ALLOWED() { + return httpStatus.METHOD_NOT_ALLOWED(); + } + + public int NOT_ACCEPTABLE() { + return httpStatus.NOT_ACCEPTABLE(); + } + + public int PROXY_AUTHENTICATION_REQUIRED() { + return httpStatus.PROXY_AUTHENTICATION_REQUIRED(); + } + + public int REQUEST_TIMEOUT() { + return httpStatus.REQUEST_TIMEOUT(); + } + + public int CONFLICT() { + return httpStatus.CONFLICT(); + } + + public int GONE() { + return httpStatus.GONE(); + } + + public int LENGTH_REQUIRED() { + return httpStatus.LENGTH_REQUIRED(); + } + + public int PRECONDITION_FAILED() { + return httpStatus.PRECONDITION_FAILED(); + } + + public int PAYLOAD_TOO_LARGE() { + return httpStatus.PAYLOAD_TOO_LARGE(); + } + + @Deprecated + public int REQUEST_ENTITY_TOO_LARGE() { + return httpStatus.REQUEST_ENTITY_TOO_LARGE(); + } + + public int URI_TOO_LONG() { + return httpStatus.URI_TOO_LONG(); + } + + @Deprecated + public int REQUEST_URI_TOO_LONG() { + return httpStatus.REQUEST_URI_TOO_LONG(); + } + + public int UNSUPPORTED_MEDIA_TYPE() { + return httpStatus.UNSUPPORTED_MEDIA_TYPE(); + } + + public int REQUESTED_RANGE_NOT_SATISFIABLE() { + return httpStatus.REQUESTED_RANGE_NOT_SATISFIABLE(); + } + + public int EXPECTATION_FAILED() { + return httpStatus.EXPECTATION_FAILED(); + } + + public int I_AM_A_TEAPOT() { + return httpStatus.I_AM_A_TEAPOT(); + } + + @Deprecated + public int INSUFFICIENT_SPACE_ON_RESOURCE() { + return httpStatus.INSUFFICIENT_SPACE_ON_RESOURCE(); + } + + @Deprecated + public int METHOD_FAILURE() { + return httpStatus.METHOD_FAILURE(); + } + + @Deprecated + public int DESTINATION_LOCKED() { + return httpStatus.DESTINATION_LOCKED(); + } + + public int UNPROCESSABLE_ENTITY() { + return httpStatus.UNPROCESSABLE_ENTITY(); + } + + public int LOCKED() { + return httpStatus.LOCKED(); + } + + public int FAILED_DEPENDENCY() { + return httpStatus.FAILED_DEPENDENCY(); + } + + public int UPGRADE_REQUIRED() { + return httpStatus.UPGRADE_REQUIRED(); + } + + public int PRECONDITION_REQUIRED() { + return httpStatus.PRECONDITION_REQUIRED(); + } + + public int TOO_MANY_REQUESTS() { + return httpStatus.TOO_MANY_REQUESTS(); + } + + public int REQUEST_HEADER_FIELDS_TOO_LARGE() { + return httpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE(); + } + + public int UNAVAILABLE_FOR_LEGAL_REASONS() { + return httpStatus.UNAVAILABLE_FOR_LEGAL_REASONS(); + } + + public int INTERNAL_SERVER_ERROR() { + return httpStatus.INTERNAL_SERVER_ERROR(); + } + + public int NOT_IMPLEMENTED() { + return httpStatus.NOT_IMPLEMENTED(); + } + + public int BAD_GATEWAY() { + return httpStatus.BAD_GATEWAY(); + } + + public int SERVICE_UNAVAILABLE() { + return httpStatus.SERVICE_UNAVAILABLE(); + } + + public int GATEWAY_TIMEOUT() { + return httpStatus.GATEWAY_TIMEOUT(); + } + + public int HTTP_VERSION_NOT_SUPPORTED() { + return httpStatus.HTTP_VERSION_NOT_SUPPORTED(); + } + + public int VARIANT_ALSO_NEGOTIATES() { + return httpStatus.VARIANT_ALSO_NEGOTIATES(); + } + + public int INSUFFICIENT_STORAGE() { + return httpStatus.INSUFFICIENT_STORAGE(); + } + + public int LOOP_DETECTED() { + return httpStatus.LOOP_DETECTED(); + } + + public int BANDWIDTH_LIMIT_EXCEEDED() { + return httpStatus.BANDWIDTH_LIMIT_EXCEEDED(); + } + + public int NOT_EXTENDED() { + return httpStatus.NOT_EXTENDED(); + } + + public int NETWORK_AUTHENTICATION_REQUIRED() { + return httpStatus.NETWORK_AUTHENTICATION_REQUIRED(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Response response = (Response) o; + return async == response.async && Objects.equals(status, response.status) + && Objects.equals(delay, response.delay) + && Objects.equals(headers, response.headers) + && Objects.equals(cookies, response.cookies) + && Objects.equals(body, response.body) + && Objects.equals(bodyMatchers, response.bodyMatchers); + } + + @Override + public int hashCode() { + return Objects.hash(status, delay, headers, cookies, body, async, bodyMatchers); + } + + @Override + public String toString() { + return "Response{" + "\nstatus=" + status + ", \n\tdelay=" + delay + + ", \n\theaders=" + headers + ", \n\tcookies=" + cookies + ", \n\tbody=" + + body + ", \n\tasync=" + async + ", \n\tbodyMatchers=" + bodyMatchers + + '}'; + } + + /** + * Allows to configure HTTP headers. + * @param consumer function to manipulate the URL + */ + public void headers(Consumer consumer) { + this.headers = new Response.ResponseHeaders(); + consumer.accept((ResponseHeaders) this.headers); + } + + /** + * Allows to configure HTTP cookies. + * @param consumer function to manipulate the URL + */ + public void cookies(Consumer consumer) { + this.cookies = new Response.ResponseCookies(); + consumer.accept((ResponseCookies) this.cookies); + } + + /** + * @deprecated Deprecated in favor of bodyMatchers to support other future + * bodyMatchers too + * @param consumer function to manipulate the URL + */ + @Deprecated + public void testMatchers(Consumer consumer) { + log.warn("testMatchers method is deprecated. Please use bodyMatchers instead"); + bodyMatchers(consumer); + } + + /** + * Allows to set matchers for the body. + * @param consumer function to manipulate the URL + */ + public void bodyMatchers(Consumer consumer) { + this.bodyMatchers = new ResponseBodyMatchers(); + consumer.accept(this.bodyMatchers); + } + + /** + * Allows to configure HTTP headers. + * @param consumer function to manipulate the URL + */ + public void headers(@DelegatesTo(ResponseHeaders.class) Closure consumer) { + this.headers = new Response.ResponseHeaders(); + consumer.setDelegate(this.headers); + consumer.call(); + } + + /** + * Allows to configure HTTP cookies. + * @param consumer function to manipulate the URL + */ + public void cookies(@DelegatesTo(Response.ResponseCookies.class) Closure consumer) { + this.cookies = new Response.ResponseCookies(); + consumer.setDelegate(this.cookies); + consumer.call(); + } + + /** + * @deprecated Deprecated in favor of bodyMatchers to support other future + * bodyMatchers too + * @param consumer function to manipulate the URL + */ + @Deprecated + public void testMatchers(@DelegatesTo(ResponseBodyMatchers.class) Closure consumer) { + log.warn("testMatchers method is deprecated. Please use bodyMatchers instead"); + bodyMatchers(consumer); + } + + /** + * Allows to set matchers for the body. + * @param consumer function to manipulate the URL + */ + public void bodyMatchers(@DelegatesTo(ResponseBodyMatchers.class) Closure consumer) { + this.bodyMatchers = new ResponseBodyMatchers(); + consumer.setDelegate(this.bodyMatchers); + consumer.call(); + } + + static class ResponseHeaders extends Headers { + + private final Common common = new Common(); + + @Override + public DslProperty matching(final String value) { + return this.common.$(this.common.p(notEscaped(Pattern.compile( + RegexpUtils.escapeSpecialRegexWithSingleEscape(value) + ".*"))), + this.common.c(value)); + } + + } + + static class ResponseCookies extends Cookies { + + private final Common common = new Common(); + + @Override + public DslProperty matching(final String value) { + return this.common.$(this.common.p(this.common + .regex(RegexpUtils.escapeSpecialRegexWithSingleEscape(value) + ".*")), + this.common.c(value)); + } + + } + + private class ServerResponse extends Response { + + ServerResponse(Response enclosing, Response request) { + super(request); + } + + } + + private class ClientResponse extends Response { + + ClientResponse(Response enclosing, Response request) { + super(request); + } + + } + + private class ServerPatternValueDslProperty + extends PatternValueDslProperty { + + @Override + protected ServerDslProperty createProperty(Pattern pattern, + Object generatedValue) { + return new ServerDslProperty(pattern, generatedValue); + } + + } + +} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ResponseBodyMatchers.groovy b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ResponseBodyMatchers.java similarity index 55% rename from spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ResponseBodyMatchers.groovy rename to specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ResponseBodyMatchers.java index eb2298501d..24ca6660f4 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ResponseBodyMatchers.groovy +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ResponseBodyMatchers.java @@ -14,31 +14,34 @@ * limitations under the License. */ -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic -import groovy.transform.ToString +package org.springframework.cloud.contract.spec.internal; /** - * Body matchers for the response side (output message, REST response) + * Body matchers for the response side (output message, REST response). * * @author Marcin Grzejszczak * @author Tim Ysewyn * @since 1.0.3 */ -@CompileStatic -@ToString(includePackage = false, includeSuper = true) -class ResponseBodyMatchers extends BodyMatchers { +public class ResponseBodyMatchers extends BodyMatchers { - MatchingTypeValue byType() { - return new MatchingTypeValue(type: MatchingType.TYPE) + public MatchingTypeValue byType() { + MatchingTypeValue value = new MatchingTypeValue(); + value.setType(MatchingType.TYPE); + return value; } - MatchingTypeValue byCommand(String execute) { - return new MatchingTypeValue(MatchingType.COMMAND, new ExecutionProperty(execute)) + public MatchingTypeValue byCommand(String execute) { + MatchingTypeValue value = new MatchingTypeValue(); + value.setType(MatchingType.COMMAND); + value.setValue(new ExecutionProperty(execute)); + return value; } - MatchingTypeValue byNull() { - return new MatchingTypeValue(MatchingType.NULL, null) + public MatchingTypeValue byNull() { + MatchingTypeValue value = new MatchingTypeValue(); + value.setType(MatchingType.NULL); + return value; } + } diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ServerDslProperty.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ServerDslProperty.java new file mode 100644 index 0000000000..3d04ae8051 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ServerDslProperty.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +/** + * Represents a server side {@link DslProperty}. + * + * @since 1.0.0 + */ +public class ServerDslProperty extends DslProperty { + + public ServerDslProperty(Object singleValue) { + super(singleValue); + } + + public ServerDslProperty(Object server, Object client) { + super(client, server); + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ServerInput.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ServerInput.java new file mode 100644 index 0000000000..b83e4976b8 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ServerInput.java @@ -0,0 +1,25 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +class ServerInput extends Input { + + ServerInput(Input request) { + super(request); + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ServerOutputMessage.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ServerOutputMessage.java new file mode 100644 index 0000000000..08f037748f --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ServerOutputMessage.java @@ -0,0 +1,25 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +class ServerOutputMessage extends OutputMessage { + + ServerOutputMessage(OutputMessage request) { + super(request); + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Url.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Url.java new file mode 100644 index 0000000000..7afdf18bc7 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Url.java @@ -0,0 +1,104 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.Objects; +import java.util.function.Consumer; + +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; + +import org.springframework.cloud.contract.spec.util.ValidateUtils; + +/** + * Represents a URL that may contain query parameters. + * + * @since 1.0.0 + */ +public class Url extends DslProperty { + + private QueryParameters queryParameters; + + public Url(DslProperty prop) { + super(prop.getClientValue(), prop.getServerValue()); + ValidateUtils.validateServerValueIsAvailable(prop.getServerValue(), "Url"); + } + + public Url(Object url) { + super(url, testUrl(url)); + ValidateUtils.validateServerValueIsAvailable(url, "Url"); + } + + // Can be overridable by extensions + private static Object testUrl(Object url) { + return DslPropertyConverter.INSTANCE.testSide(url); + } + + public QueryParameters getQueryParameters() { + return queryParameters; + } + + public void setQueryParameters(QueryParameters queryParameters) { + this.queryParameters = queryParameters; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + Url url = (Url) o; + return Objects.equals(queryParameters, url.queryParameters); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), queryParameters); + } + + @Override + public String toString() { + return "Url{" + "\nqueryParameters=" + queryParameters + "} \n" + + super.toString(); + } + + /** + * The query parameters part of the contract. + * @param consumer function to manipulate the query parameters + */ + public void queryParameters(Consumer consumer) { + this.queryParameters = new QueryParameters(); + consumer.accept(this.queryParameters); + } + + /** + * The query parameters part of the contract. + * @param consumer function to manipulate the query parameters + */ + public void queryParameters(@DelegatesTo(QueryParameters.class) Closure consumer) { + this.queryParameters = new QueryParameters(); + consumer.setDelegate(this.queryParameters); + consumer.call(); + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/UrlHelper.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/UrlHelper.java new file mode 100644 index 0000000000..06b1331fb5 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/UrlHelper.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.util.regex.Pattern; + +/** + * Taken from https://gist.github.com/skeller88/5eb73dc0090d4ff1249a. + */ +final class UrlHelper { + + /** + * Example: "http". Also called 'protocol'. Scheme component is optional, even though + * the RFC doesn't make it optional. Since ((RegexProperty) this regex is validating a + * submitted callback url, which determines where the browser will navigate to after a + * successful authentication, the browser will use http or https for the scheme by + * default. Not borrowed from dperini in order to allow any scheme type. + */ + private static final String REGEX_SCHEME = "[A-Za-z][+-.\\w^_]*:"; + + private static final String HTTPS_REGEX_SCHEME = "https:"; + + private static final String REGEX_AUTHORATIVE_DECLARATION = "/{2}"; + + private static final String REGEX_USERINFO = "(?:\\S+(?::\\S*)?@)?"; + + private static final String REGEX_HOST = "(?:" + + "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + + "|" + "(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)" + + "(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*" + + "(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))|(?:localhost))"; + + private static final String REGEX_PORT = "(?::\\d{2,5})?"; + + private static final String REGEX_RESOURCE_PATH = "(?:/\\S*)?"; + + protected static final Pattern HTTPS_URL = Pattern.compile( + "^(?:" + HTTPS_REGEX_SCHEME + REGEX_AUTHORATIVE_DECLARATION + REGEX_USERINFO + + REGEX_HOST + REGEX_PORT + REGEX_RESOURCE_PATH + ")$"); + + protected static final Pattern URL = Pattern.compile("^(?:(?:" + REGEX_SCHEME + + REGEX_AUTHORATIVE_DECLARATION + ")?" + REGEX_USERINFO + REGEX_HOST + + REGEX_PORT + REGEX_RESOURCE_PATH + ")$"); + + private UrlHelper() { + throw new IllegalStateException("Can't instantiate an utility class"); + } + +} diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/UrlPath.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/UrlPath.java new file mode 100644 index 0000000000..77276487c7 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/UrlPath.java @@ -0,0 +1,39 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.internal; + +/** + * Represents a url path. Syntactic sugar when working with {@link QueryParameters}. It's + * logically equal to {@link Url}. + * + * @since 1.0.0 + */ +public class UrlPath extends Url { + + public UrlPath(Object path) { + super(path); + } + + public UrlPath(String path) { + super(path); + } + + public UrlPath(DslProperty path) { + super(path); + } + +} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/util/RegexpUtils.groovy b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/util/RegexpUtils.java similarity index 50% rename from spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/util/RegexpUtils.groovy rename to specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/util/RegexpUtils.java index 9a25cd2857..1688ae301d 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/util/RegexpUtils.groovy +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/util/RegexpUtils.java @@ -14,29 +14,30 @@ * limitations under the License. */ -package org.springframework.cloud.contract.spec.util +package org.springframework.cloud.contract.spec.util; -import java.util.regex.Pattern - -import groovy.transform.CompileStatic +import java.util.regex.Pattern; /** - * Useful utility methods to work with regular expressions + * Useful utility methods to work with regular expressions. * * @since 1.0.2 */ -@CompileStatic -class RegexpUtils { +public final class RegexpUtils { - private final static Pattern SPECIAL_REGEX_CHARS = Pattern. - compile('[{}()\\[\\].+*?^$\\\\|]') - - static String escapeSpecialRegexChars(String str) { - return SPECIAL_REGEX_CHARS.matcher(str).replaceAll('\\\\\\\\$0') + private RegexpUtils() { + throw new IllegalStateException("Can't instantiate a utility class"); } - static String escapeSpecialRegexWithSingleEscape(String str) { - return SPECIAL_REGEX_CHARS.matcher(str).replaceAll('\\\\$0') + public static String escapeSpecialRegexChars(String str) { + return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\\\\\$0"); } + public static String escapeSpecialRegexWithSingleEscape(String str) { + return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\$0"); + } + + private static final Pattern SPECIAL_REGEX_CHARS = Pattern + .compile("[{}()\\[\\].+*?^$\\\\|]"); + } diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/util/ValidateUtils.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/util/ValidateUtils.java new file mode 100644 index 0000000000..a3138bbe9d --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/util/ValidateUtils.java @@ -0,0 +1,95 @@ +/* + * Copyright 2013-2019 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. + */ + +package org.springframework.cloud.contract.spec.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +import org.springframework.cloud.contract.spec.internal.DslProperty; +import org.springframework.cloud.contract.spec.internal.MatchingStrategy; +import org.springframework.cloud.contract.spec.internal.RegexProperty; + +/** + * Checks the validity of DSL entries. + * + * @since 1.0.0 + */ +public final class ValidateUtils { + + private ValidateUtils() { + throw new IllegalStateException("Can't instantiate an utility class"); + } + + /** + * Allowed matching types on the server side. + */ + public static List ALLOWED_MATCHING_TYPES_ON_SERVER_SIDE = new ArrayList<>( + Arrays.asList(MatchingStrategy.Type.EQUAL_TO, MatchingStrategy.Type.ABSENT)); + + /** + * Validates if for given object the server value is present. + * @param value value to check + * @param msg potential exception message + * @return object if everything is fine + */ + public static Object validateServerValueIsAvailable(Object value, String msg) { + if (value instanceof Pattern) { + validateServerValue((Pattern) value, msg); + } + else if (value instanceof RegexProperty) { + RegexProperty property = (RegexProperty) value; + validateServerValue(property.getPattern(), msg); + } + else if (value instanceof MatchingStrategy) { + validateServerValue((MatchingStrategy) value, msg); + } + else if (value instanceof DslProperty) { + validateServerValue((DslProperty) value, msg); + } + else { + validateServerValue(value, msg); + } + + return value; + } + + public static void validateServerValue(Pattern pattern, String msg) { + throw new IllegalStateException(msg + " can\'t be a pattern for the server side"); + } + + public static void validateServerValue(MatchingStrategy matchingStrategy, + String msg) { + if (!ALLOWED_MATCHING_TYPES_ON_SERVER_SIDE.contains(matchingStrategy.getType())) { + throw new IllegalStateException(msg + " can\'t be of a matching type: " + + String.valueOf(matchingStrategy.getType()) + + " for the server side"); + } + + validateServerValue(matchingStrategy.getServerValue(), msg); + } + + public static void validateServerValue(DslProperty value, String msg) { + validateServerValue(value.getServerValue(), msg); + } + + public static void validateServerValue(Object value, String msg) { + // OK + } + +} diff --git a/spring-cloud-contract-spec/src/main/groovy/repackaged/nl/flotsam/xeger/Xeger.java b/specs/spring-cloud-contract-spec-java/src/main/java/repackaged/nl/flotsam/xeger/Xeger.java similarity index 100% rename from spring-cloud-contract-spec/src/main/groovy/repackaged/nl/flotsam/xeger/Xeger.java rename to specs/spring-cloud-contract-spec-java/src/main/java/repackaged/nl/flotsam/xeger/Xeger.java diff --git a/spring-cloud-contract-spec/src/test/groovy/repackaged/nl/flotsam/xeger/XegerTest.java b/specs/spring-cloud-contract-spec-java/src/test/groovy/repackaged/nl/flotsam/xeger/XegerTest.java similarity index 100% rename from spring-cloud-contract-spec/src/test/groovy/repackaged/nl/flotsam/xeger/XegerTest.java rename to specs/spring-cloud-contract-spec-java/src/test/groovy/repackaged/nl/flotsam/xeger/XegerTest.java diff --git a/spring-cloud-contract-spec/src/test/groovy/repackaged/nl/flotsam/xeger/XegerUtilsTest.java b/specs/spring-cloud-contract-spec-java/src/test/groovy/repackaged/nl/flotsam/xeger/XegerUtilsTest.java similarity index 100% rename from spring-cloud-contract-spec/src/test/groovy/repackaged/nl/flotsam/xeger/XegerUtilsTest.java rename to specs/spring-cloud-contract-spec-java/src/test/groovy/repackaged/nl/flotsam/xeger/XegerUtilsTest.java diff --git a/specs/spring-cloud-contract-spec/README.adoc b/specs/spring-cloud-contract-spec/README.adoc new file mode 100644 index 0000000000..4e17b7fbd9 --- /dev/null +++ b/specs/spring-cloud-contract-spec/README.adoc @@ -0,0 +1,3 @@ +=== Spring Cloud Contract Specification + +This module contains both Groovy and Java specs. \ No newline at end of file diff --git a/specs/spring-cloud-contract-spec/pom.xml b/specs/spring-cloud-contract-spec/pom.xml new file mode 100644 index 0000000000..266db46ff9 --- /dev/null +++ b/specs/spring-cloud-contract-spec/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + org.springframework.cloud + spring-cloud-contract-parent + 2.2.0.BUILD-SNAPSHOT + ../.. + + spring-cloud-contract-spec + jar + Spring Cloud Contract Spec + Spring Cloud Contract Spec + + + org.springframework.cloud + spring-cloud-contract-spec-java + + + org.springframework.cloud + spring-cloud-contract-spec-groovy + + + org.spockframework + spock-core + test + + + org.springframework.boot + spring-boot-starter-test + test + + + info.solidsoft.spock + spock-global-unroll + test + + + + + + org.codehaus.gmavenplus + gmavenplus-plugin + + + + addTestSources + generateTestStubs + compileTests + removeTestStubs + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + + diff --git a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy b/specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy similarity index 86% rename from spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy rename to specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy index 4c36c70029..41c3741742 100644 --- a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy +++ b/specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy @@ -16,12 +16,12 @@ package org.springframework.cloud.contract.spec.internal + import spock.lang.Specification import org.springframework.cloud.contract.spec.Contract import static org.assertj.core.api.Assertions.assertThat - /** * @author Marcin Grzejszczak */ @@ -270,6 +270,16 @@ then: status OK() } } + a.request.method == b.request.method + a.request.url == b.request.url + def firstHeader = a.request.headers.entries.first() + def secondHeader = b.request.headers.entries.first() + firstHeader == secondHeader + a.request.headers.entries == b.request.headers.entries + a.request.headers == b.request.headers + a.request == b.request + a.response.status == b.response.status + a.response == b.response a == b } @@ -362,6 +372,24 @@ then: } } } + a.request.method == b.request.method + a.request.url == b.request.url + def aFirstHeader = a.request.headers.entries.first() + def bFirstHeader = b.request.headers.entries.first() + aFirstHeader == bFirstHeader + def aLastHeader = a.request.headers.entries.last() + def bLastHeader = b.request.headers.entries.last() + aLastHeader == bLastHeader + a.request.headers.entries == b.request.headers.entries + a.request.headers == b.request.headers + a.request.body == b.request.body + a.request == b.request + a.response.status == b.response.status + def aRespFirstHeader = a.response.headers.entries.first() + def bRespFirstHeader = b.response.headers.entries.first() + aRespFirstHeader == bRespFirstHeader + a.response.body == b.response.body + a.response == b.response a == b } diff --git a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/CookiesSpec.groovy b/specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/CookiesSpec.groovy similarity index 100% rename from spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/CookiesSpec.groovy rename to specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/CookiesSpec.groovy diff --git a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ExecutionPropertySpec.groovy b/specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ExecutionPropertySpec.groovy similarity index 100% rename from spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ExecutionPropertySpec.groovy rename to specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ExecutionPropertySpec.groovy diff --git a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/HttpStatusTests.java b/specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/HttpStatusTests.java similarity index 100% rename from spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/HttpStatusTests.java rename to specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/HttpStatusTests.java diff --git a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/InputSpec.groovy b/specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/InputSpec.groovy similarity index 100% rename from spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/InputSpec.groovy rename to specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/InputSpec.groovy diff --git a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/MatchingTypeSpec.groovy b/specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/MatchingTypeSpec.groovy similarity index 100% rename from spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/MatchingTypeSpec.groovy rename to specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/MatchingTypeSpec.groovy diff --git a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/OutputMessageSpec.groovy b/specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/OutputMessageSpec.groovy similarity index 100% rename from spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/OutputMessageSpec.groovy rename to specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/OutputMessageSpec.groovy diff --git a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/RegexPatternsSpec.groovy b/specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/RegexPatternsSpec.groovy similarity index 100% rename from spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/RegexPatternsSpec.groovy rename to specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/RegexPatternsSpec.groovy diff --git a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/RequestSpec.groovy b/specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/RequestSpec.groovy similarity index 100% rename from spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/RequestSpec.groovy rename to specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/RequestSpec.groovy diff --git a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ResponseSpec.groovy b/specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ResponseSpec.groovy similarity index 100% rename from spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ResponseSpec.groovy rename to specs/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ResponseSpec.groovy diff --git a/spring-cloud-contract-dependencies/pom.xml b/spring-cloud-contract-dependencies/pom.xml index 34dce5aa17..36710b1f6f 100644 --- a/spring-cloud-contract-dependencies/pom.xml +++ b/spring-cloud-contract-dependencies/pom.xml @@ -32,6 +32,16 @@ spring-cloud-contract-spec ${project.version} + + org.springframework.cloud + spring-cloud-contract-spec-java + ${project.version} + + + org.springframework.cloud + spring-cloud-contract-spec-groovy + ${project.version} + org.springframework.cloud spring-cloud-contract-verifier diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/Contract.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/Contract.groovy deleted file mode 100644 index 6353699050..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/Contract.groovy +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec - -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString -import groovy.transform.TypeChecked - -import org.springframework.cloud.contract.spec.internal.Input -import org.springframework.cloud.contract.spec.internal.OutputMessage -import org.springframework.cloud.contract.spec.internal.Request -import org.springframework.cloud.contract.spec.internal.Response - -/** - * The point of entry to the DSL - * - * @since 1.0.0 - */ -@TypeChecked -@EqualsAndHashCode -@ToString(includePackage = false, includeNames = true) -class Contract { - - /** - * You can set the level of priority of this contract. If there are two contracts - * mapped for example to the same endpoint, then the one with greater priority should - * take precedence. A priority of 1 is highest and takes precedence over a priority of 2. - */ - Integer priority - /** - * The HTTP request part of the contract - */ - Request request - /** - * The HTTP response part of the contract - */ - Response response - /** - * The label by which you'll reference the contract on the message consumer side - */ - String label - /** - * Description of a contract. May be used in the documentation generation. - */ - String description - /** - * Name of the generated test / stub. If not provided then the file name will be used. - * If you have multiple contracts in a single file and you don't provide this value - * then a prefix will be added to the file with the index number while iterating - * over the collection of contracts. - * - * Remember to have a unique name for every single contract. Otherwise you might - * generate tests that have two identical methods or you will override the stubs. - */ - String name - /** - * The input side of a messaging contract. - */ - Input input - /** - * The output side of a messaging contract. - */ - OutputMessage outputMessage - - /** - * Whether the contract should be ignored or not. - */ - boolean ignored - - protected Contract() {} - - /** - * Factory method to create the DSL - */ - static Contract make(@DelegatesTo(Contract) Closure closure) { - Contract dsl = new Contract() - closure.delegate = dsl - closure() - assertContract(dsl) - return dsl - } - - static void assertContract(Contract dsl) { - if (dsl.request) { - if (!dsl.request.url && !dsl.request.urlPath) { - throw new IllegalStateException("URL is missing for HTTP contract") - } - if (!dsl.request.method) { - throw new IllegalStateException("Method is missing for HTTP contract") - } - } - if (dsl.response) { - if (!dsl.response.status) { - throw new IllegalStateException("Status is missing for HTTP contract") - } - } - // Can't assert messaging part cause Pact doesn't require destinations it seems - } - - /** - * You can set the level of priority of this contract. If there are two contracts - * mapped for example to the same endpoint, then the one with greater priority should - * take precedence. A priority of 1 is highest and takes precedence over a priority of 2. - */ - void priority(int priority) { - this.priority = priority - } - - /** - * Name of the generated test / stub. If not provided then the file name will be used. - * If you have multiple contracts in a single file and you don't provide this value - * then a prefix will be added to the file with the index number while iterating - * over the collection of contracts. - * - * Remember to have a unique name for every single contract. Otherwise you might - * generate tests that have two identical methods or you will override the stubs. - */ - void name(String name) { - this.name = name - } - - /** - * Label used by the messaging contracts to trigger a message on the consumer side - * - * @param label - name of the label of a messaging contract to trigger - */ - void label(String label) { - this.label = label - } - - /** - * Description text. Might be used to describe the usage scenario. - * - * @param description - value of the description - */ - void description(String description) { - this.description = description - } - - /** - * The HTTP request part of the contract - */ - void request(@DelegatesTo(Request) Closure closure) { - this.request = new Request() - closure.delegate = request - closure() - } - - /** - * The HTTP response part of the contract - */ - void response(@DelegatesTo(Response) Closure closure) { - this.response = new Response() - closure.delegate = response - closure() - } - - void input(@DelegatesTo(Input) Closure closure) { - this.input = new Input() - closure.delegate = input - closure() - } - - /** - * The output side of a messaging contract. - */ - void outputMessage(@DelegatesTo(OutputMessage) Closure closure) { - this.outputMessage = new OutputMessage() - closure.delegate = outputMessage - closure() - } - - /** - * Whether the contract should be ignored or not. - */ - void ignored() { - this.ignored = true - } - -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Body.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Body.groovy deleted file mode 100644 index dff2fa6dbe..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Body.groovy +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString - -/** - * Represents a body of a request / response or a message - * - * @since 1.0.0 - */ -@ToString(includePackage = false, includeNames = true, includeSuper = true) -@EqualsAndHashCode(callSuper = true) -@CompileStatic -class Body extends DslProperty { - - Body(Map body) { - super( - extractValue(body, { p -> p instanceof DslProperty ? ((DslProperty) p).clientValue : p }), - extractValue(body, { p -> p instanceof DslProperty ? ((DslProperty) p).serverValue : p })) - } - - private static Map extractValue(Map body, Closure valueProvider) { - body.collectEntries { Map.Entry entry -> - [(entry.key): valueProvider(entry.value)] - } as Map - } - - Body(List bodyAsList) { - super(bodyAsList.collect { DslProperty p -> p.clientValue }, bodyAsList. - collect { DslProperty p -> p.serverValue }) - } - - Body(Object bodyAsValue) { - this("${bodyAsValue}") - } - - Body(byte[] bodyAsValue) { - super(bodyAsValue) - } - - Body(Number bodyAsValue) { - super(bodyAsValue) - } - - Body(String bodyAsValue) { - super(bodyAsValue, bodyAsValue) - } - - Body(GString bodyAsValue) { - super(bodyAsValue, bodyAsValue) - } - - Body(DslProperty bodyAsValue) { - super(bodyAsValue.clientValue, bodyAsValue.serverValue) - } - - Body(Serializable serializable) { - super(serializable, serializable) - } - - Body(MatchingStrategy matchingStrategy) { - super(matchingStrategy, matchingStrategy) - } - -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/BodyMatcher.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/BodyMatcher.groovy deleted file mode 100644 index ee213ee7a2..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/BodyMatcher.groovy +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -/** - * Matchers for the given path. - * - * @author Marcin Grzejszczak - * @since 1.0.3 - */ -interface BodyMatcher { - - /** - * What kind of matching are we dealing with - */ - MatchingType matchingType() - - /** - * Path to the path. Example for JSON it will be JSON Path - */ - String path() - - /** - * Optional value that the given path should be checked against. - * If there is no value then presence will be checked only together with - * type check. Example if we expect a JSON Path path {@code $.a} to be matched - * by type, the defined response body contained an integer but the actual one - * contained a string then the assertion should fail - */ - Object value() - - /** - * Min no of occurrence when matching by type. In all other cases it will be ignored - */ - Integer minTypeOccurrence() - - /** - * Max no of occurrence when matching by type. In all other cases it will be ignored - */ - Integer maxTypeOccurrence() -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/BodyMatchers.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/BodyMatchers.groovy deleted file mode 100644 index 391c7f9385..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/BodyMatchers.groovy +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import java.util.regex.Pattern - -import groovy.transform.Canonical -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString - -/** - * Matching strategy of dynamic parts of the body. - * - * @author Marcin Grzejszczak - * @author Olga Maciaszek-Sharma - * @since 1.0.3 - */ -@ToString(includeFields = true, includePackage = false) -class BodyMatchers { - private final RegexPatterns regexPatterns = new RegexPatterns() - protected final List matchers = [] - - void jsonPath(String path, MatchingTypeValue matchingType) { - this.matchers << new PathBodyMatcher(path, matchingType) - } - - /** - * Adds xPath matcher; even though same implementation as in {@link BodyMatchers#jsonPath(java.lang.String, org.springframework.cloud.contract.spec.internal.MatchingTypeValue)}, - * added for logical coherence in xml - * @param xPath the xPath used to find the element to match - * @param matchingTypeValue to match the element found by the xPath against - */ - void xPath(String xPath, MatchingTypeValue matchingTypeValue) { - matchers << new PathBodyMatcher(xPath, matchingTypeValue) - } - - /** - * @deprecated use{@link #matchers()} - */ - @Deprecated - List jsonPathMatchers() { - return matchers() - } - - boolean hasMatchers() { - return !this.matchers.empty - } - - List matchers() { - return this.matchers - } - - MatchingTypeValue byDate() { - return new MatchingTypeValue(MatchingType.DATE, this.regexPatterns.isoDate()) - } - - MatchingTypeValue byTime() { - return new MatchingTypeValue(MatchingType.TIME, this.regexPatterns.isoTime()) - } - - MatchingTypeValue byTimestamp() { - return new MatchingTypeValue(MatchingType.TIMESTAMP, this.regexPatterns. - isoDateTime()) - } - - RegexMatchingTypeValue byRegex(String regex) { - return byRegex(Pattern.compile(regex)) - } - - RegexMatchingTypeValue byRegex(RegexProperty regex) { - assert regex - return new RegexMatchingTypeValue(MatchingType.REGEX, regex) - } - - RegexMatchingTypeValue byRegex(Pattern regex) { - assert regex - return new RegexMatchingTypeValue(MatchingType.REGEX, new RegexProperty(regex)) - } - - MatchingTypeValue byEquality() { - return new MatchingTypeValue(MatchingType.EQUALITY, null) - } - - MatchingTypeValue byType(@DelegatesTo(MatchingTypeValueHolder) Closure closure) { - MatchingTypeValueHolder matchingTypeValue = new MatchingTypeValueHolder() - closure.delegate = matchingTypeValue - closure() - return matchingTypeValue.matchingTypeValue - } - - boolean equals(o) { - if (this.is(o)) { - return true - } - if (this.getClass() != o.class) { - return false - } - BodyMatchers that = (BodyMatchers) o - List thisMatchers = this.matchers - List thatMatchers = that.matchers - if (thisMatchers.size() != thatMatchers.size()) { - return false - } - if (new HashSet<>(thisMatchers) != new HashSet(thatMatchers)) { - return false - } - return true - } - - int hashCode() { - return (this.matchers != null ? this.matchers.hashCode() : 0) - } -} - -/** - * Matching type with corresponding values - */ - -@ToString(includePackage = false) -@EqualsAndHashCode -class RegexMatchingTypeValue extends MatchingTypeValue { - - RegexMatchingTypeValue(MatchingType type, Object value, Integer minTypeOccurrence, Integer maxTypeOccurrence) { - super(type, value, minTypeOccurrence, maxTypeOccurrence) - } - - RegexMatchingTypeValue(MatchingType type, Object value) { - super(type, value) - } - - RegexMatchingTypeValue asInteger() { - return typed(Integer) - } - - private RegexMatchingTypeValue typed(Class clazz) { - assert this.value instanceof RegexProperty - RegexProperty regexProperty = (RegexProperty) this.value - return new RegexMatchingTypeValue( - this.type, new RegexProperty(regexProperty.clientValue, - regexProperty.serverValue, clazz), - this.minTypeOccurrence, this.maxTypeOccurrence - ) - } - - RegexMatchingTypeValue asDouble() { - return typed(Double) - } - - RegexMatchingTypeValue asFloat() { - return typed(Float) - } - - RegexMatchingTypeValue asLong() { - return typed(Long) - } - - RegexMatchingTypeValue asShort() { - return typed(Short) - } - - RegexMatchingTypeValue asString() { - return typed(String) - } - - RegexMatchingTypeValue asBooleanType() { - return typed(Boolean) - } -} - -/** - * Matching type with corresponding values - */ -@Canonical -@ToString(includePackage = false) -class MatchingTypeValue { - MatchingType type - - /** - * Value to check - */ - Object value - - /** - * Min occurrence when matching by type - */ - Integer minTypeOccurrence - - /** - * Max occurrence when matching by type - */ - Integer maxTypeOccurrence -} - -@CompileStatic -@ToString(includePackage = false) -@EqualsAndHashCode -class MatchingTypeValueHolder { - MatchingTypeValue matchingTypeValue = new MatchingTypeValue(type: MatchingType.TYPE) - - MatchingTypeValue minOccurrence(int number) { - this.matchingTypeValue.minTypeOccurrence = number - return this.matchingTypeValue - } - - MatchingTypeValue maxOccurrence(int number) { - this.matchingTypeValue.maxTypeOccurrence = number - return this.matchingTypeValue - } - - MatchingTypeValue occurrence(int number) { - this.matchingTypeValue.minTypeOccurrence = number - this.matchingTypeValue.maxTypeOccurrence = number - return this.matchingTypeValue - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Common.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Common.groovy deleted file mode 100644 index ff33fcd4d0..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Common.groovy +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import java.nio.charset.Charset -import java.util.regex.Pattern - -import groovy.transform.TypeChecked - -/** - * Contains useful common methods for the DSL. - * - * @TypeChecked instead of @CompileStatic due to usage of double dispatch. - * Double dispatch doesn't work if you're using @CompileStatic - * - * @since 1.0.0 - */ -@TypeChecked -class Common { - - @Delegate - private final RegexPatterns regexPatterns = new RegexPatterns() - - Map convertObjectsToDslProperties(Map body) { - return body.collectEntries { - Map.Entry entry -> - [(entry.key): toDslProperty(entry.value)] - } as Map - } - - Collection convertObjectsToDslProperties(List body) { - return (body.collect { - Object element -> toDslProperty(element) - } as List) - } - - DslProperty toDslProperty(Object property) { - return new DslProperty(property) - } - - DslProperty toDslProperty(Map property) { - return new DslProperty(property.collectEntries { - [(it.key): toDslProperty(it.value)] - }) - } - - DslProperty toDslProperty(List property) { - return new DslProperty(property.collect { - toDslProperty(it) - }) - } - - DslProperty toDslProperty(DslProperty property) { - return property - } - - NamedProperty named(DslProperty name, DslProperty value) { - return new NamedProperty(name, value) - } - - NamedProperty named(DslProperty name, DslProperty value, DslProperty contentType) { - return new NamedProperty(name, value, contentType) - } - - NamedProperty named(Map namedMap) { - return new NamedProperty(namedMap) - } - - DslProperty value(DslProperty value) { - return value - } - - DslProperty $(DslProperty value) { - return value - } - - DslProperty value(Object value) { - return new DslProperty(value) - } - - DslProperty $(Object value) { - return new DslProperty(value) - } - - DslProperty value(ClientDslProperty client, ServerDslProperty server) { - assertThatSidesMatch(client.clientValue, server.serverValue) - return new DslProperty(client.clientValue, server.serverValue) - } - - DslProperty $(ClientDslProperty client, ServerDslProperty server) { - return value(client, server) - } - - DslProperty value(ServerDslProperty server, ClientDslProperty client) { - assertThatSidesMatch(client.clientValue, server.serverValue) - return new DslProperty(client.clientValue, server.serverValue) - } - - DslProperty $(ServerDslProperty server, ClientDslProperty client) { - return value(server, client) - } - - RegexProperty regex(String regex) { - return regexProperty(Pattern.compile(regex)) - } - - RegexProperty regex(RegexProperty regex) { - return regex - } - - // Backward compatibility with RegexPatterns - RegexProperty regex(Pattern regex) { - return regexProperty(regex) - } - - OptionalProperty optional(Object object) { - return new OptionalProperty(object) - } - - RegexProperty regexProperty(Object object) { - return new RegexProperty(object) - } - - ExecutionProperty execute(String commandToExecute) { - return new ExecutionProperty(commandToExecute) - } - - ClientDslProperty client(Object clientValue) { - return new ClientDslProperty(clientValue) - } - - /** - * Helper method to provide a better name for the consumer side - */ - ClientDslProperty stub(Object clientValue) { - return new ClientDslProperty(clientValue) - } - - /** - * Helper method to provide a better name for the consumer side - */ - ClientDslProperty consumer(Object clientValue) { - return new ClientDslProperty(clientValue) - } - - ServerDslProperty server(Object serverValue) { - return new ServerDslProperty(serverValue) - } - - /** - * Helper method to provide a better name for the consumer side - */ - ClientDslProperty c(Object clientValue) { - return new ClientDslProperty(clientValue) - } - - ServerDslProperty p(Object serverValue) { - return new ServerDslProperty(serverValue) - } - - /** - * Helper method to provide a better name for the producer side - */ - ServerDslProperty test(Object serverValue) { - return new ServerDslProperty(serverValue) - } - - /** - * Read file contents as String - * - * @param relativePath of the file to read - * @return String file contents - */ - FromFileProperty file(String relativePath) { - return file(relativePath, Charset.defaultCharset()) - } - - /** - * Read file contents as bytes[] - * - * @param relativePath of the file to read - * @return String file contents - */ - FromFileProperty fileAsBytes(String relativePath) { - return new FromFileProperty(fileLocation(relativePath), byte[]) - } - - /** - * Read file contents as String with the given Charset - * - * @param relativePath of the file to read - * @param charset to use for converting the bytes to String - * @return String file contents - */ - FromFileProperty file(String relativePath, Charset charset) { - return new FromFileProperty(fileLocation(relativePath), String, charset) - } - - /** - * Read file contents as array of bytes - * - * @param relativePath of the file to read - * @return file contents as an array of bytes - */ - private File fileLocation(String relativePath) { - URL resource = Thread.currentThread().getContextClassLoader(). - getResource(relativePath) - if (resource == null) { - throw new IllegalStateException("File [${relativePath}] is not present") - } - return new File(resource.toURI()) - } - - /** - * Helper method to provide a better name for the producer side - */ - ServerDslProperty producer(Object clientValue) { - return new ServerDslProperty(clientValue) - } - - void assertThatSidesMatch(OptionalProperty stubSide, Object testSide) { - assert testSide ==~ Pattern.compile(stubSide.optionalPattern()) - } - - void assertThatSidesMatch(Pattern pattern, String value) { - assert value ==~ pattern - } - - void assertThatSidesMatch(String value, Pattern pattern) { - assert value ==~ pattern - } - - void assertThatSidesMatch(MatchingStrategy firstSide, MatchingStrategy secondSide) { - if (firstSide.type == MatchingStrategy.Type.ABSENT && secondSide != MatchingStrategy.Type.ABSENT) { - throwAbsentError() - } - } - - void assertThatSidesMatch(MatchingStrategy firstSide, Object secondSide) { - if (firstSide.type == MatchingStrategy.Type.ABSENT) { - throwAbsentError() - } - } - - void assertThatSidesMatch(Object firstSide, MatchingStrategy secondSide) { - if (secondSide.type == MatchingStrategy.Type.ABSENT) { - throwAbsentError() - } - } - - private void throwAbsentError() { - throw new IllegalStateException("Absent cannot only be used only on one side") - } - - void assertThatSidesMatch(Object firstSide, Object secondSide) { - // do nothing - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/CompositeContractTemplate.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/CompositeContractTemplate.groovy deleted file mode 100644 index d097344999..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/CompositeContractTemplate.groovy +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic - -import org.springframework.cloud.contract.spec.ContractTemplate - -/** - * For backward compatibility when assertions take place, - * first checks the custom setup. Writes in a new format, can read - * the old format. - * - * @author Marcin Grzejszczak - * @since 2.1.0 - */ -@CompileStatic -class CompositeContractTemplate implements ContractTemplate { - - private final CustomHandlebarsContractTemplate custom = new CustomHandlebarsContractTemplate() - private final HandlebarsContractTemplate template = new HandlebarsContractTemplate() - - @Override - boolean startsWithTemplate(String text) { - if (this.custom.startsWithTemplate(text)) { - return true - } - return template.startsWithTemplate(text) - } - - @Override - boolean startsWithEscapedTemplate(String text) { - return this.template.startsWithEscapedTemplate(text) - } - - @Override - String openingTemplate() { - return this.template.openingTemplate() - } - - @Override - String closingTemplate() { - return this.template.closingTemplate() - } - - @Override - String escapedOpeningTemplate() { - return this.template.escapedOpeningTemplate() - } - - @Override - String escapedClosingTemplate() { - return this.template.escapedClosingTemplate() - } - - @Override - String url() { - return this.template.url() - } - - @Override - String query(String key) { - return this.template.query(key) - } - - @Override - String query(String key, int index) { - return this.template.query(key, index) - } - - @Override - String path() { - return this.template.path() - } - - @Override - String path(int index) { - return this.template.path(index) - } - - @Override - String header(String key) { - return this.template.header(key) - } - - @Override - String header(String key, int index) { - return this.template.header(key, index) - } - - @Override - String cookie(String key) { - return this.template.cookie(key) - } - - @Override - String body() { - return this.template.body() - } - - @Override - String escapedBody() { - // WireMock doesn't support proper escaping of JSON body - // that's why we need to use our custom handlebars extension - return this.custom.escapedBody() - } - - @Override - String escapedBody(String jsonPath) { - return this.template.escapedBody(jsonPath) - } - - @Override - String body(String jsonPath) { - return this.template.body(jsonPath) - } - - @Override - String escapedUrl() { - return this.template.escapedUrl() - } - - @Override - String escapedQuery(String key) { - return this.template.escapedQuery(key) - } - - @Override - String escapedQuery(String key, int index) { - return this.template.escapedQuery(key, index) - } - - @Override - String escapedPath() { - return this.template.escapedPath() - } - - @Override - String escapedPath(int index) { - return this.template.escapedPath(index) - } - - @Override - String escapedHeader(String key) { - return this.template.escapedHeader(key) - } - - @Override - String escapedHeader(String key, int index) { - return this.template.escapedHeader(key, index) - } - - @Override - String escapedCookie(String key) { - return this.template.escapedCookie(key) - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Cookie.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Cookie.groovy deleted file mode 100644 index 20747a636d..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Cookie.groovy +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString - -/** - * Represents a http cookie - * - * @author Alex Xandra Albert Sim - * @since 1.2.5 - */ -@EqualsAndHashCode(includeFields = true, callSuper = true) -@ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true, includeSuper = true) -@CompileStatic -class Cookie extends DslProperty { - - String key - - Cookie(String key, DslProperty dslProperty) { - super(dslProperty.clientValue, dslProperty.serverValue) - this.key = key - } - - Cookie(String key, MatchingStrategy value) { - super(value) - this.key = key - } - - Cookie(String key, Object value) { - super(value) - this.key = key - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Cookies.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Cookies.groovy deleted file mode 100644 index 4717fb2e5b..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Cookies.groovy +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import java.util.function.Consumer - -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString -import groovy.transform.TypeChecked - -/** - * Represents a set of http cookies - * - * @author Alex Xandra Albert Sim - * @since 1.2.5 - */ -@EqualsAndHashCode(includeFields = true) -@ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true) -@TypeChecked -class Cookies { - - Set entries = [] - - void cookie(Map singleCookie) { - Map.Entry first = singleCookie.entrySet().first() - entries << new Cookie(first?.key, first?.value) - } - - void cookie(String cookieKey, Object cookieValue) { - entries << new Cookie(cookieKey, cookieValue) - } - - void executeForEachCookie(Closure closure) { - entries?.each { - cookie -> closure(cookie) - } - } - - void executeForEachCookie(Consumer consumer) { - entries?.each { - cookie -> consumer.accept(cookie) - } - } - - DslProperty matching(String value) { - return new DslProperty(value) - } - - boolean equals(o) { - if (this.is(o)) { - return true - } - if (getClass() != o.class) { - return false - } - Cookies cookies = (Cookies) o - if (cookies != cookies.entries) { - return false - } - return true - } - - int hashCode() { - return entries.hashCode() - } - - /** - * Converts the headers into their stub side representations and returns as - * a map of String key => Object value. - */ - Map asStubSideMap() { - def acc = [:].withDefault { [] as Collection } - return entries. - inject(acc as Map) { Map map, Cookie cookie -> - map[cookie.key] = cookie.clientValue - return map - } as Map - } - - /** - * Converts the headers into their stub side representations and returns as - * a map of String key => Object value. - */ - Map asTestSideMap() { - def acc = [:].withDefault { [] as Collection } - return entries. - inject(acc as Map) { Map map, Cookie cookie -> - map[cookie.key] = cookie.serverValue - return map - } as Map - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/CustomHandlebarsContractTemplate.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/CustomHandlebarsContractTemplate.groovy deleted file mode 100644 index a169828f45..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/CustomHandlebarsContractTemplate.groovy +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic -import groovy.transform.PackageScope - -import org.springframework.cloud.contract.spec.ContractTemplate - -/** - * Represents the structure of templates using Handlebars compatible with - * WireMock template model requirements. - * - * @author Marcin Grzejszczak - * @since 1.1.0* - * @deprecated use{@link HandlebarsContractTemplate} - */ -@CompileStatic -@PackageScope -class CustomHandlebarsContractTemplate implements ContractTemplate { - - @Override - String openingTemplate() { - return "{{{" - } - - @Override - String closingTemplate() { - return "}}}" - } - - @Override - String url() { - return wrapped("request.url") - } - - @Override - String query(String key) { - return query(key, 0) - } - - @Override - String query(String key, int index) { - return wrapped("request.query.${key}.[${index}]") - } - - @Override - String path() { - return wrapped("request.path") - } - - @Override - String path(int index) { - return wrapped("request.path.[${index}]") - } - - @Override - String header(String key) { - return header(key, 0) - } - - @Override - String header(String key, int index) { - return wrapped("request.headers.${key}.[${index}]") - } - - @Override - String cookie(String key) { - return wrapped("request.cookies.${key}") - } - - @Override - String body() { - return wrapped("request.body") - } - - @Override - String escapedBody() { - return wrapped("escapejsonbody") - } - - @Override - String escapedBody(String jsonPath) { - return body(jsonPath) - } - - @Override - String body(String jsonPath) { - return wrapped("jsonpath this '${jsonPath}'") - } - - private String wrapped(String text) { - return openingTemplate() + text + closingTemplate() - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/DslProperty.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/DslProperty.groovy deleted file mode 100644 index a6c654849f..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/DslProperty.groovy +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic -import groovy.transform.ToString - -/** - * Represents an element of a DSL that can contain client or sever side values - * - * @since 1.0.0 - */ -@CompileStatic -@ToString(includePackage = false, includeNames = true) -class DslProperty implements Serializable { - - final T clientValue - final T serverValue - - DslProperty(T clientValue, T serverValue) { - this.clientValue = clientValue - this.serverValue = serverValue - } - - DslProperty(T singleValue) { - this.clientValue = singleValue - this.serverValue = singleValue - } - - boolean isSingleValue() { - return this.clientValue == this.serverValue || - (this.clientValue != null && this.serverValue == null) || - (this.serverValue != null && this.clientValue == null) - } - - boolean equals(o) { - if (this.is(o)) { - return true - } - if (getClass() != o.class) { - return false - } - DslProperty that = (DslProperty) o - if (this.clientValue != that.clientValue) { - return false - } - if (this.serverValue != that.serverValue) { - return false - } - return true - } - - int hashCode() { - int result - result = (this.clientValue != null ? this.clientValue.hashCode() : 0) - result = 31 * result - +(this.serverValue != null ? this.serverValue. - hashCode() : 0) - return result - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ExecutionProperty.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ExecutionProperty.groovy deleted file mode 100644 index d08c60692d..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ExecutionProperty.groovy +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString - -/** - * Represents a property that will become an executable method in the - * generated tests - * - * @since 1.0.0 - */ -@CompileStatic -@EqualsAndHashCode -@ToString(includePackage = false, includeNames = true) -class ExecutionProperty implements Serializable { - - private static final String PLACEHOLDER_VALUE = '$it' - - final String executionCommand - - ExecutionProperty(String executionCommand) { - this.executionCommand = executionCommand - } - - /** - * Inserts the provided code as a parameter to the method and returns - * the code that represents that method execution - */ - String insertValue(String valueToInsert) { - return executionCommand.replace(PLACEHOLDER_VALUE, valueToInsert) - } - - @Override - String toString() { - return executionCommand - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/FromFileProperty.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/FromFileProperty.groovy deleted file mode 100644 index a501beb9e4..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/FromFileProperty.groovy +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import java.nio.charset.Charset - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString -/** - * Represents a property that will become a File content - * - * @since 2.1.0 - */ -@CompileStatic -@EqualsAndHashCode -@ToString(includePackage = false, includeNames = true) -class FromFileProperty implements Serializable { - - final File file - final String charset - final Class type - - FromFileProperty(File file, Class type) { - this(file, type, Charset.defaultCharset()) - } - - FromFileProperty(File file, Class type, Charset charset) { - this.file = file - this.type = type - this.charset = charset.toString() - } - - boolean isString() { - return this.type == String - } - - boolean isByte() { - // need to take class from a Groovy file, otherwise - // boolean#getAt(List) static compilation issue is thrown - return this.type == GroovyClass.BYTE_CLASS - } - - String asString() { - return new String(this.file.bytes, this.charset) - } - - String fileName() { - return this.file.getName() - } - - byte[] asBytes() { - return this.file.bytes - } - - @Override - String toString() { - return asString() - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/FromRequest.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/FromRequest.groovy deleted file mode 100644 index 7701459184..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/FromRequest.groovy +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic - -import org.springframework.cloud.contract.spec.ContractTemplate - -/** - * Helper class to reference the request body parameters - * - * @author Marcin Grzejszczak - * @since 1.1.0 - */ -@CompileStatic -class FromRequest { - - private final ContractTemplate template - - FromRequest() { - this.template = template() - } - - private ContractTemplate template() { - return new CompositeContractTemplate() - } - - /** - * URL path and query - */ - DslProperty url() { - return new DslProperty(template.escapedUrl()) - } - - /** - * First value of a query parameter e.g. request.query.search - * @param key - */ - DslProperty query(String key) { - return new DslProperty(template.escapedQuery(key)) - } - - /** - * nth value of a query parameter (zero indexed) e.g. request.query.search.[5] - * @param key - * @param index - */ - DslProperty query(String key, int index) { - return new DslProperty(template.escapedQuery(key, index)) - } - - /** - * URL path - */ - DslProperty path() { - return new DslProperty(template.escapedPath()) - } - - /** - * nth value of a URL path (zero indexed) e.g. {{{ request.path.[2] }}}* @param index - */ - DslProperty path(int index) { - return new DslProperty(template.escapedPath(index)) - } - - /** - * First value of a request header e.g. request.headers.X-Request-Id - * @param key - */ - DslProperty header(String key) { - return new DslProperty(template.escapedHeader(key)) - } - - /** - * nth value of a request header (zero indexed) e.g. request.headers.X-Request-Id - * @param key - */ - DslProperty header(String key, int index) { - return new DslProperty(template.escapedHeader(key, index)) - } - - /** - * Retruns the tempalte for retrieving the first value of a cookie with certain key - * @param key - */ - DslProperty cookie(String key) { - return new DslProperty(template.escapedCookie(key)) - } - - /** - * Request body text (avoid for non-text bodies) - */ - DslProperty body() { - return new DslProperty(template.escapedBody()) - } - - /** - * Request body text for the given JsonPath - */ - DslProperty body(String jsonPath) { - return new DslProperty(template.escapedBody(jsonPath)) - } - - /** - * Unescaped URL path and query - */ - DslProperty rawUrl() { - return new DslProperty(template.url()) - } - - /** - * Unescaped First value of a query parameter e.g. request.query.search - * @param key - */ - DslProperty rawQuery(String key) { - return new DslProperty(template.query(key)) - } - - /** - * Unescaped nth value of a query parameter (zero indexed) e.g. request.query.search.[5] - * @param key - * @param index - */ - DslProperty rawQuery(String key, int index) { - return new DslProperty(template.query(key, index)) - } - - /** - * Unescaped URL path - */ - DslProperty rawPath() { - return new DslProperty(template.path()) - } - - /** - * Unescaped nth value of a URL path (zero indexed) e.g. {{{ request.path.[2] }}}* @param index - */ - DslProperty rawPath(int index) { - return new DslProperty(template.path(index)) - } - - /** - * Unescaped First value of a request header e.g. request.headers.X-Request-Id - * @param key - */ - DslProperty rawHeader(String key) { - return new DslProperty(template.header(key)) - } - - /** - * Unescaped nth value of a request header (zero indexed) e.g. request.headers.X-Request-Id - * @param key - */ - DslProperty rawHeader(String key, int index) { - return new DslProperty(template.header(key, index)) - } - - /** - * Unescaped Retruns the tempalte for retrieving the first value of a cookie with certain key - * @param key - */ - DslProperty rawCookie(String key) { - return new DslProperty(template.cookie(key)) - } - - /** - * Unescaped Request body text (avoid for non-text bodies) - */ - DslProperty rawBody() { - return new DslProperty(template.body()) - } - - /** - * Unescaped Request body text for the given JsonPath - */ - DslProperty rawBody(String jsonPath) { - return new DslProperty(template.body(jsonPath)) - } - -} - - diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/HandlebarsContractTemplate.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/HandlebarsContractTemplate.groovy deleted file mode 100644 index 0ba8f635fe..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/HandlebarsContractTemplate.groovy +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic - -import org.springframework.cloud.contract.spec.ContractTemplate - -/** - * Represents the structure of templates using Handlebars compatible with - * WireMock template model requirements. - * - * @author Marcin Grzejszczak - * @since 1.1.0 - */ -@CompileStatic -class HandlebarsContractTemplate implements ContractTemplate { - - @Override - String openingTemplate() { - return "{{" - } - - @Override - String closingTemplate() { - return "}}" - } - - @Override - String escapedOpeningTemplate() { - return "{{{" - } - - @Override - String escapedClosingTemplate() { - return "}}}" - } - - @Override - String url() { - return wrapped("request.url") - } - - @Override - String query(String key) { - return query(key, 0) - } - - @Override - String query(String key, int index) { - return wrapped("request.query.${key}.[${index}]") - } - - @Override - String path() { - return wrapped("request.path") - } - - @Override - String path(int index) { - return wrapped("request.path.[${index}]") - } - - @Override - String header(String key) { - return header(key, 0) - } - - @Override - String header(String key, int index) { - return wrapped("request.headers.${key}.[${index}]") - } - - @Override - String cookie(String key) { - return wrapped("request.cookies.${key}") - } - - @Override - String body() { - return wrapped("request.body") - } - - @Override - String escapedBody() { - return escapedWrapped("request.body") - } - - @Override - String escapedBody(String jsonPath) { - return escapedWrapped("jsonPath request.body '${jsonPath}'") - } - - @Override - String body(String jsonPath) { - return wrapped("jsonPath request.body '${jsonPath}'") - } - - @Override - String escapedUrl() { - return escapedWrapped("request.url") - } - - @Override - String escapedQuery(String key) { - return escapedQuery(key, 0) - } - - @Override - String escapedQuery(String key, int index) { - return escapedWrapped("request.query.${key}.[${index}]") - } - - @Override - String escapedPath() { - return escapedWrapped("request.path") - } - - @Override - String escapedPath(int index) { - return escapedWrapped("request.path.[${index}]") - } - - @Override - String escapedHeader(String key) { - return escapedHeader(key, 0) - } - - @Override - String escapedHeader(String key, int index) { - return escapedWrapped("request.headers.${key}.[${index}]") - } - - @Override - String escapedCookie(String key) { - return escapedWrapped("request.cookies.${key}") - } - - private String wrapped(String text) { - return openingTemplate() + text + closingTemplate() - } - - private String escapedWrapped(String text) { - return escapedOpeningTemplate() + text + escapedClosingTemplate() - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Header.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Header.groovy deleted file mode 100644 index fb0aeb3343..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Header.groovy +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString - -/** - * Represents a header of a request / response or a message - * - * @since 1.0.0 - */ -@EqualsAndHashCode(includeFields = true, callSuper = true) -@ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true, includeSuper = true) -@CompileStatic -class Header extends DslProperty { - - String name - - Header(String name, DslProperty dslProperty) { - super(dslProperty.clientValue, dslProperty.serverValue) - this.name = name - } - - Header(String name, MatchingStrategy value) { - super(value) - this.name = name - } - - Header(String name, Object value) { - super(value) - this.name = name - } - -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Headers.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Headers.groovy deleted file mode 100644 index 9931991184..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Headers.groovy +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import java.util.function.Consumer -import java.util.regex.Pattern - -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString -import groovy.transform.TypeChecked -import groovy.transform.stc.ClosureParams -import groovy.transform.stc.SimpleType -/** - * Represents a set of headers of a request / response or a message - * - * @since 1.0.0 - */ -@EqualsAndHashCode(includeFields = true) -@ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true) -@TypeChecked -class Headers { - - @Delegate - MediaTypes mediaTypes = new MediaTypes() - @Delegate - HttpHeaders httpHeaders = new HttpHeaders() - @Delegate - MessagingHeaders messagingHeaders = new MessagingHeaders() - - Set
entries = [] - - void header(Map singleHeader) { - Map.Entry first = singleHeader.entrySet().first() - entries << new Header(first?.key, first?.value) - } - - void header(String headerKey, Object headerValue) { - entries << new Header(headerKey, headerValue) - } - - void executeForEachHeader(Closure closure) { - entries?.each { - header -> closure(header) - } - } - - void executeForEachHeader(Consumer
consumer) { - entries?.each { - header -> consumer.accept(header) - } - } - - void headers(Set
headers) { - entries.addAll(headers) - } - - void accept(String contentType) { - header(accept(), matching(contentType)) - } - - void contentType(String contentType) { - header(httpHeaders.contentType(), matching(contentType)) - } - - void messagingContentType(String contentType) { - header(messagingHeaders.messagingContentType(), matching(contentType)) - } - - /** - * If for the consumer / producer you want to match exactly only - * the root of content type. I.e. {@code application/json;charset=UTF8} - * you care only about {@code application/json} then you should - * use this method - */ - DslProperty matching(String value) { - return new DslProperty(value) - } - - protected NotToEscapePattern notEscaped(Pattern pattern) { - return new NotToEscapePattern(pattern) - } - - /** - * Converts the headers via the provided closure - */ - Map asMap(@ClosureParams(value = SimpleType.class, - options = [ - "java.lang.String", - "org.springframework.cloud.contract.spec.internal.DslProperty" - ]) Closure closure) { - def acc = [:].withDefault { [] as Collection } - return entries. - inject(acc as Map) { Map map, Header header -> - map[header.name] = closure(header.name, header) - return map - } as Map - } - - /** - * Converts the headers into their stub side representations and returns as - * a map of String key => Object value. - */ - Map asStubSideMap() { - return asMap({ String headerName, DslProperty prop -> prop.clientValue }) - } - - /** - * Converts the headers into their stub side representations and returns as - * a map of String key => Object value. - */ - Map asTestSideMap() { - return asMap({ String headerName, DslProperty prop -> prop.serverValue }) - } - - boolean equals(o) { - if (this.is(o)) { - return true - } - if (getClass() != o.class) { - return false - } - Headers headers = (Headers) o - if (entries != headers.entries) { - return false - } - return true - } - - int hashCode() { - return entries.hashCode() - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/HttpMethods.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/HttpMethods.groovy deleted file mode 100644 index eaad72b18f..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/HttpMethods.groovy +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString - -/** - * Contains Http Methods - * - * @author Marcin Grzejszczak - * @since 1.0.2 - */ -@CompileStatic -@EqualsAndHashCode(includeFields = true) -@ToString(includePackage = false) -class HttpMethods { - - HttpMethod GET() { - return HttpMethod.GET - } - - HttpMethod HEAD() { - return HttpMethod.HEAD - } - - HttpMethod POST() { - return HttpMethod.POST - } - - HttpMethod PUT() { - return HttpMethod.PUT - } - - HttpMethod PATCH() { - return HttpMethod.PATCH - } - - HttpMethod DELETE() { - return HttpMethod.DELETE - } - - HttpMethod OPTIONS() { - return HttpMethod.OPTIONS - } - - HttpMethod TRACE() { - return HttpMethod.TRACE - } - - enum HttpMethod { - GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Input.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Input.groovy deleted file mode 100644 index 849ba99274..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Input.groovy +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import java.util.regex.Pattern - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString -import groovy.transform.TypeChecked -import groovy.util.logging.Commons - -/** - * Represents an input for messaging. The input can be a message or some - * action inside the application. - * - * @author Marcin Grzejszczak - * @author Tim Ysewyn - * @since 1.0.0 - */ -@Commons -@TypeChecked -@EqualsAndHashCode -@ToString(includePackage = false, includeNames = true) -class Input extends Common { - - @Delegate - ClientPatternValueDslProperty property = new ClientPatternValueDslProperty() - - DslProperty messageFrom - ExecutionProperty triggeredBy - Headers messageHeaders = new Headers() - BodyType messageBody - ExecutionProperty assertThat - BodyMatchers bodyMatchers - - Input() {} - - Input(Input input) { - this.messageFrom = input.messageFrom - this.messageHeaders = input.messageHeaders - this.messageBody = input.messageBody - } - - /** - * Name of a destination from which message would come to trigger action in the system - */ - void messageFrom(String messageFrom) { - this.messageFrom = new DslProperty<>(messageFrom) - } - - /** - * Name of a destination from which message would come to trigger action in the system - */ - void messageFrom(DslProperty messageFrom) { - this.messageFrom = messageFrom - } - - /** - * Function that needs to be executed to trigger action in the system - */ - void triggeredBy(String triggeredBy) { - this.triggeredBy = new ExecutionProperty(triggeredBy) - } - - BodyType messageBody(Object bodyAsValue) { - this.messageBody = new BodyType(bodyAsValue) - } - - void messageHeaders(@DelegatesTo(Headers) Closure closure) { - this.messageHeaders = new Headers() - closure.delegate = messageHeaders - closure() - } - - DslProperty value(ClientDslProperty client) { - Object dynamicValue = client.clientValue - Object concreteValue = client.serverValue - if (dynamicValue instanceof RegexProperty) { - return dynamicValue.dynamicClientConcreteProducer() - } - return new DslProperty(dynamicValue, concreteValue) - } - - DslProperty value(RegexProperty prop) { - return value(client(prop)) - } - - DslProperty $(RegexProperty prop) { - return value(client(prop)) - } - - DslProperty $(ClientDslProperty client) { - return value(client) - } - - @Override - RegexProperty regexProperty(Object object) { - return new RegexProperty(object).dynamicClientConcreteProducer() - } - - @EqualsAndHashCode(includeFields = true, callSuper = true) - @ToString(includeSuper = true) - static class BodyType extends DslProperty { - - BodyType(Object clientValue, Object serverValue) { - super(clientValue, serverValue) - } - - BodyType(Object singleValue) { - super(singleValue) - } - } - - void assertThat(String assertThat) { - this.assertThat = new ExecutionProperty(assertThat) - } - - /** - * @deprecated Deprecated in favor of bodyMatchers to support other future bodyMatchers too - */ - @Deprecated - void stubMatchers(@DelegatesTo(BodyMatchers) Closure closure) { - log.warn("stubMatchers method is deprecated. Please use bodyMatchers instead") - bodyMatchers(closure) - } - - void bodyMatchers(@DelegatesTo(BodyMatchers) Closure closure) { - this.bodyMatchers = new BodyMatchers() - closure.delegate = this.bodyMatchers - closure() - } - - @CompileStatic - @EqualsAndHashCode(includeFields = true) - @ToString(includePackage = false) - private class ClientPatternValueDslProperty extends PatternValueDslProperty { - - @Override - protected ClientDslProperty createProperty(Pattern pattern, Object generatedValue) { - return new ClientDslProperty(pattern, generatedValue) - } - } -} - - -@CompileStatic -@EqualsAndHashCode -@ToString(includePackage = false) -class ServerInput extends Input { - ServerInput(Input request) { - super(request) - } -} - -@CompileStatic -@EqualsAndHashCode -@ToString(includePackage = false) -class ClientInput extends Input { - ClientInput(Input request) { - super(request) - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MatchingStrategy.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MatchingStrategy.groovy deleted file mode 100644 index cec8d31d8a..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MatchingStrategy.groovy +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString - -/** - * Represents a matching strategy for a JSON - * - * @since 1.0.0 - */ -@EqualsAndHashCode(includeFields = true, callSuper = true) -@ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true, includeSuper = true) -@CompileStatic -class MatchingStrategy extends DslProperty { - - Type type - JSONCompareMode jsonCompareMode - - MatchingStrategy(Object value, Type type) { - this(value, type, null) - } - - MatchingStrategy(Object value, Type type, JSONCompareMode jsonCompareMode) { - super(value) - this.type = type - this.jsonCompareMode = jsonCompareMode - } - - MatchingStrategy(DslProperty value, Type type) { - this(value, type, null) - } - - MatchingStrategy(DslProperty value, Type type, JSONCompareMode jsonCompareMode) { - super(value.clientValue, value.serverValue) - this.type = type - this.jsonCompareMode = jsonCompareMode - } - - enum Type { - EQUAL_TO("equalTo"), CONTAINS("containing"), MATCHING("matching"), NOT_MATCHING("notMatching"), - EQUAL_TO_JSON("equalToJson"), EQUAL_TO_XML("equalToXml"), ABSENT("absent"), BINARY_EQUAL_TO("binaryEqualTo") - - final String name - - Type(name) { - this.name = name - } - } - -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MatchingType.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MatchingType.groovy deleted file mode 100644 index aeabb34b76..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MatchingType.groovy +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic - -/** - * Represents the type of matching the should be done against - * the body of the request or response. - * - * @author Marcin Grzejszczak - * @author Tim Ysewyn - * @author Olga Maciaszek-Sharma - * @since 1.0.3 - */ -@CompileStatic -enum MatchingType { - /** - * The default approach - ensures that the value received - * in a body for the given path is as presented in the contract body - */ - EQUALITY, - /** - * Verification by type - is the type received in a body - * for the given path is of the same type. If it's a collection - * you can verify number of occurrences - */ - TYPE, - /** - * Special version of regex for date check - */ - DATE, - /** - * Special version of regex for time check - */ - TIME, - /** - * Special version of regex for timestamp check - */ - TIMESTAMP, - /** - * Verification if the value for the given path matches the - * provided regex - */ - REGEX, - /** - * The user can provide custom command to execute - */ - COMMAND, - /** - * Verification if the value for the given path is null - */ - NULL - - static boolean regexRelated(MatchingType type) { - if (type == EQUALITY || type == TYPE || type == COMMAND || type == NULL) { - return false - } - return true - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MediaTypes.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MediaTypes.groovy deleted file mode 100644 index c0f7a3e77f..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MediaTypes.groovy +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString - -/** - * Contains most commonly used media types - * - * @author Marcin Grzejszczak - * @since 1.0.2 - */ -@CompileStatic -@EqualsAndHashCode -@ToString(includePackage = false) -class MediaTypes { - - String allValue() { - return "*/*" - } - - String applicationAtomXml() { - return "application/atom+xml" - } - - String applicationFormUrlencoded() { - return "application/x-www-form-urlencoded" - } - - String applicationJson() { - return "application/json" - } - - String applicationJsonUtf8() { - return applicationJson() + ";charset=UTF-8" - } - - String applicationOctetStream() { - return "application/octet-stream" - } - - String applicationPdf() { - return "application/pdf" - } - - String applicationXhtmlXml() { - return "application/xhtml+xml" - } - - String applicationXml() { - return "application/xml" - } - - String imageGif() { - return "image/gif" - } - - String imageJpeg() { - return "image/jpeg" - } - - String imagePng() { - return "image/png" - } - - String multipartFormData() { - return "multipart/form-data" - } - - String textHtml() { - return "text/html" - } - - String textMarkdown() { - return "text/markdown" - } - - String textPlain() { - return "text/plain" - } - - String textXml() { - return "text/xml" - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Multipart.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Multipart.groovy deleted file mode 100644 index e4de84b441..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Multipart.groovy +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString - -@ToString(includePackage = false, includeFields = true, includeNames = true, includeSuper = true) -@EqualsAndHashCode(callSuper = true) -@CompileStatic -class Multipart extends DslProperty { - - Multipart(Map multipart) { - super( - extractValue(multipart, { DslProperty p -> p.clientValue }), - extractValue(multipart, { DslProperty p -> p.serverValue })) - } - - private static Map extractValue(Map multipart, Closure valueProvider) { - return multipart.collectEntries { Map.Entry entry -> - [(entry.key): valueProvider(entry.value)] - } as Map - } - - Multipart(List multipartAsList) { - super(multipartAsList.collect { DslProperty p -> p.clientValue }, multipartAsList. - collect { DslProperty p -> p.serverValue }) - } - - Multipart(Object multipartAsValue) { - this("${multipartAsValue}") - } - - Multipart(GString multipartAsValue) { - super(multipartAsValue, multipartAsValue) - } - - Multipart(DslProperty multipartAsValue) { - super(multipartAsValue.clientValue, multipartAsValue.serverValue) - } - - Multipart(MatchingStrategy matchingStrategy) { - super(matchingStrategy, matchingStrategy) - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/NamedProperty.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/NamedProperty.groovy deleted file mode 100644 index 6bc791f6cc..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/NamedProperty.groovy +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString - -/** - * Represents a property that has name and content. Used together with - * multipart requests. - * - * @since 1.0.0 - */ -@ToString(includePackage = false, includeNames = true) -@EqualsAndHashCode(includeFields = true) -@CompileStatic -class NamedProperty { - - private static final String NAME = 'name' - private static final String CONTENT = 'content' - private static final String CONTENT_TYPE = 'contentType' - - DslProperty name - DslProperty value - DslProperty contentType - - NamedProperty(DslProperty name, DslProperty value) { - this.name = name - this.value = value - this.contentType = null - } - - NamedProperty(DslProperty name, DslProperty value, DslProperty contentType) { - this.name = name - this.value = value - this.contentType = contentType - } - - NamedProperty(Map namedMap) { - this(asDslProperty(namedMap?.get(NAME)), - asDslProperty(namedMap?.get(CONTENT)), - asDslProperty(namedMap?.get(CONTENT_TYPE))) - } - - static DslProperty asDslProperty(Object o) { - if (o == null) { - return null - } - if (o instanceof DslProperty) { - return o - } - return new DslProperty(o) - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/OptionalProperty.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/OptionalProperty.groovy deleted file mode 100644 index 84ef03c347..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/OptionalProperty.groovy +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import java.util.regex.Pattern - -import groovy.transform.CompileStatic -import groovy.transform.ToString -import repackaged.nl.flotsam.xeger.Xeger - -/** - * Represents a property that may or may not be there - * - * @since 1.0.0 - */ -@CompileStatic -@ToString(includePackage = false, includeNames = true) -class OptionalProperty implements Serializable, CanBeDynamic { - final Object value - - OptionalProperty(Object value) { - this.value = value - } - - /** - * String version of a regular expression that wraps the provided value - * in an optional function - */ - String optionalPattern() { - return "(${value()})?" - } - - String value() { - return this.value instanceof RegexProperty ? - ((RegexProperty) this.value).pattern.pattern() : this.value - } - - protected Pattern optionalPatternValue() { - return Pattern.compile(optionalPattern()) - } - - @Override - String toString() { - return optionalPattern() - } - - @Override - Object generateConcreteValue() { - return new Xeger(optionalPattern()).generate() - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/OutputMessage.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/OutputMessage.groovy deleted file mode 100644 index e836730674..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/OutputMessage.groovy +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import java.util.regex.Pattern - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString -import groovy.transform.TypeChecked -import groovy.util.logging.Commons - -/** - * Represents an output for messaging. Used for verifying - * the body and headers that are sent. - * - * @author Marcin Grzejszczak - * @author Tim Ysewyn - * @since 1.0.0 - */ -@Commons -@TypeChecked -@EqualsAndHashCode -@ToString(includePackage = false, includeNames = true) -class OutputMessage extends Common { - - @Delegate - ServerPatternValueDslProperty property = new ServerPatternValueDslProperty() - - DslProperty sentTo - Headers headers - DslProperty body - ExecutionProperty assertThat - ResponseBodyMatchers bodyMatchers - - OutputMessage() {} - - OutputMessage(OutputMessage outputMessage) { - this.sentTo = outputMessage.sentTo - this.headers = outputMessage.headers - this.body = outputMessage.body - } - - void sentTo(String sentTo) { - this.sentTo = new DslProperty(sentTo) - } - - void sentTo(DslProperty sentTo) { - this.sentTo = sentTo - } - - void body(Object bodyAsValue) { - this.body = new DslProperty(bodyAsValue) - } - - void body(DslProperty bodyAsValue) { - this.body = bodyAsValue - } - - void headers(@DelegatesTo(Headers) Closure closure) { - this.headers = new Headers() - closure.delegate = headers - closure() - } - - void assertThat(String assertThat) { - this.assertThat = new ExecutionProperty(assertThat) - } - - /** - * @deprecated - use the server dsl property - */ - @Deprecated - DslProperty value(ClientDslProperty clientDslProperty) { - return value(new ServerDslProperty(clientDslProperty.serverValue, clientDslProperty.clientValue)) - } - - DslProperty value(ServerDslProperty serverDslProperty) { - Object concreteValue = serverDslProperty.clientValue - Object dynamicValue = serverDslProperty.serverValue - // for the output messages ran via stub runner, - // entries have to have fixed values - if (dynamicValue instanceof RegexProperty) { - return dynamicValue - .concreteClientEscapedDynamicProducer() - } - return new DslProperty(concreteValue, dynamicValue) - } - - /** - * @deprecated - use the server dsl property - */ - @Deprecated - DslProperty $(ClientDslProperty client) { - return value(client) - } - - DslProperty $(ServerDslProperty property) { - return value(property) - } - - DslProperty $(Pattern pattern) { - return value(new RegexProperty(pattern)) - } - - DslProperty $(RegexProperty pattern) { - return value(pattern) - } - - DslProperty value(RegexProperty pattern) { - return value(producer(pattern)) - } - - DslProperty $(OptionalProperty property) { - return value(producer(property.optionalPatternValue())) - } - - @Override - RegexProperty regexProperty(Object object) { - return new RegexProperty(object).concreteClientDynamicProducer() - } - - /** - * @deprecated Deprecated in favor of bodyMatchers to support other future bodyMatchers too - */ - @Deprecated - void testMatchers(@DelegatesTo(ResponseBodyMatchers) Closure closure) { - log.warn("testMatchers method is deprecated. Please use bodyMatchers instead") - bodyMatchers(closure) - } - - void bodyMatchers(@DelegatesTo(ResponseBodyMatchers) Closure closure) { - this.bodyMatchers = new ResponseBodyMatchers() - closure.delegate = this.bodyMatchers - closure() - } - - @CompileStatic - @EqualsAndHashCode(includeFields = true) - @ToString(includePackage = false) - private class ServerPatternValueDslProperty extends PatternValueDslProperty { - - @Override - protected ServerDslProperty createProperty(Pattern pattern, Object generatedValue) { - return new ServerDslProperty(pattern, generatedValue) - } - } -} - -@CompileStatic -@EqualsAndHashCode -@ToString(includePackage = false) -class ServerOutputMessage extends OutputMessage { - ServerOutputMessage(OutputMessage request) { - super(request) - } -} - -@CompileStatic -@EqualsAndHashCode -@ToString(includePackage = false) -class ClientOutputMessage extends OutputMessage { - ClientOutputMessage(OutputMessage request) { - super(request) - } -} - diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/PathBodyMatcher.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/PathBodyMatcher.groovy deleted file mode 100644 index ff1e6c2636..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/PathBodyMatcher.groovy +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.Canonical -import groovy.transform.CompileStatic -import groovy.transform.ToString - -/** - * @author Marcin Grzejszczak - */ -@ToString(includePackage = false) -@Canonical -@CompileStatic -class PathBodyMatcher implements BodyMatcher { - - String path - MatchingTypeValue matchingTypeValue - - @Override - MatchingType matchingType() { - return this.matchingTypeValue.type - } - - @Override - String path() { - return this.path - } - - @Override - Object value() { - return this.matchingTypeValue.value - } - - @Override - Integer minTypeOccurrence() { - return this.matchingTypeValue.minTypeOccurrence - } - - @Override - Integer maxTypeOccurrence() { - return this.matchingTypeValue.maxTypeOccurrence - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/PatternValueDslProperty.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/PatternValueDslProperty.groovy deleted file mode 100644 index 544dbfc45f..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/PatternValueDslProperty.groovy +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import java.util.regex.Pattern - -import groovy.transform.CompileStatic -import groovy.transform.PackageScope -import org.apache.commons.lang3.RandomStringUtils - -/** - * @author Marcin Grzejszczak - * @author Tim Ysewyn - */ -@PackageScope -@CompileStatic -abstract class PatternValueDslProperty implements RegexCreatingProperty { - - private final Random random = new Random() - - protected T createAndValidateProperty(Pattern pattern, Object object = null) { - if (object) { - String generatedValue = object as String - boolean matches = pattern.matcher(generatedValue).matches() - if (!matches) { - throw new IllegalStateException("The generated value [${generatedValue}] doesn't match the pattern [${pattern.pattern()}]") - } - return createProperty(pattern, object) - } - return createProperty(pattern, object) - } - - /** - * Method to generate the PatternValue. The resulting implementation - * will create either a Client or a Server side impl. - * - * @param pattern - pattern for which the value will be generated or reused - * @param generatedValue - Nullable - potential generated value to be reused - * @return {@link DslProperty} wrapping a pattern and generated value - */ - protected abstract T createProperty(Pattern pattern, Object generatedValue) - - @Override - T anyAlphaUnicode() { - return createAndValidateProperty(RegexPatterns.ONLY_ALPHA_UNICODE, - RandomStringGenerator.randomString(20)) - } - - @Override - T anyAlphaNumeric() { - return createAndValidateProperty(RegexPatterns.ALPHA_NUMERIC, - RandomStringUtils.randomAlphanumeric(20)) - } - - @Override - T anyNumber() { - return createAndValidateProperty(RegexPatterns.NUMBER, this.random.nextInt()) - } - - @Override - T anyInteger() { - return createAndValidateProperty(RegexPatterns.INTEGER, this.random.nextInt()) - } - - @Override - T anyPositiveInt() { - return createAndValidateProperty(RegexPatterns.POSITIVE_INT, Math. - abs(this.random.nextInt() + 1)) - } - - @Override - T anyDouble() { - return createAndValidateProperty(RegexPatterns.DOUBLE, this.random. - nextInt(100) - + this.random.nextDouble()) - } - - @Override - T anyHex() { - return createAndValidateProperty(RegexPatterns.HEX, - RandomStringUtils.random(10, "0123456789abcdef")) - } - - @Override - T aBoolean() { - return createAndValidateProperty(RegexPatterns.TRUE_OR_FALSE) - } - - @Override - T anyIpAddress() { - return createAndValidateProperty(RegexPatterns.IP_ADDRESS, "192.168.0." - + this.random. - nextInt(10)) - } - - @Override - T anyHostname() { - return createAndValidateProperty(RegexPatterns.HOSTNAME_PATTERN, "https://foo" - + this.random. - nextInt() - + ".com") - } - - @Override - T anyEmail() { - return createAndValidateProperty(RegexPatterns.EMAIL, "foo@bar" - + this.random. - nextInt() - + ".com") - } - - @Override - T anyUrl() { - return createAndValidateProperty(RegexPatterns.URL, "https://foo" - + this.random. - nextInt() - + ".com") - } - - @Override - T anyHttpsUrl() { - return createAndValidateProperty(RegexPatterns.HTTPS_URL, "https://baz" - + this.random. - nextInt() - + ".com") - } - - @Override - T anyUuid() { - return createAndValidateProperty(RegexPatterns.UUID, UUID.randomUUID().toString()) - } - - @Override - T anyDate() { - int d = this.random.nextInt(8) + 1 - return createAndValidateProperty(RegexPatterns.ANY_DATE, "201$d-0$d-1$d") - } - - @Override - T anyDateTime() { - int d = this.random.nextInt(8) + 1 - return createAndValidateProperty(RegexPatterns.ANY_DATE_TIME, "201$d-0$d-1${d}T12:23:34") - } - - @Override - T anyTime() { - int d = this.random.nextInt(9) - return createAndValidateProperty(RegexPatterns.ANY_TIME, "12:2$d:3$d") - } - - @Override - T anyIso8601WithOffset() { - int d = this.random.nextInt(8) + 1 - return createAndValidateProperty(RegexPatterns.ISO8601_WITH_OFFSET, "201$d-0$d-1${d}T12:23:34.123Z") - } - - @Override - T anyNonBlankString() { - return createAndValidateProperty(RegexPatterns.NON_BLANK, - RandomStringGenerator.randomString(20)) - } - - @Override - T anyNonEmptyString() { - return createAndValidateProperty(RegexPatterns.NON_EMPTY, - RandomStringGenerator.randomString(20)) - } - - @Override - T anyOf(String... values) { - return createAndValidateProperty(RegexPatterns.anyOf(values), values[0]) - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/QueryParameter.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/QueryParameter.groovy deleted file mode 100644 index 327e8c972b..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/QueryParameter.groovy +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString - -import static org.springframework.cloud.contract.spec.util.ValidateUtils.validateServerValueIsAvailable - -/** - * Represents a single HTTP query parameter - * - * @since 1.0.0 - */ -@EqualsAndHashCode(callSuper = true) -@ToString(includePackage = false, ignoreNulls = true, includeNames = true, includeSuper = true) -@CompileStatic -class QueryParameter extends DslProperty { - - String name - - QueryParameter(String name, DslProperty dslProperty) { - super(dslProperty.clientValue, dslProperty.serverValue) - validateServerValueIsAvailable(dslProperty.serverValue, "Query parameter '$name'") - this.name = name - } - - QueryParameter(String name, MatchingStrategy matchingStrategy) { - super(matchingStrategy) - validateServerValueIsAvailable(matchingStrategy, "Query parameter '$name'") - this.name = name - } - - QueryParameter(String name, Object value) { - super(value) - validateServerValueIsAvailable(value, "Query parameter '$name'") - this.name = name - } - -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/QueryParameters.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/QueryParameters.groovy deleted file mode 100644 index e2f520fd26..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/QueryParameters.groovy +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString -import groovy.transform.TypeChecked - -@EqualsAndHashCode -@ToString(includePackage = false, ignoreNulls = true, includeNames = true) -@TypeChecked -class QueryParameters { - - List parameters = [] - - void parameter(Map singleParameter) { - Map.Entry first = singleParameter.entrySet().first() - parameters << new QueryParameter(first?.key, first?.value) - } - - void parameter(String parameterName, Object parameterValue) { - parameters << new QueryParameter(parameterName, parameterValue) - } - -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexPatterns.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexPatterns.groovy deleted file mode 100644 index bb26a0d03f..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexPatterns.groovy +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import java.util.regex.Pattern - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString - -/** - * Contains most common regular expression patterns - * - * @author Marcin Grzejszczak - * @author Tim Ysewyn - * @since 1.0.0 - */ -@CompileStatic -@EqualsAndHashCode -@ToString(includePackage = false) -class RegexPatterns { - - // tag::regexps[] - protected static final Pattern TRUE_OR_FALSE = Pattern.compile(/(true|false)/) - protected static final Pattern ALPHA_NUMERIC = Pattern.compile('[a-zA-Z0-9]+') - protected static final Pattern ONLY_ALPHA_UNICODE = Pattern.compile(/[\p{L}]*/) - protected static final Pattern NUMBER = Pattern.compile('-?(\\d*\\.\\d+|\\d+)') - protected static final Pattern INTEGER = Pattern.compile('-?(\\d+)') - protected static final Pattern POSITIVE_INT = Pattern.compile('([1-9]\\d*)') - protected static final Pattern DOUBLE = Pattern.compile('-?(\\d*\\.\\d+)') - protected static final Pattern HEX = Pattern.compile('[a-fA-F0-9]+') - protected static final Pattern IP_ADDRESS = Pattern. - compile('([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])') - protected static final Pattern HOSTNAME_PATTERN = Pattern. - compile('((http[s]?|ftp):/)/?([^:/\\s]+)(:[0-9]{1,5})?') - protected static final Pattern EMAIL = Pattern. - compile('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}') - protected static final Pattern URL = UrlHelper.URL - protected static final Pattern HTTPS_URL = UrlHelper.HTTPS_URL - protected static final Pattern UUID = Pattern. - compile('[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}') - protected static final Pattern ANY_DATE = Pattern. - compile('(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])') - protected static final Pattern ANY_DATE_TIME = Pattern. - compile('([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])') - protected static final Pattern ANY_TIME = Pattern. - compile('(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])') - protected static final Pattern NON_EMPTY = Pattern.compile(/[\S\s]+/) - protected static final Pattern NON_BLANK = Pattern.compile(/^\s*\S[\S\s]*/) - protected static final Pattern ISO8601_WITH_OFFSET = Pattern. - compile(/([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\.\d{3})?(Z|[+-][01]\d:[0-5]\d)/) - - protected static Pattern anyOf(String... values) { - return Pattern.compile(values.collect({ "^$it\$" }).join("|")) - } - - RegexProperty onlyAlphaUnicode() { - return new RegexProperty(ONLY_ALPHA_UNICODE).asString() - } - - RegexProperty alphaNumeric() { - return new RegexProperty(ALPHA_NUMERIC).asString() - } - - RegexProperty number() { - return new RegexProperty(NUMBER).asDouble() - } - - RegexProperty positiveInt() { - return new RegexProperty(POSITIVE_INT).asInteger() - } - - RegexProperty anyBoolean() { - return new RegexProperty(TRUE_OR_FALSE).asBooleanType() - } - - RegexProperty anInteger() { - return new RegexProperty(INTEGER).asInteger() - } - - RegexProperty aDouble() { - return new RegexProperty(DOUBLE).asDouble() - } - - RegexProperty ipAddress() { - return new RegexProperty(IP_ADDRESS).asString() - } - - RegexProperty hostname() { - return new RegexProperty(HOSTNAME_PATTERN).asString() - } - - RegexProperty email() { - return new RegexProperty(EMAIL).asString() - } - - RegexProperty url() { - return new RegexProperty(URL).asString() - } - - RegexProperty httpsUrl() { - return new RegexProperty(HTTPS_URL).asString() - } - - RegexProperty uuid() { - return new RegexProperty(UUID).asString() - } - - RegexProperty isoDate() { - return new RegexProperty(ANY_DATE).asString() - } - - RegexProperty isoDateTime() { - return new RegexProperty(ANY_DATE_TIME).asString() - } - - RegexProperty isoTime() { - return new RegexProperty(ANY_TIME).asString() - } - - RegexProperty iso8601WithOffset() { - return new RegexProperty(ISO8601_WITH_OFFSET).asString() - } - - RegexProperty nonEmpty() { - return new RegexProperty(NON_EMPTY).asString() - } - - RegexProperty nonBlank() { - return new RegexProperty(NON_BLANK).asString() - } - - // end::regexps[] - - static String multipartParam(Object name, Object value) { - return ".*--(.*)\r\nContent-Disposition: form-data; name=\"$name\"\r\n(Content-Type: .*\r\n)?(Content-Transfer-Encoding: .*\r\n)?(Content-Length: \\d+\r\n)?\r\n$value\r\n--\\1.*" - } - - static String multipartFile(Object name, Object filename, Object content, Object contentType) { - return ".*--(.*)\r\nContent-Disposition: form-data; name=\"$name\"; filename=\"$filename\"\r\n(Content-Type: ${toContentType(contentType)}\r\n)?(Content-Transfer-Encoding: .*\r\n)?(Content-Length: \\d+\r\n)?\r\n$content\r\n--\\1.*" - } - - private static String toContentType(Object contentType) { - if (contentType == null) { - return '.*' - } - if (contentType instanceof RegexProperty) { - return contentType.pattern() - } - return contentType.toString() - } -} - -/** - * Taken from https://gist.github.com/skeller88/5eb73dc0090d4ff1249a - */ -class UrlHelper { - /** - * Example: "http". Also called 'protocol'. - * Scheme component is optional, even though the RFC doesn't make it optional. Since ((RegexProperty) this regex is validating a - * submitted callback url, which determines where the browser will navigate to after a successful authentication, - * the browser will use http or https for the scheme by default. - * Not borrowed from dperini in order to allow any scheme type. - */ - private static final String REGEX_SCHEME = "[A-Za-z][+-.\\w^_]*:" - - private static final String HTTPS_REGEX_SCHEME = "https:" - - // Example: "//". - private static final String REGEX_AUTHORATIVE_DECLARATION = "/{2}" - - // Optional component. Example: "suzie:abc123@". The use of the format "user:password" is deprecated. - private static final String REGEX_USERINFO = "(?:\\S+(?::\\S*)?@)?" - - // Examples: "fitbit.com", "22.231.113.64", "localhost" - private static final String REGEX_HOST = "(?:" + - // @Author = https://www.regular-expressions.info/examples.html - // IP address - "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + - "|" + - // host name - "(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)" + - // domain name - "(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*" + - // TLD identifier must have >= 2 characters - "(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))|(?:localhost))" - - // Example: ":8042". - private static final String REGEX_PORT = "(?::\\d{2,5})?" - - //Example: "/user/heartrate?foo=bar#element1". - private static final String REGEX_RESOURCE_PATH = "(?:/\\S*)?" - - protected static final Pattern HTTPS_URL = Pattern. - compile("^(?:" + HTTPS_REGEX_SCHEME + REGEX_AUTHORATIVE_DECLARATION + - REGEX_USERINFO + REGEX_HOST + REGEX_PORT + REGEX_RESOURCE_PATH + ")\$") - - protected static final Pattern URL = Pattern. - compile("^(?:(?:" + REGEX_SCHEME + REGEX_AUTHORATIVE_DECLARATION + ")?" + - REGEX_USERINFO + REGEX_HOST + REGEX_PORT + REGEX_RESOURCE_PATH + ")\$") -} - diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexProperty.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexProperty.groovy deleted file mode 100644 index 65d33a2f5c..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexProperty.groovy +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import java.util.regex.Matcher -import java.util.regex.Pattern - -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString -import groovy.transform.TypeChecked -import org.apache.commons.text.StringEscapeUtils -import repackaged.nl.flotsam.xeger.Xeger - -/** - * Represents a regular expression property - * - * @since 2.1.0 - */ -@TypeChecked -@EqualsAndHashCode -@ToString(includePackage = false, includeNames = true) -class RegexProperty extends DslProperty implements CanBeDynamic { - final Pattern pattern - private final Class clazz - - RegexProperty(Object value) { - this(value, value, null) - } - - RegexProperty(Object client, Object server) { - this(client, server, null) - } - - RegexProperty(Object client, Object server, Class clazz) { - super(client, server) - boolean clientDynamic = client instanceof Pattern || - client instanceof RegexProperty - boolean serverDynamic = server instanceof Pattern || - server instanceof RegexProperty - if (!clientDynamic && !serverDynamic) { - throw new IllegalStateException("Neither client not server side is dynamic") - } - Object dynamicValue = clientDynamic ? client : server - if (dynamicValue instanceof Pattern) { - this.pattern = dynamicValue - this.clazz = clazz ?: String - } - else if (dynamicValue instanceof RegexProperty) { - RegexProperty regexProperty = ((RegexProperty) dynamicValue) - this.pattern = regexProperty.pattern - this.clazz = clazz ?: regexProperty.clazz - } - else { - this.clazz = clazz - } - } - - Matcher matcher(CharSequence input) { - return this.pattern.matcher(input) - } - - String pattern() { - return this.pattern.pattern() - } - - Class clazz() { - return this.class - } - - RegexProperty asInteger() { - return new RegexProperty(this.clientValue, this.serverValue, Integer) - } - - RegexProperty asDouble() { - return new RegexProperty(this.clientValue, this.serverValue, Double) - } - - RegexProperty asFloat() { - return new RegexProperty(this.clientValue, this.serverValue, Float) - } - - RegexProperty asLong() { - return new RegexProperty(this.clientValue, this.serverValue, Long) - } - - RegexProperty asShort() { - return new RegexProperty(this.clientValue, this.serverValue, Short) - } - - RegexProperty asString() { - return new RegexProperty(this.clientValue, this.serverValue, String) - } - - RegexProperty asBooleanType() { - return new RegexProperty(this.clientValue, this.serverValue, Boolean) - } - - Object generate() { - int retries = 3; - try { - String generatedValue = new Xeger(this.pattern.pattern()).generate() - switch (this.clazz) { - case Integer: return Integer.parseInt(generatedValue) - case Double: return Double.parseDouble(generatedValue) - case Float: return Float.parseFloat(generatedValue) - case Long: return Long.parseLong(generatedValue) - case Short: return Short.parseShort(generatedValue) - case Boolean: return Boolean.parseBoolean(generatedValue) - default: return generatedValue - } - } catch(NumberFormatException ex) { - if (retries > 0) { - retries = retries - 1 - return generate() - } - throw ex - } - } - - Object generateAndEscapeJavaStringIfNeeded() { - Object generated = generate() - if (isNumber()) { - return generated - } - return StringEscapeUtils.escapeJava(generated as String) - } - - private boolean isNumber() { - return Number.isAssignableFrom(this.clazz) - } - - RegexProperty dynamicClientConcreteProducer() { - return new RegexProperty(this.pattern, generate(), this.clazz) - } - - RegexProperty concreteClientDynamicProducer() { - return new RegexProperty(generate(), this.pattern, this.clazz) - } - - RegexProperty concreteClientEscapedDynamicProducer() { - return new RegexProperty( - generateAndEscapeJavaStringIfNeeded(), this.pattern, this.clazz) - } - - RegexProperty dynamicClientEscapedConcreteProducer() { - return new RegexProperty(this.pattern, - generateAndEscapeJavaStringIfNeeded(), this.clazz) - } - - boolean equals(o) { - if (this.is(o)) { - return true - } - if (getClass() != o.class) { - return false - } - if (!super.equals(o)) { - return false - } - RegexProperty that = (RegexProperty) o - if (this.clazz != that.clazz) { - return false - } - if (this.pattern != that.pattern) { - return false - } - return true - } - - int hashCode() { - int result = super.hashCode() - result = 31 * result + (this.pattern != null ? pattern.hashCode() : 0) - result = 31 * result + (this.clazz != null ? clazz.hashCode() : 0) - return result - } - - @Override - String toString() { - return this.pattern() - } - - @Override - Object generateConcreteValue() { - return generate() - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Request.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Request.groovy deleted file mode 100644 index 632383d2fd..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Request.groovy +++ /dev/null @@ -1,449 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import java.util.regex.Pattern - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString -import groovy.transform.TypeChecked -import groovy.util.logging.Commons - -import org.springframework.cloud.contract.spec.util.RegexpUtils - -/** - * Represents the request side of the HTTP communication - * - * @author Marcin Grzejszczak - * @author Tim Ysewyn - * @since 1.0.0 - */ -@Commons -@TypeChecked -@EqualsAndHashCode -@ToString(includePackage = false, includeNames = true) -class Request extends Common { - - @Delegate - ClientPatternValueDslProperty property = new ClientPatternValueDslProperty() - @Delegate - HttpMethods httpMethods = new HttpMethods() - - DslProperty method - Url url - UrlPath urlPath - Headers headers - Cookies cookies - Body body - Multipart multipart - BodyMatchers bodyMatchers - - Request() { - } - - Request(Request request) { - this.method = request.method - this.url = request.url - this.urlPath = request.urlPath - this.headers = request.headers - this.cookies = request.cookies - this.body = request.body - this.multipart = request.multipart - } - - /** - * Name of the HTTP method - */ - void method(String method) { - this.method = toDslProperty(method) - } - - /** - * Name of the HTTP method - */ - void method(HttpMethods.HttpMethod httpMethod) { - this.method = toDslProperty(httpMethod.toString()) - } - - /** - * Name of the HTTP method - */ - void method(DslProperty method) { - this.method = toDslProperty(method) - } - - /** - * URL to which the request will be sent - */ - void url(Object url) { - this.url = new Url(url) - } - - /** - * URL to which the request will be sent - */ - void url(DslProperty url) { - this.url = new Url(url) - } - - void url(Object url, @DelegatesTo(UrlPath) Closure closure) { - this.url = new Url(url) - closure.delegate = this.url - closure() - } - - /** - * URL to which the request will be sent. Allows to customize - * additional query parameters if needed - */ - void url(DslProperty url, @DelegatesTo(UrlPath) Closure closure) { - this.url = new Url(url) - closure.delegate = this.url - closure() - } - - /** - * URL to which the request will be sent - */ - void urlPath(String path) { - this.urlPath = new UrlPath(path) - } - - /** - * URL to which the request will be sent - */ - void urlPath(GString path) { - this.urlPath = new UrlPath(path) - } - - /** - * URL to which the request will be sent - */ - void urlPath(DslProperty path) { - this.urlPath = new UrlPath(path) - } - - /** - * URL to which the request will be sent. Allows to customize - * additional query parameters if needed - */ - void urlPath(String path, @DelegatesTo(UrlPath) Closure closure) { - this.urlPath = new UrlPath(path) - closure.delegate = urlPath - closure() - } - - /** - * URL to which the request will be sent. Allows to customize - * additional query parameters if needed - */ - void urlPath(GString path, @DelegatesTo(UrlPath) Closure closure) { - this.urlPath = new UrlPath(path) - closure.delegate = urlPath - closure() - } - - /** - * URL to which the request will be sent. Allows to customize - * additional query parameters if needed - */ - void urlPath(DslProperty path, @DelegatesTo(UrlPath) Closure closure) { - this.urlPath = new UrlPath(path) - closure.delegate = urlPath - closure() - } - - /** - * Allows to configure HTTP headers - */ - void headers(@DelegatesTo(RequestHeaders) Closure closure) { - this.headers = new RequestHeaders() - closure.delegate = headers - closure() - } - - /** - * Allows to configure HTTP cookies - */ - void cookies(@DelegatesTo(RequestCookies) Closure closure) { - this.cookies = new RequestCookies() - closure.delegate = cookies - closure() - } - - /** - * Allows set an HTTP body - */ - void body(Map body) { - this.body = new Body(convertObjectsToDslProperties(body)) - } - - /** - * Allows set an HTTP body - */ - void body(List body) { - this.body = new Body(convertObjectsToDslProperties(body)) - } - - /** - * Allows set an HTTP body - */ - void body(DslProperty dslProperty) { - this.body = new Body(dslProperty) - } - - /** - * Allows set an HTTP body - */ - void body(Object bodyAsValue) { - this.body = new Body(bodyAsValue) - } - - Body getBody() { - return body - } - - /** - * Allows to set multipart via the map notation - */ - void multipart(Map body) { - this.multipart = new Multipart(convertObjectsToDslProperties(body)) - } - - /** - * Allows to set multipart via lists - */ - void multipart(List multipartAsList) { - this.multipart = new Multipart(convertObjectsToDslProperties(multipartAsList)) - } - - /** - * Allows to set multipart value - */ - void multipart(DslProperty dslProperty) { - this.multipart = new Multipart(dslProperty) - } - - /** - * Allows to set multipart value - */ - void multipart(Object multipartAsValue) { - this.multipart = new Multipart(multipartAsValue) - } - - /** - * Sets the equality check to the given query parameter - */ - MatchingStrategy equalTo(Object value) { - return new MatchingStrategy(value, MatchingStrategy.Type.EQUAL_TO) - } - - /** - * Sets the containing check to the given query parameter - */ - MatchingStrategy containing(Object value) { - return new MatchingStrategy(value, MatchingStrategy.Type.CONTAINS) - } - - /** - * Sets the matching check to the given query parameter - */ - MatchingStrategy matching(Object value) { - return new MatchingStrategy(value, MatchingStrategy.Type.MATCHING) - } - - /** - * Sets the not matching check to the given query parameter - */ - MatchingStrategy notMatching(Object value) { - return new MatchingStrategy(value, MatchingStrategy.Type.NOT_MATCHING) - } - - /** - * Sets the XML equality check to the body - */ - MatchingStrategy equalToXml(Object value) { - return new MatchingStrategy(value, MatchingStrategy.Type.EQUAL_TO_XML) - } - - /** - * Sets the JSON equality check to the body - */ - MatchingStrategy equalToJson(Object value) { - return new MatchingStrategy(value, MatchingStrategy.Type.EQUAL_TO_JSON) - } - - /** - * Sets absence scheck to the given query parameter - */ - MatchingStrategy absent() { - return new MatchingStrategy(true, MatchingStrategy.Type.ABSENT) - } - - void assertThatSidesMatch(Object stubSide, OptionalProperty testSide) { - throw new IllegalStateException("Optional can be used only for the stub side of the request!") - } - - /** - * Allows to set a dynamic value for the given element - */ - DslProperty value(ClientDslProperty client) { - Object concreteValue = client.serverValue - Object dynamicValue = client.clientValue - if (dynamicValue instanceof RegexProperty && client.isSingleValue()) { - return dynamicValue.dynamicClientEscapedConcreteProducer() - } - else if (concreteValue instanceof RegexProperty && !client.isSingleValue()) { - concreteValue = dynamicValue - } - return new DslProperty(dynamicValue, concreteValue) - } - - /** - * Allows to set a dynamic value for the given regular expression element - */ - DslProperty $(RegexProperty property) { - return value(property) - } - - /** - * Allows to set a dynamic value for the given regular expression element - */ - DslProperty value(RegexProperty property) { - return value(client(property)) - } - - /** - * Allows to set a dynamic value for the given element - */ - DslProperty $(ClientDslProperty client) { - return value(client) - } - - /** - * Allows to set a dynamic value for the Pattern element - */ - DslProperty value(Pattern client) { - return value(new RegexProperty(client)) - } - - /** - * Allows to set a dynamic value for the given Pattern element - */ - DslProperty $(Pattern client) { - return value(client) - } - - @Override - RegexProperty regexProperty(Object object) { - return new RegexProperty(object).dynamicClientConcreteProducer() - } - - /** - * @deprecated Deprecated in favor of bodyMatchers to support other future bodyMatchers too - */ - @Deprecated - void stubMatchers(@DelegatesTo(BodyMatchers) Closure closure) { - log.warn("stubMatchers method is deprecated. Please use bodyMatchers instead") - bodyMatchers(closure) - } - - /** - * Allows to set matchers for the body - */ - void bodyMatchers(@DelegatesTo(BodyMatchers) Closure closure) { - this.bodyMatchers = new BodyMatchers() - closure.delegate = this.bodyMatchers - closure() - } - - /** - * Allows to set a dynamic value for client and server side - */ - @Override - DslProperty value(ClientDslProperty client, ServerDslProperty server) { - if (server.clientValue instanceof RegexProperty) { - throw new IllegalStateException("You can't have a regular expression for the request on the server side") - } - return super.value(client, server) - } - - /** - * Allows to set a dynamic value for client and server side - */ - @Override - DslProperty value(ServerDslProperty server, ClientDslProperty client) { - if (server.clientValue instanceof RegexProperty) { - throw new IllegalStateException("You can't have a regular expression for the request on the server side") - } - return super.value(server, client) - } - - @CompileStatic - @EqualsAndHashCode(includeFields = true) - @ToString(includePackage = false) - private class ServerRequest extends Request { - ServerRequest(Request request) { - super(request) - } - } - - @CompileStatic - @EqualsAndHashCode(includeFields = true) - @ToString(includePackage = false) - private class ClientRequest extends Request { - ClientRequest(Request request) { - super(request) - } - } - - @CompileStatic - @EqualsAndHashCode(includeFields = true) - @ToString(includePackage = false) - private class RequestHeaders extends Headers { - - @Override - DslProperty matching(String value) { - return $(c(regex("${RegexpUtils.escapeSpecialRegexWithSingleEscape(value)}.*")), - p(value)) - } - } - - @CompileStatic - @EqualsAndHashCode(includeFields = true) - @ToString(includePackage = false) - private class RequestCookies extends Cookies { - - @Override - DslProperty matching(String value) { - return $(c(regex("${RegexpUtils.escapeSpecialRegexWithSingleEscape(value)}.*")), - p(value)) - } - } - - @CompileStatic - @EqualsAndHashCode(includeFields = true) - @ToString(includePackage = false) - private class ClientPatternValueDslProperty extends PatternValueDslProperty { - - @Override - protected ClientDslProperty createProperty(Pattern pattern, Object generatedValue) { - return new ClientDslProperty(pattern, generatedValue) - } - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Response.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Response.groovy deleted file mode 100644 index b1c53f51ea..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Response.groovy +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import java.util.regex.Pattern - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString -import groovy.transform.TypeChecked -import groovy.util.logging.Commons - -import org.springframework.cloud.contract.spec.util.RegexpUtils - -/** - * Represents the response side of the HTTP communication - * - * @author Marcin Grzejszczak - * @author Tim Ysewyn - * @since 1.0.0 - */ -@Commons -@TypeChecked -@EqualsAndHashCode -@ToString(includePackage = false, includeFields = true) -class Response extends Common { - - @Delegate - ServerPatternValueDslProperty property = new ServerPatternValueDslProperty() - @Delegate - HttpStatus httpStatus = new HttpStatus() - - DslProperty status - DslProperty delay - Headers headers - Cookies cookies - Body body - boolean async - ResponseBodyMatchers bodyMatchers - - Response() { - } - - Response(Response response) { - this.status = response.status - this.headers = response.headers - this.cookies = response.cookies - this.body = response.body - } - - /** - * Allows to set the HTTP status - */ - void status(int status) { - this.status = toDslProperty(status) - } - - /** - * Allows to set the HTTP status - */ - void status(DslProperty status) { - this.status = toDslProperty(status) - } - - /** - * Allows to configure HTTP headers - */ - void headers(@DelegatesTo(ResponseHeaders) Closure closure) { - this.headers = new ResponseHeaders() - closure.delegate = headers - closure() - } - - /** - * Allows to configure HTTP cookies - */ - void cookies(@DelegatesTo(ResponseCookies) Closure closure) { - this.cookies = new ResponseCookies() - closure.delegate = cookies - closure() - } - - /** - * Allows set an HTTP body - */ - void body(Map body) { - this.body = new Body(convertObjectsToDslProperties(body)) - } - - /** - * Allows set an HTTP body - */ - void body(List body) { - this.body = new Body(convertObjectsToDslProperties(body)) - } - - /** - * Allows set an HTTP body - */ - void body(Object bodyAsValue) { - if (bodyAsValue instanceof List) { - body(bodyAsValue as List) - } - else { - this.body = new Body(bodyAsValue) - } - } - - /** - * Allows to set a fixed delay of the response in milliseconds - */ - void fixedDelayMilliseconds(int timeInMilliseconds) { - this.delay = toDslProperty(timeInMilliseconds) - } - - /** - * Turns on the asynchronous mode for this contract. Used with MockMvc and the - * Servlet 3.0 features - */ - void async() { - this.async = true - } - - void assertThatSidesMatch(OptionalProperty stubSide, Object testSide) { - throw new IllegalStateException("Optional can be used only in the test side of the response!") - } - - /** - * Allows to set a dynamic value for the given element - */ - DslProperty value(ServerDslProperty server) { - Object dynamicValue = server.serverValue - Object concreteValue = server.clientValue - if (dynamicValue instanceof RegexProperty && server.isSingleValue()) { - return ((RegexProperty) dynamicValue).concreteClientDynamicProducer() - } - return new DslProperty(concreteValue, dynamicValue) - } - - /** - * Allows to set a dynamic value for the given element - */ - DslProperty $(ServerDslProperty server) { - return value(server) - } - - /** - * Allows to set a dynamic value for the given element - */ - DslProperty value(Pattern server) { - return value(new RegexProperty(server)) - } - - /** - * Allows to set a dynamic value for the given element - */ - DslProperty value(RegexProperty server) { - return value(new ServerDslProperty(server)) - } - - /** - * Allows to set a dynamic value for the given element - */ - DslProperty $(RegexProperty server) { - return value(server) - } - - /** - * Allows to set a dynamic value for the given element - */ - DslProperty $(Pattern server) { - return value(new RegexProperty(server)) - } - - @Override - RegexProperty regexProperty(Object object) { - return new RegexProperty(object).concreteClientDynamicProducer() - } - - /** - * @deprecated Deprecated in favor of bodyMatchers to support other future bodyMatchers too - */ - @Deprecated - void testMatchers(@DelegatesTo(ResponseBodyMatchers) Closure closure) { - log.warn("testMatchers method is deprecated. Please use bodyMatchers instead") - bodyMatchers(closure) - } - - /** - * Allows to set matchers for the body - */ - void bodyMatchers(@DelegatesTo(ResponseBodyMatchers) Closure closure) { - this.bodyMatchers = new ResponseBodyMatchers() - closure.delegate = this.bodyMatchers - closure() - } - - /** - * Allows to reference entries from the request - */ - FromRequest fromRequest() { - return new FromRequest() - } - - /** - * Allows to set a dynamic value for the given element - */ - @Override - DslProperty value(ClientDslProperty client, ServerDslProperty server) { - if (client.clientValue instanceof RegexProperty) { - throw new IllegalStateException("You can't have a regular expression for the response on the client side") - } - return super.value(client, server) - } - - /** - * Allows to set a dynamic value for the given element - */ - @Override - DslProperty value(ServerDslProperty server, ClientDslProperty client) { - if (client.clientValue instanceof RegexProperty) { - throw new IllegalStateException("You can't have a regular expression for the response on the client side") - } - return super.value(server, client) - } - - @CompileStatic - @EqualsAndHashCode(callSuper = true) - @ToString(includePackage = false) - private class ServerResponse extends Response { - ServerResponse(Response request) { - super(request) - } - } - - @CompileStatic - @EqualsAndHashCode(callSuper = true) - @ToString(includePackage = false) - private class ClientResponse extends Response { - ClientResponse(Response request) { - super(request) - } - } - - @CompileStatic - @EqualsAndHashCode(includeFields = true) - @ToString(includePackage = false) - private class ResponseHeaders extends Headers { - - @Override - DslProperty matching(String value) { - return $(p(notEscaped(Pattern. - compile("${RegexpUtils.escapeSpecialRegexWithSingleEscape(value)}.*"))), - c(value)) - } - } - - @CompileStatic - @EqualsAndHashCode(includeFields = true) - @ToString(includePackage = false) - private class ResponseCookies extends Cookies { - - @Override - DslProperty matching(String value) { - return $(p(regex("${RegexpUtils.escapeSpecialRegexWithSingleEscape(value)}.*")), - c(value)) - } - } - - @CompileStatic - @EqualsAndHashCode(includeFields = true) - @ToString(includePackage = false) - private class ServerPatternValueDslProperty extends PatternValueDslProperty { - - @Override - protected ServerDslProperty createProperty(Pattern pattern, Object generatedValue) { - return new ServerDslProperty(pattern, generatedValue) - } - } -} - diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ServerDslProperty.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ServerDslProperty.groovy deleted file mode 100644 index 8e4084140a..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ServerDslProperty.groovy +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString - -/** - * Represents a server side {@link DslProperty} - * - * @since 1.0.0 - */ -@CompileStatic -@EqualsAndHashCode(callSuper = true) -@ToString(includePackage = false, includeSuper = true) -class ServerDslProperty extends DslProperty { - - ServerDslProperty(Object singleValue) { - super(singleValue) - } - - ServerDslProperty(Object server, Object client) { - super(client, server) - } -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Url.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Url.groovy deleted file mode 100644 index db0cdc827c..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Url.groovy +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString -import org.codehaus.groovy.runtime.GStringImpl - -import static org.springframework.cloud.contract.spec.util.ValidateUtils.validateServerValueIsAvailable - -/** - * Represents a URL that may contain query parameters - * - * @since 1.0.0 - */ -@ToString(includePackage = false, includeNames = true, includeSuper = true) -@EqualsAndHashCode(includeFields = true, callSuper = true) -@CompileStatic -class Url extends DslProperty { - - QueryParameters queryParameters - - Url(DslProperty prop) { - super(prop.clientValue, prop.serverValue) - validateServerValueIsAvailable(prop.serverValue, "Url") - } - - Url(Object url) { - super(url, testUrl(url)) - validateServerValueIsAvailable(url, "Url") - } - - private static Object testUrl(Object url) { - if (url instanceof GString) { - boolean anyPattern = url.values.any { it instanceof RegexProperty } - if (!anyPattern) { - return url - } - String newUrl = new GStringImpl( - url.values.collect { - it instanceof RegexProperty ? - it.generate() : it - } as String[], - Arrays.copyOf(url.strings, url.strings.length) as String[] - ).toString() - return new Url(newUrl) - } - return url - } - - void queryParameters(@DelegatesTo(QueryParameters) Closure closure) { - this.queryParameters = new QueryParameters() - closure.delegate = queryParameters - closure() - } - -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/UrlPath.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/UrlPath.groovy deleted file mode 100644 index 4a264b9d8e..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/UrlPath.groovy +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.internal - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString - -/** - * Represents a url path. Syntactic sugar when working with - * {@link QueryParameters}. It's logically equal to {@link Url} - * - * @since 1.0.0 - */ -@ToString(includePackage = false, includeFields = true, includeNames = true, includeSuper = true) -@EqualsAndHashCode(includeFields = true, callSuper = true) -@CompileStatic -class UrlPath extends Url { - - UrlPath(String path) { - super(path) - } - - UrlPath(GString path) { - super(path) - } - - UrlPath(DslProperty path) { - super(path) - } - -} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/util/ValidateUtils.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/util/ValidateUtils.groovy deleted file mode 100644 index 8edea6fe33..0000000000 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/util/ValidateUtils.groovy +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2013-2019 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. - */ - -package org.springframework.cloud.contract.spec.util - -import java.util.regex.Pattern - -import groovy.transform.TypeChecked - -import org.springframework.cloud.contract.spec.internal.DslProperty -import org.springframework.cloud.contract.spec.internal.MatchingStrategy - -import static org.springframework.cloud.contract.spec.internal.MatchingStrategy.Type.ABSENT -import static org.springframework.cloud.contract.spec.internal.MatchingStrategy.Type.EQUAL_TO - -/** - * Checks the validity of DSL entries. - * - * Do not change to {@code @CompileStatic} since it's using double dispatch. - * - * @since 1.0.0 - */ -@TypeChecked -class ValidateUtils { - - static Object validateServerValueIsAvailable(Object value) { - validateServerValueIsAvailable(value, "Server value") - return value - } - - static Object validateServerValueIsAvailable(Object value, String msg) { - validateServerValue(value, msg) - return value - } - - static void validateServerValue(Pattern pattern, String msg) { - throw new IllegalStateException("$msg can't be a pattern for the server side") - } - - static List ALLOWED_MATCHING_TYPES_ON_SERVER_SIDE = [EQUAL_TO, ABSENT] - - static void validateServerValue(MatchingStrategy matchingStrategy, String msg) { - if (!ALLOWED_MATCHING_TYPES_ON_SERVER_SIDE.contains(matchingStrategy.type)) { - throw new IllegalStateException("$msg can't be of a matching type: $matchingStrategy.type for the server side") - } - validateServerValue(matchingStrategy.serverValue, msg) - } - - static void validateServerValue(DslProperty value, String msg) { - validateServerValue(value.serverValue, msg) - } - - static void validateServerValue(Object value, String msg) { - // OK - } - -} diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubRunnerPropertyUtilsSpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubRunnerPropertyUtilsSpec.groovy index ff707b543b..716ba2bd93 100644 --- a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubRunnerPropertyUtilsSpec.groovy +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/StubRunnerPropertyUtilsSpec.groovy @@ -1,11 +1,11 @@ /* - * Copyright 2013-2018 the original author or authors. + * Copyright 2013-2019 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 + * 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, diff --git a/spring-cloud-contract-stub-runner/src/test/java/org/springframework/cloud/contract/stubrunner/TestCustomYamlContractConverter.java b/spring-cloud-contract-stub-runner/src/test/java/org/springframework/cloud/contract/stubrunner/TestCustomYamlContractConverter.java index 1235e6cb2d..cb49206460 100644 --- a/spring-cloud-contract-stub-runner/src/test/java/org/springframework/cloud/contract/stubrunner/TestCustomYamlContractConverter.java +++ b/spring-cloud-contract-stub-runner/src/test/java/org/springframework/cloud/contract/stubrunner/TestCustomYamlContractConverter.java @@ -23,8 +23,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Optional; -import groovy.lang.Closure; - import org.springframework.cloud.contract.spec.Contract; import org.springframework.cloud.contract.spec.ContractConverter; @@ -50,7 +48,7 @@ public class TestCustomYamlContractConverter implements ContractConverter { @Override public Collection convertFrom(File file) { - return Collections.singleton(Contract.make(Closure.IDENTITY)); + return Collections.singleton(new Contract()); } @Override diff --git a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/wiremock/WireMockToDslConverter.groovy b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/wiremock/WireMockToDslConverter.groovy index a2f3f76eeb..1c3c410f63 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/wiremock/WireMockToDslConverter.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/wiremock/WireMockToDslConverter.groovy @@ -17,6 +17,7 @@ package org.springframework.cloud.contract.verifier.wiremock import java.nio.charset.StandardCharsets +import java.util.regex.Pattern import groovy.io.FileType import groovy.json.JsonOutput @@ -25,6 +26,8 @@ import groovy.json.JsonSlurper import groovy.transform.CompileDynamic import groovy.xml.XmlUtil import repackaged.nl.flotsam.xeger.Xeger + +import org.springframework.cloud.contract.spec.internal.RegexProperty import org.springframework.cloud.contract.verifier.util.ContentUtils import org.springframework.cloud.contract.spec.Contract @@ -79,7 +82,7 @@ class WireMockToDslConverter { String headerName = it.key as String def entry = assertion.entrySet().first() """header(\"\"\"$headerName\"\"\", ${ - buildHeader(entry.key, entry.value) + buildRequestHeader(entry.key, entry.value) })\n""" }.join('') } @@ -116,15 +119,23 @@ class WireMockToDslConverter { } private Object parseStubDefinition(String wireMockStringStub) { - new JsonSlurper().setType(JsonParserType.LAX).parseText(wireMockStringStub) + return new JsonSlurper().setType(JsonParserType.LAX).parseText(wireMockStringStub) } - private String buildHeader(String method, Object value) { + private String buildRequestHeader(String method, Object value) { switch (method) { case 'equalTo': return wrapWithMultilineGString(value) + case 'contains': + String regex = '^.*' + value.toString() + '.*$' + String escapedRegex = escapeJava(regex) + String wrappedRegex = wrapWithMultilineGString(escapedRegex) + return 'c(regex(' + wrappedRegex + '))' default: - return "regex(${wrapWithMultilineGString(escapeJava(value as String))})" + String regex = value.toString() + String escapedRegex = escapeJava(regex) + String wrappedRegex = wrapWithMultilineGString(escapedRegex) + return 'c(regex(' + wrappedRegex + '))' } } @@ -159,7 +170,7 @@ class WireMockToDslConverter { } private String wrapWithMultilineGString(String string) { - return """\"\"\"$string\"\"\"""" + return """'''$string'''""" } private Closure withQuotedMapStringElements() { diff --git a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/WireMockToDslConverterSpec.groovy b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/WireMockToDslConverterSpec.groovy index 77ba61b5cf..1cc3a68c66 100755 --- a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/WireMockToDslConverterSpec.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/WireMockToDslConverterSpec.groovy @@ -35,11 +35,11 @@ class WireMockToDslConverterSpec extends Specification { "method": "GET", "url": "/path", "headers" : { - "Accept": { - "matches": "text/.*" - }, "X-Custom-Header": { "contains": "2134" + }, + "Accept": { + "matches": "text/.*" } } }, @@ -60,15 +60,14 @@ class WireMockToDslConverterSpec extends Specification { method 'GET' url '/path' headers { - header('Accept': $( - consumer(regex('text/.*')), - producer('text/plain') - )) header('X-Custom-Header': $( consumer(regex('^.*2134.*$')), producer('121345') )) - + header('Accept': $( + consumer(regex('text/.*')), + producer('text/plain') + )) } } response { @@ -83,19 +82,25 @@ class WireMockToDslConverterSpec extends Specification { }""") headers { header 'Content-Type': 'text/plain' - } } } when: String groovyDsl = WireMockToDslConverter.fromWireMockStub(wireMockStub) then: - def a = ContractVerifierDslConverter.convertAsCollection(new File("/"), + def contracts = ContractVerifierDslConverter.convertAsCollection(new File("/"), """org.springframework.cloud.contract.spec.Contract.make { $groovyDsl }""") def b = expectedGroovyDsl - a.first() == b + def a = contracts.first() + a.request.method == b.request.method + a.request.url == b.request.url + a.request.headers.entries.find { it.name == 'X-Custom-Header'}.clientValue.pattern() == + b.request.headers.entries.find { it.name == 'X-Custom-Header'}.clientValue.pattern() + a.request.headers.entries.find { it.name == 'Accept'}.clientValue.pattern() == + b.request.headers.entries.find { it.name == 'Accept'}.clientValue.pattern() + a.response == b.response } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/build.gradle b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/build.gradle index 9c3d26ba57..b46be2834d 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/build.gradle +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/build.gradle @@ -83,7 +83,7 @@ dependencies { compile("org.springframework.cloud:spring-cloud-starter-contract-verifier:${project.version}") { exclude(group: 'org.codehaus.groovy') } - testCompile('org.spockframework:spock-core:1.0-groovy-2.4') { + testCompile('org.spockframework:spock-core:1.3-groovy-2.5') { exclude(group: 'org.codehaus.groovy') } testCompile 'info.solidsoft.spock:spock-global-unroll:0.5.0' diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtension.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtension.groovy index f137430434..c4313411f1 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtension.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtension.groovy @@ -16,6 +16,7 @@ package org.springframework.cloud.contract.verifier.plugin +import groovy.transform.CompileStatic import groovy.transform.ToString import org.apache.commons.logging.Log import org.apache.commons.logging.LogFactory @@ -28,6 +29,7 @@ import org.springframework.cloud.contract.verifier.config.TestMode * @author Marcin Grzejszczak */ @ToString +@CompileStatic class ContractVerifierExtension { private static final Log log = LogFactory.getLog(ContractVerifierExtension) @@ -251,8 +253,8 @@ class ContractVerifierExtension { excludedFiles: new ArrayList(this.excludedFiles), includedFiles: new ArrayList(this.includedFiles), ignoredFiles: new ArrayList(this.ignoredFiles), - imports: Arrays.asList(this.imports).toArray(), - staticImports: Arrays.asList(this.staticImports).toArray(), + imports: Arrays.asList(this.imports).toArray() as String[], + staticImports: Arrays.asList(this.staticImports).toArray() as String[], contractsDslDir: this.contractsDslDir, generatedTestSourcesDir: this.generatedTestSourcesDir, generatedTestResourcesDir: this.generatedTestResourcesDir, diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractsCopyTask.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractsCopyTask.groovy index 26a16ef62c..fdc4cfe6f8 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractsCopyTask.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractsCopyTask.groovy @@ -16,6 +16,7 @@ package org.springframework.cloud.contract.verifier.plugin +import groovy.transform.CompileDynamic import groovy.transform.CompileStatic import groovy.transform.PackageScope import org.gradle.api.internal.ConventionTask @@ -35,6 +36,7 @@ import org.springframework.cloud.contract.verifier.converter.ToYamlConverter * @since 1.0.2 */ @PackageScope +@CompileStatic class ContractsCopyTask extends ConventionTask { private static final String ORIGINAL_PATH = "original" @@ -49,10 +51,8 @@ class ContractsCopyTask extends ConventionTask { String antPattern = "${props.includedRootFolderAntPattern}*.*" String slashSeparatedGroupId = project.group.toString().replace(".", File.separator) String slashSeparatedAntPattern = antPattern.replace(slashSeparatedGroupId, project.group.toString()) - String root = OutputFolderBuilder.buildRootPath(project) - ext.contractVerifierConfigProperties = props - File outputContractsFolder = outputFolder(root, "contracts") - ext.contractsDslDir = outputContractsFolder + String root = root(props) + File outputContractsFolder = outputContractsFolder(root) project.logger.info("Downloading and unpacking files from [$file] to [$outputContractsFolder]. The inclusion ant patterns are [${antPattern}] and [${slashSeparatedAntPattern}]") copy(file, antPattern, slashSeparatedAntPattern, props, outputContractsFolder) if (getExtension().isConvertToYaml()) { @@ -61,6 +61,21 @@ class ContractsCopyTask extends ConventionTask { } + @CompileDynamic + private File outputContractsFolder(String root) { + File outputContractsFolder = outputFolder(root, "contracts") + ext.contractsDslDir = outputContractsFolder + return outputContractsFolder + } + + @CompileDynamic + private String root(ContractVerifierConfigProperties props) { + String root = OutputFolderBuilder.buildRootPath(project) + ext.contractVerifierConfigProperties = props + return root + } + + @CompileDynamic private void convertBackedUpDslsToYaml(String root, File file, String antPattern, String slashSeparatedAntPattern, ContractVerifierConfigProperties props, File outputContractsFolder) { File originalContracts = outputFolder(root, ORIGINAL_PATH) copy(file, antPattern, slashSeparatedAntPattern, props, originalContracts) @@ -69,6 +84,7 @@ class ContractsCopyTask extends ConventionTask { info("Replaced DSL files with their YAML representation at [" + ext.contractsDslDir + "]") } + @CompileDynamic protected WorkResult copy(File file, String antPattern, String slashSeparatedAntPattern, props, File outputContractsFolder) { return project.copy { from(file) diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateClientStubsFromDslTask.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateClientStubsFromDslTask.groovy index 1692b0a6b1..23f72dbc99 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateClientStubsFromDslTask.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateClientStubsFromDslTask.groovy @@ -16,6 +16,8 @@ package org.springframework.cloud.contract.verifier.plugin +import groovy.transform.CompileDynamic +import groovy.transform.CompileStatic import org.gradle.api.Task import org.gradle.api.internal.ConventionTask import org.gradle.api.tasks.TaskAction @@ -23,8 +25,6 @@ import org.gradle.api.tasks.TaskAction import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties import org.springframework.cloud.contract.verifier.converter.RecursiveFilesConverter -import static org.springframework.cloud.contract.verifier.plugin.SpringCloudContractVerifierGradlePlugin.COPY_CONTRACTS_TASK_NAME - //TODO: Implement as an incremental task: https://gradle.org/docs/current/userguide/custom_tasks.html#incremental_tasks ? /** * Generates stubs from the contracts. The name is WireMock related but the implementation @@ -32,6 +32,7 @@ import static org.springframework.cloud.contract.verifier.plugin.SpringCloudCont * * @since 2.0.0 */ +@CompileStatic class GenerateClientStubsFromDslTask extends ConventionTask { File stubsOutputDir @@ -42,7 +43,7 @@ class GenerateClientStubsFromDslTask extends ConventionTask { @TaskAction void generate() { logger.info("Stubs output dir [${getStubsOutputDir()}") - Task copyContractsTask = project.getTasksByName(COPY_CONTRACTS_TASK_NAME, false).first() + Task copyContractsTask = project.getTasksByName(SpringCloudContractVerifierGradlePlugin.COPY_CONTRACTS_TASK_NAME, false).first() ContractVerifierConfigProperties props = props(copyContractsTask) File contractsDslDir = contractsDslDir(copyContractsTask, props) logger.info("Spring Cloud Contract Verifier Plugin: Invoking DSL to client stubs conversion") @@ -54,6 +55,7 @@ class GenerateClientStubsFromDslTask extends ConventionTask { converter.processFiles() } + @CompileDynamic private ContractVerifierConfigProperties props(Task task) { try { return task.ext.contractVerifierConfigProperties @@ -66,6 +68,7 @@ class GenerateClientStubsFromDslTask extends ConventionTask { } } + @CompileDynamic private File contractsDslDir(Task task, ContractVerifierConfigProperties props) { try { return task.ext.contractsDslDir diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateServerTestsTask.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateServerTestsTask.groovy index 71ca619452..ff0f13633b 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateServerTestsTask.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateServerTestsTask.groovy @@ -16,11 +16,14 @@ package org.springframework.cloud.contract.verifier.plugin +import groovy.transform.CompileDynamic +import groovy.transform.CompileStatic import org.gradle.api.GradleException import org.gradle.api.Task import org.gradle.api.internal.ConventionTask import org.gradle.api.tasks.TaskAction +import org.springframework.cloud.contract.spec.Contract import org.springframework.cloud.contract.spec.ContractVerifierException import org.springframework.cloud.contract.verifier.TestGenerator import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties @@ -33,6 +36,7 @@ import static org.springframework.cloud.contract.verifier.plugin.SpringCloudCont * * @since 1.0.0 */ +@CompileStatic class GenerateServerTestsTask extends ConventionTask { File generatedTestSourcesDir @@ -55,21 +59,9 @@ class GenerateServerTestsTask extends ConventionTask { project.logger.info("Spring Cloud Contract Verifier Plugin: Invoking test sources generation") project.logger.info("Contracts are unpacked to [${contractsDslDir}]") project.logger.info("Included contracts are [${props.includedContracts}]") - def sourceSetType = getConfigProperties().getTestFramework() == TestFramework.SPOCK ? "groovy" : "java" - - project.sourceSets.test."${sourceSetType}" { - project.logger. - info("Registering ${getConfigProperties().generatedTestSourcesDir} as test source directory") - srcDir getConfigProperties().getGeneratedTestSourcesDir() - } - project.sourceSets.test.resources { - project.logger. - info("Registering ${getConfigProperties().generatedTestResourcesDir} as test resource directory") - srcDir getConfigProperties().getGeneratedTestResourcesDir() - } - + applySourceSets(sourceSetType) try { props = props ?: ExtensionToProperties.fromExtension(getConfigProperties()) props.contractsDslDir = contractsDslDir @@ -82,6 +74,21 @@ class GenerateServerTestsTask extends ConventionTask { } } + @CompileDynamic + private void applySourceSets(sourceSetType) { + project.sourceSets.test."${sourceSetType}" { + project.logger. + info("Registering ${getConfigProperties().generatedTestSourcesDir} as test source directory") + srcDir getConfigProperties().getGeneratedTestSourcesDir() + } + project.sourceSets.test.resources { + project.logger. + info("Registering ${getConfigProperties().generatedTestResourcesDir} as test resource directory") + srcDir getConfigProperties().getGeneratedTestResourcesDir() + } + } + + @CompileDynamic private ContractVerifierConfigProperties props(Task task) { try { return task.ext.contractVerifierConfigProperties @@ -94,6 +101,7 @@ class GenerateServerTestsTask extends ConventionTask { } } + @CompileDynamic private File contractsDslDir(Task task, ContractVerifierConfigProperties props) { try { return task.ext.contractsDslDir diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/PublishStubsToScmTask.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/PublishStubsToScmTask.groovy index 1a1bd4701e..28d8308d41 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/PublishStubsToScmTask.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/PublishStubsToScmTask.groovy @@ -16,7 +16,7 @@ package org.springframework.cloud.contract.verifier.plugin - +import groovy.transform.CompileStatic import org.gradle.api.internal.ConventionTask import org.gradle.api.tasks.TaskAction @@ -32,6 +32,7 @@ import org.springframework.cloud.contract.stubrunner.StubRunnerOptions * @author Marcin Grzejszczak * @since 2.0.0 */ +@CompileStatic class PublishStubsToScmTask extends ConventionTask { File stubsOutputDir ContractVerifierExtension configProperties diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/SpringCloudContractVerifierGradlePlugin.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/SpringCloudContractVerifierGradlePlugin.groovy index 8896628113..61c5e40d29 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/SpringCloudContractVerifierGradlePlugin.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/SpringCloudContractVerifierGradlePlugin.groovy @@ -16,7 +16,8 @@ package org.springframework.cloud.contract.verifier.plugin -import groovy.transform.PackageScope +import groovy.transform.CompileDynamic +import groovy.transform.CompileStatic import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task @@ -36,14 +37,14 @@ import org.gradle.jvm.tasks.Jar * * @since 1.0.0 */ +@CompileStatic class SpringCloudContractVerifierGradlePlugin implements Plugin { - private static final String GENERATE_SERVER_TESTS_TASK_NAME = 'generateContractTests' - private static final String DSL_TO_CLIENT_TASK_NAME = 'generateClientStubs' - @PackageScope - static final String COPY_CONTRACTS_TASK_NAME = 'copyContracts' - private static final String VERIFIER_STUBS_JAR_TASK_NAME = 'verifierStubsJar' - private static final String PUBLISH_STUBS_TO_SCM_TASK_NAME = 'publishStubsToScm' + protected static final String GENERATE_SERVER_TESTS_TASK_NAME = 'generateContractTests' + protected static final String DSL_TO_CLIENT_TASK_NAME = 'generateClientStubs' + protected static final String COPY_CONTRACTS_TASK_NAME = 'copyContracts' + protected static final String VERIFIER_STUBS_JAR_TASK_NAME = 'verifierStubsJar' + protected static final String PUBLISH_STUBS_TO_SCM_TASK_NAME = 'publishStubsToScm' private static final String GROUP_NAME = "Verification" private static final String EXTENSION_NAME = 'contracts' @@ -56,7 +57,7 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin { project.plugins.apply(GroovyPlugin) ContractVerifierExtension extension = project.extensions.create(EXTENSION_NAME, ContractVerifierExtension) GradleContractsDownloader downloader = new GradleContractsDownloader(this.project, this.project.logger) - project.check.dependsOn(GENERATE_SERVER_TESTS_TASK_NAME) + addDependsOn(project) setConfigurationDefaults(extension) Task stubsJar = createAndConfigureStubsJarTasks(extension) Task copyContracts = createAndConfigureCopyContractsTask(stubsJar, downloader, extension) @@ -67,6 +68,12 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin { addIdeaTestSources(project, extension) } + @CompileDynamic + private void addDependsOn(Project project) { + project.check.dependsOn(GENERATE_SERVER_TESTS_TASK_NAME) + } + + @CompileDynamic private addIdeaTestSources(Project project, ContractVerifierExtension extension) { def hasIdea = new File(project.rootDir, ".idea").exists() if (hasIdea) { @@ -98,6 +105,7 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin { return project.file("${project.projectDir}/src/test/resources/contracts") } + @CompileDynamic private void createGenerateTestsTask(ContractVerifierExtension extension, Task copyContracts, GradleContractsDownloader gradleContractsDownloader) { Task task = project.tasks.create(GENERATE_SERVER_TESTS_TASK_NAME, GenerateServerTestsTask) @@ -113,6 +121,7 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin { project.tasks.findByName("compileTestJava").dependsOn(task) } + @CompileDynamic private void createAndConfigurePublishStubsToScmTask(ContractVerifierExtension extension, GradleContractsDownloader gradleContractsDownloader) { Task task = project.tasks.create(PUBLISH_STUBS_TO_SCM_TASK_NAME, PublishStubsToScmTask) @@ -126,6 +135,7 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin { task.dependsOn DSL_TO_CLIENT_TASK_NAME } + @CompileDynamic private Task createAndConfigureGenerateClientStubs(ContractVerifierExtension extension, Task copyContracts) { Task task = project.tasks.create(DSL_TO_CLIENT_TASK_NAME, GenerateClientStubsFromDslTask) @@ -140,6 +150,7 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin { return task } + @CompileDynamic private Task createAndConfigureStubsJarTasks(ContractVerifierExtension extension) { Task task = stubsTask() if (task) { @@ -171,6 +182,7 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin { } } + @CompileDynamic private Task createAndConfigureCopyContractsTask(Task stubs, GradleContractsDownloader gradleContractsDownloader, ContractVerifierExtension contractVerifierExtension) { @@ -185,6 +197,7 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin { return task } + @CompileDynamic private void createAndConfigureMavenPublishPlugin(Task stubsTask, ContractVerifierExtension extension) { if (!classIsOnClasspath("org.gradle.api.publish.maven.plugins.MavenPublishPlugin")) { project.logger.debug("Maven Publish Plugin is not present - won't add default publication") @@ -214,6 +227,7 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin { } } + @CompileDynamic private boolean hasPublication(def publishingExtension) { try { return publishingExtension.publications.getByName('stubs') diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/fraudDetectionService/src/main/java/org/springframework/cloud/frauddetection/FraudDetectionController.java b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/fraudDetectionService/src/main/java/org/springframework/cloud/frauddetection/FraudDetectionController.java index 195dd96f86..858ebcf143 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/fraudDetectionService/src/main/java/org/springframework/cloud/frauddetection/FraudDetectionController.java +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/fraudDetectionService/src/main/java/org/springframework/cloud/frauddetection/FraudDetectionController.java @@ -23,10 +23,6 @@ import org.springframework.cloud.frauddetection.model.FraudCheckResult; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; -import javax.ws.rs.*; -import javax.ws.rs.core.*; -import java.math.BigDecimal; - import static org.springframework.cloud.frauddetection.model.FraudCheckStatus.FRAUD; import static org.springframework.cloud.frauddetection.model.FraudCheckStatus.OK; diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/loanApplicationService/src/main/resources/application.yml b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/loanApplicationService/src/main/resources/application.yml index 1c421cf2b7..cbe617ef03 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/loanApplicationService/src/main/resources/application.yml +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/loanApplicationService/src/main/resources/application.yml @@ -1 +1 @@ -server.port=0 \ No newline at end of file +server.port=0 diff --git a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/main/groovy/org/springframework/cloud/contract/verifier/spec/pact/RequestResponseSCContractCreator.groovy b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/main/groovy/org/springframework/cloud/contract/verifier/spec/pact/RequestResponseSCContractCreator.groovy index c443fcf38c..3bed2775ea 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/main/groovy/org/springframework/cloud/contract/verifier/spec/pact/RequestResponseSCContractCreator.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/main/groovy/org/springframework/cloud/contract/verifier/spec/pact/RequestResponseSCContractCreator.groovy @@ -42,12 +42,9 @@ import groovy.transform.CompileStatic import groovy.transform.PackageScope import org.springframework.cloud.contract.spec.Contract -import org.springframework.cloud.contract.spec.internal.DslProperty -import org.springframework.cloud.contract.spec.internal.NotToEscapePattern import org.springframework.cloud.contract.spec.internal.RegexPatterns import org.springframework.cloud.contract.verifier.util.JsonPaths import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter - /** * Creator of {@link Contract} instances * @@ -96,8 +93,8 @@ class RequestResponseSCContractCreator { MatchingRule rule = ruleGroup.rules[0] if (rule instanceof RegexMatcher) { v.each({ - header(k, new DslProperty((Object) Pattern. - compile(((RegexMatcher) rule).getRegex()), it)) + header(k, $(c(regex(((RegexMatcher) rule).getRegex())), + p(it))) }) } else { @@ -276,8 +273,9 @@ class RequestResponseSCContractCreator { MatchingRule rule = ruleGroup.rules[0] if (rule instanceof RegexMatcher) { v.each({ - header(k, new DslProperty(new DslProperty(it), new NotToEscapePattern(Pattern. - compile(((RegexMatcher) rule).getRegex())))) + header(k, $(p(regex(Pattern.compile( + ((RegexMatcher) rule).getRegex()))), + c(it))) }) } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/groovy/org/springframework/cloud/contract/verifier/spec/pact/PactContractConverterSpec.groovy b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/groovy/org/springframework/cloud/contract/verifier/spec/pact/PactContractConverterSpec.groovy index 290083dbba..7ff4d8cf61 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/groovy/org/springframework/cloud/contract/verifier/spec/pact/PactContractConverterSpec.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/groovy/org/springframework/cloud/contract/verifier/spec/pact/PactContractConverterSpec.groovy @@ -78,7 +78,7 @@ class PactContractConverterSpec extends Specification { } } headers { - contentType(applicationJson()) + header(contentType(), applicationJson()) } body(id: "123", method: "create") bodyMatchers { @@ -88,7 +88,7 @@ class PactContractConverterSpec extends Specification { response { status(200) headers { - contentType(applicationJson()) + header(contentType(), applicationJson()) } body([[ [email : "rddtGwwWMEhnkAPEmsyE", @@ -121,20 +121,20 @@ class PactContractConverterSpec extends Specification { method(POST()) url("/user-service/users") headers { - contentType(applicationJson()) + header(contentType(), applicationJson()) } body(firstName: "Arthur", lastName: "Dent") } response { status(201) headers { - contentType(applicationJson()) + header(contentType(), applicationJsonUtf8()) } body(id: 42, firstName: "Arthur", lastName: "Dent") bodyMatchers { jsonPath('''$.['id']''', byType()) - jsonPath('''$.['lastName']''', byType()) jsonPath('''$.['firstName']''', byType()) + jsonPath('''$.['lastName']''', byType()) } } } @@ -472,7 +472,7 @@ class PactContractConverterSpec extends Specification { response { status(200) headers { - contentType(applicationJson()) + header(contentType(), applicationJsonUtf8()) } body([[ [email : "rddtGwwWMEhnkAPEmsyE", @@ -499,7 +499,7 @@ class PactContractConverterSpec extends Specification { response { status(200) headers { - contentType(applicationJson()) + header(contentType(), applicationJsonUtf8()) } body([[ [email : "DPvAfkCZpOBZWzKYiDMC", @@ -533,7 +533,7 @@ class PactContractConverterSpec extends Specification { method(GET()) url("/") headers { - contentType(applicationJsonUtf8()) + header(contentType(), applicationJsonUtf8()) header("Some-Header", $(c(regex("[a-zA-Z]{9}")), p("someValue"))) header("someHeaderWithJsonContent", '{"issue":"#595"}') @@ -542,7 +542,7 @@ class PactContractConverterSpec extends Specification { response { status(200) headers { - contentType(applicationJsonUtf8()) + header(contentType(), applicationJsonUtf8()) header("Some-Header", $(c("someValue"), p(regex("[a-zA-Z]{9}")))) header("someHeaderWithJsonContent", '{"issue":"#595"}') @@ -594,7 +594,7 @@ class PactContractConverterSpec extends Specification { response { status(200) headers { - contentType(applicationJsonUtf8()) + header(contentType(), applicationJsonUtf8()) } body([ [ @@ -633,7 +633,7 @@ class PactContractConverterSpec extends Specification { response { status(200) headers { - contentType(applicationJsonUtf8()) + header(contentType(), applicationJsonUtf8()) } body([ "data": [ @@ -677,7 +677,7 @@ class PactContractConverterSpec extends Specification { when: Collection contracts = converter.convertFrom(pactv3Json) then: - contracts == expectedContracts + contracts == expectedContracts } @Issue("#889") @@ -704,7 +704,7 @@ class PactContractConverterSpec extends Specification { ]) headers { header('BOOK-NAME', 'foo') - messagingContentType(applicationJson()) + header(messagingContentType(), applicationJson()) } bodyMatchers { jsonPath('$.bookName', byType()) diff --git a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/contracts/shouldReturnFraudStats.json b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/contracts/shouldReturnFraudStats.json index 85880e28df..f34a0dff84 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/contracts/shouldReturnFraudStats.json +++ b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/contracts/shouldReturnFraudStats.json @@ -73,4 +73,4 @@ "version": "3.6.7" } } -} \ No newline at end of file +} diff --git a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/pact/pact_v2.json b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/pact/pact_v2.json index 7c9a032fcb..31567ad63a 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/pact/pact_v2.json +++ b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/pact/pact_v2.json @@ -15,7 +15,7 @@ "response": { "status": 200, "headers": { - "Content-Type": "application/json; charset=UTF-8" + "Content-Type": "application/json;charset=UTF-8" }, "body": [ [ @@ -53,7 +53,7 @@ "response": { "status": 200, "headers": { - "Content-Type": "application/json; charset=UTF-8" + "Content-Type": "application/json;charset=UTF-8" }, "body": [ [ diff --git a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/pact/pact_v3.json b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/pact/pact_v3.json index 9a23b74f2d..18e3702168 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/pact/pact_v3.json +++ b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/pact/pact_v3.json @@ -12,7 +12,7 @@ "method": "GET", "path": "/", "headers": { - "Content-Type": "application/json; charset=UTF-8", + "Content-Type": "application/json;charset=UTF-8", "Some-Header": "someValue", "someHeaderWithJsonContent": "{\"issue\":\"#595\"}" }, @@ -32,7 +32,7 @@ "response": { "status": 200, "headers": { - "Content-Type": "application/json; charset=UTF-8", + "Content-Type": "application/json;charset=UTF-8", "Some-Header": "someValue", "someHeaderWithJsonContent": "{\"issue\":\"#595\"}" }, @@ -143,7 +143,7 @@ "response": { "status": 200, "headers": { - "Content-Type": "application/json; charset=UTF-8" + "Content-Type": "application/json;charset=UTF-8" }, "body": [ { @@ -226,7 +226,7 @@ "response": { "status": 200, "headers": { - "Content-Type": "application/json; charset=UTF-8" + "Content-Type": "application/json;charset=UTF-8" }, "body": { "data": { diff --git a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/pact/pact_v3_unsupported_rule_logic.json b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/pact/pact_v3_unsupported_rule_logic.json index 70cc8989ef..7c60020a78 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/pact/pact_v3_unsupported_rule_logic.json +++ b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/pact/pact_v3_unsupported_rule_logic.json @@ -15,7 +15,7 @@ "response": { "status": 200, "headers": { - "Content-Type": "application/json; charset=UTF-8" + "Content-Type": "application/json;charset=UTF-8" }, "body": [ { diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/CommunicationType.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/CommunicationType.java index 729f45af76..2c9386b120 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/CommunicationType.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/CommunicationType.java @@ -17,7 +17,7 @@ package org.springframework.cloud.contract.verifier.builder; /** - * Describes the type of communication + * Describes the type of communication. * * @author Marcin Grzejszczak * @since 2.1.0 diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsRequestCookiesWhen.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsRequestCookiesWhen.java index 75f002a330..9b9b73e9ea 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsRequestCookiesWhen.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsRequestCookiesWhen.java @@ -23,6 +23,7 @@ import org.springframework.cloud.contract.spec.internal.Cookie; import org.springframework.cloud.contract.spec.internal.MatchingStrategy; import org.springframework.cloud.contract.spec.internal.Request; import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; +import org.springframework.cloud.contract.verifier.util.MapConverter; class JaxRsRequestCookiesWhen implements When { @@ -47,7 +48,8 @@ class JaxRsRequestCookiesWhen implements When { while (iterator.hasNext()) { Cookie cookie = iterator.next(); String value = ".cookie(" + this.bodyParser.quotedShortText(cookie.getKey()) - + ", " + this.bodyParser.quotedShortText(cookie.getServerValue()) + + ", " + this.bodyParser.quotedShortText(MapConverter + .getTestSideValuesForNonBody(cookie.getServerValue())) + ")"; if (iterator.hasNext()) { this.blockBuilder.addLine(value); diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsRequestHeadersWhen.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsRequestHeadersWhen.java index a0fef14dbd..5e4d13c2fb 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsRequestHeadersWhen.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsRequestHeadersWhen.java @@ -23,6 +23,7 @@ import org.springframework.cloud.contract.spec.internal.Header; import org.springframework.cloud.contract.spec.internal.MatchingStrategy; import org.springframework.cloud.contract.spec.internal.Request; import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; +import org.springframework.cloud.contract.verifier.util.MapConverter; class JaxRsRequestHeadersWhen implements When { @@ -47,7 +48,9 @@ class JaxRsRequestHeadersWhen implements When { while (iterator.hasNext()) { Header header = iterator.next(); String text = ".header(\"" + header.getName() + "\", " - + this.bodyParser.quotedLongText(header.getServerValue()) + ")"; + + this.bodyParser.quotedLongText(MapConverter + .getTestSideValuesForNonBody(header.getServerValue())) + + ")"; if (iterator.hasNext()) { this.blockBuilder.addLine(text); } diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsRequestWhen.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsRequestWhen.java index 831b276b37..08213d79ac 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsRequestWhen.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsRequestWhen.java @@ -19,6 +19,7 @@ package org.springframework.cloud.contract.verifier.builder; import org.springframework.cloud.contract.spec.internal.Header; import org.springframework.cloud.contract.spec.internal.Request; import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; +import org.springframework.cloud.contract.verifier.util.MapConverter; import org.springframework.util.StringUtils; class JaxRsRequestWhen implements When, JaxRsAcceptor, QueryParamsResolver { @@ -57,7 +58,8 @@ class JaxRsRequestWhen implements When, JaxRsAcceptor, QueryParamsResolver { if (foundHeader == null) { return ""; } - return foundHeader.getServerValue().toString(); + return MapConverter.getTestSideValuesForNonBody(foundHeader.getServerValue()) + .toString(); } @Override diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsUrlPathWhen.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsUrlPathWhen.java index 7159680c28..ca99d4cb6f 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsUrlPathWhen.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsUrlPathWhen.java @@ -108,6 +108,9 @@ class JaxRsUrlPathWhen implements When, JaxRsAcceptor, QueryParamsResolver { else if (o instanceof MatchingStrategy) { return allowedQueryParameter((MatchingStrategy) o); } + else if (o instanceof DslProperty) { + return allowedQueryParameter(((DslProperty) o).getServerValue()); + } return true; } diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcCookiesGiven.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcCookiesGiven.java index faa86133ce..0aa26eed07 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcCookiesGiven.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcCookiesGiven.java @@ -33,11 +33,11 @@ class MockMvcCookiesGiven implements Given { @Override public MethodVisitor apply(SingleContractMetadata metadata) { - processInput(this.blockBuilder, metadata.getContract().getRequest()); + processInput(metadata.getContract().getRequest()); return this; } - private void processInput(BlockBuilder bb, Request request) { + private void processInput(Request request) { Iterator iterator = request.getCookies().getEntries().iterator(); while (iterator.hasNext()) { Cookie cookie = iterator.next(); diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcHeadersGiven.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcHeadersGiven.java index d691550453..eefb2f89cd 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcHeadersGiven.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcHeadersGiven.java @@ -23,6 +23,7 @@ import org.springframework.cloud.contract.spec.internal.Headers; import org.springframework.cloud.contract.spec.internal.MatchingStrategy; import org.springframework.cloud.contract.spec.internal.Request; import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; +import org.springframework.cloud.contract.verifier.util.MapConverter; class MockMvcHeadersGiven implements Given { @@ -56,7 +57,9 @@ class MockMvcHeadersGiven implements Given { private String string(Header header) { return ".header(" + ContentHelper.getTestSideForNonBodyValue(header.getName()) - + ", " + ContentHelper.getTestSideForNonBodyValue(header.getServerValue()) + + ", " + + ContentHelper.getTestSideForNonBodyValue( + MapConverter.getTestSideValuesForNonBody(header.getServerValue())) + ")"; } diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMultipartGiven.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMultipartGiven.java index 502320528b..d9ca76185a 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMultipartGiven.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMultipartGiven.java @@ -22,6 +22,7 @@ import org.springframework.cloud.contract.spec.internal.NamedProperty; import org.springframework.cloud.contract.spec.internal.Request; import org.springframework.cloud.contract.verifier.config.TestFramework; import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; +import org.springframework.cloud.contract.verifier.util.MapConverter; import static org.springframework.cloud.contract.verifier.util.ContentUtils.getJavaMultipartFileParameterContent; @@ -74,7 +75,9 @@ class MockMvcMultipartGiven implements Given { private String getParameterString(Map.Entry parameter) { return ".param(" + this.bodyParser.quotedShortText(parameter.getKey()) + ", " - + this.bodyParser.quotedShortText(parameter.getValue()) + ")"; + + this.bodyParser.quotedShortText( + MapConverter.getTestSideValuesForNonBody(parameter.getValue())) + + ")"; } @Override diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcQueryParamsWhen.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcQueryParamsWhen.java index 6da2976e43..22efcfd213 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcQueryParamsWhen.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcQueryParamsWhen.java @@ -25,6 +25,7 @@ import org.springframework.cloud.contract.spec.internal.QueryParameter; import org.springframework.cloud.contract.spec.internal.Request; import org.springframework.cloud.contract.spec.internal.Url; import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; +import org.springframework.cloud.contract.verifier.util.MapConverter; class MockMvcQueryParamsWhen implements When, MockMvcAcceptor, QueryParamsResolver { @@ -87,7 +88,9 @@ class MockMvcQueryParamsWhen implements When, MockMvcAcceptor, QueryParamsResolv private String addQueryParameter(QueryParameter queryParam) { return "." + QUERY_PARAM_METHOD + "(" + this.bodyParser.quotedLongText(queryParam.getName()) + "," - + this.bodyParser.quotedLongText(resolveParamValue(queryParam)) + ")"; + + this.bodyParser.quotedLongText(resolveParamValue( + MapConverter.getTestSideValuesForNonBody(queryParam))) + + ")"; } @Override diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/QueryParamsResolver.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/QueryParamsResolver.java index 23663079a0..4a58847066 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/QueryParamsResolver.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/QueryParamsResolver.java @@ -16,6 +16,7 @@ package org.springframework.cloud.contract.verifier.builder; +import org.springframework.cloud.contract.spec.internal.DslProperty; import org.springframework.cloud.contract.spec.internal.MatchingStrategy; import org.springframework.cloud.contract.spec.internal.OptionalProperty; import org.springframework.cloud.contract.spec.internal.QueryParameter; @@ -35,6 +36,9 @@ interface QueryParamsResolver { else if (value instanceof MatchingStrategy) { return ((MatchingStrategy) value).getServerValue().toString(); } + else if (value instanceof DslProperty) { + return resolveParamValue(((DslProperty) value).getServerValue()); + } return value.toString(); } diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMockMvcMultipartGiven.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMockMvcMultipartGiven.java index 81803eff67..dcd2c8b82b 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMockMvcMultipartGiven.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMockMvcMultipartGiven.java @@ -23,6 +23,7 @@ import org.springframework.cloud.contract.spec.internal.Request; import org.springframework.cloud.contract.verifier.config.TestFramework; import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; import org.springframework.cloud.contract.verifier.util.ContentUtils; +import org.springframework.cloud.contract.verifier.util.MapConverter; class SpockMockMvcMultipartGiven implements Given { @@ -74,7 +75,9 @@ class SpockMockMvcMultipartGiven implements Given { private String getParameterString(Map.Entry parameter) { return ".param(" + this.bodyParser.quotedShortText(parameter.getKey()) + ", " - + this.bodyParser.quotedShortText(parameter.getValue()) + ")"; + + this.bodyParser.quotedShortText( + MapConverter.getTestSideValuesForNonBody(parameter.getValue())) + + ")"; } @Override diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlToContracts.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlToContracts.groovy index 07d5e2ee26..247c46bf60 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlToContracts.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlToContracts.groovy @@ -591,7 +591,7 @@ class YamlToContracts { private Headers yamlHeadersToContractHeaders(Map headers) { Set
convertedHeaders = headers.keySet().stream() - .map({ new Header(it, headers.get(it)) }) + .map({ Header.build(it, headers.get(it)) }) .collect(toSet()) Headers contractHeaders = new Headers() contractHeaders.headers(convertedHeaders) diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockRequestStubStrategy.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockRequestStubStrategy.groovy index 354d077ea1..e7ec9b332d 100755 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockRequestStubStrategy.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockRequestStubStrategy.groovy @@ -46,6 +46,7 @@ import org.springframework.cloud.contract.spec.internal.QueryParameters import org.springframework.cloud.contract.spec.internal.RegexPatterns import org.springframework.cloud.contract.spec.internal.RegexProperty import org.springframework.cloud.contract.spec.internal.Request +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata import org.springframework.cloud.contract.verifier.util.ContentType import org.springframework.cloud.contract.verifier.util.ContentUtils import org.springframework.cloud.contract.verifier.util.JsonPaths @@ -80,11 +81,14 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy { private final Request request private final ContentType contentType - WireMockRequestStubStrategy(Contract groovyDsl) { + WireMockRequestStubStrategy(Contract groovyDsl, SingleContractMetadata singleContractMetadata) { super(groovyDsl) this.request = groovyDsl.request - this.contentType = - tryToGetContentType(request?.body?.clientValue, request?.headers) + this.contentType = contentType(singleContractMetadata) + } + + protected ContentType contentType(SingleContractMetadata singleContractMetadata) { + return singleContractMetadata.evaluatedInputStubContentType } @PackageScope @@ -225,7 +229,7 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy { (it.value as NamedProperty).value.clientValue, (it.value as NamedProperty).contentType?.clientValue)) : WireMock. - matching(RegexPatterns.multipartParam(it.key, it.value))) + matching(RegexPatterns.multipartParam(it.key, MapConverter.getStubSideValuesForNonBody(it.value)))) } multipartPatterns.each { requestPattern.withRequestBody(it) @@ -312,7 +316,7 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy { } @TypeChecked(TypeCheckingMode.SKIP) - private ContentPattern convertToValuePattern(Object object) { + protected ContentPattern convertToValuePattern(Object object) { switch (object) { case Pattern: case RegexProperty: diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategy.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategy.groovy index 8648c5d8b9..1c8ad02052 100755 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategy.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategy.groovy @@ -28,12 +28,10 @@ import org.springframework.cloud.contract.spec.Contract import org.springframework.cloud.contract.spec.internal.FromFileProperty import org.springframework.cloud.contract.spec.internal.Request import org.springframework.cloud.contract.spec.internal.Response +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata import org.springframework.cloud.contract.verifier.util.ContentType import org.springframework.cloud.contract.verifier.util.MapConverter import org.springframework.core.io.support.SpringFactoriesLoader - -import static org.springframework.cloud.contract.verifier.util.ContentUtils.evaluateContentType - /** * Converts a {@link Request} into {@link ResponseDefinition} * @@ -47,10 +45,16 @@ import static org.springframework.cloud.contract.verifier.util.ContentUtils.eval class WireMockResponseStubStrategy extends BaseWireMockStubStrategy { private final Response response + private final ContentType contentType - WireMockResponseStubStrategy(Contract groovyDsl) { + WireMockResponseStubStrategy(Contract groovyDsl, SingleContractMetadata singleContractMetadata) { super(groovyDsl) this.response = groovyDsl.response + this.contentType = contentType(singleContractMetadata) + } + + protected ContentType contentType(SingleContractMetadata singleContractMetadata) { + return singleContractMetadata.evaluatedOutputStubContentType } @PackageScope @@ -91,7 +95,6 @@ class WireMockResponseStubStrategy extends BaseWireMockStubStrategy { private void appendBody(ResponseDefinitionBuilder builder) { if (response.body) { Object body = MapConverter.getStubSideValues(response.body) - ContentType contentType = evaluateContentType(response.headers, body) if (body instanceof byte[]) { builder.withBody(body) } diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockStubStrategy.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockStubStrategy.groovy index f83b585e32..ebc3b1405a 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockStubStrategy.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockStubStrategy.groovy @@ -22,6 +22,7 @@ import com.github.tomakehurst.wiremock.stubbing.StubMapping import org.springframework.cloud.contract.spec.Contract import org.springframework.cloud.contract.verifier.file.ContractMetadata +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata /** * Converts a {@link ContractMetadata} into a WireMock stub @@ -42,8 +43,9 @@ class WireMockStubStrategy { WireMockStubStrategy(String rootName, ContractMetadata contract, Contract groovyDsl) { this.rootName = rootName this.contract = contract - this.wireMockRequestStubStrategy = new WireMockRequestStubStrategy(groovyDsl) - this.wireMockResponseStubStrategy = new WireMockResponseStubStrategy(groovyDsl) + SingleContractMetadata singleContractMetadata = contract.forContract(groovyDsl) + this.wireMockRequestStubStrategy = new WireMockRequestStubStrategy(groovyDsl, singleContractMetadata) + this.wireMockResponseStubStrategy = new WireMockResponseStubStrategy(groovyDsl, singleContractMetadata) this.priority = groovyDsl.priority this.groovyDsl = groovyDsl } @@ -53,7 +55,6 @@ class WireMockStubStrategy { */ String toWireMockClientStub() { StubMapping stubMapping = new StubMapping() - RequestPattern request = wireMockRequestStubStrategy.buildClientRequestContent() ResponseDefinition response = wireMockResponseStubStrategy. buildClientResponseContent() diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/file/ContractMetadata.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/file/ContractMetadata.groovy index a2c095992d..d3c55c03d1 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/file/ContractMetadata.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/file/ContractMetadata.groovy @@ -81,6 +81,11 @@ class ContractMetadata { .findAll { it != null } .collect { new SingleContractMetadata(it, this) }) } + + SingleContractMetadata forContract(Contract contract) { + return this.convertedContractWithMetadata + .find { it.contract == contract } + } } @CompileStatic diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/ContractVerifierDslConverter.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/ContractVerifierDslConverter.groovy index a82cefe54f..b0516b735e 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/ContractVerifierDslConverter.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/ContractVerifierDslConverter.groovy @@ -19,6 +19,7 @@ package org.springframework.cloud.contract.verifier.util import groovy.transform.CompileStatic import groovy.util.logging.Commons import org.codehaus.groovy.control.CompilerConfiguration +import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer import org.springframework.cloud.contract.spec.Contract import org.springframework.util.StringUtils diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonPaths.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonPaths.groovy index 4e8a342d79..64f01ab6ca 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonPaths.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonPaths.groovy @@ -24,6 +24,6 @@ import groovy.transform.CompileStatic * @since 1.0.0 */ @CompileStatic -class JsonPaths extends HashSet { +class JsonPaths extends LinkedHashSet { } diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/RegexpBuilders.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/RegexpBuilders.groovy index 7d030ec6f2..77e8759cc5 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/RegexpBuilders.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/RegexpBuilders.groovy @@ -66,6 +66,15 @@ class RegexpBuilders { * Converts the {@link Object} passed values into their stub side String representations */ static String buildGStringRegexpForStubSide(Object o) { + if (o instanceof DslProperty) { + return buildGStringRegexpForStubSide((DslProperty) o) + } + else if (o instanceof Pattern) { + return buildGStringRegexpForStubSide((Pattern) o) + } + else if (o instanceof GString) { + return buildGStringRegexpForStubSide((GString) o) + } return escapeSpecialRegexChars(o.toString()) } diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy index ae94edaa46..037cc34f2d 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy @@ -1581,14 +1581,14 @@ class FooSpec extends Specification { \t\tand: \t\t\tDocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.getPayload())) -\t\t\tassertThatJson(parsedJson).field("['lastName']").matches("[\\\\S\\\\s]+") -\t\t\tassertThatJson(parsedJson).field("['userId']").matches("([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})?") -\t\t\tassertThatJson(parsedJson).field("['middleName']").matches("([\\\\S\\\\s]+)?") -\t\t\tassertThatJson(parsedJson).field("['version']").matches("-?(\\\\d*\\\\.\\\\d+|\\\\d+)") -\t\t\tassertThatJson(parsedJson).field("['uid']").matches("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}") \t\t\tassertThatJson(parsedJson).field("['type']").isEqualTo("CREATED") \t\t\tassertThatJson(parsedJson).field("['personId']").matches("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}") +\t\t\tassertThatJson(parsedJson).field("['userId']").matches("([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})?") \t\t\tassertThatJson(parsedJson).field("['firstName']").matches("[\\\\S\\\\s]+") +\t\t\tassertThatJson(parsedJson).field("['middleName']").matches("([\\\\S\\\\s]+)?") +\t\t\tassertThatJson(parsedJson).field("['lastName']").matches("[\\\\S\\\\s]+") +\t\t\tassertThatJson(parsedJson).field("['version']").matches("-?(\\\\d*\\\\.\\\\d+|\\\\d+)") +\t\t\tassertThatJson(parsedJson).field("['uid']").matches("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}") \t} } @@ -1637,18 +1637,17 @@ public class FooTest { \t\t// and: \t\t\tDocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.getPayload())); -\t\t\tassertThatJson(parsedJson).field("['lastName']").matches("[\\\\S\\\\s]+"); -\t\t\tassertThatJson(parsedJson).field("['userId']").matches("([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})?"); -\t\t\tassertThatJson(parsedJson).field("['middleName']").matches("([\\\\S\\\\s]+)?"); -\t\t\tassertThatJson(parsedJson).field("['version']").matches("-?(\\\\d*\\\\.\\\\d+|\\\\d+)"); -\t\t\tassertThatJson(parsedJson).field("['uid']").matches("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"); \t\t\tassertThatJson(parsedJson).field("['type']").isEqualTo("CREATED"); \t\t\tassertThatJson(parsedJson).field("['personId']").matches("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"); +\t\t\tassertThatJson(parsedJson).field("['userId']").matches("([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})?"); \t\t\tassertThatJson(parsedJson).field("['firstName']").matches("[\\\\S\\\\s]+"); +\t\t\tassertThatJson(parsedJson).field("['middleName']").matches("([\\\\S\\\\s]+)?"); +\t\t\tassertThatJson(parsedJson).field("['lastName']").matches("[\\\\S\\\\s]+"); +\t\t\tassertThatJson(parsedJson).field("['version']").matches("-?(\\\\d*\\\\.\\\\d+|\\\\d+)"); +\t\t\tassertThatJson(parsedJson).field("['uid']").matches("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"); \t} } - """ } } diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SpringTestMethodBodyBuildersSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SpringTestMethodBodyBuildersSpec.groovy index b9df72fc06..2d853d05be 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SpringTestMethodBodyBuildersSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SpringTestMethodBodyBuildersSpec.groovy @@ -1364,13 +1364,13 @@ World.'''""" } | ['"Content-Type", "multipart/form-data;boundary=AaB03x"', '.param("formParameter", "\\"formParameterValue\\"")', '.param("someBooleanParameter", "true")', - '.multiPart("file", "filename.csv", "file content".getBytes(), "application/json");'] + '.multiPart("file", "filename.csv", "file content".getBytes(), "application/json")'] "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } | ['"Content-Type", "multipart/form-data;boundary=AaB03x"', '.param("formParameter", "\\"formParameterValue\\"")', '.param("someBooleanParameter", "true")', - '.multiPart("file", "filename.csv", "file content".getBytes(), "application/json");'] + '.multiPart("file", "filename.csv", "file content".getBytes(), "application/json")'] } @@ -1423,13 +1423,13 @@ World.'''""" } | ['"Content-Type", "multipart/form-data;boundary=AaB03x"', '.param("formParameter", "\\"formParameterValue\\"")', '.param("someBooleanParameter", "true")', - '.multiPart("file", "filename.csv", "file content".getBytes());'] + '.multiPart("file", "filename.csv", "file content".getBytes())'] "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } | ['"Content-Type", "multipart/form-data;boundary=AaB03x"', '.param("formParameter", "\\"formParameterValue\\"")', '.param("someBooleanParameter", "true")', - '.multiPart("file", "filename.csv", "file content".getBytes());'] + '.multiPart("file", "filename.csv", "file content".getBytes())'] } @Issue('546') @@ -1525,13 +1525,13 @@ World.'''""" } | ['"Content-Type", "multipart/form-data;boundary=AaB03x"', '.param("formParameter", "\\"formParameterValue\\"")', '.param("someBooleanParameter", "true")', - '.multiPart("file", toString(), "file content".getBytes());'] + '.multiPart("file", toString(), "file content".getBytes())'] "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } | ['"Content-Type", "multipart/form-data;boundary=AaB03x"', '.param("formParameter", "\\"formParameterValue\\"")', '.param("someBooleanParameter", "true")', - '.multiPart("file", toString(), "file content".getBytes());'] + '.multiPart("file", toString(), "file content".getBytes())'] } diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/YamlMockMvcMethodBodyBuilderSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/YamlMockMvcMethodBodyBuilderSpec.groovy index 37837b99f7..4467e1fca9 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/YamlMockMvcMethodBodyBuilderSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/YamlMockMvcMethodBodyBuilderSpec.groovy @@ -941,13 +941,13 @@ response: } | ['"Content-Type", "multipart/form-data;boundary=AaB03x"', '.param("formParameter", "\\"formParameterValue\\"")', '.param("someBooleanParameter", "true")', - '.multiPart("file", "filename.csv", "file content".getBytes(), "application/json");'] + '.multiPart("file", "filename.csv", "file content".getBytes(), "application/json")'] "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } | ['"Content-Type", "multipart/form-data;boundary=AaB03x"', '.param("formParameter", "\\"formParameterValue\\"")', '.param("someBooleanParameter", "true")', - '.multiPart("file", "filename.csv", "file content".getBytes(), "application/json");'] + '.multiPart("file", "filename.csv", "file content".getBytes(), "application/json")'] } @@ -1014,11 +1014,11 @@ response: "mockmvc" | { properties.testMode = TestMode.MOCKMVC } | ['"Content-Type", "multipart/form-data;boundary=AaB03x"', - '.multiPart("file", "filename.csv", "file content".getBytes(), "application/json");'] + '.multiPart("file", "filename.csv", "file content".getBytes(), "application/json")'] "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } | ['"Content-Type", "multipart/form-data;boundary=AaB03x"', - '.multiPart("file", "filename.csv", "file content".getBytes(), "application/json");'] + '.multiPart("file", "filename.csv", "file content".getBytes(), "application/json")'] } @@ -1091,13 +1091,13 @@ response: } | ['"Content-Type", "multipart/form-data;boundary=AaB03x"', '.param("formParameter", "\\"formParameterValue\\"")', '.param("someBooleanParameter", "true")', - '.multiPart("file", toString(), "file content".getBytes(), "application/json");'] + '.multiPart("file", toString(), "file content".getBytes(), "application/json")'] "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } | ['"Content-Type", "multipart/form-data;boundary=AaB03x"', '.param("formParameter", "\\"formParameterValue\\"")', '.param("someBooleanParameter", "true")', - '.multiPart("file", toString(), "file content".getBytes(), "application/json");'] + '.multiPart("file", toString(), "file content".getBytes(), "application/json")'] } diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/converter/DslToYamlContractConverterSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/converter/DslToYamlContractConverterSpec.groovy index b92b2d5228..58e394d8a8 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/converter/DslToYamlContractConverterSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/converter/DslToYamlContractConverterSpec.groovy @@ -264,7 +264,7 @@ class DslToYamlContractConverterSpec extends Specification { ] yamlContract.response.matchers.headers == [ new YamlContract.TestHeaderMatcher( - key: "Content-Type", regex: "application/json.*"), + key: "Content-Type", regex: "application/json.*", regexType: YamlContract.RegexType.as_string), new YamlContract.TestHeaderMatcher( key: "Some-Header", regex: "[a-zA-Z]{9}", regexType: YamlContract.RegexType.as_string) ] @@ -408,7 +408,7 @@ class DslToYamlContractConverterSpec extends Specification { ] yamlContract.response.matchers.headers == [ new YamlContract.TestHeaderMatcher( - key: "Content-Type", regex: "application/json.*") + key: "Content-Type", regex: "application/json.*", regexType: YamlContract.RegexType.as_string) ] yamlContract.response.matchers.body == [ new YamlContract.BodyTestMatcher( diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategySpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategySpec.groovy index b60a2a4c4a..0406d04e50 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategySpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategySpec.groovy @@ -21,6 +21,8 @@ import spock.lang.Issue import spock.lang.Specification import org.springframework.cloud.contract.spec.Contract +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata +import org.springframework.cloud.contract.verifier.util.ContentType class WireMockResponseStubStrategySpec extends Specification { @@ -40,7 +42,9 @@ class WireMockResponseStubStrategySpec extends Specification { } } when: - def subject = new WireMockResponseStubStrategy(contract) + SingleContractMetadata metadata = Stub() + metadata.evaluatedOutputStubContentType >> ContentType.JSON + def subject = new WireMockResponseStubStrategy(contract, metadata) def content = subject.buildClientResponseContent() then: '{"value":1.5}' == content.body @@ -66,7 +70,9 @@ class WireMockResponseStubStrategySpec extends Specification { } } when: - def subject = new WireMockResponseStubStrategy(contract) + SingleContractMetadata metadata = Stub() + metadata.evaluatedOutputStubContentType >> ContentType.JSON + def subject = new WireMockResponseStubStrategy(contract, metadata) def content = subject.buildClientResponseContent() then: Map body = new JsonSlurper().parseText(content.body) as Map @@ -226,12 +232,22 @@ class WireMockResponseStubStrategySpec extends Specification { } } when: - def subject = new WireMockRequestStubStrategy(contract) + def subject = new WireMockRequestStubStrategy(contract, null) { + @Override + protected ContentType contentType(SingleContractMetadata singleContractMetadata) { + return ContentType.JSON + } + } subject.buildClientRequestContent() then: noExceptionThrown() when: - def response = new WireMockResponseStubStrategy(contract) + def response = new WireMockResponseStubStrategy(contract, null) { + @Override + protected ContentType contentType(SingleContractMetadata singleContractMetadata) { + return ContentType.JSON + } + } response.buildClientResponseContent() then: noExceptionThrown() diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/xml/XPathSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/xml/XPathSpec.groovy index 06e7fb9c7e..002019f78f 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/xml/XPathSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/xml/XPathSpec.groovy @@ -1,17 +1,17 @@ /* - * Copyright 2019 the original author or authors. + * Copyright 2013-2019 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 + * 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 + * 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. + * 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. */ package org.springframework.cloud.contract.verifier.util.xml diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/xml/XmlAssertionSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/xml/XmlAssertionSpec.groovy index 71543aa959..b7c24913d0 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/xml/XmlAssertionSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/xml/XmlAssertionSpec.groovy @@ -1,17 +1,17 @@ /* - * Copyright 2019 the original author or authors. + * Copyright 2013-2019 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 + * 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 + * 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. + * 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. */ package org.springframework.cloud.contract.verifier.util.xml diff --git a/spring-cloud-contract-verifier/src/test/resources/directory/with/name-with-hyphen/car-rental/diff.groovy b/spring-cloud-contract-verifier/src/test/resources/directory/with/name-with-hyphen/car-rental/diff.groovy index 27563de935..a43c4a795a 100644 --- a/spring-cloud-contract-verifier/src/test/resources/directory/with/name-with-hyphen/car-rental/diff.groovy +++ b/spring-cloud-contract-verifier/src/test/resources/directory/with/name-with-hyphen/car-rental/diff.groovy @@ -1,5 +1,3 @@ -import org.springframework.cloud.contract.spec.Contract - /* * Copyright 2013-2019 the original author or authors. * @@ -15,6 +13,9 @@ import org.springframework.cloud.contract.spec.Contract * See the License for the specific language governing permissions and * limitations under the License. */ + +import org.springframework.cloud.contract.spec.Contract + Contract.make { request { method(GET()) @@ -23,4 +24,4 @@ Contract.make { response { status(207) } -} \ No newline at end of file +} diff --git a/spring-cloud-contract-verifier/src/test/resources/directory/with/scenario/01_login.groovy b/spring-cloud-contract-verifier/src/test/resources/directory/with/scenario/01_login.groovy index fb050d1d99..d630ee6a98 100644 --- a/spring-cloud-contract-verifier/src/test/resources/directory/with/scenario/01_login.groovy +++ b/spring-cloud-contract-verifier/src/test/resources/directory/with/scenario/01_login.groovy @@ -1,5 +1,3 @@ -import org.springframework.cloud.contract.spec.Contract - /* * Copyright 2013-2019 the original author or authors. * @@ -15,6 +13,9 @@ import org.springframework.cloud.contract.spec.Contract * See the License for the specific language governing permissions and * limitations under the License. */ + +import org.springframework.cloud.contract.spec.Contract + Contract.make { request { method(GET()) @@ -23,4 +24,4 @@ Contract.make { response { status(200) } -} \ No newline at end of file +} diff --git a/spring-cloud-contract-verifier/src/test/resources/directory/with/scenario/02_showCart.groovy b/spring-cloud-contract-verifier/src/test/resources/directory/with/scenario/02_showCart.groovy index 0c9dd6b699..fafc818eaf 100644 --- a/spring-cloud-contract-verifier/src/test/resources/directory/with/scenario/02_showCart.groovy +++ b/spring-cloud-contract-verifier/src/test/resources/directory/with/scenario/02_showCart.groovy @@ -1,5 +1,3 @@ -import org.springframework.cloud.contract.spec.Contract - /* * Copyright 2013-2019 the original author or authors. * @@ -15,6 +13,9 @@ import org.springframework.cloud.contract.spec.Contract * See the License for the specific language governing permissions and * limitations under the License. */ + +import org.springframework.cloud.contract.spec.Contract + Contract.make { request { method(GET()) @@ -23,4 +24,4 @@ Contract.make { response { status(200) } -} \ No newline at end of file +} diff --git a/spring-cloud-contract-verifier/src/test/resources/directory/with/scenario/03_logout.groovy b/spring-cloud-contract-verifier/src/test/resources/directory/with/scenario/03_logout.groovy index e0cd3a5fa7..27af22c15b 100644 --- a/spring-cloud-contract-verifier/src/test/resources/directory/with/scenario/03_logout.groovy +++ b/spring-cloud-contract-verifier/src/test/resources/directory/with/scenario/03_logout.groovy @@ -1,5 +1,3 @@ -import org.springframework.cloud.contract.spec.Contract - /* * Copyright 2013-2019 the original author or authors. * @@ -15,6 +13,9 @@ import org.springframework.cloud.contract.spec.Contract * See the License for the specific language governing permissions and * limitations under the License. */ + +import org.springframework.cloud.contract.spec.Contract + Contract.make { request { method(GET()) @@ -23,4 +24,4 @@ Contract.make { response { status(200) } -} \ No newline at end of file +} diff --git a/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/different/diff.groovy b/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/different/diff.groovy index f0fa9a2c52..f399560160 100644 --- a/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/different/diff.groovy +++ b/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/different/diff.groovy @@ -1,5 +1,3 @@ -import org.springframework.cloud.contract.spec.Contract - /* * Copyright 2013-2019 the original author or authors. * @@ -15,6 +13,9 @@ import org.springframework.cloud.contract.spec.Contract * See the License for the specific language governing permissions and * limitations under the License. */ + +import org.springframework.cloud.contract.spec.Contract + Contract.make { request { method(GET()) @@ -23,4 +24,4 @@ Contract.make { response { status(200) } -} \ No newline at end of file +} diff --git a/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/other/different/diff.groovy b/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/other/different/diff.groovy index 568f5efae0..d574e0442b 100644 --- a/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/other/different/diff.groovy +++ b/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/other/different/diff.groovy @@ -1,5 +1,3 @@ -import org.springframework.cloud.contract.spec.Contract - /* * Copyright 2013-2019 the original author or authors. * @@ -15,6 +13,9 @@ import org.springframework.cloud.contract.spec.Contract * See the License for the specific language governing permissions and * limitations under the License. */ + +import org.springframework.cloud.contract.spec.Contract + Contract.make { request { method(GET()) @@ -23,4 +24,4 @@ Contract.make { response { status(203) } -} \ No newline at end of file +} diff --git a/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/other/other.groovy b/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/other/other.groovy index bd5b4c3f5b..c52b4c3515 100644 --- a/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/other/other.groovy +++ b/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/other/other.groovy @@ -1,5 +1,3 @@ -import org.springframework.cloud.contract.spec.Contract - /* * Copyright 2013-2019 the original author or authors. * @@ -15,6 +13,9 @@ import org.springframework.cloud.contract.spec.Contract * See the License for the specific language governing permissions and * limitations under the License. */ + +import org.springframework.cloud.contract.spec.Contract + Contract.make { request { method(GET()) @@ -23,4 +24,4 @@ Contract.make { response { status(202) } -} \ No newline at end of file +} diff --git a/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/other/sample.groovy b/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/other/sample.groovy index 5fb5ea2279..81764d3f68 100644 --- a/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/other/sample.groovy +++ b/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/other/sample.groovy @@ -1,5 +1,3 @@ -import org.springframework.cloud.contract.spec.Contract - /* * Copyright 2013-2019 the original author or authors. * @@ -15,6 +13,9 @@ import org.springframework.cloud.contract.spec.Contract * See the License for the specific language governing permissions and * limitations under the License. */ + +import org.springframework.cloud.contract.spec.Contract + Contract.make { request { method(GET()) @@ -23,4 +24,4 @@ Contract.make { response { status(201) } -} \ No newline at end of file +} diff --git a/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/package/exceptions/test.groovy b/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/package/exceptions/test.groovy index 0a30782d61..343886dc47 100644 --- a/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/package/exceptions/test.groovy +++ b/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/package/exceptions/test.groovy @@ -1,5 +1,3 @@ -import org.springframework.cloud.contract.spec.Contract - /* * Copyright 2013-2019 the original author or authors. * @@ -15,6 +13,9 @@ import org.springframework.cloud.contract.spec.Contract * See the License for the specific language governing permissions and * limitations under the License. */ + +import org.springframework.cloud.contract.spec.Contract + Contract.make { request { method(GET()) @@ -23,4 +24,4 @@ Contract.make { response { status(204) } -} \ No newline at end of file +} diff --git a/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/package/v1/exceptions/testv1.groovy b/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/package/v1/exceptions/testv1.groovy index 11fda5423f..aa71b8097a 100644 --- a/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/package/v1/exceptions/testv1.groovy +++ b/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/package/v1/exceptions/testv1.groovy @@ -1,5 +1,3 @@ -import org.springframework.cloud.contract.spec.Contract - /* * Copyright 2013-2019 the original author or authors. * @@ -15,6 +13,9 @@ import org.springframework.cloud.contract.spec.Contract * See the License for the specific language governing permissions and * limitations under the License. */ + +import org.springframework.cloud.contract.spec.Contract + Contract.make { request { method(GET()) @@ -23,4 +24,4 @@ Contract.make { response { status(205) } -} \ No newline at end of file +} diff --git a/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/package/v2/exceptions/testv2.groovy b/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/package/v2/exceptions/testv2.groovy index 146b18ff65..3ac333e8cd 100644 --- a/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/package/v2/exceptions/testv2.groovy +++ b/spring-cloud-contract-verifier/src/test/resources/directory/with/stubs/package/v2/exceptions/testv2.groovy @@ -1,5 +1,3 @@ -import org.springframework.cloud.contract.spec.Contract - /* * Copyright 2013-2019 the original author or authors. * @@ -15,6 +13,9 @@ import org.springframework.cloud.contract.spec.Contract * See the License for the specific language governing permissions and * limitations under the License. */ + +import org.springframework.cloud.contract.spec.Contract + Contract.make { request { method(GET()) @@ -23,4 +24,4 @@ Contract.make { response { status(206) } -} \ No newline at end of file +} diff --git a/spring-cloud-contract-verifier/src/test/resources/strange_[3.3.3]_directory/02_login.groovy b/spring-cloud-contract-verifier/src/test/resources/strange_[3.3.3]_directory/02_login.groovy index 7a088afbcc..e80cce04da 100644 --- a/spring-cloud-contract-verifier/src/test/resources/strange_[3.3.3]_directory/02_login.groovy +++ b/spring-cloud-contract-verifier/src/test/resources/strange_[3.3.3]_directory/02_login.groovy @@ -1,5 +1,3 @@ -import org.springframework.cloud.contract.spec.Contract - /* * Copyright 2013-2019 the original author or authors. * @@ -15,6 +13,9 @@ import org.springframework.cloud.contract.spec.Contract * See the License for the specific language governing permissions and * limitations under the License. */ + +import org.springframework.cloud.contract.spec.Contract + Contract.make { request { method(GET()) @@ -23,4 +24,4 @@ Contract.make { response { status(200) } -} \ No newline at end of file +} diff --git a/spring-cloud-contract-verifier/src/test/resources/strange_[3.3.3]_directory/bar/03_login.groovy b/spring-cloud-contract-verifier/src/test/resources/strange_[3.3.3]_directory/bar/03_login.groovy index 47269107b5..7eda7ddb0b 100644 --- a/spring-cloud-contract-verifier/src/test/resources/strange_[3.3.3]_directory/bar/03_login.groovy +++ b/spring-cloud-contract-verifier/src/test/resources/strange_[3.3.3]_directory/bar/03_login.groovy @@ -1,5 +1,3 @@ -import org.springframework.cloud.contract.spec.Contract - /* * Copyright 2013-2019 the original author or authors. * @@ -15,6 +13,9 @@ import org.springframework.cloud.contract.spec.Contract * See the License for the specific language governing permissions and * limitations under the License. */ + +import org.springframework.cloud.contract.spec.Contract + Contract.make { request { method(GET()) @@ -23,4 +24,4 @@ Contract.make { response { status(200) } -} \ No newline at end of file +} diff --git a/spring-cloud-contract-verifier/src/test/resources/strange_[3.3.3]_directory/foo/01_login.groovy b/spring-cloud-contract-verifier/src/test/resources/strange_[3.3.3]_directory/foo/01_login.groovy index ccf82aaea3..87632fdbe5 100644 --- a/spring-cloud-contract-verifier/src/test/resources/strange_[3.3.3]_directory/foo/01_login.groovy +++ b/spring-cloud-contract-verifier/src/test/resources/strange_[3.3.3]_directory/foo/01_login.groovy @@ -1,5 +1,3 @@ -import org.springframework.cloud.contract.spec.Contract - /* * Copyright 2013-2019 the original author or authors. * @@ -15,6 +13,9 @@ import org.springframework.cloud.contract.spec.Contract * See the License for the specific language governing permissions and * limitations under the License. */ + +import org.springframework.cloud.contract.spec.Contract + Contract.make { request { method(GET()) @@ -23,4 +24,4 @@ Contract.make { response { status(200) } -} \ No newline at end of file +} diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml index e44b819c1e..e997e48975 100644 --- a/src/checkstyle/checkstyle-suppressions.xml +++ b/src/checkstyle/checkstyle-suppressions.xml @@ -42,4 +42,7 @@ + + +