From c6b0d1f6eb395de048973c67eb9963548e6c6ab3 Mon Sep 17 00:00:00 2001 From: Tim Ysewyn Date: Fri, 9 Aug 2019 23:35:00 +0200 Subject: [PATCH] Reworked DSL for headers --- .../binary/shouldWorkWithBinaryPayload.kts | 4 +- .../fraud/shouldMarkClientAsFraud.kts | 4 +- .../fraud/shouldMarkClientAsNotFraud.kts | 4 +- .../fraud/shouldReturnFraudStats.kts | 4 +- .../shouldReturnAFraudForTheName.kts | 2 +- .../shouldReturnNonFraudForTheName.kts | 2 +- ...WorkForMultipartAsBytesWithContentType.kts | 4 +- .../shouldWorkForMultipartWithContentType.kts | 4 +- .../cloud/contract/spec/internal/HeaderDsl.kt | 29 ++ .../contract/spec/internal/HeadersDsl.kt | 312 +++++++++++++++++- .../cloud/contract/spec/internal/InputDsl.kt | 8 +- .../spec/internal/OutputMessageDsl.kt | 4 +- .../contract/spec/internal/RequestDsl.kt | 39 ++- .../contract/spec/internal/ResponseDsl.kt | 23 +- .../cloud/contract/spec/ContractTests.kt | 134 ++++++-- .../contracts/multipleDefinitions.kts | 4 +- .../contracts/shouldWorkWithBinaryPayload.kts | 4 +- .../resources/contracts/singleDefinition.kts | 4 +- 18 files changed, 517 insertions(+), 72 deletions(-) create mode 100644 specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/HeaderDsl.kt diff --git a/samples/standalone/kotlin/http-server/src/test/resources/contracts/binary/shouldWorkWithBinaryPayload.kts b/samples/standalone/kotlin/http-server/src/test/resources/contracts/binary/shouldWorkWithBinaryPayload.kts index 392b629838..0ca206b333 100644 --- a/samples/standalone/kotlin/http-server/src/test/resources/contracts/binary/shouldWorkWithBinaryPayload.kts +++ b/samples/standalone/kotlin/http-server/src/test/resources/contracts/binary/shouldWorkWithBinaryPayload.kts @@ -24,7 +24,7 @@ contract { url = url("/1") method = PUT headers { - contentType(applicationOctetStream()) + contentType = APPLICATION_OCTET_STREAM } body = bodyFromFileAsBytes("request.pdf") } @@ -32,7 +32,7 @@ contract { status = OK body = bodyFromFileAsBytes("response.pdf") headers { - contentType(applicationOctetStream()) + contentType = APPLICATION_OCTET_STREAM } } } diff --git a/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.kts b/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.kts index 90888da8d4..8d6ef23d23 100644 --- a/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.kts +++ b/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.kts @@ -27,7 +27,7 @@ contract { "client.id" to value(regex("[0-9]{10}")), "loanAmount" to 99999) headers { // (5) - contentType("application/json") + contentType = "application/json" } } response { // (6) @@ -36,7 +36,7 @@ contract { "fraudCheckStatus" to "FRAUD", "rejection.reason" to "Amount too high") headers { // (9) - contentType("application/json") + contentType = "application/json" } } } diff --git a/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsNotFraud.kts b/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsNotFraud.kts index 9b6b87ea19..334c2d5e5a 100644 --- a/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsNotFraud.kts +++ b/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsNotFraud.kts @@ -27,7 +27,7 @@ contract { "client.id" to value(consumer(regex("[0-9]{10}")), producer("1234567890")), "loanAmount" to 123.123) headers { - contentType("application/json") + contentType = "application/json" } } response { @@ -37,7 +37,7 @@ contract { "rejection.reason" to value(consumer(null), producer(execute("assertThatRejectionReasonIsNull(\$it)"))) ) headers { - contentType("application/json") + contentType = "application/json" } } } diff --git a/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraud/shouldReturnFraudStats.kts b/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraud/shouldReturnFraudStats.kts index a083b3ae6d..44e675f808 100644 --- a/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraud/shouldReturnFraudStats.kts +++ b/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraud/shouldReturnFraudStats.kts @@ -33,7 +33,7 @@ listOf( "count" to value(regex("[2-9][0-9][0-9]")) ) headers { - contentType("application/json") + contentType = "application/json" } } }, @@ -48,7 +48,7 @@ listOf( "count" to 100 ) headers { - contentType("application/json") + contentType = "application/json" } } } diff --git a/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraudname/shouldReturnAFraudForTheName.kts b/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraudname/shouldReturnAFraudForTheName.kts index a0b2459cbd..f364c70e90 100644 --- a/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraudname/shouldReturnAFraudForTheName.kts +++ b/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraudname/shouldReturnAFraudForTheName.kts @@ -28,7 +28,7 @@ contract { url = url("/frauds/name") body = body("name" to "fraud") headers { - contentType("application/json") + contentType = "application/json" } } response { diff --git a/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraudname/shouldReturnNonFraudForTheName.kts b/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraudname/shouldReturnNonFraudForTheName.kts index 96412a4d3b..2216585934 100644 --- a/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraudname/shouldReturnNonFraudForTheName.kts +++ b/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraudname/shouldReturnNonFraudForTheName.kts @@ -26,7 +26,7 @@ contract { url = url("/frauds/name") body = body("name" to `$`(anyAlphaUnicode())) headers { - contentType("application/json") + contentType = "application/json" } } response { diff --git a/samples/standalone/kotlin/http-server/src/test/resources/contracts/multipart/shouldWorkForMultipartAsBytesWithContentType.kts b/samples/standalone/kotlin/http-server/src/test/resources/contracts/multipart/shouldWorkForMultipartAsBytesWithContentType.kts index ff4f0e1360..a1fc08b242 100644 --- a/samples/standalone/kotlin/http-server/src/test/resources/contracts/multipart/shouldWorkForMultipartAsBytesWithContentType.kts +++ b/samples/standalone/kotlin/http-server/src/test/resources/contracts/multipart/shouldWorkForMultipartAsBytesWithContentType.kts @@ -36,14 +36,14 @@ contract { value("application/json")) )) headers { - contentType("multipart/form-data") + contentType = "multipart/form-data" } } response { status = OK body = body("status" to "ok") headers { - contentType("application/json") + contentType = "application/json" } } } diff --git a/samples/standalone/kotlin/http-server/src/test/resources/contracts/multipart/shouldWorkForMultipartWithContentType.kts b/samples/standalone/kotlin/http-server/src/test/resources/contracts/multipart/shouldWorkForMultipartWithContentType.kts index e5623a0149..25621a7a8b 100644 --- a/samples/standalone/kotlin/http-server/src/test/resources/contracts/multipart/shouldWorkForMultipartWithContentType.kts +++ b/samples/standalone/kotlin/http-server/src/test/resources/contracts/multipart/shouldWorkForMultipartWithContentType.kts @@ -36,14 +36,14 @@ contract { value("application/json")) )) headers { - contentType("multipart/form-data") + contentType = "multipart/form-data" } } response { status = OK body = body("status" to "ok") headers { - contentType("application/json") + contentType = "application/json" } } } diff --git a/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/HeaderDsl.kt b/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/HeaderDsl.kt new file mode 100644 index 0000000000..618aae10f7 --- /dev/null +++ b/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/HeaderDsl.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.contract.spec.internal + +import kotlin.properties.Delegates + +/** + * @author Tim Ysewyn + */ +class HeaderDsl { + + var name: String by Delegates.notNull() + + var value: Any by Delegates.notNull() +} \ No newline at end of file diff --git a/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/HeadersDsl.kt b/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/HeadersDsl.kt index 910948d1b6..bd5401f4bf 100644 --- a/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/HeadersDsl.kt +++ b/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/HeadersDsl.kt @@ -19,14 +19,316 @@ package org.springframework.cloud.contract.spec.internal /** * @author Tim Ysewyn */ -@ContractDslMarker -class HeadersDsl { +open class HeadersDsl: CommonDsl() { - var accept: String? = null - var contentType: String? = null + internal val headers = LinkedHashMap() - fun header(headerKey: String, headerValue: Any) { + var accept: Any? + get() = headers[HttpHeaders.ACCEPT] + set(accept) = storeHeaderValue(HttpHeaders.ACCEPT, matching(accept)) + var acceptCharset: Any? + get() = headers[HttpHeaders.ACCEPT_CHARSET] + set(acceptCharset) = storeHeaderValue(HttpHeaders.ACCEPT_CHARSET, acceptCharset) + + var acceptEncoding: Any? + get() = headers[HttpHeaders.ACCEPT_ENCODING] + set(acceptEncoding) = storeHeaderValue(HttpHeaders.ACCEPT_ENCODING, acceptEncoding) + + var acceptLanguage: Any? + get() = headers[HttpHeaders.ACCEPT_LANGUAGE] + set(acceptLanguage) = storeHeaderValue(HttpHeaders.ACCEPT_LANGUAGE, acceptLanguage) + + var acceptRanges: Any? + get() = headers[HttpHeaders.ACCEPT_RANGES] + set(acceptRanges) = storeHeaderValue(HttpHeaders.ACCEPT_RANGES, acceptRanges) + + var accessControlAllowCredentials: Any? + get() = headers[HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS] + set(accessControlAllowCredentials) = storeHeaderValue(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, accessControlAllowCredentials) + + var accessControlAllowHeaders: Any? + get() = headers[HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS] + set(accessControlAllowHeaders) = storeHeaderValue(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, accessControlAllowHeaders) + + var accessControlAllowMethods: Any? + get() = headers[HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS] + set(accessControlAllowMethods) = storeHeaderValue(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, accessControlAllowMethods) + + var accessControlAllowOrigin: Any? + get() = headers[HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN] + set(accessControlAllowOrigin) = storeHeaderValue(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, accessControlAllowOrigin) + + var accessControlExposeHeaders: Any? + get() = headers[HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS] + set(accessControlExposeHeaders) = storeHeaderValue(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, accessControlExposeHeaders) + + var accessControlMaxAge: Any? + get() = headers[HttpHeaders.ACCESS_CONTROL_MAX_AGE] + set(accessControlMaxAge) = storeHeaderValue(HttpHeaders.ACCESS_CONTROL_MAX_AGE, accessControlMaxAge) + + var accessControlRequestHeaders: Any? + get() = headers[HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS] + set(accessControlRequestHeaders) = storeHeaderValue(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, accessControlRequestHeaders) + + var accessControlRequestMethod: Any? + get() = headers[HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD] + set(accessControlRequestMethod) = storeHeaderValue(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, accessControlRequestMethod) + + var age: Any? + get() = headers[HttpHeaders.AGE] + set(age) = storeHeaderValue(HttpHeaders.AGE, age) + + var allow: Any? + get() = headers[HttpHeaders.ALLOW] + set(allow) = storeHeaderValue(HttpHeaders.ALLOW, allow) + + var authorization: Any? + get() = headers[HttpHeaders.AUTHORIZATION] + set(authorization) = storeHeaderValue(HttpHeaders.AUTHORIZATION, authorization) + + var cacheControl: Any? + get() = headers[HttpHeaders.CACHE_CONTROL] + set(cacheControl) = storeHeaderValue(HttpHeaders.CACHE_CONTROL, cacheControl) + + var connection: Any? + get() = headers[HttpHeaders.CONNECTION] + set(connection) = storeHeaderValue(HttpHeaders.CONNECTION, connection) + + var contentEncoding: Any? + get() = headers[HttpHeaders.CONTENT_ENCODING] + set(contentEncoding) = storeHeaderValue(HttpHeaders.CONTENT_ENCODING, contentEncoding) + + var contentDisposition: Any? + get() = headers[HttpHeaders.CONTENT_DISPOSITION] + set(contentDisposition) = storeHeaderValue(HttpHeaders.CONTENT_DISPOSITION, contentDisposition) + + var contentLanguage: Any? + get() = headers[HttpHeaders.CONTENT_LANGUAGE] + set(contentLanguage) = storeHeaderValue(HttpHeaders.CONTENT_LANGUAGE, contentLanguage) + + var contentLength: Any? + get() = headers[HttpHeaders.CONTENT_LENGTH] + set(contentLength) = storeHeaderValue(HttpHeaders.CONTENT_LENGTH, contentLength) + + var contentLocation: Any? + get() = headers[HttpHeaders.CONTENT_LOCATION] + set(contentLocation) = storeHeaderValue(HttpHeaders.CONTENT_LOCATION, contentLocation) + + var contentRange: Any? + get() = headers[HttpHeaders.CONTENT_RANGE] + set(contentRange) = storeHeaderValue(HttpHeaders.CONTENT_RANGE, contentRange) + + var contentType: Any? + get() = headers[HttpHeaders.CONTENT_TYPE] + set(contentType) = storeHeaderValue(HttpHeaders.CONTENT_TYPE, matching(contentType)) + + var cookie: Any? + get() = headers[HttpHeaders.COOKIE] + set(cookie) = storeHeaderValue(HttpHeaders.COOKIE, cookie) + + var date: Any? + get() = headers[HttpHeaders.DATE] + set(date) = storeHeaderValue(HttpHeaders.DATE, date) + + var etag: Any? + get() = headers[HttpHeaders.ETAG] + set(etag) = storeHeaderValue(HttpHeaders.ETAG, etag) + + var expect: Any? + get() = headers[HttpHeaders.EXPECT] + set(expect) = storeHeaderValue(HttpHeaders.EXPECT, expect) + + var expires: Any? + get() = headers[HttpHeaders.EXPIRES] + set(expires) = storeHeaderValue(HttpHeaders.EXPIRES, expires) + + var from: Any? + get() = headers[HttpHeaders.FROM] + set(from) = storeHeaderValue(HttpHeaders.FROM, from) + + var host: Any? + get() = headers[HttpHeaders.HOST] + set(host) = storeHeaderValue(HttpHeaders.HOST, host) + + var ifMatch: Any? + get() = headers[HttpHeaders.IF_MATCH] + set(ifMatch) = storeHeaderValue(HttpHeaders.IF_MATCH, ifMatch) + + var ifModifiedSince: Any? + get() = headers[HttpHeaders.IF_MODIFIED_SINCE] + set(ifModifiedSince) = storeHeaderValue(HttpHeaders.IF_MODIFIED_SINCE, ifModifiedSince) + + var ifNoneMatch: Any? + get() = headers[HttpHeaders.IF_NONE_MATCH] + set(ifNoneMatch) = storeHeaderValue(HttpHeaders.IF_NONE_MATCH, ifNoneMatch) + + var ifRange: Any? + get() = headers[HttpHeaders.IF_RANGE] + set(ifRange) = storeHeaderValue(HttpHeaders.IF_RANGE, ifRange) + + var ifUnmodifiedSince: Any? + get() = headers[HttpHeaders.IF_UNMODIFIED_SINCE] + set(ifUnmodifiedSince) = storeHeaderValue(HttpHeaders.IF_UNMODIFIED_SINCE, ifUnmodifiedSince) + + var lastModified: Any? + get() = headers[HttpHeaders.LAST_MODIFIED] + set(lastModified) = storeHeaderValue(HttpHeaders.LAST_MODIFIED, lastModified) + + var link: Any? + get() = headers[HttpHeaders.LINK] + set(link) = storeHeaderValue(HttpHeaders.LINK, link) + + var location: Any? + get() = headers[HttpHeaders.LOCATION] + set(location) = storeHeaderValue(HttpHeaders.LOCATION, location) + + var max_forwards: Any? + get() = headers[HttpHeaders.MAX_FORWARDS] + set(max_forwards) = storeHeaderValue(HttpHeaders.MAX_FORWARDS, max_forwards) + + var origin: Any? + get() = headers[HttpHeaders.ORIGIN] + set(origin) = storeHeaderValue(HttpHeaders.ORIGIN, origin) + + var pragma: Any? + get() = headers[HttpHeaders.PRAGMA] + set(pragma) = storeHeaderValue(HttpHeaders.PRAGMA, pragma) + + var proxyAuthenticate: Any? + get() = headers[HttpHeaders.PROXY_AUTHENTICATE] + set(proxyAuthenticate) = storeHeaderValue(HttpHeaders.PROXY_AUTHENTICATE, proxyAuthenticate) + + var proxyAuthorization: Any? + get() = headers[HttpHeaders.PROXY_AUTHORIZATION] + set(proxyAuthorization) = storeHeaderValue(HttpHeaders.PROXY_AUTHORIZATION, proxyAuthorization) + + var range: Any? + get() = headers[HttpHeaders.RANGE] + set(range) = storeHeaderValue(HttpHeaders.RANGE, range) + + var referer: Any? + get() = headers[HttpHeaders.REFERER] + set(referer) = storeHeaderValue(HttpHeaders.REFERER, referer) + + var retryAfter: Any? + get() = headers[HttpHeaders.RETRY_AFTER] + set(retryAfter) = storeHeaderValue(HttpHeaders.RETRY_AFTER, retryAfter) + + var server: Any? + get() = headers[HttpHeaders.SERVER] + set(server) = storeHeaderValue(HttpHeaders.SERVER, server) + + var setCookie: Any? + get() = headers[HttpHeaders.SET_COOKIE] + set(setCookie) = storeHeaderValue(HttpHeaders.SET_COOKIE, setCookie) + + var setCookie2: Any? + get() = headers[HttpHeaders.SET_COOKIE_2] + set(setCookie2) = storeHeaderValue(HttpHeaders.SET_COOKIE_2, setCookie2) + + var te: Any? + get() = headers[HttpHeaders.TE] + set(te) = storeHeaderValue(HttpHeaders.TE, te) + + var trailer: Any? + get() = headers[HttpHeaders.TRAILER] + set(trailer) = storeHeaderValue(HttpHeaders.TRAILER, trailer) + + var transferEncoding: Any? + get() = headers[HttpHeaders.TRANSFER_ENCODING] + set(transferEncoding) = storeHeaderValue(HttpHeaders.TRANSFER_ENCODING, transferEncoding) + + var upgrade: Any? + get() = headers[HttpHeaders.UPGRADE] + set(upgrade) = storeHeaderValue(HttpHeaders.UPGRADE, upgrade) + + var user_agent: Any? + get() = headers[HttpHeaders.USER_AGENT] + set(user_agent) = storeHeaderValue(HttpHeaders.USER_AGENT, user_agent) + + var vary: Any? + get() = headers[HttpHeaders.VARY] + set(vary) = storeHeaderValue(HttpHeaders.VARY, vary) + + var via: Any? + get() = headers[HttpHeaders.VIA] + set(via) = storeHeaderValue(HttpHeaders.VIA, via) + + var warning: Any? + get() = headers[HttpHeaders.WARNING] + set(warning) = storeHeaderValue(HttpHeaders.WARNING, warning) + + var wwwAuthenticate: Any? + get() = headers[HttpHeaders.WWW_AUTHENTICATE] + set(wwwAuthenticate) = storeHeaderValue(HttpHeaders.WWW_AUTHENTICATE, wwwAuthenticate) + + var messagingContentType: Any? + get() = headers[MessagingHeaders.MESSAGING_CONTENT_TYPE] + set(messagingContentType) = storeHeaderValue(MessagingHeaders.MESSAGING_CONTENT_TYPE, matching(messagingContentType)) + + open fun matching(value: Any?): Any? = value + + private fun storeHeaderValue(header: String, value: Any?) { + value?.also { headers[header] = value } + } + + /* HELPER VARIABLES */ + + /* MEDIA TYPES */ + + val ALL_VALUE = MediaTypes.ALL_VALUE + + val APPLICATION_ATOM_XML = MediaTypes.APPLICATION_ATOM_XML + + val APPLICATION_FORM_URLENCODED = MediaTypes.APPLICATION_FORM_URLENCODED + + val APPLICATION_JSON = MediaTypes.APPLICATION_JSON + + val APPLICATION_JSON_UTF8 = MediaTypes.APPLICATION_JSON_UTF8 + + val APPLICATION_OCTET_STREAM = MediaTypes.APPLICATION_OCTET_STREAM + + val APPLICATION_PDF = MediaTypes.APPLICATION_PDF + + val APPLICATION_XHTML_XML = MediaTypes.APPLICATION_XHTML_XML + + val APPLICATION_XML = MediaTypes.APPLICATION_XML + + val IMAGE_GIF = MediaTypes.IMAGE_GIF + + val IMAGE_JPEG = MediaTypes.IMAGE_JPEG + + val IMAGE_PNG = MediaTypes.IMAGE_PNG + + val MULTIPART_FORM_DATA = MediaTypes.MULTIPART_FORM_DATA + + val TEXT_HTML = MediaTypes.TEXT_HTML + + val TEXT_MARKDOWN = MediaTypes.TEXT_MARKDOWN + + val TEXT_PLAIN = MediaTypes.TEXT_PLAIN + + val TEXT_XML = MediaTypes.TEXT_XML + + /** + * Adds and configures a configurer. + * + * @param configurer The lambda to configure the configurer. + */ + fun header(configurer: HeaderDsl.() -> Unit) { + try { + val header = HeaderDsl().apply(configurer) + this.headers[header.name] = header.value + } catch (ex: IllegalStateException) { + throw IllegalStateException("Header is missing its name or value") + } + } + + internal fun get(): Headers { + val headers = Headers() + this.headers.forEach { (key, value) -> headers.header(key, value) } + return headers } } diff --git a/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/InputDsl.kt b/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/InputDsl.kt index e6009e7d48..26ee8f3ac2 100644 --- a/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/InputDsl.kt +++ b/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/InputDsl.kt @@ -24,7 +24,7 @@ class InputDsl : CommonDsl(), RegexCreatingProperty { var messageFrom: DslProperty? = null var triggeredBy: ExecutionProperty? = null - var messageHeaders: Headers? = null + var headers: Headers? = null var messageBody: Input.BodyType? = null var assertThat: ExecutionProperty? = null var bodyMatchers: BodyMatchers? = null @@ -33,8 +33,8 @@ class InputDsl : CommonDsl(), RegexCreatingProperty { this.messageFrom = DslProperty(messageFrom) } - fun messageHeaders(headers: Headers.() -> Unit) { - this.messageHeaders = Headers().apply(headers) + fun headers(headers: HeadersDsl.() -> Unit) { + this.headers = HeadersDsl().apply(headers).get() } fun triggeredBy(triggeredBy: String) { @@ -107,7 +107,7 @@ class InputDsl : CommonDsl(), RegexCreatingProperty { val input = Input() messageFrom?.also { input.messageFrom = messageFrom!! } triggeredBy?.also { input.triggeredBy = triggeredBy!! } - messageHeaders?.also { input.messageHeaders = messageHeaders!! } + headers?.also { input.messageHeaders = headers!! } messageBody?.also { input.messageBody = messageBody!! } assertThat?.also { input.assertThat = assertThat!! } bodyMatchers?.also { input.bodyMatchers = bodyMatchers!! } diff --git a/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/OutputMessageDsl.kt b/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/OutputMessageDsl.kt index 72bef50ec6..3fa1230c65 100644 --- a/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/OutputMessageDsl.kt +++ b/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/OutputMessageDsl.kt @@ -32,8 +32,8 @@ class OutputMessageDsl : CommonDsl(), RegexCreatingProperty { this.sentTo = DslProperty(sentTo) } - fun headers(headers: Headers.() -> Unit) { - this.headers = Headers().apply(headers) + fun headers(headers: HeadersDsl.() -> Unit) { + this.headers = HeadersDsl().apply(headers).get() } fun body(body: Any) { diff --git a/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/RequestDsl.kt b/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/RequestDsl.kt index 5e5b2bd788..959ab88333 100644 --- a/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/RequestDsl.kt +++ b/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/RequestDsl.kt @@ -18,6 +18,7 @@ package org.springframework.cloud.contract.spec.internal import org.springframework.cloud.contract.spec.toDslProperties import org.springframework.cloud.contract.spec.toDslProperty +import org.springframework.cloud.contract.spec.util.RegexpUtils import java.util.regex.Pattern /** @@ -49,8 +50,8 @@ open class RequestDsl : CommonDsl(), RegexCreatingProperty { fun path(path: DslProperty) = UrlPath(path) - fun headers(headers: Headers.() -> Unit) { - this.headers = Request.RequestHeaders().apply(headers) + fun headers(headers: HeadersDsl.() -> Unit) { + this.headers = RequestHeadersDsl().apply(headers).get() } fun cookies(cookies: Cookies.() -> Unit) { @@ -85,21 +86,21 @@ open class RequestDsl : CommonDsl(), RegexCreatingProperty { /* HTTP METHODS */ - val GET = method(HttpMethods.HttpMethod.GET.name) + val GET = method(HttpMethods.GET) - val HEAD = method(HttpMethods.HttpMethod.HEAD.name) + val HEAD = method(HttpMethods.HEAD) - val POST = method(HttpMethods.HttpMethod.POST.name) + val POST = method(HttpMethods.POST) - val PUT = method(HttpMethods.HttpMethod.PUT.name) + val PUT = method(HttpMethods.PUT) - val PATCH = method(HttpMethods.HttpMethod.PATCH.name) + val PATCH = method(HttpMethods.PATCH) - val DELETE = method(HttpMethods.HttpMethod.DELETE.name) + val DELETE = method(HttpMethods.DELETE) - val OPTIONS = method(HttpMethods.HttpMethod.OPTIONS.name) + val OPTIONS = method(HttpMethods.OPTIONS) - val TRACE = method(HttpMethods.HttpMethod.TRACE.name) + val TRACE = method(HttpMethods.TRACE) /* HELPER FUNCTIONS */ @@ -247,4 +248,22 @@ open class RequestDsl : CommonDsl(), RegexCreatingProperty { bodyMatchers?.also { request.bodyMatchers = bodyMatchers!! } return request } + + private class RequestHeadersDsl: HeadersDsl() { + + private val common = Common() + + override fun matching(value: Any?): Any? { + return value?.also { + return when(value) { + is String -> this.common.value( + c(regex(RegexpUtils.escapeSpecialRegexWithSingleEscape(value) + ".*")), + p(value) + ) + else -> value + } + } + } + + } } \ No newline at end of file diff --git a/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/ResponseDsl.kt b/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/ResponseDsl.kt index 7018369da3..92e1af647c 100644 --- a/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/ResponseDsl.kt +++ b/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/ResponseDsl.kt @@ -18,6 +18,7 @@ package org.springframework.cloud.contract.spec.internal import org.springframework.cloud.contract.spec.toDslProperties import org.springframework.cloud.contract.spec.toDslProperty +import org.springframework.cloud.contract.spec.util.RegexpUtils import java.util.regex.Pattern /** @@ -40,8 +41,8 @@ class ResponseDsl : CommonDsl(), RegexCreatingProperty { fun fixedMilliseconds(delay: Long) = delay.toDslProperty() - fun headers(headers: Headers.() -> Unit) { - this.headers = Response.ResponseHeaders().apply(headers) + fun headers(headers: HeadersDsl.() -> Unit) { + this.headers = ResponseHeadersDsl().apply(headers).get() } fun cookies(cookies: Cookies.() -> Unit) { @@ -286,4 +287,22 @@ class ResponseDsl : CommonDsl(), RegexCreatingProperty { bodyMatchers?.also { response.bodyMatchers = bodyMatchers } return response } + + private class ResponseHeadersDsl: HeadersDsl() { + + private val common = Common() + + override fun matching(value: Any?): Any? { + return value?.also { + return when(value) { + is String -> return this.common.value( + c(value), + p(NotToEscapePattern(Pattern.compile(RegexpUtils.escapeSpecialRegexWithSingleEscape(value) + ".*"))) + ) + else -> value + } + } + } + + } } diff --git a/specs/spring-cloud-contract-spec-kotlin/src/test/kotlin/org/springframework/cloud/contract/spec/ContractTests.kt b/specs/spring-cloud-contract-spec-kotlin/src/test/kotlin/org/springframework/cloud/contract/spec/ContractTests.kt index 021384b165..30c27dde5b 100644 --- a/specs/spring-cloud-contract-spec-kotlin/src/test/kotlin/org/springframework/cloud/contract/spec/ContractTests.kt +++ b/specs/spring-cloud-contract-spec-kotlin/src/test/kotlin/org/springframework/cloud/contract/spec/ContractTests.kt @@ -21,8 +21,6 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract -import org.springframework.cloud.contract.spec.internal.HttpMethods -import org.springframework.cloud.contract.spec.internal.HttpStatus import org.springframework.cloud.contract.spec.internal.RegexProperty /** @@ -39,14 +37,20 @@ class ContractTests { url = url("/foo") method = PUT headers { - header("foo", "bar") + header { + name = "foo" + value = "bar" + } } body = body("foo" to "bar") } response { status = OK headers { - header("foo2", "bar") + header { + name = "foo2" + value = "bar" + } } body = body("foo2" to "bar") } @@ -141,15 +145,21 @@ class ContractTests { input { messageFrom("input") messageBody("foo" to "bar") - messageHeaders { - header("foo", "bar") + headers { + header { + name = "foo" + value = "bar" + } } } outputMessage { sentTo("output") body("foo2" to "bar") headers { - header("foo2", "bar") + header { + name = "foo2" + value = "bar" + } } } } @@ -187,15 +197,21 @@ class ContractTests { input { messageFrom("input") messageBody("foo" to anyNonBlankString()) - messageHeaders { - header("foo", anyNumber()) + headers { + header { + name = "foo" + value = anyNumber() + } } } outputMessage { sentTo("output") body("foo2" to anyNonEmptyString()) headers { - header("foo2", anyIpAddress()) + header { + name = "foo2" + value = anyIpAddress() + } } } } @@ -324,7 +340,7 @@ then: request { method = PUT headers { - contentType(applicationJson()) + contentType = APPLICATION_JSON } url = url("/$index") } @@ -336,7 +352,7 @@ then: request { method = PUT headers { - contentType(applicationJson()) + contentType = APPLICATION_JSON } url = url("/$index") } @@ -360,7 +376,7 @@ then: request { method = PUT headers { - contentType(applicationJson()) + contentType = APPLICATION_JSON } url = url("/$index") } @@ -373,7 +389,7 @@ then: request { method = PUT headers { - contentType(applicationJson()) + contentType = APPLICATION_JSON } url = url("/$index") } @@ -397,14 +413,20 @@ then: method = GET url = url("/path") headers { - header("Accept", value( + header { + name = "Accept" + value = value( consumer(regex("text/.*")), producer("text/plain") - )) - header("X-Custom-Header", value( + ) + } + header { + name = "X-Custom-Header" + value = value( consumer(regex("^.*2134.*$")), producer("121345") - )) + ) + } } } response { @@ -415,7 +437,10 @@ then: "created" to "2014-02-02 12:23:43" ) headers { - header("Content-Type", "text/plain") + header { + name = "Content-Type" + value = "text/plain" + } } } } @@ -424,14 +449,20 @@ then: method = GET url = url("/path") headers { - header("Accept", value( - consumer(regex("text/.*")), - producer("text/plain") - )) - header("X-Custom-Header", value( - consumer(regex("^.*2134.*$")), - producer("121345") - )) + header { + name = "Accept" + value = value( + consumer(regex("text/.*")), + producer("text/plain") + ) + } + header { + name = "X-Custom-Header" + value = value( + consumer(regex("^.*2134.*$")), + producer("121345") + ) + } } } response { @@ -442,7 +473,10 @@ then: "created" to "2014-02-02 12:23:43" ) headers { - header("Content-Type", "text/plain") + header { + name = "Content-Type" + value = "text/plain" + } } } } @@ -508,7 +542,7 @@ then: "created" to "2014-02-02 12:23:43" ) headers { - contentType(applicationJson()) + contentType = APPLICATION_JSON } bodyMatchers { jsonPath("$.created", byTimestamp()) @@ -625,4 +659,46 @@ then: assertThat(response.body.serverValue).isEqualTo(listOf("foo2", "bar2")) } } + + @Test + fun `should throw error when header is not configured correctly`() { + assertThrows { + contract { + request { + method = GET + url = url("/cookie") + headers { + header { + name = "foo" + } + } + } + response { + status = OK + } + } + }.also { + assertThat(it.message).contains("Header is missing its name or value") + } + + assertThrows { + contract { + request { + method = GET + url = url("/cookie") + headers { + header { + value = "bar" + } + } + } + response { + status = OK + } + } + }.also { + assertThat(it.message).contains("Header is missing its name or value") + } + } + } \ No newline at end of file diff --git a/specs/spring-cloud-contract-spec-kotlin/src/test/resources/contracts/multipleDefinitions.kts b/specs/spring-cloud-contract-spec-kotlin/src/test/resources/contracts/multipleDefinitions.kts index 5031a0aaa2..07b549b298 100644 --- a/specs/spring-cloud-contract-spec-kotlin/src/test/resources/contracts/multipleDefinitions.kts +++ b/specs/spring-cloud-contract-spec-kotlin/src/test/resources/contracts/multipleDefinitions.kts @@ -28,7 +28,7 @@ arrayOf( status = OK body = body("count" to 200) headers { - contentType("application/vnd.fraud.v1+json") + contentType = "application/vnd.fraud.v1+json" } } }, @@ -41,7 +41,7 @@ arrayOf( status = OK body = body("count" to 100) headers { - contentType("application/vnd.fraud.v1+json") + contentType = "application/vnd.fraud.v1+json" } } } diff --git a/specs/spring-cloud-contract-spec-kotlin/src/test/resources/contracts/shouldWorkWithBinaryPayload.kts b/specs/spring-cloud-contract-spec-kotlin/src/test/resources/contracts/shouldWorkWithBinaryPayload.kts index c31ffedbb0..a893effffe 100644 --- a/specs/spring-cloud-contract-spec-kotlin/src/test/resources/contracts/shouldWorkWithBinaryPayload.kts +++ b/specs/spring-cloud-contract-spec-kotlin/src/test/resources/contracts/shouldWorkWithBinaryPayload.kts @@ -24,7 +24,7 @@ contract { url = url("/1") method = PUT headers { - contentType(applicationOctetStream()) + contentType = APPLICATION_OCTET_STREAM } body = bodyFromFileAsBytes("contracts/request.pdf") } @@ -32,7 +32,7 @@ contract { status = OK body = bodyFromFileAsBytes("contracts/response.pdf") headers { - contentType(applicationOctetStream()) + contentType = APPLICATION_OCTET_STREAM } } } diff --git a/specs/spring-cloud-contract-spec-kotlin/src/test/resources/contracts/singleDefinition.kts b/specs/spring-cloud-contract-spec-kotlin/src/test/resources/contracts/singleDefinition.kts index 705d7494d0..d5d64d99af 100644 --- a/specs/spring-cloud-contract-spec-kotlin/src/test/resources/contracts/singleDefinition.kts +++ b/specs/spring-cloud-contract-spec-kotlin/src/test/resources/contracts/singleDefinition.kts @@ -26,7 +26,7 @@ contract { "loanAmount" to 123.123 ) headers { - contentType("application/vnd.fraud.v1+json") + contentType = "application/vnd.fraud.v1+json" } } @@ -37,7 +37,7 @@ contract { "rejectionReason" to listOf(value(consumer(null), producer("assertThatRejectionReasonIsNull(\$it)"))) ) headers { - contentType("application/vnd.fraud.v1+json") + contentType = "application/vnd.fraud.v1+json" } } } \ No newline at end of file