diff --git a/docs/src/main/asciidoc/verifier_contract.adoc b/docs/src/main/asciidoc/verifier_contract.adoc index 604a691ada..23376d1d31 100644 --- a/docs/src/main/asciidoc/verifier_contract.adoc +++ b/docs/src/main/asciidoc/verifier_contract.adoc @@ -801,7 +801,7 @@ following matching possibilities: ** `byEquality()`: The value taken from the consumer's request via the provided JSON Path must be equal to the value provided in the contract. ** `byRegex(...)`: The value taken from the consumer's request via the provided JSON Path must -match the regex. +match the regex. You can also pass the type of the expected matched value (e.g. `asString()`, `asLong()` etc.) ** `byDate()`: The value taken from the consumer's request via the provided JSON Path must match the regex for an ISO Date value. ** `byTimestamp()`: The value taken from the consumer's request via the provided JSON Path must @@ -848,6 +848,7 @@ For YAML the structure of a matcher looks like this - path: $.foo type: by_regex value: bar + regexType: as_string ---- Or if you want to use one of the predefined regular expressions @@ -882,6 +883,16 @@ Below you can find the allowed list of `type`s. ** `by_command` ** `by_null` +You can also define which type the regular expression corresponds to via the `regexType` field. Below you can find the allowed list of regular expression types: + +* as_integer +* as_double +* as_float, +* as_long +* as_short +* as_boolean +* as_string + Consider the following example: .Groovy DSL diff --git a/samples/standalone/dsl/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java b/samples/standalone/dsl/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java index a686189f75..d741a5129a 100644 --- a/samples/standalone/dsl/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java +++ b/samples/standalone/dsl/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java @@ -74,7 +74,7 @@ public class LoanApplicationServiceTests { // when: int count = service.countAllFrauds(); // then: - assertThat(count).isEqualTo(200); + assertThat(count).isGreaterThanOrEqualTo(200); } @Test diff --git a/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldReturnFraudStats.groovy b/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldReturnFraudStats.groovy index 04650151ab..7e05390199 100644 --- a/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldReturnFraudStats.groovy +++ b/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldReturnFraudStats.groovy @@ -12,7 +12,7 @@ import org.springframework.cloud.contract.spec.Contract response { status OK() body([ - count: 200 + count: $(regex("[2-9][0-9][0-9]").asInteger()) ]) headers { contentType("application/json") 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 index 2256bf392d..d618796976 100644 --- 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 @@ -21,7 +21,6 @@ import groovy.transform.Canonical import groovy.transform.CompileStatic import groovy.transform.EqualsAndHashCode import groovy.transform.ToString - /** * Matching strategy of dynamic parts of the body. * @@ -58,15 +57,18 @@ class BodyMatchers { return new MatchingTypeValue(MatchingType.TIMESTAMP, this.regexPatterns.isoDateTime()) } - MatchingTypeValue byRegex(String regex) { - assert regex - return new MatchingTypeValue(MatchingType.REGEX, regex) + RegexMatchingTypeValue byRegex(String regex) { + return byRegex(Pattern.compile(regex)) } - // Backward compatibility with RegexPatterns - MatchingTypeValue byRegex(Pattern regex) { + RegexMatchingTypeValue byRegex(RegexProperty regex) { assert regex - return new MatchingTypeValue(MatchingType.REGEX, regex) + return new RegexMatchingTypeValue(MatchingType.REGEX, regex) + } + + RegexMatchingTypeValue byRegex(Pattern regex) { + assert regex + return new RegexMatchingTypeValue(MatchingType.REGEX, new RegexProperty(regex)) } MatchingTypeValue byEquality() { @@ -130,6 +132,61 @@ class JsonPathBodyMatcher implements BodyMatcher { } } +/** + * 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 */ 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 index 02fc8d45a1..e0a0332bb7 100644 --- 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 @@ -114,19 +114,27 @@ class Common { return value(server, client) } - Pattern regex(String regex) { - return Pattern.compile(regex) + RegexProperty regex(String regex) { + return regexProperty(Pattern.compile(regex)) + } + + RegexProperty regex(RegexProperty regex) { + return regex } // Backward compatibility with RegexPatterns - Pattern regex(Pattern regex) { - return regex + 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) } 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 index 0b36c1aba8..2db08f92cb 100644 --- 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 @@ -16,16 +16,13 @@ package org.springframework.cloud.contract.spec.internal -import groovy.util.logging.Commons -import groovy.util.logging.Slf4j - import java.util.regex.Pattern import groovy.transform.CompileStatic import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import groovy.transform.TypeChecked -import repackaged.nl.flotsam.xeger.Xeger +import groovy.util.logging.Commons /** * Represents an input for messaging. The input can be a message or some * action inside the application. @@ -89,17 +86,31 @@ class Input extends Common { } DslProperty value(ClientDslProperty client) { - Object clientValue = client.clientValue - if (client.clientValue instanceof Pattern) { - clientValue = new Xeger(((Pattern)client.clientValue).pattern()).generate() + Object dynamicValue = client.clientValue + Object concreteValue = client.serverValue + if (dynamicValue instanceof RegexProperty) { + return dynamicValue.dynamicClientConcreteProducer() } - return new DslProperty(client.clientValue, clientValue) + 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 { 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 index 4535c0f12f..e94ad65272 100644 --- 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 @@ -19,7 +19,6 @@ package org.springframework.cloud.contract.spec.internal import java.util.regex.Pattern import groovy.transform.CompileStatic -import groovy.transform.PackageScope import groovy.transform.ToString /** @@ -41,7 +40,12 @@ class OptionalProperty implements Serializable { * in an optional function */ String optionalPattern() { - return "($value)?" + return "(${value()})?" + } + + String value() { + return this.value instanceof RegexProperty ? + ((RegexProperty) this.value).pattern.pattern() : this.value } protected Pattern optionalPatternValue() { 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 index 19df81f626..8ad4c6c196 100644 --- 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 @@ -16,17 +16,13 @@ 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 groovy.util.logging.Slf4j -import org.apache.commons.lang3.StringEscapeUtils -import repackaged.nl.flotsam.xeger.Xeger - -import java.util.regex.Pattern - /** * Represents an output for messaging. Used for verifying * the body and headers that are sent. @@ -83,26 +79,57 @@ class OutputMessage extends Common { this.assertThat = new ExecutionProperty(assertThat) } + /** + * @deprecated - use the server dsl property + */ + @Deprecated DslProperty value(ClientDslProperty clientDslProperty) { - Object clientValue = clientDslProperty.clientValue - // for the output messages ran via stub runner, - // entries have to have fixed values - if (clientDslProperty.clientValue instanceof Pattern) { - clientValue = StringEscapeUtils.escapeJava(new Xeger(((Pattern)clientDslProperty.clientValue).pattern()).generate()) - } - return new DslProperty(clientValue, clientDslProperty.clientValue) + 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(client(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(client(property.optionalPatternValue())) + return value(producer(property.optionalPatternValue())) + } + + @Override + RegexProperty regexProperty(Object object) { + return new RegexProperty(object).concreteClientDynamicProducer() } /** 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 index 96790fceaf..c4a820f46a 100644 --- 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 @@ -60,80 +60,80 @@ class RegexPatterns { return Pattern.compile(values.collect({"^$it\$"}).join("|")) } - Pattern onlyAlphaUnicode() { - return ONLY_ALPHA_UNICODE + RegexProperty onlyAlphaUnicode() { + return new RegexProperty(ONLY_ALPHA_UNICODE).asString() } - Pattern alphaNumeric() { - return ALPHA_NUMERIC + RegexProperty alphaNumeric() { + return new RegexProperty(ALPHA_NUMERIC).asString() } - Pattern number() { - return NUMBER + RegexProperty number() { + return new RegexProperty(NUMBER).asDouble() } - Pattern positiveInt() { - return POSITIVE_INT + RegexProperty positiveInt() { + return new RegexProperty(POSITIVE_INT).asInteger() } - Pattern anyBoolean() { - return TRUE_OR_FALSE + RegexProperty anyBoolean() { + return new RegexProperty(TRUE_OR_FALSE).asBooleanType() } - Pattern anInteger() { - return INTEGER + RegexProperty anInteger() { + return new RegexProperty(INTEGER).asInteger() } - Pattern aDouble() { - return DOUBLE + RegexProperty aDouble() { + return new RegexProperty(DOUBLE).asDouble() } - Pattern ipAddress() { - return IP_ADDRESS + RegexProperty ipAddress() { + return new RegexProperty(IP_ADDRESS).asString() } - Pattern hostname() { - return HOSTNAME_PATTERN + RegexProperty hostname() { + return new RegexProperty(HOSTNAME_PATTERN).asString() } - Pattern email() { - return EMAIL + RegexProperty email() { + return new RegexProperty(EMAIL).asString() } - Pattern url() { - return URL + RegexProperty url() { + return new RegexProperty(URL).asString() } - Pattern httpsUrl() { - return HTTPS_URL + RegexProperty httpsUrl() { + return new RegexProperty(HTTPS_URL).asString() } - Pattern uuid(){ - return UUID + RegexProperty uuid(){ + return new RegexProperty(UUID).asString() } - Pattern isoDate() { - return ANY_DATE + RegexProperty isoDate() { + return new RegexProperty(ANY_DATE).asString() } - Pattern isoDateTime() { - return ANY_DATE_TIME + RegexProperty isoDateTime() { + return new RegexProperty(ANY_DATE_TIME).asString() } - Pattern isoTime() { - return ANY_TIME + RegexProperty isoTime() { + return new RegexProperty(ANY_TIME).asString() } - Pattern iso8601WithOffset() { - return ISO8601_WITH_OFFSET + RegexProperty iso8601WithOffset() { + return new RegexProperty(ISO8601_WITH_OFFSET).asString() } - Pattern nonEmpty() { - return NON_EMPTY + RegexProperty nonEmpty() { + return new RegexProperty(NON_EMPTY).asString() } - Pattern nonBlank() { - return NON_BLANK + RegexProperty nonBlank() { + return new RegexProperty(NON_BLANK).asString() } // end::regexps[] @@ -150,7 +150,7 @@ class RegexPatterns { if (contentType == null) { return '.*' } - if (contentType instanceof Pattern) { + if (contentType instanceof RegexProperty) { return contentType.pattern() } return contentType.toString() @@ -163,7 +163,7 @@ class RegexPatterns { class UrlHelper { /** * Example: "http". Also called 'protocol'. - * Scheme component is optional, even though the RFC doesn't make it optional. Since this regex is validating a + * 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. 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 new file mode 100644 index 0000000000..b5b944142f --- /dev/null +++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexProperty.groovy @@ -0,0 +1,172 @@ +/* + * Copyright 2013-2018 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 + * + * http://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 { + 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() { + 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 + } + } + + 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() + } +} 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 index 708d07f833..ee5d3dae0c 100644 --- 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 @@ -23,8 +23,6 @@ import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import groovy.transform.TypeChecked import groovy.util.logging.Commons -import org.apache.commons.lang3.StringEscapeUtils -import repackaged.nl.flotsam.xeger.Xeger import org.springframework.cloud.contract.spec.util.RegexpUtils /** @@ -208,13 +206,22 @@ class Request extends Common { } DslProperty value(ClientDslProperty client) { - Object clientValue = client.clientValue - if (client.clientValue instanceof Pattern && client.isSingleValue()) { - clientValue = StringEscapeUtils.escapeJava(new Xeger(((Pattern)client.clientValue).pattern()).generate()) - } else if (client.clientValue instanceof Pattern && !client.isSingleValue()) { - clientValue = client.serverValue + 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(client.clientValue, clientValue) + return new DslProperty(dynamicValue, concreteValue) + } + + DslProperty $(RegexProperty property) { + return value(property) + } + + DslProperty value(RegexProperty property) { + return value(client(property)) } DslProperty $(ClientDslProperty client) { @@ -222,13 +229,18 @@ class Request extends Common { } DslProperty value(Pattern client) { - return value(new ClientDslProperty(client)) + return value(new RegexProperty(client)) } 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 */ @@ -246,7 +258,7 @@ class Request extends Common { @Override DslProperty value(ClientDslProperty client, ServerDslProperty server) { - if (server.clientValue instanceof Pattern) { + 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) @@ -254,7 +266,7 @@ class Request extends Common { @Override DslProperty value(ServerDslProperty server, ClientDslProperty client) { - if (server.clientValue instanceof Pattern) { + 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) 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 index fe7fc86da0..62e0639113 100644 --- 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 @@ -23,7 +23,6 @@ import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import groovy.transform.TypeChecked import groovy.util.logging.Commons -import repackaged.nl.flotsam.xeger.Xeger import org.springframework.cloud.contract.spec.util.RegexpUtils /** @@ -109,13 +108,12 @@ class Response extends Common { } DslProperty value(ServerDslProperty server) { - Object value = server.clientValue - if (server.clientValue instanceof Pattern && server.isSingleValue()) { - value = new Xeger(((Pattern)server.clientValue).pattern()).generate() - } else if (server.clientValue instanceof Pattern && !server.isSingleValue()) { - value = server.serverValue + Object dynamicValue = server.serverValue + Object concreteValue = server.clientValue + if (dynamicValue instanceof RegexProperty && server.isSingleValue()) { + return ((RegexProperty) dynamicValue).concreteClientDynamicProducer() } - return new DslProperty(value, server.serverValue) + return new DslProperty(concreteValue, dynamicValue) } DslProperty $(ServerDslProperty server) { @@ -123,13 +121,26 @@ class Response extends Common { } DslProperty value(Pattern server) { + return value(new RegexProperty(server)) + } + + DslProperty value(RegexProperty server) { return value(new ServerDslProperty(server)) } - DslProperty $(Pattern server) { + DslProperty $(RegexProperty server) { return value(server) } + 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 */ @@ -151,7 +162,7 @@ class Response extends Common { @Override DslProperty value(ClientDslProperty client, ServerDslProperty server) { - if (client.clientValue instanceof Pattern) { + 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) @@ -159,7 +170,7 @@ class Response extends Common { @Override DslProperty value(ServerDslProperty server, ClientDslProperty client) { - if (client.clientValue instanceof Pattern) { + 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) 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 index 28ebc9d9df..e2357fe749 100644 --- 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 @@ -16,16 +16,12 @@ 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 org.codehaus.groovy.runtime.GStringImpl -import repackaged.nl.flotsam.xeger.Xeger import static org.springframework.cloud.contract.spec.util.ValidateUtils.validateServerValueIsAvailable - /** * Represents a URL that may contain query parameters * @@ -50,13 +46,13 @@ class Url extends DslProperty { private static Object testUrl(Object url) { if (url instanceof GString) { - boolean anyPattern = url.values.any { it instanceof Pattern } + boolean anyPattern = url.values.any { it instanceof RegexProperty } if (!anyPattern) { return url } String newUrl = new GStringImpl( - url.values.collect { it instanceof Pattern ? - new Xeger(it.pattern()).generate() : it + url.values.collect { it instanceof RegexProperty ? + it.generate() : it } as String[], Arrays.copyOf(url.strings, url.strings.length) as String[] ).toString() diff --git a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/InputSpec.groovy b/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/InputSpec.groovy index 6fae3a4545..3ce89c0272 100644 --- a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/InputSpec.groovy +++ b/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/InputSpec.groovy @@ -14,7 +14,84 @@ class InputSpec extends Specification { input.with { property = $(consumer(regex("[0-9]{5}"))) } - def value = Integer.valueOf(property.serverValue as String) + def generatedValue = property.serverValue + generatedValue instanceof String + def value = Integer.valueOf(generatedValue as String) + then: + value >= 0 + value <= 99_999 + } + + def 'should set property when using the $() convenience method for Double'() { + given: + Input input = new Input() + DslProperty property + when: + input.with { + property = $(consumer(regex("[0-9]{5}").asDouble())) + } + def value = property.serverValue + value instanceof Double + then: + value >= 0 + value <= 99_999 + } + + def 'should set property when using the $() convenience method for Short'() { + given: + Input input = new Input() + DslProperty property + when: + input.with { + property = $(consumer(regex("[0-9]{1}").asShort())) + } + def value = property.serverValue + value instanceof Short + then: + value >= 0 + value <= 9 + } + + def 'should set property when using the $() convenience method for Long'() { + given: + Input input = new Input() + DslProperty property + when: + input.with { + property = $(consumer(regex("[0-9]{5}").asLong())) + } + def value = property.serverValue + value instanceof Long + then: + value >= 0 + value <= 99_999 + } + + def 'should set property when using the $() convenience method for Integer'() { + given: + Input input = new Input() + DslProperty property + when: + input.with { + property = $(consumer(regex("[0-9]{5}").asInteger())) + } + def value = property.serverValue + value instanceof Integer + then: + value >= 0 + value <= 99_999 + } + + def 'should set property when using the $() convenience method for Float'() { + given: + Input input = new Input() + DslProperty property + when: + input.with { + property = $(consumer(regex("[0-9]{5}").asFloat())) + } + def value = property.serverValue + value instanceof Float then: value >= 0 value <= 99_999 diff --git a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/OutputMessageSpec.groovy b/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/OutputMessageSpec.groovy index 0f45924ee1..6e1bd0b41f 100644 --- a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/OutputMessageSpec.groovy +++ b/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/OutputMessageSpec.groovy @@ -8,13 +8,90 @@ class OutputMessageSpec extends Specification { def 'should set property when using the $() convenience method'() { given: - Input input = new Input() + OutputMessage contract = new OutputMessage() DslProperty property when: - input.with { - property = $(consumer(regex("[0-9]{5}"))) + contract.with { + property = $(producer(regex("[0-9]{5}"))) } - def value = Integer.valueOf(property.serverValue as String) + def generatedValue = property.clientValue + generatedValue instanceof String + def value = Integer.valueOf(generatedValue as String) + then: + value >= 0 + value <= 99_999 + } + + def 'should set property when using the $() convenience method for Double'() { + given: + OutputMessage contract = new OutputMessage() + DslProperty property + when: + contract.with { + property = $(producer(regex("[0-9]{5}").asDouble())) + } + def value = property.clientValue + value instanceof Double + then: + value >= 0 + value <= 99_999 + } + + def 'should set property when using the $() convenience method for Short'() { + given: + OutputMessage contract = new OutputMessage() + DslProperty property + when: + contract.with { + property = $(producer(regex("[0-9]{1}").asShort())) + } + def value = property.clientValue + value instanceof Short + then: + value >= 0 + value <= 9 + } + + def 'should set property when using the $() convenience method for Long'() { + given: + OutputMessage contract = new OutputMessage() + DslProperty property + when: + contract.with { + property = $(producer(regex("[0-9]{5}").asLong())) + } + def value = property.clientValue + value instanceof Long + then: + value >= 0 + value <= 99_999 + } + + def 'should set property when using the $() convenience method for Integer'() { + given: + OutputMessage contract = new OutputMessage() + DslProperty property + when: + contract.with { + property = $(producer(regex("[0-9]{5}").asInteger())) + } + def value = property.clientValue + value instanceof Integer + then: + value >= 0 + value <= 99_999 + } + + def 'should set property when using the $() convenience method for Float'() { + given: + OutputMessage contract = new OutputMessage() + DslProperty property + when: + contract.with { + property = $(producer(regex("[0-9]{5}").asFloat())) + } + def value = property.clientValue + value instanceof Float then: value >= 0 value <= 99_999 diff --git a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/RequestSpec.groovy b/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/RequestSpec.groovy index 1197ed1064..141c0fb175 100644 --- a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/RequestSpec.groovy +++ b/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/RequestSpec.groovy @@ -8,30 +8,106 @@ class RequestSpec extends Specification { def 'should throw exception when on request side a value contains regex for server'() { given: - Request request = new Request() + Request contract = new Request() when: - request.with { + contract.with { value(consumer("foo"), producer(regex("foo"))) } then: thrown(IllegalStateException) when: - request.with { + contract.with { value(producer(regex("foo")), consumer("foo")) } then: thrown(IllegalStateException) } - - def 'should generate a value if only regex is passed for client'() { + def 'should set property when using the $() convenience method'() { given: - Request request = new Request() + Request contract = new Request() DslProperty property when: - request.with { - property = value(consumer(regex("[0-9]{5}"))) + contract.with { + property = $(consumer(regex("[0-9]{5}"))) } - def value = Integer.valueOf(property.serverValue as String) + def generatedValue = property.serverValue + generatedValue instanceof String + def value = Integer.valueOf(generatedValue as String) + then: + value >= 0 + value <= 99_999 + } + + def 'should set property when using the $() convenience method for Double'() { + given: + Request contract = new Request() + DslProperty property + when: + contract.with { + property = $(consumer(regex("[0-9]{5}").asDouble())) + } + def value = property.serverValue + value instanceof Double + then: + value >= 0 + value <= 99_999 + } + + def 'should set property when using the $() convenience method for Short'() { + given: + Request contract = new Request() + DslProperty property + when: + contract.with { + property = $(consumer(regex("[0-9]{1}").asShort())) + } + def value = property.serverValue + value instanceof Short + then: + value >= 0 + value <= 9 + } + + def 'should set property when using the $() convenience method for Long'() { + given: + Request contract = new Request() + DslProperty property + when: + contract.with { + property = $(consumer(regex("[0-9]{5}").asLong())) + } + def value = property.serverValue + value instanceof Long + then: + value >= 0 + value <= 99_999 + } + + def 'should set property when using the $() convenience method for Integer'() { + given: + Request contract = new Request() + DslProperty property + when: + contract.with { + property = $(consumer(regex("[0-9]{5}").asInteger())) + } + def value = property.serverValue + value instanceof Integer + then: + value >= 0 + value <= 99_999 + } + + def 'should set property when using the $() convenience method for Float'() { + given: + Request contract = new Request() + DslProperty property + when: + contract.with { + property = $(consumer(regex("[0-9]{5}").asFloat())) + } + def value = property.serverValue + value instanceof Float then: value >= 0 value <= 99_999 diff --git a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ResponseSpec.groovy b/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ResponseSpec.groovy index e25aa95c03..583feee7be 100644 --- a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ResponseSpec.groovy +++ b/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ResponseSpec.groovy @@ -1,9 +1,6 @@ package org.springframework.cloud.contract.spec.internal -import spock.lang.Specification - -import java.util.regex.Pattern - +import spock.lang.Specification /** * @author Marcin Grzejszczak */ @@ -26,18 +23,95 @@ class ResponseSpec extends Specification { thrown(IllegalStateException) } - def 'should generate a value if only regex is passed for server'() { + + def 'should set property when using the $() convenience method'() { given: - Response request = new Response() + Response contract = new Response() DslProperty property when: - request.with { - property = value(producer(regex("[0-9]{5}"))) + contract.with { + property = $(producer(regex("[0-9]{5}"))) } - def value = Integer.valueOf(property.clientValue as String) + def generatedValue = property.clientValue + generatedValue instanceof String + def value = Integer.valueOf(generatedValue as String) + then: + value >= 0 + value <= 99_999 + } + + def 'should set property when using the $() convenience method for Double'() { + given: + Response contract = new Response() + DslProperty property + when: + contract.with { + property = $(producer(regex("[0-9]{5}").asDouble())) + } + def value = property.clientValue + value instanceof Double + then: + value >= 0 + value <= 99_999 + } + + def 'should set property when using the $() convenience method for Short'() { + given: + Response contract = new Response() + DslProperty property + when: + contract.with { + property = $(producer(regex("[0-9]{1}").asShort())) + } + def value = property.clientValue + value instanceof Short + then: + value >= 0 + value <= 9 + } + + def 'should set property when using the $() convenience method for Long'() { + given: + Response contract = new Response() + DslProperty property + when: + contract.with { + property = $(producer(regex("[0-9]{5}").asLong())) + } + def value = property.clientValue + value instanceof Long + then: + value >= 0 + value <= 99_999 + } + + def 'should set property when using the $() convenience method for Integer'() { + given: + Response contract = new Response() + DslProperty property + when: + contract.with { + property = $(producer(regex("[0-9]{5}").asInteger())) + } + def value = property.clientValue + value instanceof Integer + then: + value >= 0 + value <= 99_999 + } + + def 'should set property when using the $() convenience method for Float'() { + given: + Response contract = new Response() + DslProperty property + when: + contract.with { + property = $(producer(regex("[0-9]{5}").asFloat())) + } + def value = property.clientValue + value instanceof Float then: value >= 0 value <= 99_999 - (property.serverValue as Pattern).pattern() == '[0-9]{5}' } } diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/camel/StubRunnerCamelPredicate.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/camel/StubRunnerCamelPredicate.java index e036727700..691e10400a 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/camel/StubRunnerCamelPredicate.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/camel/StubRunnerCamelPredicate.java @@ -37,6 +37,7 @@ import org.springframework.cloud.contract.spec.internal.BodyMatcher; import org.springframework.cloud.contract.spec.internal.BodyMatchers; import org.springframework.cloud.contract.spec.internal.FromFileProperty; import org.springframework.cloud.contract.spec.internal.Header; +import org.springframework.cloud.contract.spec.internal.RegexProperty; import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper; import org.springframework.cloud.contract.verifier.util.ContentType; import org.springframework.cloud.contract.verifier.util.ContentUtils; @@ -133,8 +134,8 @@ class StubRunnerCamelPredicate implements Predicate { BodyMatchers matchers = groovyDsl.getInput().getBodyMatchers(); matches = matchesForJsonPayload(groovyDsl, inputMessage, matchers, dslBody); } - else if (dslBody instanceof Pattern && inputMessage instanceof String) { - Pattern pattern = (Pattern) dslBody; + else if (dslBody instanceof RegexProperty && inputMessage instanceof String) { + Pattern pattern = ((RegexProperty) dslBody).getPattern(); matches = pattern.matcher((String) inputMessage).matches(); bodyUnmatchedLog(dslBody, matches, pattern); } else { @@ -206,8 +207,8 @@ class StubRunnerCamelPredicate implements Predicate { Object value = it.getClientValue(); Object valueInHeader = headers.get(name); boolean matches; - if (value instanceof Pattern) { - Pattern pattern = (Pattern) value; + if (value instanceof RegexProperty) { + Pattern pattern = ((RegexProperty) value).getPattern(); matches = pattern.matcher(valueInHeader.toString()).matches(); } else { @@ -225,8 +226,8 @@ class StubRunnerCamelPredicate implements Predicate { } private String unmatchedText(Object expectedValue) { - return expectedValue instanceof Pattern - ? "match pattern [" + ((Pattern) expectedValue).pattern() + "]" + return expectedValue instanceof RegexProperty + ? "match pattern [" + ((RegexProperty) expectedValue).pattern() + "]" : "be equal to [" + expectedValue + "]"; } diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/integration/StubRunnerIntegrationMessageSelector.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/integration/StubRunnerIntegrationMessageSelector.java index 01946465d4..657d4ec9d1 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/integration/StubRunnerIntegrationMessageSelector.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/integration/StubRunnerIntegrationMessageSelector.java @@ -36,6 +36,7 @@ import org.springframework.cloud.contract.spec.internal.BodyMatcher; import org.springframework.cloud.contract.spec.internal.BodyMatchers; import org.springframework.cloud.contract.spec.internal.FromFileProperty; import org.springframework.cloud.contract.spec.internal.Header; +import org.springframework.cloud.contract.spec.internal.RegexProperty; import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper; import org.springframework.cloud.contract.verifier.util.ContentType; import org.springframework.cloud.contract.verifier.util.ContentUtils; @@ -151,8 +152,8 @@ class StubRunnerIntegrationMessageSelector implements MessageSelector { BodyMatchers matchers = groovyDsl.getInput().getBodyMatchers(); matches = matchesForJsonPayload(groovyDsl, inputMessage, matchers, dslBody); } - else if (dslBody instanceof Pattern && inputMessage instanceof String) { - Pattern pattern = (Pattern) dslBody; + else if (dslBody instanceof RegexProperty && inputMessage instanceof String) { + Pattern pattern = ((RegexProperty) dslBody).getPattern(); matches = pattern.matcher((String) inputMessage).matches(); bodyUnmatchedLog(dslBody, matches, pattern); } else { @@ -224,8 +225,8 @@ class StubRunnerIntegrationMessageSelector implements MessageSelector { Object value = it.getClientValue(); Object valueInHeader = headers.get(name); boolean matches; - if (value instanceof Pattern) { - Pattern pattern = (Pattern) value; + if (value instanceof RegexProperty) { + Pattern pattern = ((RegexProperty) value).getPattern(); matches = pattern.matcher(valueInHeader.toString()).matches(); } else { @@ -243,8 +244,8 @@ class StubRunnerIntegrationMessageSelector implements MessageSelector { } private String unmatchedText(Object expectedValue) { - return expectedValue instanceof Pattern - ? "match pattern [" + ((Pattern) expectedValue).pattern() + "]" + return expectedValue instanceof RegexProperty + ? "match pattern [" + ((RegexProperty) expectedValue).pattern() + "]" : "be equal to [" + expectedValue + "]"; } } diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/stream/StubRunnerStreamMessageSelector.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/stream/StubRunnerStreamMessageSelector.java index 3de6c7e08f..a9a940e506 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/stream/StubRunnerStreamMessageSelector.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/stream/StubRunnerStreamMessageSelector.java @@ -36,6 +36,7 @@ import org.springframework.cloud.contract.spec.internal.BodyMatcher; import org.springframework.cloud.contract.spec.internal.BodyMatchers; import org.springframework.cloud.contract.spec.internal.FromFileProperty; import org.springframework.cloud.contract.spec.internal.Header; +import org.springframework.cloud.contract.spec.internal.RegexProperty; import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper; import org.springframework.cloud.contract.verifier.util.ContentType; import org.springframework.cloud.contract.verifier.util.ContentUtils; @@ -150,8 +151,8 @@ class StubRunnerStreamMessageSelector implements MessageSelector { BodyMatchers matchers = groovyDsl.getInput().getBodyMatchers(); matches = matchesForJsonPayload(groovyDsl, inputMessage, matchers, dslBody); } - else if (dslBody instanceof Pattern && inputMessage instanceof String) { - Pattern pattern = (Pattern) dslBody; + else if (dslBody instanceof RegexProperty && inputMessage instanceof String) { + Pattern pattern = ((RegexProperty) dslBody).getPattern(); matches = pattern.matcher((String) inputMessage).matches(); bodyUnmatchedLog(dslBody, matches, pattern); } else { @@ -223,8 +224,8 @@ class StubRunnerStreamMessageSelector implements MessageSelector { Object value = it.getClientValue(); Object valueInHeader = headers.get(name); boolean matches; - if (value instanceof Pattern) { - Pattern pattern = (Pattern) value; + if (value instanceof RegexProperty) { + Pattern pattern = ((RegexProperty) value).getPattern(); matches = pattern.matcher(valueInHeader.toString()).matches(); } else { @@ -242,8 +243,8 @@ class StubRunnerStreamMessageSelector implements MessageSelector { } private String unmatchedText(Object expectedValue) { - return expectedValue instanceof Pattern - ? "match pattern [" + ((Pattern) expectedValue).pattern() + "]" + return expectedValue instanceof RegexProperty + ? "match pattern [" + ((RegexProperty) expectedValue).pattern() + "]" : "be equal to [" + expectedValue + "]"; } } 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 f2167aeaf8..888f237952 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 @@ -16,12 +16,14 @@ package org.springframework.cloud.contract.verifier.wiremock -import org.springframework.cloud.contract.spec.Contract -import org.springframework.cloud.contract.verifier.dsl.wiremock.WireMockStubMapping -import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter +import java.util.regex.Pattern + import spock.lang.Specification -import java.util.regex.Pattern +import org.springframework.cloud.contract.spec.Contract +import org.springframework.cloud.contract.spec.internal.RegexProperty +import org.springframework.cloud.contract.verifier.dsl.wiremock.WireMockStubMapping +import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter class WireMockToDslConverterSpec extends Specification { @@ -148,7 +150,7 @@ class WireMockToDslConverterSpec extends Specification { $groovyDsl }""") def b = expectedGroovyDsl - (a.first().request.url.clientValue as Pattern).pattern() == (b.request.url.clientValue as Pattern).pattern() + (a.first().request.url.clientValue as RegexProperty).pattern() == (b.request.url.clientValue as Pattern).pattern() } def 'should convert WireMock stub with response body containing integer'() { @@ -401,7 +403,7 @@ class WireMockToDslConverterSpec extends Specification { $groovyDsl }""").first() and: - (evaluatedGroovyDsl.request.body.clientValue as Pattern).pattern() == (expectedGroovyDsl.request.body.clientValue as Pattern).pattern() + (evaluatedGroovyDsl.request.body.clientValue as RegexProperty).pattern() == (expectedGroovyDsl.request.body.clientValue as Pattern).pattern() } def 'should convert WireMock stub with request body with equalToJson'() { @@ -522,7 +524,7 @@ class WireMockToDslConverterSpec extends Specification { $groovyDsl }""").first() and: - (evaluatedGroovyDsl.request.body.clientValue as Pattern).pattern() == (expectedGroovyDsl.request.body.clientValue as Pattern).pattern() + (evaluatedGroovyDsl.request.body.clientValue as RegexProperty).pattern() == (expectedGroovyDsl.request.body.clientValue as Pattern).pattern() } def 'should convert WireMock stub with priorities'() { diff --git a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/main/groovy/org/springframework/cloud/contract/verifier/spec/pact/ValueGeneratorConverter.groovy b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/main/groovy/org/springframework/cloud/contract/verifier/spec/pact/ValueGeneratorConverter.groovy index 18efdc5f30..817e11e76d 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/main/groovy/org/springframework/cloud/contract/verifier/spec/pact/ValueGeneratorConverter.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/main/groovy/org/springframework/cloud/contract/verifier/spec/pact/ValueGeneratorConverter.groovy @@ -15,6 +15,8 @@ */ package org.springframework.cloud.contract.verifier.spec.pact +import java.util.regex.Pattern + import au.com.dius.pact.model.generators.Category import au.com.dius.pact.model.generators.DateGenerator import au.com.dius.pact.model.generators.DateTimeGenerator @@ -30,13 +32,13 @@ import au.com.dius.pact.model.generators.TimeGenerator import au.com.dius.pact.model.generators.UuidGenerator import groovy.transform.CompileStatic import groovy.transform.PackageScope + import org.springframework.cloud.contract.spec.internal.Body import org.springframework.cloud.contract.spec.internal.DslProperty import org.springframework.cloud.contract.spec.internal.OutputMessage +import org.springframework.cloud.contract.spec.internal.RegexProperty import org.springframework.cloud.contract.verifier.util.ContentUtils -import java.util.regex.Pattern - /** * @author Tim Ysewyn * @Since 2.0.0 @@ -129,8 +131,9 @@ class ValueGeneratorConverter { } } else if (v instanceof DslProperty) { traverse(v, dslPropertyValueProvider, path, generators, category) - } else if (v instanceof Pattern) { - switch (v.pattern()) { + } else if (v instanceof RegexProperty || v instanceof Pattern) { + RegexProperty regexProperty = new RegexProperty(v) + switch (regexProperty.pattern()) { case INTEGER_PATTERN: generators.addGenerator(category, path, new RandomIntGenerator(0, Integer.MAX_VALUE)) break @@ -159,7 +162,7 @@ class ValueGeneratorConverter { generators.addGenerator(category, path, RandomBooleanGenerator.INSTANCE) break default: - generators.addGenerator(category, path, new RegexGenerator(v.pattern())) + generators.addGenerator(category, path, new RegexGenerator(regexProperty.pattern())) break } } diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JUnitMessagingMethodBodyBuilder.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JUnitMessagingMethodBodyBuilder.groovy index b44e41f564..0989886f20 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JUnitMessagingMethodBodyBuilder.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JUnitMessagingMethodBodyBuilder.groovy @@ -30,6 +30,7 @@ import org.springframework.cloud.contract.spec.internal.Header import org.springframework.cloud.contract.spec.internal.Input import org.springframework.cloud.contract.spec.internal.NamedProperty import org.springframework.cloud.contract.spec.internal.NotToEscapePattern +import org.springframework.cloud.contract.spec.internal.RegexProperty import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties import org.springframework.cloud.contract.verifier.util.MapConverter @@ -264,6 +265,10 @@ class JUnitMessagingMethodBodyBuilder extends MessagingMethodBodyBuilder { return "" } + protected String convertHeaderComparison(RegexProperty headerValue) { + return convertHeaderComparison(headerValue.pattern) + } + protected String createHeaderComparison(Object headerValue) { String escapedHeader = convertUnicodeEscapesIfRequired("$headerValue") return "isEqualTo(\"$escapedHeader\");" diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy index 3602c61a64..7e7ceca7ef 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy @@ -41,6 +41,7 @@ import org.springframework.cloud.contract.spec.internal.MatchingType import org.springframework.cloud.contract.spec.internal.NamedProperty import org.springframework.cloud.contract.spec.internal.OptionalProperty import org.springframework.cloud.contract.spec.internal.QueryParameter +import org.springframework.cloud.contract.spec.internal.RegexProperty import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties import org.springframework.cloud.contract.verifier.template.HandlebarsTemplateProcessor import org.springframework.cloud.contract.verifier.template.TemplateProcessor @@ -197,6 +198,13 @@ abstract class MethodBodyBuilder { */ protected abstract void processHeaderElement(BlockBuilder blockBuilder, String property, Pattern pattern) + /** + * Appends to the {@link BlockBuilder} the assertion for the given header path + */ + protected void processHeaderElement(BlockBuilder blockBuilder, String property, RegexProperty regexProperty) { + processHeaderElement(blockBuilder, property, regexProperty.pattern) + } + /** * Appends to the {@link BlockBuilder} the assertion for the given header path */ @@ -522,7 +530,7 @@ abstract class MethodBodyBuilder { protected void methodForEqualityCheck(BodyMatcher bodyMatcher, BlockBuilder bb, Object copiedBody) { String path = quotedAndEscaped(bodyMatcher.path()) Object retrievedValue = value(copiedBody, bodyMatcher) - retrievedValue = retrievedValue instanceof Pattern ? ((Pattern) retrievedValue).pattern() : retrievedValue + retrievedValue = retrievedValue instanceof RegexProperty ? ((RegexProperty) retrievedValue).getPattern().pattern() : retrievedValue String valueAsParam = retrievedValue instanceof String ? quotedAndEscaped(retrievedValue.toString()) : retrievedValue.toString() if (arrayRelated(path) && MatchingType.regexRelated(bodyMatcher.matchingType())) { buildCustomMatchingConditionForEachElement(bb, path, valueAsParam) diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMessagingMethodBodyBuilder.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMessagingMethodBodyBuilder.groovy index c9a9bee7f1..dfb0231800 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMessagingMethodBodyBuilder.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMessagingMethodBodyBuilder.groovy @@ -27,6 +27,7 @@ import org.springframework.cloud.contract.spec.internal.Header import org.springframework.cloud.contract.spec.internal.Input import org.springframework.cloud.contract.spec.internal.NamedProperty import org.springframework.cloud.contract.spec.internal.NotToEscapePattern +import org.springframework.cloud.contract.spec.internal.RegexProperty import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties import org.springframework.cloud.contract.verifier.util.MapConverter @@ -251,6 +252,10 @@ class SpockMessagingMethodBodyBuilder extends MessagingMethodBodyBuilder { return " == '$headerValue'" } + protected String convertHeaderComparison(RegexProperty headerValue) { + return convertHeaderComparison(headerValue.pattern) + } + protected String convertHeaderComparison(Pattern headerValue) { String converted = escapeJava(convertUnicodeEscapesIfRequired(headerValue.pattern())) return "==~ java.util.regex.Pattern.compile('${converted}')" diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMethodRequestProcessingBodyBuilder.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMethodRequestProcessingBodyBuilder.groovy index 09f15723f0..70bc3e6ad4 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMethodRequestProcessingBodyBuilder.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMethodRequestProcessingBodyBuilder.groovy @@ -24,6 +24,7 @@ import org.springframework.cloud.contract.spec.internal.Cookie import org.springframework.cloud.contract.spec.internal.FromFileProperty import org.springframework.cloud.contract.spec.internal.Header import org.springframework.cloud.contract.spec.internal.NamedProperty +import org.springframework.cloud.contract.spec.internal.RegexProperty import org.springframework.cloud.contract.spec.internal.Request import org.springframework.cloud.contract.spec.internal.ExecutionProperty import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties @@ -187,10 +188,18 @@ abstract class SpockMethodRequestProcessingBodyBuilder extends RequestProcessing return patternComparison(headerValue) } + protected String convertHeaderComparison(RegexProperty headerValue) { + return convertHeaderComparison(headerValue.pattern) + } + protected String convertCookieComparison(String cookieValue) { return "== '$cookieValue'" } + protected String createBodyComparison(RegexProperty bodyValue) { + return createBodyComparison(bodyValue.pattern) + } + protected String createBodyComparison(Pattern bodyValue) { String patternAsString = bodyValue.pattern() return patternComparison(RegexpBuilders.buildGStringRegexpForTestSide(patternAsString)) + ";" diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/ContractsToYaml.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/ContractsToYaml.groovy index f44fcb464b..5a58de2fd6 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/ContractsToYaml.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/ContractsToYaml.groovy @@ -16,6 +16,8 @@ import org.springframework.cloud.contract.spec.internal.MatchingType import org.springframework.cloud.contract.spec.internal.Multipart import org.springframework.cloud.contract.spec.internal.NamedProperty import org.springframework.cloud.contract.spec.internal.NotToEscapePattern +import org.springframework.cloud.contract.spec.internal.RegexProperty +import org.springframework.cloud.contract.verifier.converter.YamlContract.RegexType import org.springframework.cloud.contract.verifier.util.JsonPaths import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter import org.springframework.cloud.contract.verifier.util.MapConverter @@ -141,12 +143,12 @@ class ContractsToYaml { ) } Object url = contract.request.url?.clientValue - request.matchers.url = url instanceof Pattern ? + request.matchers.url = url instanceof RegexProperty ? new YamlContract.KeyValueMatcher(regex: url.pattern()) : url instanceof ExecutionProperty ? new YamlContract.KeyValueMatcher(command: url.toString()) : null Object urlPath = contract.request.urlPath?.clientValue - request.matchers.url = urlPath instanceof Pattern ? + request.matchers.url = urlPath instanceof RegexProperty ? new YamlContract.KeyValueMatcher(regex: urlPath.pattern()) : urlPath instanceof ExecutionProperty ? new YamlContract.KeyValueMatcher(command: urlPath.toString()) : null @@ -158,9 +160,9 @@ class ContractsToYaml { Object fileName = value.name?.clientValue Object fileContent = value.value?.clientValue Object contentType = value.contentType?.clientValue - if (fileName instanceof Pattern || - fileContent instanceof Pattern || - contentType instanceof Pattern) { + if (fileName instanceof RegexProperty || + fileContent instanceof RegexProperty || + contentType instanceof RegexProperty) { request.matchers.multipart.named << new YamlContract.MultipartNamedStubMatcher( paramName: key, fileName: valueMatcher(fileName), @@ -168,10 +170,12 @@ class ContractsToYaml { contentType: valueMatcher(contentType), ) } - } else if (value instanceof Pattern) { + } else if (value instanceof RegexProperty || value instanceof Pattern) { + RegexProperty property = new RegexProperty(value) request.matchers.multipart.params.add(new YamlContract.KeyValueMatcher( key: key, - regex: value.pattern() + regex: property.pattern(), + regexType: regexType(property.clazz()) )) } } @@ -191,27 +195,56 @@ class ContractsToYaml { } protected YamlContract.ValueMatcher valueMatcher(Object o) { - return o instanceof Pattern ? new YamlContract.ValueMatcher(regex: o.pattern()) : null + return o instanceof RegexProperty ? new YamlContract.ValueMatcher(regex: o.pattern()) : null } protected void setInputBodyMatchers(DslProperty body, List bodyMatchers) { + def testSideValues = MapConverter.getTestSideValues(body) JsonPaths paths = new JsonToJsonPathsConverter().transformToJsonPathWithStubsSideValues(body) paths?.findAll { it.valueBeforeChecking() instanceof Pattern }?.each { + Object element = JsonToJsonPathsConverter.readElement(testSideValues, it.keyBeforeChecking()) bodyMatchers << new YamlContract.BodyStubMatcher( path: it.keyBeforeChecking(), type: YamlContract.StubMatcherType.by_regex, - value: (it.valueBeforeChecking() as Pattern).pattern() + value: (it.valueBeforeChecking() as Pattern).pattern(), + regexType: regexType(element) ) } } + protected RegexType regexType(Object from) { + return regexType(from.class) + } + + protected RegexType regexType(Class clazz) { + switch(clazz) { + case Boolean: + return RegexType.as_boolean + case Long: + return RegexType.as_long + case Short: + return RegexType.as_short + case Integer: + return RegexType.as_integer + case Float: + return RegexType.as_float + case Double: + return RegexType.as_double + default: + return RegexType.as_string + } + } + protected void setOutputBodyMatchers(DslProperty body, List bodyMatchers) { + def testSideValues = MapConverter.getTestSideValues(body) JsonPaths paths = new JsonToJsonPathsConverter().transformToJsonPathWithTestsSideValues(body) paths?.findAll { it.valueBeforeChecking() instanceof Pattern }?.each { + Object element = JsonToJsonPathsConverter.readElement(testSideValues, it.keyBeforeChecking()) bodyMatchers << new YamlContract.BodyTestMatcher( path: it.keyBeforeChecking(), type: YamlContract.TestMatcherType.by_regex, - value: (it.valueBeforeChecking() as Pattern).pattern() + value: (it.valueBeforeChecking() as Pattern).pattern(), + regexType: regexType(element) ) } if (body?.serverValue instanceof Pattern) { @@ -259,10 +292,12 @@ class ContractsToYaml { protected void setInputHeadersMatchers(Headers headers, List headerMatchers) { headers?.asStubSideMap()?.each { String key, Object value -> - if (value instanceof Pattern) { + if (value instanceof RegexProperty || value instanceof Pattern) { + RegexProperty property = new RegexProperty(value) headerMatchers << new YamlContract.KeyValueMatcher( key: key, - regex: value.pattern(), + regex: property.pattern(), + regexType: regexType(property.clazz()) ) } } @@ -270,10 +305,12 @@ class ContractsToYaml { protected void setOutputHeadersMatchers(Headers headers, List headerMatchers) { headers?.asTestSideMap()?.each { String key, Object value -> - if (value instanceof Pattern) { + if (value instanceof RegexProperty || value instanceof Pattern) { + RegexProperty property = new RegexProperty(value) headerMatchers << new YamlContract.TestHeaderMatcher( key: key, - regex: value.pattern(), + regex: property.pattern(), + regexType: regexType(property.clazz()) ) } else if (value instanceof ExecutionProperty) { headerMatchers << new YamlContract.TestHeaderMatcher( @@ -283,7 +320,7 @@ class ContractsToYaml { } else if (value instanceof NotToEscapePattern) { headerMatchers << new YamlContract.TestHeaderMatcher( key: key, - regex: value.serverValue.pattern(), + regex: ((Pattern) value.serverValue).pattern(), ) } } diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlContract.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlContract.groovy index 7d08c271a3..0e36de4193 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlContract.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlContract.groovy @@ -107,6 +107,12 @@ class YamlContract { public PredefinedRegex predefined public Integer minOccurrence public Integer maxOccurrence + public RegexType regexType + } + + @CompileStatic + enum RegexType { + as_integer, as_double, as_float, as_long, as_short, as_boolean, as_string } @CompileStatic @@ -145,6 +151,7 @@ class YamlContract { public Integer minOccurrence public Integer maxOccurrence public PredefinedRegex predefined + public RegexType regexType } @CompileStatic @@ -155,6 +162,7 @@ class YamlContract { public String regex public PredefinedRegex predefined public String command + public RegexType regexType } @CompileStatic @@ -181,6 +189,7 @@ class YamlContract { public String regex public String command public PredefinedRegex predefined + public RegexType regexType } @CompileStatic @@ -191,6 +200,7 @@ class YamlContract { public String regex public String command public PredefinedRegex predefined + public RegexType regexType } @CompileStatic 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 2eb90a306b..24018ecda6 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 @@ -535,35 +535,35 @@ class YamlToContracts { RegexPatterns patterns = new RegexPatterns() switch (predefinedRegex) { case YamlContract.PredefinedRegex.only_alpha_unicode: - return patterns.onlyAlphaUnicode() + return patterns.onlyAlphaUnicode().pattern case YamlContract.PredefinedRegex.number: - return patterns.number() + return patterns.number().pattern case YamlContract.PredefinedRegex.any_double: - return patterns.aDouble() + return patterns.aDouble().pattern case YamlContract.PredefinedRegex.any_boolean: - return patterns.anyBoolean() + return patterns.anyBoolean().pattern case YamlContract.PredefinedRegex.ip_address: - return patterns.ipAddress() + return patterns.ipAddress().pattern case YamlContract.PredefinedRegex.hostname: - return patterns.hostname() + return patterns.hostname().pattern case YamlContract.PredefinedRegex.email: - return patterns.email() + return patterns.email().pattern case YamlContract.PredefinedRegex.url: - return patterns.url() + return patterns.url().pattern case YamlContract.PredefinedRegex.uuid: - return patterns.uuid() + return patterns.uuid().pattern case YamlContract.PredefinedRegex.iso_date: - return patterns.isoDate() + return patterns.isoDate().pattern case YamlContract.PredefinedRegex.iso_date_time: - return patterns.isoDateTime() + return patterns.isoDateTime().pattern case YamlContract.PredefinedRegex.iso_time: - return patterns.isoTime() + return patterns.isoTime().pattern case YamlContract.PredefinedRegex.iso_8601_with_offset: - return patterns.iso8601WithOffset() + return patterns.iso8601WithOffset().pattern case YamlContract.PredefinedRegex.non_empty: - return patterns.nonEmpty() + return patterns.nonEmpty().pattern case YamlContract.PredefinedRegex.non_blank: - return patterns.nonBlank() + return patterns.nonBlank().pattern default: throw new UnsupportedOperationException("The predefined regex [" + predefinedRegex + "] is unsupported. Use on of " + YamlContract.PredefinedRegex.values()) } 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 514220c36f..690bf0f85f 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 @@ -27,7 +27,6 @@ import com.github.tomakehurst.wiremock.matching.StringValuePattern import com.github.tomakehurst.wiremock.matching.UrlPattern import groovy.json.JsonOutput import groovy.json.StringEscapeUtils -import groovy.transform.CompileDynamic import groovy.transform.PackageScope import groovy.transform.TypeChecked import groovy.transform.TypeCheckingMode @@ -42,6 +41,7 @@ import org.springframework.cloud.contract.spec.internal.NamedProperty import org.springframework.cloud.contract.spec.internal.OptionalProperty 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.util.ContentType import org.springframework.cloud.contract.verifier.util.ContentUtils @@ -51,7 +51,6 @@ import org.springframework.cloud.contract.verifier.util.MapConverter import static org.springframework.cloud.contract.verifier.util.RegexpBuilders.buildGStringRegexpForStubSide import static org.springframework.cloud.contract.verifier.util.RegexpBuilders.buildJSONRegexpMatch - /** * Converts a {@link Request} into {@link RequestPattern} * @@ -174,8 +173,8 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy { private UrlPattern urlPattern() { Object urlPath = urlPathOrUrlIfQueryPresent() if (urlPath) { - if(urlPath instanceof Pattern) { - return WireMock.urlPathMatching(getStubSideValue(urlPath.toString()) as String) + if(urlPath instanceof Pattern || urlPath instanceof RegexProperty) { + return WireMock.urlPathMatching(getStubSideValue(new RegexProperty(urlPath).pattern()) as String) } else { return WireMock.urlPathEqualTo(getStubSideValue(urlPath.toString()) as String) } @@ -184,8 +183,8 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy { throw new IllegalStateException("URL is required!") } Object url = getUrlIfGstring(request?.url?.clientValue) - if (url instanceof Pattern) { - return WireMock.urlMatching((url as Pattern).pattern()) + if (url instanceof Pattern || url instanceof RegexProperty) { + return WireMock.urlMatching(new RegexProperty(url).pattern()) } return WireMock.urlEqualTo(url.toString()) } @@ -204,8 +203,12 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy { private Object getUrlIfGstring(Object clientSide) { if (clientSide instanceof GString) { - if (clientSide.values.any { getStubSideValue(it) instanceof Pattern }) { - return Pattern.compile(getStubSideValue(clientSide).toString()) + if (clientSide.values.any { + def value = getStubSideValue(it) + return value instanceof Pattern || value instanceof RegexProperty + }) { + String string = getStubSideValue(clientSide).toString() + return new RegexProperty(Pattern.compile(string)) } else { return getStubSideValue(clientSide).toString() } @@ -224,8 +227,8 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy { private static ContentPattern convertToValuePattern(Object object, ContentType contentType) { switch (object) { case Pattern: - Pattern value = object as Pattern - return WireMock.matching(value.pattern()) + case RegexProperty: + return WireMock.matching(new RegexProperty(object).pattern()) case OptionalProperty: OptionalProperty value = object as OptionalProperty return WireMock.matching(value.optionalPattern()) @@ -339,9 +342,8 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy { return containsPattern(map.entrySet()) } - @CompileDynamic private boolean containsPattern(Collection collection) { - return collection.collect(this.&containsPattern).inject('') { a, b -> a || b } + return collection.collect(this.&containsPattern).inject(false) { a, b -> a || b } } private boolean containsPattern(Object[] objects) { @@ -360,6 +362,10 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy { return true } + private boolean containsPattern(RegexProperty pattern) { + return true + } + private boolean containsPattern(Object o) { return false } diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/DelegatingJsonVerifiable.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/DelegatingJsonVerifiable.java index 68d472ef79..9834ae4054 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/DelegatingJsonVerifiable.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/DelegatingJsonVerifiable.java @@ -215,7 +215,7 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable { return readyToCheck; } - @Override public JsonVerifiable isInstanceOf(Class clazz) + @Override public MethodBufferingJsonVerifiable isInstanceOf(Class clazz) throws IllegalStateException { DelegatingJsonVerifiable readyToCheck = new FinishedDelegatingJsonVerifiable( this.delegate.jsonPath(), this.delegate.isInstanceOf(clazz), this.methodsBuffer); diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy index 947efcf3a5..2db9528704 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy @@ -16,6 +16,8 @@ package org.springframework.cloud.contract.verifier.util +import java.util.regex.Pattern + import com.jayway.jsonpath.DocumentContext import com.jayway.jsonpath.JsonPath import com.jayway.jsonpath.PathNotFoundException @@ -24,17 +26,15 @@ import groovy.json.JsonOutput import groovy.json.JsonSlurper import groovy.transform.CompileStatic import groovy.util.logging.Commons -import org.apache.commons.lang3.StringEscapeUtils + import org.springframework.cloud.contract.spec.internal.BodyMatcher import org.springframework.cloud.contract.spec.internal.BodyMatchers import org.springframework.cloud.contract.spec.internal.ExecutionProperty import org.springframework.cloud.contract.spec.internal.MatchingType import org.springframework.cloud.contract.spec.internal.OptionalProperty +import org.springframework.cloud.contract.spec.internal.RegexProperty import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties import org.springframework.util.SerializationUtils -import repackaged.nl.flotsam.xeger.Xeger - -import java.util.regex.Pattern /** * I would like to apologize to anyone who is reading this class. Since JSON is a hectic structure * this class is also hectic. The idea is to traverse the JSON structure and build a set of @@ -96,6 +96,18 @@ class JsonToJsonPathsConverter { return jsonCopy } + /** + * Retrieves the value from JSON via json path + * + * @param json - parsed JSON + * @param jsonPath - json path + * @return matching part of the json + */ + static def readElement(def json, String jsonPath) { + DocumentContext context = JsonPath.parse(json) + return context.read(jsonPath) + } + /** * Related to #391. The converted body looks different when done via the String notation than * it does when done via a map notation. When working with String body and when matchers @@ -169,8 +181,8 @@ class JsonToJsonPathsConverter { @CompileStatic static Object generatedValueIfNeeded(Object value) { - if (value instanceof Pattern) { - return StringEscapeUtils.escapeJava(new Xeger(((Pattern) value).pattern()).generate()) + if (value instanceof RegexProperty) { + return ((RegexProperty) value).generateAndEscapeJavaStringIfNeeded() } return value } diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/MethodBufferingJsonVerifiable.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/MethodBufferingJsonVerifiable.java index 80b25e32a6..8f93cd910c 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/MethodBufferingJsonVerifiable.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/MethodBufferingJsonVerifiable.java @@ -65,6 +65,9 @@ public interface MethodBufferingJsonVerifiable @Override MethodBufferingJsonVerifiable matches(String value); + @Override + MethodBufferingJsonVerifiable isInstanceOf(Class clazz); + @Override MethodBufferingJsonVerifiable isEqualTo(Boolean value); diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientMethodBuilderSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientMethodBuilderSpec.groovy index de5dca7049..73aae01682 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientMethodBuilderSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientMethodBuilderSpec.groovy @@ -1333,7 +1333,8 @@ DATA builder.appendTo(blockBuilder) def test = blockBuilder.toString() then: - test.contains('''.cookie("cookie-key", "[A-Za-z]+")''') + !test.contains('''.cookie("cookie-key", "[A-Za-z]+")''') + test.contains('''.cookie("cookie-key", "''') test.contains('''assertThat(response.getCookies().get("cookie-key")).isNotNull();''') test.contains('''assertThat(response.getCookies().get("cookie-key").getValue()).matches("[A-Za-z]+");''') and: @@ -1376,7 +1377,8 @@ DATA builder.appendTo(blockBuilder) def test = blockBuilder.toString() then: - test.contains('''.cookie('cookie-key', '[A-Za-z]+')''') + !test.contains('''.cookie('cookie-key', '[A-Za-z]+')''') + test.contains('''.cookie('cookie-key', ''') test.contains('''response.getCookies().get('cookie-key') != null''') test.contains('''response.getCookies().get('cookie-key').getValue() ==~ java.util.regex.Pattern.compile('[A-Za-z]+')''') and: diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilderSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilderSpec.groovy index a397f58cf6..ae0bb845c2 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilderSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilderSpec.groovy @@ -528,7 +528,7 @@ DocumentContext parsedJson = JsonPath.parse(json); body( [ "name" : $(consumer(~/.+/), producer('string-1')), - "updatedTs" : $(consumer(~/\d{13}/), producer(1531916906000L)), + "updatedTs" : $(consumer(regex(~/1531916906000/).asLong())), "isDisabled": $(consumer(regex(anyBoolean())), producer(true)) ] ) @@ -557,6 +557,7 @@ DocumentContext parsedJson = JsonPath.parse(json); String test = blockBuilder.toString() SyntaxChecker.tryToCompileWithoutCompileStatic(methodBuilderName, test) test.contains('''assertThatJson(parsedJson).field("['updatedTs']").isEqualTo(1531916906000L)''') + !test.contains('''"updatedTs":"1531916906000"''') and: stubMappingIsValidWireMockStub(contractDsl) where: diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderWithMatchersSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderWithMatchersSpec.groovy index 00084f20c4..293755f3d8 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderWithMatchersSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderWithMatchersSpec.groovy @@ -62,12 +62,12 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements ] ]) bodyMatchers { - jsonPath('$.duck', byRegex("[0-9]{3}")) + jsonPath('$.duck', byRegex("[0-9]{3}").asInteger()) jsonPath('$.duck', byEquality()) - jsonPath('$.alpha', byRegex(onlyAlphaUnicode())) + jsonPath('$.alpha', byRegex(onlyAlphaUnicode()).asString()) jsonPath('$.alpha', byEquality()) - jsonPath('$.number', byRegex(number())) - jsonPath('$.aBoolean', byRegex(anyBoolean())) + jsonPath('$.number', byRegex(number()).asInteger()) + jsonPath('$.aBoolean', byRegex(anyBoolean()).asBooleanType()) jsonPath('$.date', byDate()) jsonPath('$.dateTime', byTimestamp()) jsonPath('$.time', byTime()) @@ -111,18 +111,18 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements ]) bodyMatchers { // asserts the jsonpath value against manual regex - jsonPath('$.duck', byRegex("[0-9]{3}")) + jsonPath('$.duck', byRegex("[0-9]{3}").asInteger()) // asserts the jsonpath value against the provided value jsonPath('$.duck', byEquality()) // asserts the jsonpath value against some default regex - jsonPath('$.alpha', byRegex(onlyAlphaUnicode())) + jsonPath('$.alpha', byRegex(onlyAlphaUnicode()).asString()) jsonPath('$.alpha', byEquality()) - jsonPath('$.number', byRegex(number())) - jsonPath('$.positiveInteger', byRegex(anInteger())) - jsonPath('$.negativeInteger', byRegex(anInteger())) - jsonPath('$.positiveDecimalNumber', byRegex(aDouble())) - jsonPath('$.negativeDecimalNumber', byRegex(aDouble())) - jsonPath('$.aBoolean', byRegex(anyBoolean())) + jsonPath('$.number', byRegex(number()).asInteger()) + jsonPath('$.positiveInteger', byRegex(anInteger()).asInteger()) + jsonPath('$.negativeInteger', byRegex(anInteger()).asInteger()) + jsonPath('$.positiveDecimalNumber', byRegex(aDouble()).asDouble()) + jsonPath('$.negativeDecimalNumber', byRegex(aDouble()).asDouble()) + jsonPath('$.aBoolean', byRegex(anyBoolean()).asBooleanType()) // asserts vs inbuilt time related regex jsonPath('$.date', byDate()) jsonPath('$.dateTime', byTimestamp()) 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 2ce72f8f5e..df92befc26 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 @@ -2794,7 +2794,8 @@ DocumentContext parsedJson = JsonPath.parse(json); builder.appendTo(blockBuilder) def test = blockBuilder.toString() then: - test.contains('''.cookie("cookie-key", "[A-Za-z]+")''') + !test.contains('''.cookie("cookie-key", "[A-Za-z]+")''') + test.contains('''.cookie("cookie-key", "''') test.contains('''assertThat(response.getCookie("cookie-key")).isNotNull();''') test.contains('''assertThat(response.getCookie("cookie-key")).matches("[A-Za-z]+");''') and: @@ -2845,7 +2846,8 @@ DocumentContext parsedJson = JsonPath.parse(json); builder.appendTo(blockBuilder) def test = blockBuilder.toString() then: - test.contains('''.cookie("cookie-key", "[A-Za-z]+")''') + !test.contains('''.cookie("cookie-key", "[A-Za-z]+")''') + test.contains('''.cookie("cookie-key", "''') test.contains('''response.cookie('cookie-key') != null''') test.contains('''response.cookie('cookie-key') ==~ java.util.regex.Pattern.compile('[A-Za-z]+')''') and: 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 78d37d15cb..2bd0b654b4 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 @@ -540,6 +540,7 @@ response: - path: $.property2 type: by_regex value: "[0-9]{3}" + regexType: as_integer ''' Contract contractDsl = fromYaml(contract) MethodBodyBuilder builder = methodBuilder(contractDsl) @@ -929,10 +930,12 @@ request: headers: - key: 'Content-Type' regex: 'application/json.*' + regexType: as_string body: - path: $.first_name type: by_regex value: '[\\p{L}]*' + regexType: as_string - path: $.last_name type: by_regex value: '[\\p{L}]*' @@ -1127,8 +1130,10 @@ request: params: - key: formParameter regex: ".+" + regexType: as_string - key: someBooleanParameter predefined: any_boolean + regexType: as_boolean named: - paramName: file fileName: @@ -1191,6 +1196,7 @@ response: - path: $.authorities[0] type: by_regex value: '^[a-zA-Z0-9_\\- ]+$' + regexType: as_string ''' Contract contractDsl = fromYaml(contract) MethodBodyBuilder builder = new HttpSpockMethodRequestProcessingBodyBuilder(contractDsl, properties, generatedClassDataForMethod) diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/converter/YamlContractConverterSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/converter/YamlContractConverterSpec.groovy index 069646a175..a89997abb6 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/converter/YamlContractConverterSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/converter/YamlContractConverterSpec.groovy @@ -33,7 +33,6 @@ import org.springframework.cloud.contract.spec.internal.RegexPatterns import org.springframework.cloud.contract.spec.internal.Url import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter import org.springframework.cloud.contract.verifier.util.MapConverter - /** * @author Marcin Grzejszczak * @author Tim Ysewyn @@ -125,7 +124,7 @@ class YamlContractConverterSpec extends Specification { contract.request.body.clientValue == [foo: "bar"] contract.request.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.foo' contract.request.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX - contract.request.bodyMatchers.jsonPathRegexMatchers[0].value() == 'bar' + contract.request.bodyMatchers.jsonPathRegexMatchers[0].value().pattern() == 'bar' and: contract.response.status.clientValue == 200 if (yamlFile == ymlWithRest) contract.response.delay.clientValue == 1000 else !contract.response.delay @@ -138,7 +137,7 @@ class YamlContractConverterSpec extends Specification { contract.response.body.clientValue == [foo2: "bar", foo3: "baz", nullValue: null] contract.response.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.foo2' contract.response.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX - contract.response.bodyMatchers.jsonPathRegexMatchers[0].value() == 'bar' + contract.response.bodyMatchers.jsonPathRegexMatchers[0].value().pattern() == 'bar' contract.response.bodyMatchers.jsonPathRegexMatchers[1].path() == '$.foo3' contract.response.bodyMatchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.COMMAND contract.response.bodyMatchers.jsonPathRegexMatchers[1].value() == new ExecutionProperty('executeMe($it)') @@ -217,29 +216,29 @@ class YamlContractConverterSpec extends Specification { MatchingStrategy.Type.ABSENT, null) contract.request.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.duck' contract.request.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX - contract.request.bodyMatchers.jsonPathRegexMatchers[0].value() == '[0-9]{3}' + contract.request.bodyMatchers.jsonPathRegexMatchers[0].value().pattern() == '[0-9]{3}' contract.request.bodyMatchers.jsonPathRegexMatchers[1].path() == '$.duck' contract.request.bodyMatchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.EQUALITY contract.request.bodyMatchers.jsonPathRegexMatchers[2].path() == '$.alpha' contract.request.bodyMatchers.jsonPathRegexMatchers[2].matchingType() == MatchingType.REGEX - contract.request.bodyMatchers.jsonPathRegexMatchers[2].value() == patterns.onlyAlphaUnicode().pattern() + contract.request.bodyMatchers.jsonPathRegexMatchers[2].value().pattern() == patterns.onlyAlphaUnicode().pattern() contract.request.bodyMatchers.jsonPathRegexMatchers[3].path() == '$.alpha' contract.request.bodyMatchers.jsonPathRegexMatchers[3].matchingType() == MatchingType.EQUALITY contract.request.bodyMatchers.jsonPathRegexMatchers[4].path() == '$.number' contract.request.bodyMatchers.jsonPathRegexMatchers[4].matchingType() == MatchingType.REGEX - contract.request.bodyMatchers.jsonPathRegexMatchers[4].value() == patterns.number().pattern() + contract.request.bodyMatchers.jsonPathRegexMatchers[4].value().pattern() == patterns.number().pattern() contract.request.bodyMatchers.jsonPathRegexMatchers[5].path() == '$.aBoolean' contract.request.bodyMatchers.jsonPathRegexMatchers[5].matchingType() == MatchingType.REGEX - contract.request.bodyMatchers.jsonPathRegexMatchers[5].value() == patterns.anyBoolean().pattern() + contract.request.bodyMatchers.jsonPathRegexMatchers[5].value().pattern() == patterns.anyBoolean().pattern() contract.request.bodyMatchers.jsonPathRegexMatchers[6].path() == '$.date' contract.request.bodyMatchers.jsonPathRegexMatchers[6].matchingType() == MatchingType.DATE - contract.request.bodyMatchers.jsonPathRegexMatchers[6].value() == patterns.isoDate() + contract.request.bodyMatchers.jsonPathRegexMatchers[6].value().pattern() == patterns.isoDate().pattern() contract.request.bodyMatchers.jsonPathRegexMatchers[7].path() == '$.dateTime' contract.request.bodyMatchers.jsonPathRegexMatchers[7].matchingType() == MatchingType.TIMESTAMP - contract.request.bodyMatchers.jsonPathRegexMatchers[7].value() == patterns.isoDateTime() + contract.request.bodyMatchers.jsonPathRegexMatchers[7].value().pattern() == patterns.isoDateTime().pattern() contract.request.bodyMatchers.jsonPathRegexMatchers[8].path() == '$.time' contract.request.bodyMatchers.jsonPathRegexMatchers[8].matchingType() == MatchingType.TIME - contract.request.bodyMatchers.jsonPathRegexMatchers[8].value() == patterns.isoTime() + contract.request.bodyMatchers.jsonPathRegexMatchers[8].value().pattern() == patterns.isoTime().pattern() contract.request.bodyMatchers.jsonPathRegexMatchers[9].path() == "\$.['key'].['complex.key']" contract.request.bodyMatchers.jsonPathRegexMatchers[9].matchingType() == MatchingType.EQUALITY contract.request.bodyMatchers.jsonPathRegexMatchers[10].path() == '$.valueWithMin' @@ -258,29 +257,29 @@ class YamlContractConverterSpec extends Specification { contract.response.status.clientValue == 200 contract.response.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.duck' contract.response.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX - contract.response.bodyMatchers.jsonPathRegexMatchers[0].value() == '[0-9]{3}' + contract.response.bodyMatchers.jsonPathRegexMatchers[0].value().pattern() == '[0-9]{3}' contract.response.bodyMatchers.jsonPathRegexMatchers[1].path() == '$.duck' contract.response.bodyMatchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.EQUALITY contract.response.bodyMatchers.jsonPathRegexMatchers[2].path() == '$.alpha' contract.response.bodyMatchers.jsonPathRegexMatchers[2].matchingType() == MatchingType.REGEX - contract.response.bodyMatchers.jsonPathRegexMatchers[2].value() == patterns.onlyAlphaUnicode().pattern() + contract.response.bodyMatchers.jsonPathRegexMatchers[2].value().pattern() == patterns.onlyAlphaUnicode().pattern() contract.response.bodyMatchers.jsonPathRegexMatchers[3].path() == '$.alpha' contract.response.bodyMatchers.jsonPathRegexMatchers[3].matchingType() == MatchingType.EQUALITY contract.response.bodyMatchers.jsonPathRegexMatchers[4].path() == '$.number' contract.response.bodyMatchers.jsonPathRegexMatchers[4].matchingType() == MatchingType.REGEX - contract.response.bodyMatchers.jsonPathRegexMatchers[4].value() == patterns.number().pattern() + contract.response.bodyMatchers.jsonPathRegexMatchers[4].value().pattern() == patterns.number().pattern() contract.response.bodyMatchers.jsonPathRegexMatchers[5].path() == '$.aBoolean' contract.response.bodyMatchers.jsonPathRegexMatchers[5].matchingType() == MatchingType.REGEX - contract.response.bodyMatchers.jsonPathRegexMatchers[5].value() == patterns.anyBoolean().pattern() + contract.response.bodyMatchers.jsonPathRegexMatchers[5].value().pattern() == patterns.anyBoolean().pattern() contract.response.bodyMatchers.jsonPathRegexMatchers[6].path() == '$.date' contract.response.bodyMatchers.jsonPathRegexMatchers[6].matchingType() == MatchingType.DATE - contract.response.bodyMatchers.jsonPathRegexMatchers[6].value() == patterns.isoDate() + contract.response.bodyMatchers.jsonPathRegexMatchers[6].value().pattern() == patterns.isoDate().pattern() contract.response.bodyMatchers.jsonPathRegexMatchers[7].path() == '$.dateTime' contract.response.bodyMatchers.jsonPathRegexMatchers[7].matchingType() == MatchingType.TIMESTAMP - contract.response.bodyMatchers.jsonPathRegexMatchers[7].value() == patterns.isoDateTime() + contract.response.bodyMatchers.jsonPathRegexMatchers[7].value().pattern() == patterns.isoDateTime().pattern() contract.response.bodyMatchers.jsonPathRegexMatchers[8].path() == '$.time' contract.response.bodyMatchers.jsonPathRegexMatchers[8].matchingType() == MatchingType.TIME - contract.response.bodyMatchers.jsonPathRegexMatchers[8].value() == patterns.isoTime() + contract.response.bodyMatchers.jsonPathRegexMatchers[8].value().pattern() == patterns.isoTime().pattern() contract.response.bodyMatchers.jsonPathRegexMatchers[9].path() == '$.valueWithTypeMatch' contract.response.bodyMatchers.jsonPathRegexMatchers[9].matchingType() == MatchingType.TYPE contract.response.bodyMatchers.jsonPathRegexMatchers[10].path() == '$.valueWithMin' @@ -329,57 +328,57 @@ class YamlContractConverterSpec extends Specification { ((Pattern) it.clientValue).pattern == "application/json.*" && it.serverValue == "application/json" } contract.input.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.duck' contract.input.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX - contract.input.bodyMatchers.jsonPathRegexMatchers[0].value() == '[0-9]{3}' + contract.input.bodyMatchers.jsonPathRegexMatchers[0].value().pattern() == '[0-9]{3}' contract.input.bodyMatchers.jsonPathRegexMatchers[1].path() == '$.duck' contract.input.bodyMatchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.EQUALITY contract.input.bodyMatchers.jsonPathRegexMatchers[2].path() == '$.alpha' contract.input.bodyMatchers.jsonPathRegexMatchers[2].matchingType() == MatchingType.REGEX - contract.input.bodyMatchers.jsonPathRegexMatchers[2].value() == patterns.onlyAlphaUnicode().pattern() + contract.input.bodyMatchers.jsonPathRegexMatchers[2].value().pattern() == patterns.onlyAlphaUnicode().pattern() contract.input.bodyMatchers.jsonPathRegexMatchers[3].path() == '$.alpha' contract.input.bodyMatchers.jsonPathRegexMatchers[3].matchingType() == MatchingType.EQUALITY contract.input.bodyMatchers.jsonPathRegexMatchers[4].path() == '$.number' contract.input.bodyMatchers.jsonPathRegexMatchers[4].matchingType() == MatchingType.REGEX - contract.input.bodyMatchers.jsonPathRegexMatchers[4].value() == patterns.number().pattern() + contract.input.bodyMatchers.jsonPathRegexMatchers[4].value().pattern() == patterns.number().pattern() contract.input.bodyMatchers.jsonPathRegexMatchers[5].path() == '$.aBoolean' contract.input.bodyMatchers.jsonPathRegexMatchers[5].matchingType() == MatchingType.REGEX - contract.input.bodyMatchers.jsonPathRegexMatchers[5].value() == patterns.anyBoolean().pattern() + contract.input.bodyMatchers.jsonPathRegexMatchers[5].value().pattern() == patterns.anyBoolean().pattern() contract.input.bodyMatchers.jsonPathRegexMatchers[6].path() == '$.date' contract.input.bodyMatchers.jsonPathRegexMatchers[6].matchingType() == MatchingType.DATE - contract.input.bodyMatchers.jsonPathRegexMatchers[6].value() == patterns.isoDate() + contract.input.bodyMatchers.jsonPathRegexMatchers[6].value().pattern() == patterns.isoDate().pattern() contract.input.bodyMatchers.jsonPathRegexMatchers[7].path() == '$.dateTime' contract.input.bodyMatchers.jsonPathRegexMatchers[7].matchingType() == MatchingType.TIMESTAMP - contract.input.bodyMatchers.jsonPathRegexMatchers[7].value() == patterns.isoDateTime() + contract.input.bodyMatchers.jsonPathRegexMatchers[7].value().pattern() == patterns.isoDateTime().pattern() contract.input.bodyMatchers.jsonPathRegexMatchers[8].path() == '$.time' contract.input.bodyMatchers.jsonPathRegexMatchers[8].matchingType() == MatchingType.TIME - contract.input.bodyMatchers.jsonPathRegexMatchers[8].value() == patterns.isoTime() + contract.input.bodyMatchers.jsonPathRegexMatchers[8].value().pattern() == patterns.isoTime().pattern() contract.input.bodyMatchers.jsonPathRegexMatchers[9].path() == "\$.['key'].['complex.key']" contract.input.bodyMatchers.jsonPathRegexMatchers[9].matchingType() == MatchingType.EQUALITY and: contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.duck' contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX - contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[0].value() == '[0-9]{3}' + contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[0].value().pattern() == '[0-9]{3}' contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[1].path() == '$.duck' contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.EQUALITY contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[2].path() == '$.alpha' contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[2].matchingType() == MatchingType.REGEX - contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[2].value() == patterns.onlyAlphaUnicode().pattern() + contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[2].value().pattern() == patterns.onlyAlphaUnicode().pattern() contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[3].path() == '$.alpha' contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[3].matchingType() == MatchingType.EQUALITY contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[4].path() == '$.number' contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[4].matchingType() == MatchingType.REGEX - contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[4].value() == patterns.number().pattern() + contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[4].value().pattern() == patterns.number().pattern() contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[5].path() == '$.aBoolean' contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[5].matchingType() == MatchingType.REGEX - contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[5].value() == patterns.anyBoolean().pattern() + contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[5].value().pattern() == patterns.anyBoolean().pattern() contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[6].path() == '$.date' contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[6].matchingType() == MatchingType.DATE - contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[6].value() == patterns.isoDate() + contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[6].value().pattern() == patterns.isoDate().pattern() contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[7].path() == '$.dateTime' contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[7].matchingType() == MatchingType.TIMESTAMP - contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[7].value() == patterns.isoDateTime() + contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[7].value().pattern() == patterns.isoDateTime().pattern() contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[8].path() == '$.time' contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[8].matchingType() == MatchingType.TIME - contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[8].value() == patterns.isoTime() + contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[8].value().pattern() == patterns.isoTime().pattern() contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[9].path() == '$.valueWithTypeMatch' contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[9].matchingType() == MatchingType.TYPE contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[10].path() == '$.valueWithMin' @@ -462,7 +461,7 @@ class YamlContractConverterSpec extends Specification { contract.input.messageBody.clientValue == [foo: "bar"] contract.input.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.bar' contract.input.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX - contract.input.bodyMatchers.jsonPathRegexMatchers[0].value() == 'bar' + contract.input.bodyMatchers.jsonPathRegexMatchers[0].value().pattern() == 'bar' and: contract.outputMessage.assertThat.toString() == "baz()" contract.outputMessage.headers.entries.find { it.name == "foo2" && @@ -474,7 +473,7 @@ class YamlContractConverterSpec extends Specification { contract.outputMessage.body.clientValue == [foo2: "bar", foo3: "baz"] contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.foo2' contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX - contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[0].value() == 'bar' + contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[0].value().pattern() == 'bar' contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[1].path() == '$.foo3' contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.COMMAND contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[1].value() == new ExecutionProperty('executeMe($it)') diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/WireMockGroovyDslSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/WireMockGroovyDslSpec.groovy index d730a9c209..27cd2d7fb3 100755 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/WireMockGroovyDslSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/WireMockGroovyDslSpec.groovy @@ -906,7 +906,7 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie stubMappingIsValidWireMockStub(json) } - def "should not allow regexp in url for server value"() { + def "should not allow not matching query param for server value"() { when: org.springframework.cloud.contract.spec.Contract.make { request { @@ -924,7 +924,7 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie } then: def e = thrown(IllegalStateException) - e.message.contains "Url can't be a pattern for the server side" + e.message.contains "Query parameter 'age' can't be of a matching type: NOT_MATCHING for the server side" } def "should not allow regexp in query parameter for server value"() {