From 6b9e3774877ee4b212a3f9f3c13e469391b21dd6 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 8 Jul 2019 14:15:21 +0200 Subject: [PATCH] Rewriting to java (#1131) * Rewritting to java - rewritten a lot of Spring Cloud Contract Spec module to Java - extracted all Groovy stuff to a separate package - will now continue writing extension modules - spring-cloud-contract-spec will be written purely in java - spring-cloud-contract-spec-groovy will have Groovy extensions fixes gh-1130 --- README.adoc | 7 +- .../spring-cloud-contract-docker/Dockerfile | 2 +- .../get_dependencies.sh | 2 +- .../project/build.gradle | 14 + .../project/gradle/wrapper/gradle-wrapper.jar | Bin 56177 -> 55616 bytes .../project/gradlew | 18 +- .../project/gradlew.bat | 18 +- docs/src/main/asciidoc/building.adoc | 6 +- .../spring-cloud-contract-verifier.adoc | 3 +- docs/src/main/asciidoc/verifier_contract.adoc | 18 +- pom.xml | 22 +- .../README.adoc | 0 specs/pom.xml | 42 + .../README.adoc | 3 + .../spring-cloud-contract-spec-groovy/pom.xml | 82 ++ .../internal/GroovyDslPropertyConverter.java | 55 ++ .../main/resources/META-INF/spring.factories | 2 + .../spec/internal/ContractSpec.groovy | 49 +- .../README.adoc | 3 + .../spring-cloud-contract-spec-java}/pom.xml | 51 +- .../cloud/contract/spec/Contract.java | 372 ++++++++ .../contract/spec/ContractConverter.java | 32 +- .../cloud/contract/spec/ContractStorer.java | 0 .../cloud/contract/spec/ContractTemplate.java | 0 .../spec/ContractVerifierException.java | 16 +- .../cloud/contract/spec/internal/Body.java | 81 ++ .../contract/spec/internal/BodyMatcher.java | 58 ++ .../contract/spec/internal/BodyMatchers.java | 156 ++++ .../contract/spec/internal/CanBeDynamic.java | 2 +- .../spec/internal/ClientDslProperty.java | 34 + .../contract/spec/internal/ClientInput.java | 8 +- .../spec/internal/ClientOutputMessage.java | 15 +- .../cloud/contract/spec/internal/Common.java | 402 ++++++++ .../internal/CompositeContractTemplate.java | 169 ++++ .../contract/spec/internal/ContractUtils.java | 47 + .../cloud/contract/spec/internal/Cookie.java | 87 ++ .../cloud/contract/spec/internal/Cookies.java | 114 +++ .../CustomHandlebarsContractTemplate.java | 105 +++ .../contract/spec/internal/DslProperty.java | 91 ++ .../spec/internal/DslPropertyConverter.java | 95 ++ .../spec/internal/ExecutionProperty.java | 73 ++ .../spec/internal/FromFileProperty.java | 96 ++ .../contract/spec/internal/FromRequest.java | 219 +++++ .../internal/HandlebarsContractTemplate.java | 160 ++++ .../cloud/contract/spec/internal/Header.java | 86 ++ .../cloud/contract/spec/internal/Headers.java | 490 ++++++++++ .../contract/spec/internal/HttpHeaders.java | 439 +++++---- .../contract/spec/internal/HttpMethods.java | 65 ++ .../contract/spec/internal/HttpStatus.java | 555 +++++++---- .../cloud/contract/spec/internal/Input.java | 405 ++++++++ .../spec/internal/JSONCompareMode.java | 35 +- .../spec/internal/MatchingStrategy.java | 149 +++ .../contract/spec/internal/MatchingType.java | 69 ++ .../spec/internal/MatchingTypeValue.java | 127 +++ .../internal/MatchingTypeValueHolder.java | 39 + .../contract/spec/internal/MediaTypes.java | 95 ++ .../spec/internal/MessagingHeaders.java | 35 + .../contract/spec/internal/Multipart.java | 67 ++ .../contract/spec/internal/NamedProperty.java | 127 +++ .../spec/internal/NotToEscapePattern.java | 18 +- .../spec/internal/OptionalProperty.java | 69 ++ .../contract/spec/internal/OutputMessage.java | 400 ++++++++ .../spec/internal/PathBodyMatcher.java | 84 ++ .../internal/PatternValueDslProperty.java | 190 ++++ .../spec/internal/QueryParameter.java | 99 ++ .../spec/internal/QueryParameters.java | 74 ++ .../spec/internal/RandomStringGenerator.java | 0 .../spec/internal/RegexCreatingProperty.java | 53 +- .../spec/internal/RegexMatchingTypeValue.java | 72 ++ .../contract/spec/internal/RegexPatterns.java | 189 ++++ .../contract/spec/internal/RegexProperty.java | 224 +++++ .../cloud/contract/spec/internal/Request.java | 848 +++++++++++++++++ .../contract/spec/internal/Response.java | 864 ++++++++++++++++++ .../spec/internal/ResponseBodyMatchers.java | 31 +- .../spec/internal/ServerDslProperty.java | 34 + .../contract/spec/internal/ServerInput.java | 25 + .../spec/internal/ServerOutputMessage.java | 25 + .../cloud/contract/spec/internal/Url.java | 104 +++ .../contract/spec/internal/UrlHelper.java | 63 ++ .../cloud/contract/spec/internal/UrlPath.java | 39 + .../cloud/contract/spec/util/RegexpUtils.java | 29 +- .../contract/spec/util/ValidateUtils.java | 95 ++ .../repackaged/nl/flotsam/xeger/Xeger.java | 0 .../nl/flotsam/xeger/XegerTest.java | 0 .../nl/flotsam/xeger/XegerUtilsTest.java | 0 specs/spring-cloud-contract-spec/README.adoc | 3 + specs/spring-cloud-contract-spec/pom.xml | 67 ++ .../spec/internal/ContractSpec.groovy | 30 +- .../contract/spec/internal/CookiesSpec.groovy | 0 .../internal/ExecutionPropertySpec.groovy | 0 .../spec/internal/HttpStatusTests.java | 0 .../contract/spec/internal/InputSpec.groovy | 0 .../spec/internal/MatchingTypeSpec.groovy | 0 .../spec/internal/OutputMessageSpec.groovy | 0 .../spec/internal/RegexPatternsSpec.groovy | 0 .../contract/spec/internal/RequestSpec.groovy | 0 .../spec/internal/ResponseSpec.groovy | 0 spring-cloud-contract-dependencies/pom.xml | 10 + .../cloud/contract/spec/Contract.groovy | 194 ---- .../cloud/contract/spec/internal/Body.groovy | 82 -- .../contract/spec/internal/BodyMatcher.groovy | 55 -- .../spec/internal/BodyMatchers.groovy | 230 ----- .../contract/spec/internal/Common.groovy | 272 ------ .../internal/CompositeContractTemplate.groovy | 171 ---- .../contract/spec/internal/Cookie.groovy | 50 - .../contract/spec/internal/Cookies.groovy | 106 --- .../CustomHandlebarsContractTemplate.groovy | 109 --- .../contract/spec/internal/DslProperty.groovy | 75 -- .../spec/internal/ExecutionProperty.groovy | 54 -- .../spec/internal/FromFileProperty.groovy | 74 -- .../contract/spec/internal/FromRequest.groovy | 196 ---- .../HandlebarsContractTemplate.groovy | 160 ---- .../contract/spec/internal/Header.groovy | 50 - .../contract/spec/internal/Headers.groovy | 146 --- .../contract/spec/internal/HttpMethods.groovy | 69 -- .../cloud/contract/spec/internal/Input.groovy | 177 ---- .../spec/internal/MatchingStrategy.groovy | 67 -- .../spec/internal/MatchingType.groovy | 75 -- .../contract/spec/internal/MediaTypes.groovy | 101 -- .../contract/spec/internal/Multipart.groovy | 60 -- .../spec/internal/NamedProperty.groovy | 69 -- .../spec/internal/OptionalProperty.groovy | 65 -- .../spec/internal/OutputMessage.groovy | 181 ---- .../spec/internal/PathBodyMatcher.groovy | 58 -- .../internal/PatternValueDslProperty.groovy | 187 ---- .../spec/internal/QueryParameter.groovy | 55 -- .../spec/internal/QueryParameters.groovy | 39 - .../spec/internal/RegexPatterns.groovy | 216 ----- .../spec/internal/RegexProperty.groovy | 200 ---- .../contract/spec/internal/Request.groovy | 449 --------- .../contract/spec/internal/Response.groovy | 295 ------ .../spec/internal/ServerDslProperty.groovy | 40 - .../cloud/contract/spec/internal/Url.groovy | 72 -- .../contract/spec/internal/UrlPath.groovy | 46 - .../contract/spec/util/ValidateUtils.groovy | 70 -- .../StubRunnerPropertyUtilsSpec.groovy | 4 +- .../TestCustomYamlContractConverter.java | 4 +- .../wiremock/WireMockToDslConverter.groovy | 21 +- .../WireMockToDslConverterSpec.groovy | 27 +- .../build.gradle | 2 +- .../plugin/ContractVerifierExtension.groovy | 6 +- .../verifier/plugin/ContractsCopyTask.groovy | 24 +- .../GenerateClientStubsFromDslTask.groovy | 9 +- .../plugin/GenerateServerTestsTask.groovy | 34 +- .../plugin/PublishStubsToScmTask.groovy | 3 +- ...ngCloudContractVerifierGradlePlugin.groovy | 30 +- .../FraudDetectionController.java | 4 - .../src/main/resources/application.yml | 2 +- .../RequestResponseSCContractCreator.groovy | 12 +- .../pact/PactContractConverterSpec.groovy | 26 +- .../contracts/shouldReturnFraudStats.json | 2 +- .../src/test/resources/pact/pact_v2.json | 4 +- .../src/test/resources/pact/pact_v3.json | 8 +- .../pact/pact_v3_unsupported_rule_logic.json | 2 +- .../verifier/builder/CommunicationType.java | 2 +- .../builder/JaxRsRequestCookiesWhen.java | 4 +- .../builder/JaxRsRequestHeadersWhen.java | 5 +- .../verifier/builder/JaxRsRequestWhen.java | 4 +- .../verifier/builder/JaxRsUrlPathWhen.java | 3 + .../verifier/builder/MockMvcCookiesGiven.java | 4 +- .../verifier/builder/MockMvcHeadersGiven.java | 5 +- .../builder/MockMvcMultipartGiven.java | 5 +- .../builder/MockMvcQueryParamsWhen.java | 5 +- .../verifier/builder/QueryParamsResolver.java | 4 + .../builder/SpockMockMvcMultipartGiven.java | 5 +- .../verifier/converter/YamlToContracts.groovy | 2 +- .../WireMockRequestStubStrategy.groovy | 14 +- .../WireMockResponseStubStrategy.groovy | 13 +- .../dsl/wiremock/WireMockStubStrategy.groovy | 7 +- .../verifier/file/ContractMetadata.groovy | 5 + .../util/ContractVerifierDslConverter.groovy | 1 + .../contract/verifier/util/JsonPaths.groovy | 2 +- .../verifier/util/RegexpBuilders.groovy | 9 + .../MessagingMethodBodyBuilderSpec.groovy | 21 +- .../SpringTestMethodBodyBuildersSpec.groovy | 12 +- .../YamlMockMvcMethodBodyBuilderSpec.groovy | 12 +- .../DslToYamlContractConverterSpec.groovy | 4 +- .../WireMockResponseStubStrategySpec.groovy | 24 +- .../verifier/util/xml/XPathSpec.groovy | 20 +- .../verifier/util/xml/XmlAssertionSpec.groovy | 20 +- .../name-with-hyphen/car-rental/diff.groovy | 7 +- .../directory/with/scenario/01_login.groovy | 7 +- .../with/scenario/02_showCart.groovy | 7 +- .../directory/with/scenario/03_logout.groovy | 7 +- .../with/stubs/different/diff.groovy | 7 +- .../with/stubs/other/different/diff.groovy | 7 +- .../directory/with/stubs/other/other.groovy | 7 +- .../directory/with/stubs/other/sample.groovy | 7 +- .../with/stubs/package/exceptions/test.groovy | 7 +- .../stubs/package/v1/exceptions/testv1.groovy | 7 +- .../stubs/package/v2/exceptions/testv2.groovy | 7 +- .../strange_[3.3.3]_directory/02_login.groovy | 7 +- .../bar/03_login.groovy | 7 +- .../foo/01_login.groovy | 7 +- src/checkstyle/checkstyle-suppressions.xml | 3 + 195 files changed, 9167 insertions(+), 5336 deletions(-) rename {spring-cloud-contract-spec => specs}/README.adoc (100%) create mode 100644 specs/pom.xml create mode 100644 specs/spring-cloud-contract-spec-groovy/README.adoc create mode 100644 specs/spring-cloud-contract-spec-groovy/pom.xml create mode 100644 specs/spring-cloud-contract-spec-groovy/src/main/groovy/org/springframework/cloud/contract/spec/internal/GroovyDslPropertyConverter.java create mode 100644 specs/spring-cloud-contract-spec-groovy/src/main/resources/META-INF/spring.factories rename spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MessagingHeaders.groovy => specs/spring-cloud-contract-spec-groovy/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy (57%) create mode 100644 specs/spring-cloud-contract-spec-java/README.adoc rename {spring-cloud-contract-spec => specs/spring-cloud-contract-spec-java}/pom.xml (71%) create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/Contract.java rename spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/ContractConverter.groovy => specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/ContractConverter.java (72%) rename {spring-cloud-contract-spec/src/main/groovy => specs/spring-cloud-contract-spec-java/src/main/java}/org/springframework/cloud/contract/spec/ContractStorer.java (100%) rename {spring-cloud-contract-spec/src/main/groovy => specs/spring-cloud-contract-spec-java/src/main/java}/org/springframework/cloud/contract/spec/ContractTemplate.java (100%) rename spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/ContractVerifierException.groovy => specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/ContractVerifierException.java (68%) create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Body.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/BodyMatcher.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/BodyMatchers.java rename {spring-cloud-contract-spec/src/main/groovy => specs/spring-cloud-contract-spec-java/src/main/java}/org/springframework/cloud/contract/spec/internal/CanBeDynamic.java (95%) create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ClientDslProperty.java rename spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/GroovyClass.java => specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ClientInput.java (79%) rename spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/JSONCompareMode.groovy => specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ClientOutputMessage.java (76%) create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Common.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/CompositeContractTemplate.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ContractUtils.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Cookie.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Cookies.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/CustomHandlebarsContractTemplate.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/DslProperty.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/DslPropertyConverter.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ExecutionProperty.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/FromFileProperty.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/FromRequest.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/HandlebarsContractTemplate.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Header.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Headers.java rename spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/HttpHeaders.groovy => specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/HttpHeaders.java (55%) create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/HttpMethods.java rename spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/HttpStatus.groovy => specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/HttpStatus.java (51%) create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Input.java rename spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ClientDslProperty.groovy => specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/JSONCompareMode.java (57%) create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MatchingStrategy.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MatchingType.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MatchingTypeValue.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MatchingTypeValueHolder.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MediaTypes.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/MessagingHeaders.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Multipart.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/NamedProperty.java rename spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/NotToEscapePattern.groovy => specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/NotToEscapePattern.java (64%) create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/OptionalProperty.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/OutputMessage.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/PathBodyMatcher.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/PatternValueDslProperty.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/QueryParameter.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/QueryParameters.java rename {spring-cloud-contract-spec/src/main/groovy => specs/spring-cloud-contract-spec-java/src/main/java}/org/springframework/cloud/contract/spec/internal/RandomStringGenerator.java (100%) rename spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexCreatingProperty.groovy => specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexCreatingProperty.java (55%) create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexMatchingTypeValue.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexPatterns.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexProperty.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Request.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Response.java rename spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ResponseBodyMatchers.groovy => specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ResponseBodyMatchers.java (55%) create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ServerDslProperty.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ServerInput.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/ServerOutputMessage.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/Url.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/UrlHelper.java create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/UrlPath.java rename spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/util/RegexpUtils.groovy => specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/util/RegexpUtils.java (50%) create mode 100644 specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/util/ValidateUtils.java rename {spring-cloud-contract-spec/src/main/groovy => specs/spring-cloud-contract-spec-java/src/main/java}/repackaged/nl/flotsam/xeger/Xeger.java (100%) rename {spring-cloud-contract-spec => specs/spring-cloud-contract-spec-java}/src/test/groovy/repackaged/nl/flotsam/xeger/XegerTest.java (100%) rename {spring-cloud-contract-spec => specs/spring-cloud-contract-spec-java}/src/test/groovy/repackaged/nl/flotsam/xeger/XegerUtilsTest.java (100%) create mode 100644 specs/spring-cloud-contract-spec/README.adoc create mode 100644 specs/spring-cloud-contract-spec/pom.xml rename {spring-cloud-contract-spec => specs/spring-cloud-contract-spec}/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy (86%) rename {spring-cloud-contract-spec => specs/spring-cloud-contract-spec}/src/test/groovy/org/springframework/cloud/contract/spec/internal/CookiesSpec.groovy (100%) rename {spring-cloud-contract-spec => specs/spring-cloud-contract-spec}/src/test/groovy/org/springframework/cloud/contract/spec/internal/ExecutionPropertySpec.groovy (100%) rename {spring-cloud-contract-spec => specs/spring-cloud-contract-spec}/src/test/groovy/org/springframework/cloud/contract/spec/internal/HttpStatusTests.java (100%) rename {spring-cloud-contract-spec => specs/spring-cloud-contract-spec}/src/test/groovy/org/springframework/cloud/contract/spec/internal/InputSpec.groovy (100%) rename {spring-cloud-contract-spec => specs/spring-cloud-contract-spec}/src/test/groovy/org/springframework/cloud/contract/spec/internal/MatchingTypeSpec.groovy (100%) rename {spring-cloud-contract-spec => specs/spring-cloud-contract-spec}/src/test/groovy/org/springframework/cloud/contract/spec/internal/OutputMessageSpec.groovy (100%) rename {spring-cloud-contract-spec => specs/spring-cloud-contract-spec}/src/test/groovy/org/springframework/cloud/contract/spec/internal/RegexPatternsSpec.groovy (100%) rename {spring-cloud-contract-spec => specs/spring-cloud-contract-spec}/src/test/groovy/org/springframework/cloud/contract/spec/internal/RequestSpec.groovy (100%) rename {spring-cloud-contract-spec => specs/spring-cloud-contract-spec}/src/test/groovy/org/springframework/cloud/contract/spec/internal/ResponseSpec.groovy (100%) delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/Contract.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Body.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/BodyMatcher.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/BodyMatchers.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Common.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/CompositeContractTemplate.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Cookie.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Cookies.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/CustomHandlebarsContractTemplate.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/DslProperty.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ExecutionProperty.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/FromFileProperty.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/FromRequest.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/HandlebarsContractTemplate.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Header.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Headers.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/HttpMethods.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Input.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MatchingStrategy.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MatchingType.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MediaTypes.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Multipart.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/NamedProperty.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/OptionalProperty.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/OutputMessage.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/PathBodyMatcher.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/PatternValueDslProperty.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/QueryParameter.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/QueryParameters.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexPatterns.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexProperty.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Request.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Response.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ServerDslProperty.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Url.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/UrlPath.groovy delete mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/util/ValidateUtils.groovy 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 29953ea141f55e3b8fc691d31b5ca8816d89fa87..5c2d1cf016b3885f6930543d57b744ea8c220a1a 100644 GIT binary patch delta 47360 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 delta 47987 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 P{tQ5B1&<&s7}); 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 @@ + + +