Simplification of the dsl (#151)

with this change to DSL we're adding some helper methods and DSL simplifications 

- c(...) / p(...) - for consumer / producer
- for regex - e.g. $(anyUrl()) - insead of $(consumer(regex(url()))
- Added helper headers and media types
- Added helper http methods

fixes #149
This commit is contained in:
Marcin Grzejszczak
2016-11-18 15:38:35 +01:00
committed by GitHub
parent df80cc8f1f
commit b1175ff9bd
20 changed files with 1164 additions and 100 deletions

View File

@@ -29,4 +29,8 @@ class ClientDslProperty extends DslProperty {
ClientDslProperty(Object singleValue) {
super(singleValue)
}
ClientDslProperty(Object client, Object server) {
super(client, server)
}
}

View File

@@ -144,6 +144,17 @@ class Common {
return new ServerDslProperty(serverValue)
}
/**
* Helper method to provide a better name for the consumer side
*/
ClientDslProperty c(Object clientValue) {
return new ClientDslProperty(clientValue)
}
ServerDslProperty p(Object serverValue) {
return new ServerDslProperty(serverValue)
}
/**
* Helper method to provide a better name for the producer side
*/

View File

@@ -42,4 +42,10 @@ class DslProperty<T> {
this.clientValue = singleValue
this.serverValue = singleValue
}
boolean isSingleValue() {
return this.clientValue == this.serverValue ||
(this.clientValue != null && this.serverValue == null ) ||
(this.serverValue != null && this.clientValue == null )
}
}

View File

@@ -30,6 +30,9 @@ import groovy.transform.TypeChecked
@TypeChecked
class Headers {
@Delegate MediaTypes mediaTypes = new MediaTypes()
@Delegate HttpHeaders httpHeaders = new HttpHeaders()
Set<Header> entries = []
void header(Map<String, Object> singleHeader) {
@@ -47,6 +50,24 @@ class Headers {
}
}
void accept(String contentType) {
header(accept(), matching(contentType))
}
void contentType(String contentType) {
header(httpHeaders.contentType(), matching(contentType))
}
/**
* If for the consumer / producer you want to match exactly only
* the root of content type. I.e. {@code application/json;charset=UTF8}
* you care only about {@code application/json} then you should
* use this method
*/
DslProperty matching(String value) {
return new DslProperty(value)
}
/**
* Converts the headers into their stub side representations and returns as
* a map of String key => Object value.

View File

@@ -0,0 +1,480 @@
package org.springframework.cloud.contract.spec.internal
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
/**
* Contains most commonly used http headers
*
* @author Marcin Grzejszczak
* @since 1.0.2
*/
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
class HttpHeaders {
/**
* The HTTP {@code Accept} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-5.3.2">Section 5.3.2 of RFC 7231</a>
*/
String accept() {
return "Accept"
}
/**
* The HTTP {@code Accept-Charset} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-5.3.3">Section 5.3.3 of RFC 7231</a>
*/
String acceptCharset() {
return "Accept-Charset"
}
/**
* The HTTP {@code Accept-Encoding} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-5.3.4">Section 5.3.4 of RFC 7231</a>
*/
String acceptEncoding() {
return "Accept-Encoding"
}
/**
* The HTTP {@code Accept-Language} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-5.3.5">Section 5.3.5 of RFC 7231</a>
*/
String acceptLanguage() {
return "Accept-Language"
}
/**
* The HTTP {@code Accept-Ranges} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7233#section-2.3">Section 5.3.5 of RFC 7233</a>
*/
String acceptRanges() {
return "Accept-Ranges"
}
/**
* The CORS {@code Access-Control-Allow-Credentials} response header field name.
* @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
String accessControlAllowCredentials() {
return "Access-Control-Allow-Credentials"
}
/**
* The CORS {@code Access-Control-Allow-Headers} response header field name.
* @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
String accessControlAllowHeaders() {
return "Access-Control-Allow-Headers"
}
/**
* The CORS {@code Access-Control-Allow-Methods} response header field name.
* @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
String accessControlAllowMethods() {
return "Access-Control-Allow-Methods"
}
/**
* The CORS {@code Access-Control-Allow-Origin} response header field name.
* @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
String accessControlAllowOrigin() {
return "Access-Control-Allow-Origin"
}
/**
* The CORS {@code Access-Control-Expose-Headers} response header field name.
* @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
String accessControlExposeHeaders() {
return "Access-Control-Expose-Headers"
}
/**
* The CORS {@code Access-Control-Max-Age} response header field name.
* @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
String accessControlMaxAge() {
return "Access-Control-Max-Age"
}
/**
* The CORS {@code Access-Control-Request-Headers} request header field name.
* @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
String accessControlRequestHeaders() {
return "Access-Control-Request-Headers"
}
/**
* The CORS {@code Access-Control-Request-Method} request header field name.
* @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
String accessControlRequestMethod() {
return "Access-Control-Request-Method"
}
/**
* The HTTP {@code Age} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7234#section-5.1">Section 5.1 of RFC 7234</a>
*/
String age() {
return "Age"
}
/**
* The HTTP {@code Allow} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-7.4.1">Section 7.4.1 of RFC 7231</a>
*/
String allow() {
return "Allow"
}
/**
* The HTTP {@code Authorization} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7235#section-4.2">Section 4.2 of RFC 7235</a>
*/
String authorization() {
return "Authorization"
}
/**
* The HTTP {@code Cache-Control} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7234#section-5.2">Section 5.2 of RFC 7234</a>
*/
String cacheControl() {
return "Cache-Control"
}
/**
* The HTTP {@code Connection} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7230#section-6.1">Section 6.1 of RFC 7230</a>
*/
String connection() {
return "Connection"
}
/**
* The HTTP {@code Content-Encoding} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.2.2">Section 3.1.2.2 of RFC 7231</a>
*/
String contentEncoding() {
return "Content-Encoding"
}
/**
* The HTTP {@code Content-Disposition} header field name
* @see <a href="http://tools.ietf.org/html/rfc6266">RFC 6266</a>
*/
String contentDisposition() {
return "Content-Disposition"
}
/**
* The HTTP {@code Content-Language} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.3.2">Section 3.1.3.2 of RFC 7231</a>
*/
String contentLanguage() {
return "Content-Language"
}
/**
* The HTTP {@code Content-Length} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7230#section-3.3.2">Section 3.3.2 of RFC 7230</a>
*/
String contentLength() {
return "Content-Length"
}
/**
* The HTTP {@code Content-Location} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.4.2">Section 3.1.4.2 of RFC 7231</a>
*/
String contentLocation() {
return "Content-Location"
}
/**
* The HTTP {@code Content-Range} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7233#section-4.2">Section 4.2 of RFC 7233</a>
*/
String contentRange() {
return "Content-Range"
}
/**
* The HTTP {@code Content-Type} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.1.5">Section 3.1.1.5 of RFC 7231</a>
*/
String contentType() {
return "Content-Type"
}
/**
* The HTTP {@code Cookie} header field name.
* @see <a href="http://tools.ietf.org/html/rfc2109#section-4.3.4">Section 4.3.4 of RFC 2109</a>
*/
String cookie() {
return "Cookie"
}
/**
* The HTTP {@code Date} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-7.1.1.2">Section 7.1.1.2 of RFC 7231</a>
*/
String date() {
return "Date"
}
/**
* The HTTP {@code ETag} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a>
*/
String etag() {
return "ETag"
}
/**
* The HTTP {@code Expect} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-5.1.1">Section 5.1.1 of RFC 7231</a>
*/
String expect() {
return "Expect"
}
/**
* The HTTP {@code Expires} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7234#section-5.3">Section 5.3 of RFC 7234</a>
*/
String expires() {
return "Expires"
}
/**
* The HTTP {@code From} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-5.5.1">Section 5.5.1 of RFC 7231</a>
*/
String from() {
return "From"
}
/**
* The HTTP {@code Host} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7230#section-5.4">Section 5.4 of RFC 7230</a>
*/
String host() {
return "Host"
}
/**
* The HTTP {@code If-Match} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7232#section-3.1">Section 3.1 of RFC 7232</a>
*/
String ifMatch() {
return "If-Match"
}
/**
* The HTTP {@code If-Modified-Since} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7232#section-3.3">Section 3.3 of RFC 7232</a>
*/
String ifModifiedSince() {
return "If-Modified-Since"
}
/**
* The HTTP {@code If-None-Match} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7232#section-3.2">Section 3.2 of RFC 7232</a>
*/
String ifNoneMatch() {
return "If-None-Match"
}
/**
* The HTTP {@code If-Range} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7233#section-3.2">Section 3.2 of RFC 7233</a>
*/
String ifRange() {
return "If-Range"
}
/**
* The HTTP {@code If-Unmodified-Since} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7232#section-3.4">Section 3.4 of RFC 7232</a>
*/
String ifUnmodifiedSince() {
return "If-Unmodified-Since"
}
/**
* The HTTP {@code Last-Modified} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7232#section-2.2">Section 2.2 of RFC 7232</a>
*/
String lastModified() {
return "Last-Modified"
}
/**
* The HTTP {@code Link} header field name.
* @see <a href="http://tools.ietf.org/html/rfc5988">RFC 5988</a>
*/
String link() {
return "Link"
}
/**
* The HTTP {@code Location} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-7.1.2">Section 7.1.2 of RFC 7231</a>
*/
String location() {
return "Location"
}
/**
* The HTTP {@code Max-Forwards} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-5.1.2">Section 5.1.2 of RFC 7231</a>
*/
String max_forwards() {
return "Max-Forwards"
}
/**
* The HTTP {@code Origin} header field name.
* @see <a href="http://tools.ietf.org/html/rfc6454">RFC 6454</a>
*/
String origin() {
return "Origin"
}
/**
* The HTTP {@code Pragma} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7234#section-5.4">Section 5.4 of RFC 7234</a>
*/
String pragma() {
return "Pragma"
}
/**
* The HTTP {@code Proxy-Authenticate} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7235#section-4.3">Section 4.3 of RFC 7235</a>
*/
String proxyAuthenticate() {
return "Proxy-Authenticate"
}
/**
* The HTTP {@code Proxy-Authorization} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7235#section-4.4">Section 4.4 of RFC 7235</a>
*/
String proxyAuthorization() {
return "Proxy-Authorization"
}
/**
* The HTTP {@code Range} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7233#section-3.1">Section 3.1 of RFC 7233</a>
*/
String range() {
return "Range"
}
/**
* The HTTP {@code Referer} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-5.5.2">Section 5.5.2 of RFC 7231</a>
*/
String referer() {
return "Referer"
}
/**
* The HTTP {@code Retry-After} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-7.1.3">Section 7.1.3 of RFC 7231</a>
*/
String retryAfter() {
return "Retry-After"
}
/**
* The HTTP {@code Server} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-7.4.2">Section 7.4.2 of RFC 7231</a>
*/
String server() {
return "Server"
}
/**
* The HTTP {@code Set-Cookie} header field name.
* @see <a href="http://tools.ietf.org/html/rfc2109#section-4.2.2">Section 4.2.2 of RFC 2109</a>
*/
String setCookie() {
return "Set-Cookie"
}
/**
* The HTTP {@code Set-Cookie2} header field name.
* @see <a href="http://tools.ietf.org/html/rfc2965">RFC 2965</a>
*/
String setCookie2() {
return "Set-Cookie2"
}
/**
* The HTTP {@code TE} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7230#section-4.3">Section 4.3 of RFC 7230</a>
*/
String te() {
return "TE"
}
/**
* The HTTP {@code Trailer} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7230#section-4.4">Section 4.4 of RFC 7230</a>
*/
String trailer() {
return "Trailer"
}
/**
* The HTTP {@code Transfer-Encoding} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7230#section-3.3.1">Section 3.3.1 of RFC 7230</a>
*/
String transferEncoding() {
return "Transfer-Encoding"
}
/**
* The HTTP {@code Upgrade} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7230#section-6.7">Section 6.7 of RFC 7230</a>
*/
String upgrade() {
return "Upgrade"
}
/**
* The HTTP {@code User-Agent} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-5.5.3">Section 5.5.3 of RFC 7231</a>
*/
String user_agent() {
return "User-Agent"
}
/**
* The HTTP {@code Vary} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7231#section-7.1.4">Section 7.1.4 of RFC 7231</a>
*/
String vary() {
return "Vary"
}
/**
* The HTTP {@code Via} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7230#section-5.7.1">Section 5.7.1 of RFC 7230</a>
*/
String via() {
return "Via"
}
/**
* The HTTP {@code Warning} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7234#section-5.5">Section 5.5 of RFC 7234</a>
*/
String warning() {
return "Warning"
}
/**
* The HTTP {@code WWW-Authenticate} header field name.
* @see <a href="http://tools.ietf.org/html/rfc7235#section-4.1">Section 4.1 of RFC 7235</a>
*/
String wwwAuthenticate() {
return "WWW-Authenticate"
}
}

View File

@@ -0,0 +1,53 @@
package org.springframework.cloud.contract.spec.internal
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
/**
* Contains Http Methods
*
* @author Marcin Grzejszczak
* @since 1.0.2
*/
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
class HttpMethods {
HttpMethod GET() {
return HttpMethod.GET
}
HttpMethod HEAD() {
return HttpMethod.HEAD
}
HttpMethod POST() {
return HttpMethod.POST
}
HttpMethod PUT() {
return HttpMethod.PUT
}
HttpMethod PATCH() {
return HttpMethod.PATCH
}
HttpMethod DELETE() {
return HttpMethod.DELETE
}
HttpMethod OPTIONS() {
return HttpMethod.OPTIONS
}
HttpMethod TRACE() {
return HttpMethod.TRACE
}
enum HttpMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}
}

View File

@@ -0,0 +1,85 @@
package org.springframework.cloud.contract.spec.internal
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
/**
* Contains most commonly used media types
*
* @author Marcin Grzejszczak
* @since 1.0.2
*/
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
class MediaTypes {
String allValue() {
return "*/*"
}
String applicationAtomXml() {
return "application/atom+xml"
}
String applicationFormUrlencoded() {
return "application/x-www-form-urlencoded"
}
String applicationJson() {
return "application/json"
}
String applicationJsonUtf8() {
return applicationJson() + ";charset=UTF-8"
}
String applicationOctetStream() {
return "application/octet-stream"
}
String applicationPdf() {
return "application/pdf"
}
String applicationXhtmlXml() {
return "application/xhtml+xml"
}
String applicationXml() {
return "application/xml"
}
String imageGif() {
return "image/gif"
}
String imageJpeg() {
return "image/jpeg"
}
String imagePng() {
return "image/png"
}
String multipartFormData() {
return "multipart/form-data"
}
String textHtml() {
return "text/html"
}
String textMarkdown() {
return "text/markdown"
}
String textPlain() {
return "text/plain"
}
String textXml() {
return "text/xml"
}
}

View File

@@ -0,0 +1,82 @@
package org.springframework.cloud.contract.spec.internal
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import java.util.regex.Pattern
/**
* @author Marcin Grzejszczak
*/
@PackageScope
@CompileStatic
abstract class PatternValueDslProperty<T extends DslProperty> {
private final Random random = new Random()
protected T createAndValidateProperty(Pattern pattern, Object object = null) {
if (object) {
String generatedValue = object as String
boolean matches = pattern.matcher(generatedValue).matches()
if (!matches) {
throw new IllegalStateException("The generated value [${generatedValue}] doesn't match the pattern [${pattern.pattern()}]")
}
return createProperty(pattern, generatedValue)
}
return createProperty(pattern, object)
}
/**
* Method to generate the PatternValue. The resulting implementation
* will create either a Client or a Server side impl.
*
* @param pattern - pattern for which the value will be generated or reused
* @param generatedValue - Nullable - potential generated value to be reused
* @return {@link DslProperty} wrapping a pattern and generated value
*/
protected abstract T createProperty(Pattern pattern, Object generatedValue)
T anyAlphaUnicode() {
return createAndValidateProperty(RegexPatterns.ONLY_ALPHA_UNICODE, randomString(20))
}
T anyNumber() {
return createAndValidateProperty(RegexPatterns.NUMBER, this.random.nextInt())
}
T aBoolean() {
return createAndValidateProperty(RegexPatterns.TRUE_OR_FALSE)
}
T anyIpAddress() {
return createAndValidateProperty(RegexPatterns.IP_ADDRESS)
}
T anyHostname() {
return createAndValidateProperty(RegexPatterns.HOSTNAME_PATTERN, "http://foo" + this.random.nextInt() + ".com")
}
T anyEmail() {
return createAndValidateProperty(RegexPatterns.EMAIL, "foo@bar" + this.random.nextInt() + ".com")
}
T anyUrl() {
return createAndValidateProperty(RegexPatterns.URL, "http://foo" + this.random.nextInt() + ".com")
}
T anyUuid(){
return createAndValidateProperty(RegexPatterns.UUID, UUID.randomUUID().toString())
}
private static String randomString(int length) {
char[] characterSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray()
Random random = new Random()
char[] result = new char[length]
for (int i = 0; i < result.length; i++) {
// picks a random index out of character set > random character
int randomCharIndex = random.nextInt(characterSet.length)
result[i] = characterSet[randomCharIndex]
}
return new String(result)
}
}

View File

@@ -19,7 +19,6 @@ package org.springframework.cloud.contract.spec.internal
import groovy.transform.CompileStatic
import java.util.regex.Pattern
/**
* Contains most common regular expression patterns
*
@@ -29,14 +28,14 @@ import java.util.regex.Pattern
class RegexPatterns {
// tag::regexps[]
private static final Pattern TRUE_OR_FALSE = Pattern.compile(/(true|false)/)
private static final Pattern ONLY_ALPHA_UNICODE = Pattern.compile(/[\p{L}]*/)
private static final Pattern NUMBER = Pattern.compile('-?\\d*(\\.\\d+)?')
private static final Pattern IP_ADDRESS = Pattern.compile('([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])');
private static final Pattern HOSTNAME_PATTERN = Pattern.compile('((http[s]?|ftp):\\/)\\/?([^:\\/\\s]+)(:[0-9]{1,5})?');
private static final Pattern EMAIL = Pattern.compile('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}');
private static final Pattern URL = Pattern.compile('((www\\.|(http|https|ftp|news|file)+\\:\\/\\/)[_.a-z0-9-]+\\.[a-z0-9\\/_:@=.+?,##%&~-]*[^.|\\\'|\\# |!|\\(|?|,| |>|<|;|\\)])')
private static final Pattern UUID = Pattern.compile('[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}')
protected static final Pattern TRUE_OR_FALSE = Pattern.compile(/(true|false)/)
protected static final Pattern ONLY_ALPHA_UNICODE = Pattern.compile(/[\p{L}]*/)
protected static final Pattern NUMBER = Pattern.compile('-?\\d*(\\.\\d+)?')
protected static final Pattern IP_ADDRESS = Pattern.compile('([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])');
protected static final Pattern HOSTNAME_PATTERN = Pattern.compile('((http[s]?|ftp):\\/)\\/?([^:\\/\\s]+)(:[0-9]{1,5})?');
protected static final Pattern EMAIL = Pattern.compile('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}');
protected static final Pattern URL = Pattern.compile('((www\\.|(http|https|ftp|news|file)+\\:\\/\\/)[_.a-z0-9-]+\\.[a-z0-9\\/_:@=.+?,##%&~-]*[^.|\\\'|\\# |!|\\(|?|,| |>|<|;|\\)])')
protected static final Pattern UUID = Pattern.compile('[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}')
String onlyAlphaUnicode() {
return ONLY_ALPHA_UNICODE.pattern()

View File

@@ -20,6 +20,7 @@ import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.transform.TypeChecked
import org.springframework.cloud.contract.spec.util.RegexpUtils
import repackaged.nl.flotsam.xeger.Xeger
import java.util.regex.Pattern
@@ -33,10 +34,13 @@ import java.util.regex.Pattern
@ToString(includePackage = false, includeNames = true)
class Request extends Common {
@Delegate ClientPatternValueDslProperty property = new ClientPatternValueDslProperty()
@Delegate HttpMethods httpMethods = new HttpMethods()
DslProperty method
Url url
UrlPath urlPath
Headers headers
RequestHeaders headers
Body body
Multipart multipart
@@ -56,6 +60,10 @@ class Request extends Common {
this.method = toDslProperty(method)
}
void method(HttpMethods.HttpMethod httpMethod) {
this.method = toDslProperty(httpMethod.toString())
}
void method(DslProperty method) {
this.method = toDslProperty(method)
}
@@ -100,8 +108,8 @@ class Request extends Common {
closure()
}
void headers(@DelegatesTo(Headers) Closure closure) {
this.headers = new Headers()
void headers(@DelegatesTo(RequestHeaders) Closure closure) {
this.headers = new RequestHeaders()
closure.delegate = headers
closure()
}
@@ -176,8 +184,10 @@ class Request extends Common {
DslProperty value(ClientDslProperty client) {
Object clientValue = client.clientValue
if (client.clientValue instanceof Pattern) {
if (client.clientValue instanceof Pattern && client.isSingleValue()) {
clientValue = new Xeger(((Pattern)client.clientValue).pattern()).generate()
} else if (client.clientValue instanceof Pattern && !client.isSingleValue()) {
clientValue = client.serverValue
}
return new DslProperty(client.clientValue, clientValue)
}
@@ -202,22 +212,44 @@ class Request extends Common {
return super.value(server, client)
}
}
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
class ServerRequest extends Request {
ServerRequest(Request request) {
super(request)
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
private class ServerRequest extends Request {
ServerRequest(Request request) {
super(request)
}
}
}
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
class ClientRequest extends Request {
ClientRequest(Request request) {
super(request)
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
private class ClientRequest extends Request {
ClientRequest(Request request) {
super(request)
}
}
}
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
private class RequestHeaders extends Headers {
@Override
DslProperty matching(String value) {
return $(c(regex("${RegexpUtils.escapeSpecialRegexWithSingleEscape(value)}.*")),
p(value))
}
}
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
private class ClientPatternValueDslProperty extends PatternValueDslProperty<ClientDslProperty> {
@Override
protected ClientDslProperty createProperty(Pattern pattern, Object generatedValue) {
return new ClientDslProperty(pattern, generatedValue)
}
}
}

View File

@@ -20,10 +20,10 @@ import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.transform.TypeChecked
import org.springframework.cloud.contract.spec.util.RegexpUtils
import repackaged.nl.flotsam.xeger.Xeger
import java.util.regex.Pattern
/**
* Represents the response side of the HTTP communication
*
@@ -34,9 +34,11 @@ import java.util.regex.Pattern
@ToString(includePackage = false, includeFields = true)
class Response extends Common {
@Delegate ServerPatternValueDslProperty property = new ServerPatternValueDslProperty()
DslProperty status
DslProperty delay
Headers headers
ResponseHeaders headers
Body body
boolean async
@@ -57,8 +59,8 @@ class Response extends Common {
this.status = toDslProperty(status)
}
void headers(@DelegatesTo(Headers) Closure closure) {
this.headers = new Headers()
void headers(@DelegatesTo(ResponseHeaders) Closure closure) {
this.headers = new ResponseHeaders()
closure.delegate = headers
closure()
}
@@ -89,8 +91,10 @@ class Response extends Common {
DslProperty value(ServerDslProperty server) {
Object value = server.clientValue
if (server.clientValue instanceof Pattern) {
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
}
return new DslProperty(value, server.serverValue)
}
@@ -114,22 +118,46 @@ class Response extends Common {
}
return super.value(server, client)
}
}
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
class ServerResponse extends Response {
ServerResponse(Response request) {
super(request)
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
private class ServerResponse extends Response {
ServerResponse(Response request) {
super(request)
}
}
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
private class ClientResponse extends Response {
ClientResponse(Response request) {
super(request)
}
}
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
private class ResponseHeaders extends Headers {
@Override
DslProperty matching(String value) {
return $(p(Pattern.compile("${RegexpUtils.escapeSpecialRegexWithSingleEscape(value)}.*")),
c(value))
}
}
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
private class ServerPatternValueDslProperty extends PatternValueDslProperty<ServerDslProperty> {
@Override
protected ServerDslProperty createProperty(Pattern pattern, Object generatedValue) {
return new ServerDslProperty(pattern, generatedValue)
}
}
}
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
class ClientResponse extends Response {
ClientResponse(Response request) {
super(request)
}
}

View File

@@ -33,4 +33,8 @@ class ServerDslProperty extends DslProperty {
ServerDslProperty(Object singleValue) {
super(singleValue)
}
ServerDslProperty(Object server, Object client) {
super(client, server)
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2013-2016 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.util
import groovy.transform.CompileStatic
import java.util.regex.Pattern
/**
* Useful utility methods to work with regular expresisons
*
* @since 1.0.2
*/
@CompileStatic
class RegexpUtils {
private final static Pattern SPECIAL_REGEX_CHARS = Pattern.compile('[{}()\\[\\].+*?^$\\\\|]')
static String escapeSpecialRegexChars(String str) {
return SPECIAL_REGEX_CHARS.matcher(str).replaceAll('\\\\\\\\$0')
}
static String escapeSpecialRegexWithSingleEscape(String str) {
return SPECIAL_REGEX_CHARS.matcher(str).replaceAll('\\\\$0')
}
}