Rewrite spring-cloud-contract-pact module (#1520)

part of gh-1470
This commit is contained in:
Stessy Delcroix
2020-09-29 18:41:30 +02:00
committed by GitHub
parent ea4bb31077
commit a9c862f9f8
22 changed files with 1889 additions and 1680 deletions

View File

@@ -1,222 +0,0 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact
import java.util.regex.Pattern
import au.com.dius.pact.consumer.dsl.DslPart
import au.com.dius.pact.consumer.dsl.PactDslJsonArray
import au.com.dius.pact.consumer.dsl.PactDslJsonBody
import au.com.dius.pact.core.model.OptionalBody
import au.com.dius.pact.core.model.Request
import au.com.dius.pact.core.model.Response
import au.com.dius.pact.core.model.generators.Generator
import au.com.dius.pact.core.model.messaging.Message
import com.jayway.jsonpath.Configuration
import com.jayway.jsonpath.internal.EvaluationContext
import com.jayway.jsonpath.internal.Path
import com.jayway.jsonpath.internal.PathRef
import com.jayway.jsonpath.internal.path.PathCompiler
import groovy.json.JsonException
import groovy.json.JsonSlurper
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.springframework.cloud.contract.spec.internal.Body
import org.springframework.cloud.contract.spec.internal.ClientDslProperty
import org.springframework.cloud.contract.spec.internal.DslProperty
import org.springframework.cloud.contract.spec.internal.ServerDslProperty
import org.springframework.cloud.contract.verifier.util.ContentUtils
/**
* @author Tim Ysewyn
* @Since 2.0.0
*/
@CompileStatic
@PackageScope
class BodyConverter {
private static final JsonSlurper jsonSlurper = new JsonSlurper()
static DslPart toPactBody(Body body, Closure dslPropertyValueExtractor) {
return traverse(body, null, dslPropertyValueExtractor)
}
static DslPart toPactBody(DslProperty dslProperty, Closure dslPropertyValueExtractor) {
return traverse(dslProperty, null, dslPropertyValueExtractor)
}
private static DslPart traverse(Object value, DslPart parent, Closure dslPropertyValueExtractor) {
boolean isRoot = parent == null
Object v = value
if (v instanceof DslProperty) {
v = dslPropertyValueExtractor(v)
}
if (v instanceof GString) {
v = ContentUtils.extractValue(v, dslPropertyValueExtractor)
}
if (v instanceof String) {
v = v.trim()
if (v.startsWith("{") && v.endsWith("}")) {
try {
v = jsonSlurper.parseText(v as String)
}
catch (JsonException ex) { /*it wasn't a JSON string after all...*/
}
}
}
DslPart p = isRoot ? createRootDslPart(v) : parent
if (v instanceof Map) {
processMap(v as Map, p as PactDslJsonBody, dslPropertyValueExtractor)
}
else if (v instanceof Collection) {
processCollection(v as Collection, p as PactDslJsonArray, dslPropertyValueExtractor)
}
return p
}
private static DslPart createRootDslPart(Object value) {
return value instanceof Collection ? new PactDslJsonArray() : new PactDslJsonBody()
}
private static void processCollection(Collection values, PactDslJsonArray jsonArray, Closure dslPropertyValueExtractor) {
values.forEach({
Object v = it
if (v instanceof DslProperty) {
v = dslPropertyValueExtractor(v)
}
if (v instanceof GString) {
v = ContentUtils.extractValue(v, dslPropertyValueExtractor)
}
if (v == null) {
jsonArray.nullValue()
}
else if (v instanceof String) {
jsonArray.string(v)
}
else if (v instanceof Number) {
jsonArray.number(v)
}
else if (v instanceof Map) {
PactDslJsonBody current = jsonArray.object()
traverse(v, current, dslPropertyValueExtractor)
current.closeObject()
}
else if (v instanceof Collection) {
PactDslJsonArray current = jsonArray.array()
traverse(v, current, dslPropertyValueExtractor)
current.closeArray()
}
})
}
private static void processMap(Map<String, Object> values, PactDslJsonBody jsonObject, Closure dslPropertyValueExtractor) {
values.forEach({ String k, Object v ->
if (v instanceof DslProperty) {
v = dslPropertyValueExtractor(v)
}
if (v instanceof GString) {
v = ContentUtils.extractValue(v, dslPropertyValueExtractor)
}
if (v == null) {
jsonObject.nullValue(k)
}
else if (v instanceof String) {
jsonObject.stringType(k, v)
}
else if (v instanceof Number) {
jsonObject.numberValue(k, v)
}
else if (v instanceof Map) {
PactDslJsonBody current = jsonObject.object(k)
traverse(v, current, dslPropertyValueExtractor)
current.closeObject()
}
else if (v instanceof Collection) {
PactDslJsonArray current = jsonObject.array(k)
traverse(v, current, dslPropertyValueExtractor)
current.closeArray()
}
})
}
static def toSCCBody(Request request) {
def body = parseBody(request.body)
if (request.generators.isNotEmpty()
&& request.generators.categories.
containsKey(au.com.dius.pact.core.model.generators.Category.BODY)) {
applyGenerators(body, request.generators.categories.
get(au.com.dius.pact.core.model.generators.Category.BODY)) { Object currentValue, Pattern pattern, Object generatedValue ->
return new DslProperty<Object>(new ClientDslProperty(pattern, generatedValue), currentValue)
}
}
return body
}
static def toSCCBody(Response response) {
def body = parseBody(response.body)
if (response.generators.isNotEmpty()
&& response.generators.categories.
containsKey(au.com.dius.pact.core.model.generators.Category.BODY)) {
applyGenerators(body, response.generators.categories.
get(au.com.dius.pact.core.model.generators.Category.BODY)) { Object currentValue, Pattern pattern, Object generatedValue ->
return new DslProperty<Object>(currentValue, new ServerDslProperty(pattern, generatedValue))
}
}
return body
}
static def toSCCBody(Message message) {
def body = parseBody(message.contents)
if (message.generators.isNotEmpty()
&& message.generators.categories.
containsKey(au.com.dius.pact.core.model.generators.Category.BODY)) {
applyGenerators(body, message.generators.categories.
get(au.com.dius.pact.core.model.generators.Category.BODY)) { Object currentValue, Pattern pattern, Object generatedValue ->
return new DslProperty<Object>(new ClientDslProperty(pattern, generatedValue), currentValue)
}
}
return body
}
private static def parseBody(OptionalBody optionalBody) {
if (optionalBody.present) {
return new JsonSlurper().parse(optionalBody.value)
}
else {
return optionalBody.value
}
}
private static void applyGenerators(def body, Map<String, Generator> generatorsPerPath, Closure<DslProperty> dslPropertyProvider) {
Configuration configuration = Configuration.defaultConfiguration()
generatorsPerPath.each { String path, Generator generator ->
Path compiledPath = PathCompiler.compile(path)
EvaluationContext evaluationContext = compiledPath.
evaluate(body, body, configuration, true)
evaluationContext.updateOperations().each { PathRef pathRef ->
pathRef.convert({ Object currentValue, Configuration config ->
return ValueGeneratorConverter.
convert(generator) { Pattern pattern, Object generatedValue ->
return dslPropertyProvider(currentValue, pattern, generatedValue)
}
}, configuration)
}
}
}
}

View File

@@ -1,117 +0,0 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact
import au.com.dius.pact.core.model.matchingrules.Category
import au.com.dius.pact.core.model.matchingrules.DateMatcher
import au.com.dius.pact.core.model.matchingrules.EqualsMatcher
import au.com.dius.pact.core.model.matchingrules.MaxTypeMatcher
import au.com.dius.pact.core.model.matchingrules.MinMaxTypeMatcher
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher
import au.com.dius.pact.core.model.matchingrules.NullMatcher
import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher
import au.com.dius.pact.core.model.matchingrules.RegexMatcher
import au.com.dius.pact.core.model.matchingrules.TimeMatcher
import au.com.dius.pact.core.model.matchingrules.TimestampMatcher
import au.com.dius.pact.core.model.matchingrules.TypeMatcher
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.springframework.cloud.contract.spec.internal.BodyMatcher
import org.springframework.cloud.contract.spec.internal.BodyMatchers
import org.springframework.cloud.contract.spec.internal.MatchingType
import org.springframework.cloud.contract.spec.internal.RegexPatterns
/**
* @author Tim Ysewyn
* @Since 2.0.0
*/
@CompileStatic
@PackageScope
class MatchingRulesConverter {
static Category matchingRulesForBody(BodyMatchers bodyMatchers) {
return matchingRulesFor("body", bodyMatchers)
}
private static Category matchingRulesFor(String categoryName, BodyMatchers bodyMatchers) {
Category category = new Category(categoryName)
bodyMatchers.matchers().forEach({ BodyMatcher it ->
String key = getMatcherKey(it.path())
MatchingType matchingType = it.matchingType()
switch (matchingType) {
case MatchingType.NULL:
category.addRule(key, NullMatcher.INSTANCE)
break
case MatchingType.EQUALITY:
category.addRule(key, EqualsMatcher.INSTANCE)
break
case MatchingType.TYPE:
if (it.minTypeOccurrence() && it.maxTypeOccurrence()) {
category.
addRule(key, new MinMaxTypeMatcher(it.minTypeOccurrence(), it.
maxTypeOccurrence()))
}
else if (it.minTypeOccurrence()) {
category.addRule(key, new MinTypeMatcher(it.minTypeOccurrence()))
}
else if (it.maxTypeOccurrence()) {
category.addRule(key, new MaxTypeMatcher(it.maxTypeOccurrence()))
}
else {
category.addRule(key, TypeMatcher.INSTANCE)
}
break
case MatchingType.DATE:
category.addRule(key, new DateMatcher())
break
case MatchingType.TIME:
category.addRule(key, new TimeMatcher())
break
case MatchingType.TIMESTAMP:
category.addRule(key, new TimestampMatcher())
break
case MatchingType.REGEX:
String pattern = it.value().toString()
if (pattern == RegexPatterns.number().pattern()) {
category.
addRule(key, new NumberTypeMatcher(NumberTypeMatcher.NumberType.NUMBER))
}
else if (pattern == RegexPatterns.anInteger().pattern()) {
category.
addRule(key, new NumberTypeMatcher(NumberTypeMatcher.NumberType.INTEGER))
}
else if (pattern == RegexPatterns.aDouble().pattern()) {
category.
addRule(key, new NumberTypeMatcher(NumberTypeMatcher.NumberType.DECIMAL))
}
else {
category.addRule(key, new RegexMatcher(pattern))
}
break
default:
break
}
})
return category
}
private static String getMatcherKey(String path) {
return "${path.startsWith('$') ? path.substring(1) : path}"
}
}

View File

@@ -1,120 +0,0 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact
import au.com.dius.pact.consumer.MessagePactBuilder
import au.com.dius.pact.consumer.dsl.DslPart
import au.com.dius.pact.core.model.messaging.MessagePact
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.spec.internal.DslProperty
import org.springframework.cloud.contract.spec.internal.Header
import org.springframework.cloud.contract.spec.internal.Headers
import org.springframework.cloud.contract.spec.internal.Input
import org.springframework.cloud.contract.spec.internal.OutputMessage
import org.springframework.cloud.contract.verifier.util.ContentUtils
/**
* Creator of {@link MessagePact} instances
*
* @author Tim Ysewyn
* @since 2.0.0
*/
@CompileStatic
@PackageScope
class MessagePactCreator {
private static final Closure clientValueExtractor = { DslProperty property -> property.clientValue }
MessagePact createFromContract(List<Contract> contracts) {
if (contracts.empty) {
return null
}
Names names = NamingUtil.name(contracts.get(0))
MessagePactBuilder pactBuilder = MessagePactBuilder.consumer(names.consumer)
.hasPactWith(names.producer)
contracts.each { Contract contract ->
pactBuilder = pactBuilder
.given(getGiven(contract.input))
.expectsToReceive(getOutcome(contract))
if (contract.outputMessage) {
OutputMessage message = contract.outputMessage
if (message.body) {
DslPart pactResponseBody = BodyConverter.
toPactBody(message.body, clientValueExtractor)
if (message.bodyMatchers) {
pactResponseBody.setMatchers(MatchingRulesConverter.
matchingRulesForBody(message.bodyMatchers))
}
pactResponseBody.setGenerators(ValueGeneratorConverter.
extract(message, { DslProperty dslProperty -> dslProperty.serverValue }))
pactBuilder = pactBuilder.withContent(pactResponseBody)
}
if (message.headers) {
pactBuilder = pactBuilder.withMetadata(getMetadata(message.headers))
}
}
}
return pactBuilder.toPact()
}
private String getGiven(Input input) {
if (input.triggeredBy) {
return input.triggeredBy.executionCommand
}
else if (input.messageFrom) {
return "received message from " + clientValueExtractor.call(input.messageFrom)
}
else {
return ""
}
}
private String getOutcome(Contract contract) {
if (contract.outputMessage) {
OutputMessage message = contract.outputMessage
return "message sent to " + clientValueExtractor.call(message.sentTo)
}
else {
return "assert that " + contract.input.assertThat.executionCommand
}
}
private Map<String, String> getMetadata(Headers headers) {
return headers.entries.collectEntries({ Header header ->
return [(header.name): extractValue(header)]
})
}
private String extractValue(Object value) {
Object v = value
if (v instanceof DslProperty) {
v = clientValueExtractor.call(v)
}
if (v instanceof GString) {
v = ContentUtils.extractValue(v, clientValueExtractor)
}
if (v instanceof String) {
return v
}
else {
return v.toString()
}
}
}

View File

@@ -1,180 +0,0 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact
import au.com.dius.pact.core.model.matchingrules.Category
import au.com.dius.pact.core.model.matchingrules.DateMatcher
import au.com.dius.pact.core.model.matchingrules.MatchingRule
import au.com.dius.pact.core.model.matchingrules.MatchingRuleGroup
import au.com.dius.pact.core.model.matchingrules.MaxTypeMatcher
import au.com.dius.pact.core.model.matchingrules.MinMaxTypeMatcher
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher
import au.com.dius.pact.core.model.matchingrules.NullMatcher
import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher
import au.com.dius.pact.core.model.matchingrules.RegexMatcher
import au.com.dius.pact.core.model.matchingrules.RuleLogic
import au.com.dius.pact.core.model.matchingrules.TimeMatcher
import au.com.dius.pact.core.model.matchingrules.TimestampMatcher
import au.com.dius.pact.core.model.matchingrules.TypeMatcher
import au.com.dius.pact.core.model.messaging.Message
import au.com.dius.pact.core.model.messaging.MessagePact
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.spec.internal.RegexPatterns
import org.springframework.cloud.contract.verifier.util.JsonPaths
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter
/**
* Creator of {@link Contract} instances
*
* @author Tim Ysewyn
* @since 2.0.0
*/
@CompileStatic
@PackageScope
class MessagingSCContractCreator {
private static final String FULL_BODY = '$'
private static final String DESTINATION_KEY = "sentTo"
private static final List<String> NON_HEADER_META_DATA = [DESTINATION_KEY]
Collection<Contract> convertFrom(MessagePact pact) {
return pact.messages.collect({ Message message ->
Contract.make {
label("$message.description")
if (!message.providerStates.isEmpty()) {
input {
triggeredBy(getTriggeredBy(message))
}
}
outputMessage {
if (message.contents.present) {
body(BodyConverter.toSCCBody(message))
Category bodyRules = message.matchingRules.
rulesForCategory('body')
if (bodyRules && !bodyRules.matchingRules.isEmpty()) {
bodyMatchers {
bodyRules.matchingRules.
each { String key, MatchingRuleGroup ruleGroup ->
if (ruleGroup.ruleLogic != RuleLogic.AND) {
throw new UnsupportedOperationException("Currently only the AND combination rule logic is supported")
}
if (FULL_BODY == key) {
JsonPaths jsonPaths = JsonToJsonPathsConverter.
transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(message.contents.value)
jsonPaths.each {
jsonPath(it.keyBeforeChecking(), byType())
}
}
else {
ruleGroup.rules.each { MatchingRule rule ->
if (rule instanceof NullMatcher) {
jsonPath(key, byNull())
}
else if (rule instanceof RegexMatcher) {
jsonPath(key, byRegex(rule.regex))
}
else if (rule instanceof DateMatcher) {
jsonPath(key, byDate())
}
else if (rule instanceof TimeMatcher) {
jsonPath(key, byTime())
}
else if (rule instanceof TimestampMatcher) {
jsonPath(key, byTimestamp())
}
else if (rule instanceof MinTypeMatcher) {
jsonPath(key, byType() {
minOccurrence((rule as MinTypeMatcher).min)
})
}
else if (rule instanceof MinMaxTypeMatcher) {
jsonPath(key, byType() {
minOccurrence((rule as MinMaxTypeMatcher).min)
maxOccurrence((rule as MinMaxTypeMatcher).max)
})
}
else if (rule instanceof MaxTypeMatcher) {
jsonPath(key, byType() {
maxOccurrence((rule as MaxTypeMatcher).max)
})
}
else if (rule instanceof TypeMatcher) {
jsonPath(key, byType())
}
else if (rule instanceof NumberTypeMatcher) {
switch (rule.numberType) {
case NumberTypeMatcher.NumberType.NUMBER:
jsonPath(key,
byRegex(RegexPatterns.number()))
break
case NumberTypeMatcher.NumberType.INTEGER:
jsonPath(key,
byRegex(RegexPatterns.anInteger()))
break
case NumberTypeMatcher.NumberType.DECIMAL:
jsonPath(key,
byRegex(RegexPatterns.aDouble()))
break
default:
throw new RuntimeException("Unsupported number type!")
}
}
}
}
}
}
}
}
if (!message.metaData.isEmpty()) {
headers {
message.metaData.each { String k, Object val ->
String v = val.toString()
if (k.equalsIgnoreCase("contentType")) {
messagingContentType(v)
}
else if (!NON_HEADER_META_DATA.contains(k)) {
header(k, v)
}
}
}
}
String dest = findDestination(message)
if (dest) {
sentTo(dest)
}
}
}
})
}
private String getTriggeredBy(Message message) {
return message.providerStates.first().name
.replace(':', ' ')
.replace(' ', '_')
.replace('(', '')
.replace(')', '')
.uncapitalize() + "()"
}
private String findDestination(Message message) {
return message.metaData.get(DESTINATION_KEY) ?: ""
}
}

View File

@@ -1,62 +0,0 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.springframework.cloud.contract.spec.Contract
/**
* @author Marcin Grzejszczak
* @since
*/
@PackageScope
@CompileStatic
final class NamingUtil {
// consumer___producer___testname
private static final String SEPARATOR = "___"
protected static Names name(Contract contract) {
String contractName = contract.name
if (!contractName || !contractName.contains(SEPARATOR)) {
return new Names(["Consumer", "Provider", ""] as String[])
}
return new Names(contractName.split(SEPARATOR))
}
}
@PackageScope
@CompileStatic
class Names {
final String consumer
final String producer
final String test
Names(String[] strings) {
this.consumer = strings[0]
this.producer = strings[1]
this.test = strings.length >= 2 ? strings[2] : ""
}
@Override
String toString() {
return this.consumer + "_" + this.producer
}
}

View File

@@ -1,101 +0,0 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact
import au.com.dius.pact.core.model.DefaultPactReader
import au.com.dius.pact.core.model.Pact
import au.com.dius.pact.core.model.PactSpecVersion
import au.com.dius.pact.core.model.RequestResponsePact
import au.com.dius.pact.core.model.messaging.MessagePact
import groovy.json.JsonOutput
import groovy.transform.CompileStatic
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.spec.ContractConverter
/**
* Converter of JSON PACT file
*
* @author Marcin Grzejszczak
* @author Tim Ysewyn
* @since 1.1.0
*/
@CompileStatic
class PactContractConverter implements ContractConverter<Collection<Pact>> {
private final RequestResponseSCContractCreator requestResponseSCContractCreator = new RequestResponseSCContractCreator()
private final MessagingSCContractCreator messagingSCContractCreator = new MessagingSCContractCreator()
private final RequestResponsePactCreator requestResponsePactCreator = new RequestResponsePactCreator()
private final MessagePactCreator messagePactCreator = new MessagePactCreator()
@Override
boolean isAccepted(File file) {
try {
DefaultPactReader.INSTANCE.loadPact(file)
return true
}
catch (Exception e) {
return false
}
}
@Override
Collection<Contract> convertFrom(File file) {
Pact pact = DefaultPactReader.INSTANCE.loadPact(file)
if (pact instanceof RequestResponsePact) {
return requestResponseSCContractCreator.
convertFrom(pact as RequestResponsePact)
}
if (pact instanceof MessagePact) {
return messagingSCContractCreator.convertFrom(pact as MessagePact)
}
throw new UnsupportedOperationException("We currently don't support pact contracts of type" + pact.class.simpleName)
}
@Override
Collection<Pact> convertTo(Collection<Contract> contracts) {
List<Pact> pactContracts = new ArrayList<>()
Map<String, List<Contract>> groupedContracts = contracts.
groupBy { NamingUtil.name(it).toString() }
for (List<Contract> list : groupedContracts.values()) {
List<Contract> httpOnly = list.findAll { it.request }
List<Contract> messagingOnly = list.findAll { it.input }
RequestResponsePact responsePact = requestResponsePactCreator.
createFromContract(httpOnly)
if (responsePact) {
pactContracts.add(responsePact)
}
MessagePact messagePact = messagePactCreator.createFromContract(messagingOnly)
if (messagePact) {
pactContracts.add(messagePact)
}
}
return pactContracts
}
@Override
Map<String, byte[]> store(Collection<Pact> contracts) {
return contracts.collectEntries {
return [(name(it)): JsonOutput.
prettyPrint(JsonOutput.toJson(it.toMap(PactSpecVersion.V3))).bytes]
}
}
protected String name(Pact contract) {
return contract.consumer.name + "_" + contract.provider.name + "_" + String.
valueOf(Math.abs(contract.hashCode())) + ".json"
}
}

View File

@@ -1,295 +0,0 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact
import java.util.regex.Pattern
import au.com.dius.pact.consumer.ConsumerPactBuilder
import au.com.dius.pact.consumer.dsl.DslPart
import au.com.dius.pact.consumer.dsl.PactDslRequestWithPath
import au.com.dius.pact.consumer.dsl.PactDslResponse
import au.com.dius.pact.consumer.dsl.PactDslWithProvider
import au.com.dius.pact.core.model.RequestResponsePact
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.spec.internal.Body
import org.springframework.cloud.contract.spec.internal.Cookies
import org.springframework.cloud.contract.spec.internal.DslProperty
import org.springframework.cloud.contract.spec.internal.ExecutionProperty
import org.springframework.cloud.contract.spec.internal.Header
import org.springframework.cloud.contract.spec.internal.QueryParameters
import org.springframework.cloud.contract.spec.internal.RegexProperty
import org.springframework.cloud.contract.spec.internal.Request
import org.springframework.cloud.contract.spec.internal.Response
/**
* Creator of {@link RequestResponsePact} instances
*
* @author Tim Ysewyn
* @since 2.0.0
*/
@CompileStatic
@PackageScope
class RequestResponsePactCreator {
RequestResponsePact createFromContract(List<Contract> contracts) {
if (contracts.empty) {
return null
}
Names names = NamingUtil.name(contracts.get(0))
PactDslWithProvider pactDslWithProvider = ConsumerPactBuilder
.consumer(names.consumer).hasPactWith(names.producer)
PactDslResponse pactDslResponse = null
contracts.each { Contract contract ->
assertNoExecutionProperty(contract)
PactDslRequestWithPath pactDslRequest = pactDslResponse ?
createPactDslRequestWithPath(contract, pactDslResponse) :
createPactDslRequestWithPath(contract, pactDslWithProvider)
pactDslResponse = createPactDslResponse(contract, pactDslRequest)
}
return pactDslResponse.toPact()
}
private void assertNoExecutionProperty(Contract contract) {
assertNoExecutionPropertyInBody(contract.request.body,
{ DslProperty dslProperty -> dslProperty.serverValue })
assertNoExecutionPropertyInBody(contract.response.body,
{ DslProperty dslProperty -> dslProperty.clientValue })
}
private void assertNoExecutionPropertyInBody(Body body, Closure dslPropertyValueExtractor) {
traverseValues(body, dslPropertyValueExtractor, {
if (it instanceof ExecutionProperty) {
throw new UnsupportedOperationException("We can't convert a contract that has execution property")
}
})
}
private void traverseValues(def value, Closure dslPropertyValueExtractor, Closure closure) {
if (value instanceof DslProperty) {
traverseValues(
dslPropertyValueExtractor(value), dslPropertyValueExtractor, closure)
}
else if (value instanceof Map) {
value.values().
forEach({ traverseValues(it, dslPropertyValueExtractor, closure) })
}
else if (value instanceof Collection) {
value.forEach({ traverseValues(it, dslPropertyValueExtractor, closure) })
}
else {
closure(value)
}
}
private PactDslRequestWithPath createPactDslRequestWithPath(Contract contract, PactDslResponse pactDslResponse) {
Request request = contract.request
PactDslRequestWithPath pactDslRequest = pactDslResponse
.uponReceiving(contract.description ?: "")
.path(url(request))
.method(request.method.serverValue.toString())
String query = query(request)
if (query) {
pactDslRequest = pactDslRequest.encodedQuery(query)
}
if (request.headers) {
request.headers.entries.each { Header header ->
pactDslRequest = processHeader(pactDslRequest, header)
}
}
if (request.cookies) {
pactDslRequest = processCookies(pactDslRequest, request.cookies)
}
if (request.body) {
DslPart pactRequestBody = BodyConverter.
toPactBody(request.body, { DslProperty property -> property.serverValue })
if (request.bodyMatchers) {
pactRequestBody.setMatchers(MatchingRulesConverter.
matchingRulesForBody(request.bodyMatchers))
}
pactRequestBody.setGenerators(ValueGeneratorConverter.
extract(request.body, { DslProperty dslProperty -> dslProperty.clientValue }))
pactDslRequest = pactDslRequest.body(pactRequestBody)
}
return pactDslRequest
}
private PactDslRequestWithPath createPactDslRequestWithPath(Contract contract, PactDslWithProvider pactDslWithProvider) {
Request request = contract.request
PactDslRequestWithPath pactDslRequest = pactDslWithProvider
.uponReceiving(contract.description ?: "")
.path(url(request))
.method(request.method.serverValue.toString())
String query = query(request)
if (query) {
pactDslRequest = pactDslRequest.encodedQuery(query)
}
if (request.headers) {
request.headers.entries.each { Header header ->
pactDslRequest = processHeader(pactDslRequest, header)
}
}
if (request.body) {
DslPart pactRequestBody = BodyConverter.
toPactBody(request.body, { DslProperty property -> property.serverValue })
if (request.bodyMatchers) {
pactRequestBody.setMatchers(MatchingRulesConverter.
matchingRulesForBody(request.bodyMatchers))
}
pactRequestBody.setGenerators(ValueGeneratorConverter.
extract(request.body, { DslProperty dslProperty -> dslProperty.clientValue }))
pactDslRequest = pactDslRequest.body(pactRequestBody)
}
return pactDslRequest
}
private PactDslResponse createPactDslResponse(Contract contract, PactDslRequestWithPath pactDslRequest) {
Response response = contract.response
PactDslResponse pactDslResponse = pactDslRequest.willRespondWith()
.status(response.status.clientValue as Integer)
if (response.headers) {
response.headers.entries.each { Header header ->
pactDslResponse = processHeader(pactDslResponse, header)
}
}
if (response.cookies) {
pactDslResponse = processCookies(pactDslResponse, response.cookies)
}
if (response.body) {
DslPart pactResponseBody = BodyConverter.
toPactBody(response.body, { DslProperty property -> property.clientValue })
if (response.bodyMatchers) {
pactResponseBody.setMatchers(MatchingRulesConverter.
matchingRulesForBody(response.bodyMatchers))
}
pactResponseBody.setGenerators(ValueGeneratorConverter.
extract(response.body, { DslProperty dslProperty -> dslProperty.serverValue }))
pactDslResponse = pactDslResponse.body(pactResponseBody)
}
return pactDslResponse
}
private String url(Request request) {
if (request.urlPath) {
return request.urlPath.serverValue.toString()
}
else if (request.url) {
return request.url.serverValue.toString()
}
throw new IllegalStateException("No url provided")
}
private String query(Request request) {
String query = null
QueryParameters params = queryParams(request)
if (params) {
query = ""
params.parameters.eachWithIndex { param, index ->
query += param.name + '=' + param.serverValue
if (index + 1 < params.parameters.size()) {
query += '&'
}
}
}
return query
}
private QueryParameters queryParams(Request request) {
if (request.urlPath) {
return request.urlPath.queryParameters
}
else if (request.url) {
return request.url.queryParameters
}
throw new IllegalStateException("No url provided")
}
private PactDslRequestWithPath processHeader(PactDslRequestWithPath pactDslRequest, Header header) {
if (header.isSingleValue()) {
String value = getDslPropertyServerValue(header).toString()
return pactDslRequest.headers(header.name, value)
}
else {
String regex = getDslPropertyClientValue(header).toString()
String example = getDslPropertyServerValue(header).toString()
return pactDslRequest.matchHeader(header.name, regex, example)
}
}
private PactDslRequestWithPath processCookies(PactDslRequestWithPath pactDslRequest, Cookies cookies) {
Map<String, Object> stubSideCookies = cookies.asStubSideMap()
Collection<RegexProperty> regexProperties = stubSideCookies.values().findAll { it instanceof Pattern || it instanceof RegexProperty }.collect { new RegexProperty(it)}
if (!regexProperties.empty) {
String regex = regexProperties.collect { it.pattern() }.join("|")
return pactDslRequest.matchHeader("Cookie", regex, testSideCookieExample(cookies))
}
else {
return pactDslRequest.headers("Cookie", testSideCookieExample(cookies))
}
}
private String testSideCookieExample(Cookies cookies) {
return cookies.asTestSideMap().collect { it.key + "=" + it.value.toString() }.join(";")
}
private PactDslResponse processHeader(PactDslResponse pactDslResponse, Header header) {
if (header.isSingleValue()) {
String value = getDslPropertyClientValue(header).toString()
return pactDslResponse.headers([(header.name): value])
}
else {
String regex = getDslPropertyServerValue(header).toString()
String example = getDslPropertyClientValue(header).toString()
return pactDslResponse.matchHeader(header.name, regex, example)
}
}
private PactDslResponse processCookies(PactDslResponse pactDslResponse, Cookies cookies) {
Map<String, Object> testSideCookies = cookies.asTestSideMap()
Collection<RegexProperty> regexProperties = testSideCookies.values().findAll { it instanceof Pattern || it instanceof RegexProperty }.collect { new RegexProperty(it)}
if (!regexProperties.empty) {
String regex = regexProperties.collect { it.pattern() }.join("|")
return pactDslResponse.matchHeader("Cookie", regex, stubSideCookieExample(cookies))
}
else {
return pactDslResponse.headers(["Cookie": stubSideCookieExample(cookies)])
}
}
private String stubSideCookieExample(Cookies cookies) {
return cookies.asStubSideMap().collect { it.key + "=" + it.value.toString() }.join(";")
}
private Object getDslPropertyClientValue(Object o) {
Object value = o
if (value instanceof DslProperty) {
value = getDslPropertyClientValue(value.getClientValue())
}
return value
}
private Object getDslPropertyServerValue(Object o) {
Object value = o
if (value instanceof DslProperty) {
value = getDslPropertyServerValue(value.getServerValue())
}
return value
}
}

View File

@@ -1,391 +0,0 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact
import java.util.regex.Pattern
import au.com.dius.pact.core.model.OptionalBody
import au.com.dius.pact.core.model.ProviderState
import au.com.dius.pact.core.model.Request
import au.com.dius.pact.core.model.RequestResponseInteraction
import au.com.dius.pact.core.model.RequestResponsePact
import au.com.dius.pact.core.model.Response
import au.com.dius.pact.core.model.matchingrules.Category
import au.com.dius.pact.core.model.matchingrules.DateMatcher
import au.com.dius.pact.core.model.matchingrules.MatchingRule
import au.com.dius.pact.core.model.matchingrules.MatchingRuleGroup
import au.com.dius.pact.core.model.matchingrules.MaxTypeMatcher
import au.com.dius.pact.core.model.matchingrules.MinMaxTypeMatcher
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher
import au.com.dius.pact.core.model.matchingrules.NullMatcher
import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher
import au.com.dius.pact.core.model.matchingrules.RegexMatcher
import au.com.dius.pact.core.model.matchingrules.RuleLogic
import au.com.dius.pact.core.model.matchingrules.TimeMatcher
import au.com.dius.pact.core.model.matchingrules.TimestampMatcher
import au.com.dius.pact.core.model.matchingrules.TypeMatcher
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.spec.internal.RegexPatterns
import org.springframework.cloud.contract.verifier.util.JsonPaths
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter
/**
* Creator of {@link Contract} instances
*
* @author Tim Ysewyn
* @since 2.0.0
*/
@CompileStatic
@PackageScope
class RequestResponseSCContractCreator {
private static final String FULL_BODY = '$'
Collection<Contract> convertFrom(RequestResponsePact pact) {
return pact.interactions.collect { RequestResponseInteraction interaction ->
Contract.make {
description(buildDescription(interaction))
request {
Request request = interaction.request
method(request.method)
if (request.query) {
url(request.path) {
queryParameters {
request.query.each { String key, List<String> value ->
value.each { String singleValue ->
parameter(key, singleValue)
}
}
}
}
}
else {
url(request.path)
}
if (request.headers) {
Category headerRules = request.matchingRules.
rulesForCategory('header')
headers {
request.headers.each { String k, List<String> v ->
if (k.equalsIgnoreCase("Cookie")) {
return
}
if (headerRules.matchingRules.containsKey(k)) {
MatchingRuleGroup ruleGroup = headerRules.matchingRules.
get(k)
if (ruleGroup.rules.size() > 1) {
throw new UnsupportedOperationException("Currently only 1 rule at a time for a header is supported")
}
MatchingRule rule = ruleGroup.rules[0]
if (rule instanceof RegexMatcher) {
v.each({
header(k, $(c(regex(((RegexMatcher) rule).getRegex())),
p(it)))
})
}
else {
throw new UnsupportedOperationException("Currently only the header matcher of type regex is supported")
}
}
else {
v.each({
header(k, it)
})
}
}
}
}
if (request.headers.containsKey("Cookie")) {
Category headerRules = request.matchingRules.
rulesForCategory('header')
String[] splitHeader = request.headers.get("Cookie").first().split(";")
Map<String, String> foundCookies = splitHeader.collectEntries {
String[] keyValue = it.split("=")
return [(keyValue[0]): keyValue[1]]
}
cookies {
foundCookies.each { k, v ->
if (headerRules.matchingRules.containsKey("Cookie")) {
MatchingRuleGroup ruleGroup = headerRules.matchingRules.
get("Cookie")
if (ruleGroup.rules.size() > 1) {
throw new UnsupportedOperationException("Currently only 1 rule at a time for a header is supported")
}
MatchingRule rule = ruleGroup.rules[0]
if (rule instanceof RegexMatcher) {
v.each({
cookie(k, $(c(regex(((RegexMatcher) rule).getRegex())),
p(it)))
})
}
else {
throw new UnsupportedOperationException("Currently only the header matcher of type regex is supported")
}
}
else {
cookie(k, v)
}
}
}
}
if (request.body.state == OptionalBody.State.PRESENT) {
def parsedBody = BodyConverter.toSCCBody(request)
if (parsedBody instanceof Map) {
body(parsedBody as Map)
}
else if (parsedBody instanceof List) {
body(parsedBody as List)
}
else {
body(parsedBody.toString())
}
}
Category bodyRules = request.matchingRules.rulesForCategory('body')
if (bodyRules && !bodyRules.matchingRules.isEmpty()) {
bodyMatchers {
bodyRules.matchingRules.
each { String key, MatchingRuleGroup ruleGroup ->
if (ruleGroup.ruleLogic != RuleLogic.AND) {
throw new UnsupportedOperationException("Currently only the AND combination rule logic is supported")
}
ruleGroup.rules.each { MatchingRule rule ->
if (rule instanceof RegexMatcher) {
jsonPath(key, byRegex(rule.regex))
}
else if (rule instanceof DateMatcher) {
jsonPath(key, byDate())
}
else if (rule instanceof TimeMatcher) {
jsonPath(key, byTime())
}
else if (rule instanceof TimestampMatcher) {
jsonPath(key, byTimestamp())
}
else if (rule instanceof NumberTypeMatcher) {
switch (rule.numberType) {
case NumberTypeMatcher.NumberType.NUMBER:
jsonPath(key,
byRegex(RegexPatterns.number()))
break
case NumberTypeMatcher.NumberType.INTEGER:
jsonPath(key, byRegex(RegexPatterns.
anInteger()))
break
case NumberTypeMatcher.NumberType.DECIMAL:
jsonPath(key,
byRegex(RegexPatterns.aDouble()))
break
default:
throw new RuntimeException("Unsupported number type!")
}
}
}
}
}
}
}
response {
Response response = interaction.response
status(response.status)
if (response.body.present) {
def parsedBody = BodyConverter.toSCCBody(response)
if (parsedBody instanceof Map) {
body(parsedBody as Map)
}
else if (parsedBody instanceof List) {
body(parsedBody as List)
}
else {
body(parsedBody.toString())
}
}
Category bodyRules = response.matchingRules.rulesForCategory('body')
if (bodyRules && !bodyRules.matchingRules.isEmpty()) {
bodyMatchers {
bodyRules.matchingRules.
each { String key, MatchingRuleGroup ruleGroup ->
if (ruleGroup.ruleLogic != RuleLogic.AND) {
throw new UnsupportedOperationException("Currently only the AND combination rule logic is supported")
}
if (FULL_BODY == key) {
JsonPaths jsonPaths = JsonToJsonPathsConverter.
transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(response.body.value instanceof byte[] ? new String(response.body.value) : response.body.value)
jsonPaths.each {
jsonPath(it.keyBeforeChecking(), byType())
}
}
else {
ruleGroup.rules.each { MatchingRule rule ->
if (rule instanceof NullMatcher) {
jsonPath(key, byNull())
}
else if (rule instanceof RegexMatcher) {
jsonPath(key, byRegex(rule.regex))
}
else if (rule instanceof DateMatcher) {
jsonPath(key, byDate())
}
else if (rule instanceof TimeMatcher) {
jsonPath(key, byTime())
}
else if (rule instanceof TimestampMatcher) {
jsonPath(key, byTimestamp())
}
else if (rule instanceof MinTypeMatcher) {
jsonPath(key, byType() {
minOccurrence((rule as MinTypeMatcher).min)
})
}
else if (rule instanceof MinMaxTypeMatcher) {
jsonPath(key, byType() {
minOccurrence((rule as MinMaxTypeMatcher).min)
maxOccurrence((rule as MinMaxTypeMatcher).max)
})
}
else if (rule instanceof MaxTypeMatcher) {
jsonPath(key, byType() {
maxOccurrence((rule as MaxTypeMatcher).max)
})
}
else if (rule instanceof TypeMatcher) {
jsonPath(key, byType())
}
else if (rule instanceof NumberTypeMatcher) {
switch (rule.numberType) {
case NumberTypeMatcher.NumberType.NUMBER:
jsonPath(key,
byRegex(RegexPatterns.
number()))
break
case NumberTypeMatcher.NumberType.INTEGER:
jsonPath(key,
byRegex(RegexPatterns.
anInteger()))
break
case NumberTypeMatcher.NumberType.DECIMAL:
jsonPath(key,
byRegex(RegexPatterns.
aDouble()))
break
default:
throw new UnsupportedOperationException("Unsupported number type!")
}
}
}
}
}
}
}
if (response.headers) {
Category headerRules = response.matchingRules.
rulesForCategory('header')
headers {
response.headers.forEach({ String k, List<String> v ->
if (k.equalsIgnoreCase("Cookie")) {
return
}
if (headerRules.matchingRules.containsKey(k)) {
MatchingRuleGroup ruleGroup = headerRules.matchingRules.
get(k)
if (ruleGroup.rules.size() > 1) {
throw new UnsupportedOperationException("Currently only 1 rule at a time for a header is supported")
}
MatchingRule rule = ruleGroup.rules[0]
if (rule instanceof RegexMatcher) {
v.each({
header(k, $(p(regex(Pattern.compile(
((RegexMatcher) rule).getRegex()))),
c(it)))
})
}
else {
throw new UnsupportedOperationException("Currently only the header matcher of type regex is supported")
}
}
else {
v.each({
header(k, it)
})
}
})
}
}
if (response.headers.containsKey("Cookie")) {
Category headerRules = response.matchingRules.
rulesForCategory('header')
String[] splitHeader = response.headers.get("Cookie").first().split(";")
Map<String, String> foundCookies = splitHeader.collectEntries {
String[] keyValue = it.split("=")
return [(keyValue[0]): keyValue[1]]
}
cookies {
foundCookies.each { k, v ->
if (headerRules.matchingRules.containsKey("Cookie")) {
MatchingRuleGroup ruleGroup = headerRules.matchingRules.
get("Cookie")
if (ruleGroup.rules.size() > 1) {
throw new UnsupportedOperationException("Currently only 1 rule at a time for a header is supported")
}
MatchingRule rule = ruleGroup.rules[0]
if (rule instanceof RegexMatcher) {
v.each({
cookie(k, $(p(regex(Pattern.compile(
((RegexMatcher) rule).getRegex()))),
c(it)))
})
}
else {
throw new UnsupportedOperationException("Currently only the header matcher of type regex is supported")
}
}
else {
cookie(k, v)
}
}
}
}
}
}
}
}
private String buildDescription(RequestResponseInteraction interaction) {
String description = "$interaction.description"
interaction.providerStates.forEach({ ProviderState it ->
description += " $it.name"
if (!it.params.isEmpty()) {
Map<String, Object> params = it.params
description += "("
params.forEach({ String k, Object v ->
description += k + ": " + v.toString()
if (params.keySet().last() != k) {
description += ", "
}
})
description += ")"
}
})
return description
}
}

View File

@@ -1,191 +0,0 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact
import java.util.regex.Pattern
import au.com.dius.pact.core.model.generators.Category
import au.com.dius.pact.core.model.generators.DateGenerator
import au.com.dius.pact.core.model.generators.DateTimeGenerator
import au.com.dius.pact.core.model.generators.Generator
import au.com.dius.pact.core.model.generators.Generators
import au.com.dius.pact.core.model.generators.RandomBooleanGenerator
import au.com.dius.pact.core.model.generators.RandomDecimalGenerator
import au.com.dius.pact.core.model.generators.RandomHexadecimalGenerator
import au.com.dius.pact.core.model.generators.RandomIntGenerator
import au.com.dius.pact.core.model.generators.RandomStringGenerator
import au.com.dius.pact.core.model.generators.RegexGenerator
import au.com.dius.pact.core.model.generators.TimeGenerator
import au.com.dius.pact.core.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
/**
* @author Tim Ysewyn
* @Since 2.0.0
*/
@CompileStatic
@PackageScope
class ValueGeneratorConverter {
private static final Pattern INTEGER = Pattern.compile(INTEGER_PATTERN)
private static final String INTEGER_PATTERN = '-?(\\d+)'
private static final Pattern DECIMAL = Pattern.compile(DECIMAL_PATTERN)
private static final String DECIMAL_PATTERN = '-?(\\d*\\.\\d+)'
private static final Pattern HEX = Pattern.compile(HEX_PATTERN)
private static final String HEX_PATTERN = '[a-fA-F0-9]+'
private static final Pattern ALPHA_NUMERIC = Pattern.compile(ALPHA_NUMERIC_PATTERN)
private static final String ALPHA_NUMERIC_PATTERN = '[a-zA-Z0-9]+'
private static final Pattern UUID = Pattern.compile(UUID_PATTERN)
private static final String UUID_PATTERN = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}'
private static final Pattern ANY_DATE = Pattern.compile(ANY_DATE_PATTERN)
private static final String ANY_DATE_PATTERN = '(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])'
private static final Pattern ANY_TIME = Pattern.compile(ANY_TIME_PATTERN)
private static final String ANY_TIME_PATTERN = '(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])'
private static final Pattern ANY_DATE_TIME = Pattern.compile(ANY_DATE_TIME_PATTERN)
private static final String ANY_DATE_TIME_PATTERN = '([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])'
private static final Pattern TRUE_OR_FALSE = Pattern.compile(TRUE_OR_FALSE_PATTERN)
private static final String TRUE_OR_FALSE_PATTERN = /(true|false)/
static DslProperty convert(Generator generator, Closure<DslProperty> dslPropertyProvider) {
Pattern pattern
if (generator instanceof RandomIntGenerator) {
pattern = INTEGER
}
else if (generator instanceof RandomDecimalGenerator) {
pattern = DECIMAL
}
else if (generator instanceof RandomHexadecimalGenerator) {
pattern = HEX
}
else if (generator instanceof RandomStringGenerator) {
pattern = ALPHA_NUMERIC
}
else if (generator instanceof RegexGenerator) {
pattern = Pattern.compile(generator.regex)
}
else if (generator instanceof UuidGenerator) {
pattern = UUID
}
else if (generator instanceof DateGenerator) {
pattern = getDateTimePattern(generator.format, ANY_DATE)
}
else if (generator instanceof TimeGenerator) {
pattern = getDateTimePattern(generator.format, ANY_TIME)
}
else if (generator instanceof DateTimeGenerator) {
pattern = getDateTimePattern(generator.format, ANY_DATE_TIME)
}
else if (generator instanceof RandomBooleanGenerator) {
pattern = TRUE_OR_FALSE
}
if (pattern == null) {
throw new UnsupportedOperationException("We currently don't support a generator of type " + generator.class.simpleName)
}
else {
Object generatedValue = generator.generate([:])
return dslPropertyProvider(pattern, generatedValue)
}
}
private static Pattern getDateTimePattern(String format, Pattern defaultPattern) {
return format ? Pattern.compile(format) : defaultPattern
}
static Generators extract(Body body, Closure dslPropertyValueProvider) {
Generators generators = new Generators()
traverse(body, dslPropertyValueProvider, '', generators, Category.BODY)
return generators
}
static Generators extract(OutputMessage message, Closure dslPropertyValueProvider) {
Generators generators = new Generators()
traverse(message.body, dslPropertyValueProvider, '', generators, Category.BODY)
return generators
}
private static void traverse(Object value, Closure dslPropertyValueProvider, String path, Generators generators, Category category) {
Object v = value
if (v instanceof DslProperty) {
v = dslPropertyValueProvider(v)
}
if (v instanceof GString) {
v = ContentUtils.extractValue(v, dslPropertyValueProvider)
}
if (v instanceof Map) {
v.each { Map.Entry entry ->
traverse(entry.value, dslPropertyValueProvider, path + "." + entry.key, generators, category)
}
}
else if (v instanceof Collection) {
v.eachWithIndex { def entry, int index ->
traverse(entry, dslPropertyValueProvider, path + "[" + index + "]", generators, category)
}
}
else if (v instanceof DslProperty) {
traverse(v, dslPropertyValueProvider, path, generators, category)
}
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
case DECIMAL_PATTERN:
generators.
addGenerator(category, path, new RandomDecimalGenerator(10))
break
case HEX_PATTERN:
generators.
addGenerator(category, path, new RandomHexadecimalGenerator(10))
break
case ALPHA_NUMERIC_PATTERN:
generators.addGenerator(category, path, new RandomStringGenerator(10))
break
case UUID_PATTERN:
generators.addGenerator(category, path, UuidGenerator.INSTANCE)
break
case ANY_DATE_PATTERN:
generators.addGenerator(category, path, new DateGenerator())
break
case ANY_TIME_PATTERN:
generators.addGenerator(category, path, new TimeGenerator())
break
case ANY_DATE_TIME_PATTERN:
generators.addGenerator(category, path, new DateTimeGenerator())
break
case TRUE_OR_FALSE_PATTERN:
generators.
addGenerator(category, path, RandomBooleanGenerator.INSTANCE)
break
default:
generators.
addGenerator(category, path, new RegexGenerator(regexProperty.
pattern()))
break
}
}
}
}

View File

@@ -0,0 +1,231 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;
import au.com.dius.pact.consumer.dsl.DslPart;
import au.com.dius.pact.consumer.dsl.PactDslJsonArray;
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
import au.com.dius.pact.core.model.OptionalBody;
import au.com.dius.pact.core.model.Request;
import au.com.dius.pact.core.model.Response;
import au.com.dius.pact.core.model.generators.Generator;
import au.com.dius.pact.core.model.messaging.Message;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.internal.EvaluationContext;
import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.internal.path.PathCompiler;
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
import groovy.lang.GString;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.contract.spec.internal.ClientDslProperty;
import org.springframework.cloud.contract.spec.internal.DslProperty;
import org.springframework.cloud.contract.spec.internal.ServerDslProperty;
import org.springframework.cloud.contract.verifier.util.ContentUtils;
/**
* @author Tim Ysewyn
* @author Stessy Delcroix
* @since 2.0.0
*/
final class BodyConverter {
private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.INSTANCE.getMapper();
private BodyConverter() {
}
static DslPart toPactBody(DslProperty<?> dslProperty, Function<DslProperty<?>, Object> dslPropertyValueExtractor) {
return traverse(dslProperty, null, dslPropertyValueExtractor);
}
private static DslPart traverse(Object value, DslPart parent,
Function<DslProperty<?>, Object> dslPropertyValueExtractor) {
boolean isRoot = parent == null;
Object v = value;
if (v instanceof DslProperty) {
v = dslPropertyValueExtractor.apply((DslProperty<?>) v);
}
if (v instanceof GString) {
v = ContentUtils.extractValue((GString) v, dslPropertyValueExtractor);
}
if (v instanceof String) {
String stringValue = ((String) v).trim();
if (StringUtils.startsWith(stringValue, "{") && StringUtils.endsWith(stringValue, "}")) {
try {
v = OBJECT_MAPPER.readValue(stringValue, Object.class);
}
catch (JsonProcessingException ex) { /*
* it wasn't a JSON string after
* all...
*/
}
}
}
DslPart p = isRoot ? createRootDslPart(v) : parent;
if (v instanceof Map) {
processMap((Map) v, (PactDslJsonBody) p, dslPropertyValueExtractor);
}
else if (v instanceof Collection) {
processCollection((Collection) v, (PactDslJsonArray) p, dslPropertyValueExtractor);
}
return p;
}
private static DslPart createRootDslPart(Object value) {
return value instanceof Collection ? new PactDslJsonArray() : new PactDslJsonBody();
}
private static void processCollection(Collection values, PactDslJsonArray jsonArray,
Function<DslProperty<?>, Object> dslPropertyValueExtractor) {
values.forEach(v -> {
if (v instanceof DslProperty) {
v = dslPropertyValueExtractor.apply((DslProperty<?>) v);
}
if (v instanceof GString) {
v = ContentUtils.extractValue((GString) v, dslPropertyValueExtractor);
}
if (v == null) {
jsonArray.nullValue();
}
else if (v instanceof String) {
jsonArray.string((String) v);
}
else if (v instanceof Number) {
jsonArray.number((Number) v);
}
else if (v instanceof Map) {
PactDslJsonBody current = jsonArray.object();
traverse(v, current, dslPropertyValueExtractor);
current.closeObject();
}
else if (v instanceof Collection) {
PactDslJsonArray current = jsonArray.array();
traverse(v, current, dslPropertyValueExtractor);
current.closeArray();
}
});
}
private static void processMap(Map<String, Object> values, PactDslJsonBody jsonObject,
Function<DslProperty<?>, Object> dslPropertyValueExtractor) {
values.forEach((k, v) -> {
if (v instanceof DslProperty) {
v = dslPropertyValueExtractor.apply((DslProperty<?>) v);
}
if (v instanceof GString) {
v = ContentUtils.extractValue((GString) v, dslPropertyValueExtractor);
}
if (v == null) {
jsonObject.nullValue(k);
}
else if (v instanceof String) {
jsonObject.stringType(k, (String) v);
}
else if (v instanceof Number) {
jsonObject.numberValue(k, (Number) v);
}
else if (v instanceof Map) {
PactDslJsonBody current = jsonObject.object(k);
traverse(v, current, dslPropertyValueExtractor);
current.closeObject();
}
else if (v instanceof Collection) {
PactDslJsonArray current = jsonObject.array(k);
traverse(v, current, dslPropertyValueExtractor);
current.closeArray();
}
});
}
static Object toSCCBody(Request request) {
Object body = parseBody(request.getBody());
if (request.getGenerators().isNotEmpty() && request.getGenerators().getCategories()
.containsKey(au.com.dius.pact.core.model.generators.Category.BODY)) {
applyGenerators(body,
request.getGenerators().getCategories().get(au.com.dius.pact.core.model.generators.Category.BODY),
currentValue -> pattern -> generatedValue -> new DslProperty<>(
new ClientDslProperty(pattern, generatedValue), currentValue));
}
return body;
}
static Object toSCCBody(Response response) {
Object body = parseBody(response.getBody());
if (response.getGenerators().isNotEmpty() && response.getGenerators().getCategories()
.containsKey(au.com.dius.pact.core.model.generators.Category.BODY)) {
applyGenerators(body,
response.getGenerators().getCategories().get(au.com.dius.pact.core.model.generators.Category.BODY),
currentValue -> pattern -> generatedValue -> new DslProperty<>(currentValue,
new ServerDslProperty(pattern, generatedValue)));
}
return body;
}
static Object toSCCBody(Message message) {
Object body = parseBody(message.getContents());
if (message.getGenerators().isNotEmpty() && message.getGenerators().getCategories()
.containsKey(au.com.dius.pact.core.model.generators.Category.BODY)) {
applyGenerators(body,
message.getGenerators().getCategories().get(au.com.dius.pact.core.model.generators.Category.BODY),
currentValue -> pattern -> generatedValue -> new DslProperty<>(
new ClientDslProperty(pattern, generatedValue), currentValue));
}
return body;
}
private static Object parseBody(OptionalBody optionalBody) {
if (optionalBody.isPresent()) {
try {
return OBJECT_MAPPER.readValue(optionalBody.getValue(), Object.class);
}
catch (IOException e) {
throw new RuntimeException("Body could not be read", e);
}
}
else {
return optionalBody.getValue();
}
}
private static void applyGenerators(Object body, Map<String, Generator> generatorsPerPath,
Function<Pattern, Function<Object, Function<Object, DslProperty<Object>>>> dslPropertyProvider) {
Configuration configuration = Configuration.builder().jsonProvider(new JacksonJsonProvider(OBJECT_MAPPER))
.build();
generatorsPerPath.forEach((path, generator) -> {
Path compiledPath = PathCompiler.compile(path);
EvaluationContext evaluationContext = compiledPath.evaluate(body, body, configuration, true);
evaluationContext.updateOperations().forEach(pathRef -> {
pathRef.convert(((currentValue, config) -> ValueGeneratorConverter.convert(generator,
(pattern, generatedValue) -> dslPropertyProvider.apply(pattern).apply(generatedValue)
.apply(currentValue))),
configuration);
});
});
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact;
import au.com.dius.pact.core.model.matchingrules.Category;
import au.com.dius.pact.core.model.matchingrules.DateMatcher;
import au.com.dius.pact.core.model.matchingrules.EqualsMatcher;
import au.com.dius.pact.core.model.matchingrules.MaxTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.MinMaxTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.NullMatcher;
import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.RegexMatcher;
import au.com.dius.pact.core.model.matchingrules.TimeMatcher;
import au.com.dius.pact.core.model.matchingrules.TimestampMatcher;
import au.com.dius.pact.core.model.matchingrules.TypeMatcher;
import org.springframework.cloud.contract.spec.internal.BodyMatchers;
import org.springframework.cloud.contract.spec.internal.MatchingType;
import org.springframework.cloud.contract.spec.internal.RegexPatterns;
/**
* @author Tim Ysewyn
* @author Stessy Delcroix
* @since 2.0.0
*/
final class MatchingRulesConverter {
private MatchingRulesConverter() {
}
static Category matchingRulesForBody(BodyMatchers bodyMatchers) {
return matchingRulesFor("body", bodyMatchers);
}
private static Category matchingRulesFor(String categoryName, BodyMatchers bodyMatchers) {
Category category = new Category(categoryName);
bodyMatchers.matchers().forEach((b) -> {
String key = getMatcherKey(b.path());
MatchingType matchingType = b.matchingType();
switch (matchingType) {
case NULL:
category.addRule(key, NullMatcher.INSTANCE);
break;
case EQUALITY:
category.addRule(key, EqualsMatcher.INSTANCE);
break;
case TYPE:
if (b.minTypeOccurrence() != null && b.maxTypeOccurrence() != null) {
category.addRule(key, new MinMaxTypeMatcher(b.minTypeOccurrence(), b.maxTypeOccurrence()));
}
else if (b.minTypeOccurrence() != null) {
category.addRule(key, new MinTypeMatcher(b.minTypeOccurrence()));
}
else if (b.maxTypeOccurrence() != null) {
category.addRule(key, new MaxTypeMatcher(b.maxTypeOccurrence()));
}
else {
category.addRule(key, TypeMatcher.INSTANCE);
}
break;
case DATE:
category.addRule(key, new DateMatcher());
break;
case TIME:
category.addRule(key, new TimeMatcher());
break;
case TIMESTAMP:
category.addRule(key, new TimestampMatcher());
break;
case REGEX:
String pattern = b.value().toString();
if (pattern.equals(RegexPatterns.number().pattern())) {
category.addRule(key, new NumberTypeMatcher(NumberTypeMatcher.NumberType.NUMBER));
}
else if (pattern.equals(RegexPatterns.anInteger().pattern())) {
category.addRule(key, new NumberTypeMatcher(NumberTypeMatcher.NumberType.INTEGER));
}
else if (pattern.equals(RegexPatterns.aDouble().pattern())) {
category.addRule(key, new NumberTypeMatcher(NumberTypeMatcher.NumberType.DECIMAL));
}
else {
category.addRule(key, new RegexMatcher(pattern));
}
break;
default:
break;
}
});
return category;
}
private static String getMatcherKey(String path) {
return path.startsWith("$") ? path.substring(1) : path;
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import au.com.dius.pact.consumer.MessagePactBuilder;
import au.com.dius.pact.consumer.dsl.DslPart;
import au.com.dius.pact.core.model.messaging.MessagePact;
import groovy.lang.GString;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.internal.DslProperty;
import org.springframework.cloud.contract.spec.internal.Header;
import org.springframework.cloud.contract.spec.internal.Headers;
import org.springframework.cloud.contract.spec.internal.Input;
import org.springframework.cloud.contract.spec.internal.OutputMessage;
import org.springframework.cloud.contract.verifier.util.ContentUtils;
/**
* Creator of {@link MessagePact} instances.
*
* @author Tim Ysewyn
* @author Stessy Delcroix
* @since 2.0.0
*/
class MessagePactCreator {
private static final Function<DslProperty<?>, Object> clientValueExtractor = DslProperty::getClientValue;
MessagePact createFromContract(List<Contract> contracts) {
if (CollectionUtils.isEmpty(contracts)) {
return null;
}
Names names = NamingUtil.name(contracts.get(0));
MessagePactBuilder pactBuilder = MessagePactBuilder.consumer(names.getConsumer())
.hasPactWith(names.getProducer());
for (Contract contract : contracts) {
pactBuilder = pactBuilder.given(getGiven(contract.getInput())).expectsToReceive(getOutcome(contract));
if (contract.getOutputMessage() != null) {
OutputMessage message = contract.getOutputMessage();
if (message.getBody() != null) {
DslPart pactResponseBody = BodyConverter.toPactBody(message.getBody(), clientValueExtractor);
if (message.getBodyMatchers() != null) {
pactResponseBody
.setMatchers(MatchingRulesConverter.matchingRulesForBody(message.getBodyMatchers()));
}
pactResponseBody
.setGenerators(ValueGeneratorConverter.extract(message, DslProperty::getServerValue));
pactBuilder = pactBuilder.withContent(pactResponseBody);
}
if (message.getHeaders() != null) {
pactBuilder = pactBuilder.withMetadata(getMetadata(message.getHeaders()));
}
}
}
return pactBuilder.toPact();
}
private String getGiven(Input input) {
if (input.getTriggeredBy() != null) {
return input.getTriggeredBy().getExecutionCommand();
}
else if (input.getMessageFrom() != null) {
return "received message from " + clientValueExtractor.apply(input.getMessageFrom());
}
else {
return "";
}
}
private String getOutcome(Contract contract) {
if (contract.getOutputMessage() != null) {
OutputMessage message = contract.getOutputMessage();
return "message sent to " + clientValueExtractor.apply(message.getSentTo());
}
else {
return "assert that " + contract.getInput().getAssertThat().getExecutionCommand();
}
}
private Map<String, String> getMetadata(Headers headers) {
return headers.getEntries().stream().collect(Collectors.toMap(Header::getName, this::extractValue));
}
private String extractValue(Object value) {
Object v = value;
if (v instanceof DslProperty) {
v = clientValueExtractor.apply((DslProperty) v);
}
if (v instanceof GString) {
v = ContentUtils.extractValue((GString) v, clientValueExtractor);
}
if (v instanceof String) {
return (String) v;
}
else {
return v.toString();
}
}
}

View File

@@ -0,0 +1,188 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import au.com.dius.pact.core.model.matchingrules.Category;
import au.com.dius.pact.core.model.matchingrules.DateMatcher;
import au.com.dius.pact.core.model.matchingrules.MatchingRule;
import au.com.dius.pact.core.model.matchingrules.MaxTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.MinMaxTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.NullMatcher;
import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.RegexMatcher;
import au.com.dius.pact.core.model.matchingrules.RuleLogic;
import au.com.dius.pact.core.model.matchingrules.TimeMatcher;
import au.com.dius.pact.core.model.matchingrules.TimestampMatcher;
import au.com.dius.pact.core.model.matchingrules.TypeMatcher;
import au.com.dius.pact.core.model.messaging.Message;
import au.com.dius.pact.core.model.messaging.MessagePact;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.internal.Headers;
import org.springframework.cloud.contract.spec.internal.RegexPatterns;
import org.springframework.cloud.contract.spec.internal.ResponseBodyMatchers;
import org.springframework.cloud.contract.verifier.util.JsonPaths;
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter;
/**
* Creator of {@link Contract} instances.
*
* @author Tim Ysewyn
* @author Stessy Delcroix
* @since 2.0.0
*/
class MessagingSCContractCreator {
private static final String FULL_BODY = "$";
private static final String DESTINATION_KEY = "sentTo";
private static final List<String> NON_HEADER_META_DATA = Collections.singletonList(DESTINATION_KEY);
Collection<Contract> convertFrom(MessagePact pact) {
return pact.getMessages().stream().map(message -> Contract.make(contract -> {
contract.label(message.getDescription());
if (CollectionUtils.isNotEmpty(message.getProviderStates())) {
contract.input(i -> i.triggeredBy(this.getTriggeredBy(message)));
}
contract.outputMessage((outputMessage) -> {
if (message.getContents().isPresent()) {
outputMessage.body(BodyConverter.toSCCBody(message));
Category bodyRules = message.getMatchingRules().rulesForCategory("body");
if (bodyRules != null && MapUtils.isNotEmpty(bodyRules.getMatchingRules())) {
outputMessage.bodyMatchers((responseBodyMatchers) -> outputMessageBodyMatchers(message,
bodyRules, responseBodyMatchers));
}
}
if (MapUtils.isNotEmpty(message.getMetaData())) {
outputMessage.headers((headers) -> outputMessageHeaders(message, headers));
}
String dest = findDestination(message);
if (StringUtils.isNotBlank(dest)) {
outputMessage.sentTo(dest);
}
});
})).collect(Collectors.toList());
}
private void outputMessageHeaders(Message message, Headers headers) {
message.getMetaData().forEach((key, value) -> {
String matchingRuleGroup = value.toString();
if (key.equalsIgnoreCase("contentType")) {
headers.messagingContentType(matchingRuleGroup);
}
else if (!NON_HEADER_META_DATA.contains(key)) {
headers.header(key, matchingRuleGroup);
}
});
}
private void outputMessageBodyMatchers(Message message, Category bodyRules,
ResponseBodyMatchers responseBodyMatchers) {
bodyRules.getMatchingRules().forEach((matchingRuleKey, matchingRuleGroup) -> {
if (matchingRuleGroup.getRuleLogic() != RuleLogic.AND) {
throw new UnsupportedOperationException("Currently only the AND combination rule logic is supported");
}
if (FULL_BODY.equals(matchingRuleKey)) {
JsonPaths jsonPaths = JsonToJsonPathsConverter
.transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(message.getContents().getValue());
jsonPaths.forEach((j) -> {
responseBodyMatchers.jsonPath(j.keyBeforeChecking(), responseBodyMatchers.byType());
});
}
else {
matchingRuleGroup.getRules().forEach((rule) -> {
applyJsonPathToResponseBodyMatchers(rule, matchingRuleKey, responseBodyMatchers);
});
}
});
}
private String getTriggeredBy(Message message) {
String triggeredBy = message.getProviderStates().get(0).getName().replaceAll(":", " ").replaceAll(" ", "_")
.replaceAll("\\(", "").replaceAll("\\)", "");
return StringUtils.uncapitalize(triggeredBy) + "()";
}
private String findDestination(Message message) {
return message.getMetaData().get(DESTINATION_KEY) != null
? message.getMetaData().get(DESTINATION_KEY).toString() : "";
}
void applyJsonPathToResponseBodyMatchers(MatchingRule matchingRule, String key,
ResponseBodyMatchers responseBodyMatchers) {
if (matchingRule instanceof NullMatcher) {
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byNull());
}
else if (matchingRule instanceof RegexMatcher) {
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byRegex(((RegexMatcher) matchingRule).getRegex()));
}
else if (matchingRule instanceof DateMatcher) {
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byDate());
}
else if (matchingRule instanceof TimeMatcher) {
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byTime());
}
else if (matchingRule instanceof TimestampMatcher) {
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byTimestamp());
}
else if (matchingRule instanceof MinTypeMatcher) {
responseBodyMatchers.jsonPath(key,
responseBodyMatchers.byType((b) -> b.minOccurrence(((MinTypeMatcher) matchingRule).getMin())));
}
else if (matchingRule instanceof MinMaxTypeMatcher) {
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byType((c) -> {
c.minOccurrence(((MinMaxTypeMatcher) matchingRule).getMin());
c.maxOccurrence(((MinMaxTypeMatcher) matchingRule).getMax());
}));
}
else if (matchingRule instanceof MaxTypeMatcher) {
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byType((c) -> {
c.maxOccurrence(((MaxTypeMatcher) matchingRule).getMax());
}));
}
else if (matchingRule instanceof TypeMatcher) {
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byType());
}
else if (matchingRule instanceof NumberTypeMatcher) {
switch (((NumberTypeMatcher) matchingRule).getNumberType()) {
case NUMBER:
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byRegex(RegexPatterns.number()));
break;
case INTEGER:
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byRegex(RegexPatterns.anInteger()));
break;
case DECIMAL:
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byRegex(RegexPatterns.aDouble()));
break;
default:
throw new RuntimeException("Unsupported number type!");
}
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact;
/**
* @author Marcin Grzejszczak
* @author Stessy Delcroix
* @since
*/
class Names {
private final String consumer;
private final String producer;
private final String test;
Names(String[] strings) {
this.consumer = strings[0];
this.producer = strings[1];
this.test = strings.length >= 2 ? strings[2] : "";
}
@Override
public String toString() {
return this.consumer + "_" + this.producer;
}
String getConsumer() {
return consumer;
}
String getProducer() {
return producer;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact;
import org.springframework.cloud.contract.spec.Contract;
/**
* @author Marcin Grzejszczak
* @author Stessy Delcroix
* @since
*/
final class NamingUtil {
// consumer___producer___testname
private static final String SEPARATOR = "___";
private NamingUtil() {
}
static Names name(Contract contract) {
String contractName = contract.getName();
if (contractName == null || !contractName.contains(SEPARATOR)) {
return new Names(new String[] { "Consumer", "Provider", "" });
}
return new Names(contractName.split(SEPARATOR));
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* {@link ObjectMapper} singleton instance creation.
* @author Stessy Delcroix
* @since 2.0.0
*/
public enum ObjectMapperFactory {
/**
* Singleton instance constant.
*/
INSTANCE;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
static {
OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_YAML_COMMENTS, true);
OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
OBJECT_MAPPER.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
}
public ObjectMapper getMapper() {
return OBJECT_MAPPER;
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import au.com.dius.pact.core.model.DefaultPactReader;
import au.com.dius.pact.core.model.Pact;
import au.com.dius.pact.core.model.PactSpecVersion;
import au.com.dius.pact.core.model.RequestResponsePact;
import au.com.dius.pact.core.model.messaging.MessagePact;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.core.util.Separators;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.ContractConverter;
/**
* Converter of JSON PACT file.
*
* @author Marcin Grzejszczak
* @author Tim Ysewyn
* @author Stessy Delcroix
* @since 1.1.0
*/
public class PactContractConverter implements ContractConverter<Collection<Pact<?>>> {
private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.INSTANCE.getMapper();
private final RequestResponseSCContractCreator requestResponseSCContractCreator = new RequestResponseSCContractCreator();
private final MessagingSCContractCreator messagingSCContractCreator = new MessagingSCContractCreator();
private final RequestResponsePactCreator requestResponsePactCreator = new RequestResponsePactCreator();
private final MessagePactCreator messagePactCreator = new MessagePactCreator();
@Override
public boolean isAccepted(File file) {
try {
DefaultPactReader.INSTANCE.loadPact(file);
return true;
}
catch (Exception e) {
return false;
}
}
@Override
public Collection<Contract> convertFrom(File file) {
Pact<?> pact = DefaultPactReader.INSTANCE.loadPact(file);
if (pact instanceof RequestResponsePact) {
return requestResponseSCContractCreator.convertFrom((RequestResponsePact) pact);
}
if (pact instanceof MessagePact) {
return messagingSCContractCreator.convertFrom((MessagePact) pact);
}
throw new UnsupportedOperationException(
"We currently don't support pact contracts of type" + pact.getClass().getSimpleName());
}
@Override
public Collection<Pact<?>> convertTo(Collection<Contract> contracts) {
List<Pact<?>> pactContracts = new ArrayList<>();
Map<String, List<Contract>> groupedContracts = contracts.stream()
.collect(Collectors.groupingBy(c -> NamingUtil.name(c).toString()));
for (List<Contract> list : groupedContracts.values()) {
List<Contract> httpOnly = list.stream().filter(c -> c.getRequest() != null).collect(Collectors.toList());
List<Contract> messagingOnly = list.stream().filter(c -> c.getInput() != null).collect(Collectors.toList());
RequestResponsePact responsePact = requestResponsePactCreator.createFromContract(httpOnly);
if (responsePact != null) {
pactContracts.add(responsePact);
}
MessagePact messagePact = messagePactCreator.createFromContract(messagingOnly);
if (messagePact != null) {
pactContracts.add(messagePact);
}
}
return pactContracts;
}
@Override
public Map<String, byte[]> store(Collection<Pact<?>> contracts) {
return contracts.stream().collect(Collectors.toMap(this::name, c -> {
try {
return this.buildPrettyPrint(OBJECT_MAPPER.writeValueAsString(c.toMap(PactSpecVersion.V3))).getBytes();
}
catch (JsonProcessingException e) {
throw new IllegalArgumentException("The pact contract is not a valid map", e);
}
}));
}
protected String name(Pact<?> contract) {
return contract.getConsumer().getName() + "_" + contract.getProvider().getName() + "_"
+ Math.abs(contract.hashCode()) + ".json";
}
private String buildPrettyPrint(String contract) {
try {
Object intermediateObjectForPrettyPrinting = OBJECT_MAPPER.reader().readValue(contract, Object.class);
DefaultIndenter customIndenter = new DefaultIndenter(" ", "\n");
return OBJECT_MAPPER
.writer(new CustomPrettyPrinter().withArrayIndenter(customIndenter)
.withObjectIndenter(customIndenter))
.writeValueAsString(intermediateObjectForPrettyPrinting);
}
catch (IOException e) {
throw new RuntimeException("WireMock response body could not be pretty printed");
}
}
private static class CustomPrettyPrinter extends DefaultPrettyPrinter {
@Override
public CustomPrettyPrinter createInstance() {
return new CustomPrettyPrinter();
}
@Override
public DefaultPrettyPrinter withSeparators(Separators separators) {
_separators = separators;
_objectFieldValueSeparatorWithSpaces = separators.getObjectFieldValueSeparator() + " ";
return this;
}
}
}

View File

@@ -0,0 +1,304 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import au.com.dius.pact.consumer.ConsumerPactBuilder;
import au.com.dius.pact.consumer.dsl.DslPart;
import au.com.dius.pact.consumer.dsl.PactDslRequestWithPath;
import au.com.dius.pact.consumer.dsl.PactDslResponse;
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
import au.com.dius.pact.core.model.RequestResponsePact;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.internal.Body;
import org.springframework.cloud.contract.spec.internal.Cookies;
import org.springframework.cloud.contract.spec.internal.DslProperty;
import org.springframework.cloud.contract.spec.internal.ExecutionProperty;
import org.springframework.cloud.contract.spec.internal.Header;
import org.springframework.cloud.contract.spec.internal.QueryParameters;
import org.springframework.cloud.contract.spec.internal.RegexProperty;
import org.springframework.cloud.contract.spec.internal.Request;
import org.springframework.cloud.contract.spec.internal.Response;
import org.springframework.util.CollectionUtils;
import static java.util.stream.Collectors.joining;
/**
* Creator of {@link RequestResponsePact} instances.
*
* @author Tim Ysewyn
* @author Stessy Delcroix
* @since 2.0.0
*/
class RequestResponsePactCreator {
RequestResponsePact createFromContract(List<Contract> contracts) {
if (CollectionUtils.isEmpty(contracts)) {
return null;
}
Names names = NamingUtil.name(contracts.get(0));
PactDslWithProvider pactDslWithProvider = ConsumerPactBuilder.consumer(names.getConsumer())
.hasPactWith(names.getProducer());
PactDslResponse pactDslResponse = null;
for (Contract contract : contracts) {
assertNoExecutionProperty(contract);
PactDslRequestWithPath pactDslRequest = pactDslResponse != null
? createPactDslRequestWithPath(contract, pactDslResponse)
: createPactDslRequestWithPath(contract, pactDslWithProvider);
pactDslResponse = createPactDslResponse(contract, pactDslRequest);
}
return pactDslResponse.toPact();
}
private void assertNoExecutionProperty(Contract contract) {
assertNoExecutionPropertyInBody(contract.getRequest().getBody(), DslProperty::getServerValue);
assertNoExecutionPropertyInBody(contract.getResponse().getBody(), DslProperty::getClientValue);
}
private void assertNoExecutionPropertyInBody(Body body,
Function<DslProperty<?>, Object> dslPropertyValueExtractor) {
traverseValues(body, dslPropertyValueExtractor, (Object object) -> {
if (object instanceof ExecutionProperty) {
throw new UnsupportedOperationException("We can't convert a contract that has execution property");
}
return object;
});
}
private void traverseValues(Object value, Function<DslProperty<?>, Object> dslPropertyValueExtractor,
Function<Object, Object> function) {
if (value instanceof DslProperty) {
traverseValues(dslPropertyValueExtractor.apply((DslProperty<?>) value), dslPropertyValueExtractor,
function);
}
else if (value instanceof Map) {
((Map) value).values().forEach(v -> traverseValues(v, dslPropertyValueExtractor, function));
}
else if (value instanceof Collection) {
((Collection<?>) value).forEach(v -> traverseValues(v, dslPropertyValueExtractor, function));
}
else {
function.apply(value);
}
}
private PactDslRequestWithPath createPactDslRequestWithPath(Contract contract, PactDslResponse pactDslResponse) {
Request request = contract.getRequest();
PactDslRequestWithPath pactDslRequest = pactDslResponse
.uponReceiving(StringUtils.isNotBlank(contract.getDescription()) ? contract.getDescription() : "")
.path(url(request)).method(request.getMethod().getServerValue().toString());
String query = query(request);
if (StringUtils.isNotBlank(query)) {
pactDslRequest = pactDslRequest.encodedQuery(query);
}
final PactDslRequestWithPath finalPactDslRequest = pactDslRequest;
if (request.getHeaders() != null) {
request.getHeaders().getEntries().forEach(h -> processHeader(finalPactDslRequest, h));
}
if (request.getCookies() != null) {
pactDslRequest = processCookies(finalPactDslRequest, request.getCookies());
}
if (request.getBody() != null) {
DslPart pactRequestBody = BodyConverter.toPactBody(request.getBody(), DslProperty::getClientValue);
if (request.getBodyMatchers() != null) {
pactRequestBody.setMatchers(MatchingRulesConverter.matchingRulesForBody(request.getBodyMatchers()));
}
pactRequestBody
.setGenerators(ValueGeneratorConverter.extract(request.getBody(), DslProperty::getClientValue));
pactDslRequest = pactDslRequest.body(pactRequestBody);
}
return pactDslRequest;
}
private PactDslRequestWithPath createPactDslRequestWithPath(Contract contract,
PactDslWithProvider pactDslWithProvider) {
Request request = contract.getRequest();
PactDslRequestWithPath pactDslRequest = pactDslWithProvider
.uponReceiving(StringUtils.isNotBlank(contract.getDescription()) ? contract.getDescription() : "")
.path(url(request)).method(request.getMethod().getServerValue().toString());
String query = query(request);
if (StringUtils.isNotBlank(query)) {
pactDslRequest = pactDslRequest.encodedQuery(query);
}
final PactDslRequestWithPath finalPactDslRequest = pactDslRequest;
if (request.getHeaders() != null) {
request.getHeaders().getEntries().forEach(h -> {
processHeader(finalPactDslRequest, h);
});
}
if (request.getBody() != null) {
DslPart pactRequestBody = BodyConverter.toPactBody(request.getBody(), DslProperty::getServerValue);
if (request.getBodyMatchers() != null) {
pactRequestBody.setMatchers(MatchingRulesConverter.matchingRulesForBody(request.getBodyMatchers()));
}
pactRequestBody
.setGenerators(ValueGeneratorConverter.extract(request.getBody(), DslProperty::getClientValue));
pactDslRequest = pactDslRequest.body(pactRequestBody);
}
return pactDslRequest;
}
private String url(Request request) {
if (request.getUrlPath() != null) {
return request.getUrlPath().getServerValue().toString();
}
else if (request.getUrl() != null) {
return request.getUrl().getServerValue().toString();
}
throw new IllegalStateException("No url provided");
}
private String query(Request request) {
final StringBuilder query = new StringBuilder();
QueryParameters params = queryParams(request);
if (params != null) {
AtomicInteger index = new AtomicInteger();
params.getParameters().forEach(p -> {
query.append(p.getName()).append('=').append(p.getServerValue());
if (index.incrementAndGet() < params.getParameters().size()) {
query.append('&');
}
});
}
return query.toString();
}
private QueryParameters queryParams(Request request) {
if (request.getUrlPath() != null) {
return request.getUrlPath().getQueryParameters();
}
else if (request.getUrl() != null) {
return request.getUrl().getQueryParameters();
}
throw new IllegalStateException("No url provided");
}
private PactDslRequestWithPath processHeader(PactDslRequestWithPath pactDslRequest, Header header) {
if (header.isSingleValue()) {
String value = getDslPropertyServerValue(header).toString();
return pactDslRequest.headers(header.getName(), value);
}
else {
String regex = getDslPropertyClientValue(header).toString();
String example = getDslPropertyServerValue(header).toString();
return pactDslRequest.matchHeader(header.getName(), regex, example);
}
}
private String stubSideCookieExample(Cookies cookies) {
return cookies.asStubSideMap().entrySet().stream().map(Object::toString).collect(joining(";"));
}
private Object getDslPropertyClientValue(Object o) {
Object value = o;
if (value instanceof DslProperty) {
value = getDslPropertyClientValue(((DslProperty) value).getClientValue());
}
return value;
}
private Object getDslPropertyServerValue(Object o) {
Object value = o;
if (value instanceof DslProperty) {
value = getDslPropertyServerValue(((DslProperty) value).getServerValue());
}
return value;
}
private PactDslRequestWithPath processCookies(PactDslRequestWithPath pactDslRequest, Cookies cookies) {
Map<String, Object> stubSideCookies = cookies.asStubSideMap();
Collection<RegexProperty> regexProperties = stubSideCookies.values().stream()
.filter(r -> r instanceof Pattern || r instanceof RegexProperty).map(RegexProperty::new)
.collect(Collectors.toList());
if (!regexProperties.isEmpty()) {
String regex = regexProperties.stream().map(RegexProperty::pattern).collect(joining("|"));
return pactDslRequest.matchHeader("Cookie", regex, testSideCookieExample(cookies));
}
else {
return pactDslRequest.headers("Cookie", testSideCookieExample(cookies));
}
}
private String testSideCookieExample(Cookies cookies) {
return cookies.asTestSideMap().entrySet().stream().map(Object::toString).collect(joining(";"));
}
private PactDslResponse createPactDslResponse(Contract contract, PactDslRequestWithPath pactDslRequest) {
Response response = contract.getResponse();
PactDslResponse pactDslResponse = pactDslRequest.willRespondWith()
.status((Integer) response.getStatus().getClientValue());
PactDslResponse finalPactDslResponse = pactDslResponse;
if (response.getHeaders() != null) {
response.getHeaders().getEntries().forEach(h -> processHeader(finalPactDslResponse, h));
}
if (response.getCookies() != null) {
pactDslResponse = processCookies(pactDslResponse, response.getCookies());
}
if (response.getBody() != null) {
DslPart pactResponseBody = BodyConverter.toPactBody(response.getBody(), DslProperty::getClientValue);
if (response.getBodyMatchers() != null) {
pactResponseBody.setMatchers(MatchingRulesConverter.matchingRulesForBody(response.getBodyMatchers()));
}
pactResponseBody
.setGenerators(ValueGeneratorConverter.extract(response.getBody(), DslProperty::getServerValue));
pactDslResponse = pactDslResponse.body(pactResponseBody);
}
return pactDslResponse;
}
private PactDslResponse processHeader(PactDslResponse pactDslResponse, Header header) {
if (header.isSingleValue()) {
String value = getDslPropertyClientValue(header).toString();
return pactDslResponse.headers(Collections.singletonMap(header.getName(), value));
}
else {
String regex = getDslPropertyServerValue(header).toString();
String example = getDslPropertyClientValue(header).toString();
return pactDslResponse.matchHeader(header.getName(), regex, example);
}
}
private PactDslResponse processCookies(PactDslResponse pactDslResponse, Cookies cookies) {
Map<String, Object> testSideCookies = cookies.asTestSideMap();
Collection<RegexProperty> regexProperties = testSideCookies.values().stream()
.filter(p -> p instanceof Pattern || p instanceof RegexProperty).map(RegexProperty::new)
.collect(Collectors.toList());
if (!regexProperties.isEmpty()) {
String regex = regexProperties.stream().map(RegexProperty::pattern).collect(joining("|"));
return pactDslResponse.matchHeader("Cookie", regex, stubSideCookieExample(cookies));
}
else {
return pactDslResponse.headers(Collections.singletonMap("Cookie", stubSideCookieExample(cookies)));
}
}
}

View File

@@ -0,0 +1,424 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import au.com.dius.pact.core.model.OptionalBody;
import au.com.dius.pact.core.model.ProviderState;
import au.com.dius.pact.core.model.Request;
import au.com.dius.pact.core.model.RequestResponseInteraction;
import au.com.dius.pact.core.model.RequestResponsePact;
import au.com.dius.pact.core.model.Response;
import au.com.dius.pact.core.model.matchingrules.Category;
import au.com.dius.pact.core.model.matchingrules.DateMatcher;
import au.com.dius.pact.core.model.matchingrules.MatchingRule;
import au.com.dius.pact.core.model.matchingrules.MatchingRuleGroup;
import au.com.dius.pact.core.model.matchingrules.MaxTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.MinMaxTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.NullMatcher;
import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.RegexMatcher;
import au.com.dius.pact.core.model.matchingrules.RuleLogic;
import au.com.dius.pact.core.model.matchingrules.TimeMatcher;
import au.com.dius.pact.core.model.matchingrules.TimestampMatcher;
import au.com.dius.pact.core.model.matchingrules.TypeMatcher;
import org.apache.commons.collections4.MapUtils;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.internal.RegexPatterns;
import org.springframework.cloud.contract.verifier.util.JsonPaths;
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter;
/**
* Creator of {@link Contract} instances.
*
* @author Tim Ysewyn
* @author Stessy Delcroix
* @since 2.0.0
*/
class RequestResponseSCContractCreator {
private static final String FULL_BODY = "$";
Collection<Contract> convertFrom(RequestResponsePact pact) {
return pact.getInteractions().stream().map(interaction -> Contract.make(contract -> {
mapContractDescription(interaction, contract);
mapContractRequest(interaction, contract);
mapContractResponse(interaction, contract);
})).collect(Collectors.toList());
}
private void mapContractDescription(RequestResponseInteraction interaction, Contract contract) {
contract.description(buildDescription(interaction));
}
private void mapContractRequest(RequestResponseInteraction interaction, Contract contract) {
contract.request((contractRequest) -> {
Request pactRequest = interaction.getRequest();
contractRequest.method(pactRequest.getMethod());
mapRequestUrl(contractRequest, pactRequest);
if (MapUtils.isNotEmpty(pactRequest.getHeaders())) {
mapRequestHeaders(contractRequest, pactRequest);
}
if (pactRequest.getHeaders().containsKey("Cookie")) {
mapRequestCookies(contractRequest, pactRequest);
}
if (pactRequest.getBody().getState() == OptionalBody.State.PRESENT) {
mapRequestBody(contractRequest, pactRequest);
}
Category bodyRules = pactRequest.getMatchingRules().rulesForCategory("body");
if (MapUtils.isNotEmpty(bodyRules.getMatchingRules())) {
mapRequestBodyRules(contractRequest, bodyRules);
}
});
}
private void mapContractResponse(RequestResponseInteraction interaction, Contract contract) {
contract.response((contractResponse) -> {
Response pactResponse = interaction.getResponse();
contractResponse.status(pactResponse.getStatus());
if (pactResponse.getBody().isPresent()) {
mapResponseBody(contractResponse, pactResponse);
}
Category bodyRules = pactResponse.getMatchingRules().rulesForCategory("body");
if (MapUtils.isNotEmpty(bodyRules.getMatchingRules())) {
mapResponseBodyRules(contractResponse, pactResponse, bodyRules);
}
if (MapUtils.isNotEmpty(pactResponse.getHeaders())) {
mapResponseHeaders(contractResponse, pactResponse);
}
if (pactResponse.getHeaders().containsKey("Cookie")) {
mapResponseCookies(contractResponse, pactResponse);
}
});
}
private void mapResponseBodyRules(org.springframework.cloud.contract.spec.internal.Response contractResponse,
Response pactResponse, Category bodyRules) {
contractResponse.bodyMatchers((bodyMatchers) -> {
bodyRules.getMatchingRules().forEach((key, matchindRuleGroup) -> {
if (matchindRuleGroup.getRuleLogic() != RuleLogic.AND) {
throw new UnsupportedOperationException(
"Currently only the AND combination rule logic is supported");
}
if (FULL_BODY.equals(key)) {
JsonPaths jsonPaths = JsonToJsonPathsConverter
.transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(
new String(pactResponse.getBody().getValue()));
jsonPaths.forEach(
(jsonPath) -> bodyMatchers.jsonPath(jsonPath.keyBeforeChecking(), bodyMatchers.byType()));
}
else {
matchindRuleGroup.getRules().forEach((matchingRule) -> {
if (matchingRule instanceof NullMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byNull());
}
else if (matchingRule instanceof RegexMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(((RegexMatcher) matchingRule).getRegex()));
}
else if (matchingRule instanceof DateMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byDate());
}
else if (matchingRule instanceof TimeMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byTime());
}
else if (matchingRule instanceof TimestampMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byTimestamp());
}
else if (matchingRule instanceof MinTypeMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byType((valueHolder) -> valueHolder
.minOccurrence((((MinTypeMatcher) matchingRule).getMin()))));
}
else if (matchingRule instanceof MinMaxTypeMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byType((valueHolder) -> {
valueHolder.minOccurrence((((MinMaxTypeMatcher) matchingRule).getMin()));
valueHolder.maxOccurrence((((MinMaxTypeMatcher) matchingRule).getMax()));
}));
}
else if (matchingRule instanceof MaxTypeMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byType((valueHolder) -> valueHolder
.maxOccurrence((((MaxTypeMatcher) matchingRule).getMax()))));
}
else if (matchingRule instanceof TypeMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byType());
}
else if (matchingRule instanceof NumberTypeMatcher) {
switch (((NumberTypeMatcher) matchingRule).getNumberType()) {
case NUMBER:
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(RegexPatterns.number()));
break;
case INTEGER:
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(RegexPatterns.anInteger()));
break;
case DECIMAL:
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(RegexPatterns.aDouble()));
break;
default:
throw new UnsupportedOperationException("Unsupported number type!");
}
}
});
}
});
});
}
private void mapResponseBody(org.springframework.cloud.contract.spec.internal.Response contractResponse,
Response pactResponse) {
Object parsedBody = BodyConverter.toSCCBody(pactResponse);
if (parsedBody instanceof Map) {
contractResponse.body((Map) parsedBody);
}
else if (parsedBody instanceof List) {
contractResponse.body((List) parsedBody);
}
else {
contractResponse.body(parsedBody.toString());
}
}
private void mapRequestBodyRules(org.springframework.cloud.contract.spec.internal.Request contractRequest,
Category bodyRules) {
contractRequest.bodyMatchers((bodyMatchers) -> {
bodyRules.getMatchingRules().forEach((key, matchingRuleGroup) -> {
if (matchingRuleGroup.getRuleLogic() != RuleLogic.AND) {
throw new UnsupportedOperationException(
"Currently only the AND combination rule logic is supported");
}
matchingRuleGroup.getRules().forEach((matchingRule) -> {
if (matchingRule instanceof RegexMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(((RegexMatcher) matchingRule).getRegex()));
}
else if (matchingRule instanceof DateMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byDate());
}
else if (matchingRule instanceof TimeMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byTime());
}
else if (matchingRule instanceof TimestampMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byTimestamp());
}
else if (matchingRule instanceof NumberTypeMatcher) {
switch (((NumberTypeMatcher) matchingRule).getNumberType()) {
case NUMBER:
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(RegexPatterns.number()));
break;
case INTEGER:
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(RegexPatterns.anInteger()));
break;
case DECIMAL:
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(RegexPatterns.aDouble()));
break;
default:
throw new RuntimeException("Unsupported number type!");
}
}
});
});
});
}
private void mapRequestBody(org.springframework.cloud.contract.spec.internal.Request contractRequest,
Request pactRequest) {
Object parsedBody = BodyConverter.toSCCBody(pactRequest);
if (parsedBody instanceof Map) {
contractRequest.body((Map) parsedBody);
}
else if (parsedBody instanceof List) {
contractRequest.body((List) parsedBody);
}
else {
contractRequest.body(parsedBody.toString());
}
}
private void mapRequestCookies(org.springframework.cloud.contract.spec.internal.Request contractRequest,
Request pactRequest) {
Category headerRules = pactRequest.getMatchingRules().rulesForCategory("header");
String[] splitCookiesHeader = pactRequest.getHeaders().get("Cookie").get(0).split(";");
Map<String, String> foundCookies = Stream.of(splitCookiesHeader).map((cookieHeader) -> cookieHeader.split("="))
.collect(Collectors.toMap(splittedCookieHeader -> splittedCookieHeader[0],
splittedCookieHeader -> splittedCookieHeader[1]));
contractRequest.cookies((cookies) -> foundCookies.forEach((key, value) -> {
if (headerRules.getMatchingRules().containsKey("Cookie")) {
MatchingRuleGroup matchingRuleGroup = headerRules.getMatchingRules().get("Cookie");
if (matchingRuleGroup.getRules().size() > 1) {
throw new UnsupportedOperationException(
"Currently only 1 rule at a time for a header is supported");
}
MatchingRule matchingRule = matchingRuleGroup.getRules().get(0);
if (matchingRule instanceof RegexMatcher) {
cookies.cookie(key,
contractRequest.$(
contractRequest.c(contractRequest.regex(((RegexMatcher) matchingRule).getRegex())),
contractRequest.p(value)));
}
else {
throw new UnsupportedOperationException(
"Currently only the header matcher of type regex is supported");
}
}
else {
cookies.cookie(key, value);
}
}));
}
private void mapResponseCookies(org.springframework.cloud.contract.spec.internal.Response contractResponse,
Response pactResponse) {
Category headerRules = pactResponse.getMatchingRules().rulesForCategory("header");
String[] splitCookiesHeader = pactResponse.getHeaders().get("Cookie").get(0).split(";");
Map<String, String> foundCookies = Stream.of(splitCookiesHeader).map((cookieHeader) -> cookieHeader.split("="))
.collect(Collectors.toMap(splittedCookieHeader -> splittedCookieHeader[0],
splittedCookieHeader -> splittedCookieHeader[1]));
contractResponse.cookies((cookies) -> foundCookies.forEach((key, value) -> {
if (headerRules.getMatchingRules().containsKey("Cookie")) {
MatchingRuleGroup matchingRuleGroup = headerRules.getMatchingRules().get("Cookie");
if (matchingRuleGroup.getRules().size() > 1) {
throw new UnsupportedOperationException(
"Currently only 1 rule at a time for a header is supported");
}
MatchingRule matchingRule = matchingRuleGroup.getRules().get(0);
if (matchingRule instanceof RegexMatcher) {
cookies.cookie(key, contractResponse.$(
contractResponse.p(
contractResponse.regex(Pattern.compile(((RegexMatcher) matchingRule).getRegex()))),
contractResponse.c(value)));
}
else {
throw new UnsupportedOperationException(
"Currently only the header matcher of type regex is supported");
}
}
else {
cookies.cookie(key, value);
}
}));
}
private void mapRequestUrl(org.springframework.cloud.contract.spec.internal.Request contractRequest,
Request pactRequest) {
if (MapUtils.isNotEmpty(pactRequest.getQuery())) {
contractRequest.url(pactRequest.getPath(),
(url) -> url.queryParameters((queryParameters) -> pactRequest.getQuery().forEach((key,
values) -> values.forEach((singleValue) -> queryParameters.parameter(key, singleValue)))));
}
else {
contractRequest.url(pactRequest.getPath());
}
}
private void mapRequestHeaders(org.springframework.cloud.contract.spec.internal.Request contractRequest,
Request pactRequest) {
Category headerRules = pactRequest.getMatchingRules().rulesForCategory("header");
contractRequest.headers((headers) -> pactRequest.getHeaders().forEach((key, values) -> {
if (key.equalsIgnoreCase("Cookie")) {
return;
}
if (headerRules.getMatchingRules().containsKey(key)) {
MatchingRuleGroup matchingRuleGroup = headerRules.getMatchingRules().get(key);
if (matchingRuleGroup.getRules().size() > 1) {
throw new UnsupportedOperationException(
"Currently only 1 rule at a time for a header is supported");
}
MatchingRule matchingRule = matchingRuleGroup.getRules().get(0);
if (matchingRule instanceof RegexMatcher) {
values.forEach((value) -> {
headers.header(key,
contractRequest.$(
contractRequest
.c(contractRequest.regex(((RegexMatcher) matchingRule).getRegex())),
contractRequest.p(value)));
});
}
else {
throw new UnsupportedOperationException(
"Currently only the header matcher of type regex is supported");
}
}
else {
values.forEach((value) -> headers.header(key, value));
}
}));
}
private void mapResponseHeaders(org.springframework.cloud.contract.spec.internal.Response contractResponse,
Response pactResponse) {
Category headerRules = pactResponse.getMatchingRules().rulesForCategory("header");
contractResponse.headers((headers) -> pactResponse.getHeaders().forEach((key, values) -> {
if (key.equalsIgnoreCase("Cookie")) {
return;
}
if (headerRules.getMatchingRules().containsKey(key)) {
MatchingRuleGroup matchingRuleGroup = headerRules.getMatchingRules().get(key);
if (matchingRuleGroup.getRules().size() > 1) {
throw new UnsupportedOperationException(
"Currently only 1 rule at a time for a header is supported");
}
MatchingRule matchingRule = matchingRuleGroup.getRules().get(0);
if (matchingRule instanceof RegexMatcher) {
values.forEach((value) -> {
headers.header(key,
contractResponse.$(
contractResponse.p(contractResponse
.regex(Pattern.compile(((RegexMatcher) matchingRule).getRegex()))),
contractResponse.c(value)));
});
}
else {
throw new UnsupportedOperationException(
"Currently only the header matcher of type regex is supported");
}
}
else {
values.forEach((value) -> headers.header(key, value));
}
}));
}
private String buildDescription(RequestResponseInteraction interaction) {
StringBuilder description = new StringBuilder(interaction.getDescription());
List<ProviderState> providerStates = interaction.getProviderStates();
for (ProviderState providerState : providerStates) {
description.append(" ").append(providerState.getName());
Map<String, Object> params = providerState.getParams();
if (MapUtils.isNotEmpty(params)) {
description.append("(");
params.forEach((k, v) -> {
description.append(k).append(": ").append(v.toString()).append(", ");
});
description.delete(description.length() - 2, description.length());
description.append(")");
}
}
return description.toString();
}
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright 2013-2020 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.verifier.spec.pact;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.regex.Pattern;
import au.com.dius.pact.core.model.generators.Category;
import au.com.dius.pact.core.model.generators.DateGenerator;
import au.com.dius.pact.core.model.generators.DateTimeGenerator;
import au.com.dius.pact.core.model.generators.Generator;
import au.com.dius.pact.core.model.generators.Generators;
import au.com.dius.pact.core.model.generators.RandomBooleanGenerator;
import au.com.dius.pact.core.model.generators.RandomDecimalGenerator;
import au.com.dius.pact.core.model.generators.RandomHexadecimalGenerator;
import au.com.dius.pact.core.model.generators.RandomIntGenerator;
import au.com.dius.pact.core.model.generators.RandomStringGenerator;
import au.com.dius.pact.core.model.generators.RegexGenerator;
import au.com.dius.pact.core.model.generators.TimeGenerator;
import au.com.dius.pact.core.model.generators.UuidGenerator;
import groovy.lang.GString;
import org.apache.commons.lang3.StringUtils;
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;
/**
* Convert a value for a given {@link Generator}.
*
* @author Tim Ysewyn
* @author Stessy Delcroix
* @since 2.0.0
*/
final class ValueGeneratorConverter {
private static final String INTEGER_PATTERN = "-?(\\d+)";
private static final Pattern INTEGER = Pattern.compile(INTEGER_PATTERN);
private static final String DECIMAL_PATTERN = "-?(\\d*\\.\\d+)";
private static final Pattern DECIMAL = Pattern.compile(DECIMAL_PATTERN);
private static final String HEX_PATTERN = "[a-fA-F0-9]+";
private static final Pattern HEX = Pattern.compile(HEX_PATTERN);
private static final String ALPHA_NUMERIC_PATTERN = "[a-zA-Z0-9]+";
private static final Pattern ALPHA_NUMERIC = Pattern.compile(ALPHA_NUMERIC_PATTERN);
private static final String UUID_PATTERN = "[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}";
private static final Pattern UUID = Pattern.compile(UUID_PATTERN);
private static final String ANY_DATE_PATTERN = "(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])";
private static final Pattern ANY_DATE = Pattern.compile(ANY_DATE_PATTERN);
private static final String ANY_TIME_PATTERN = "(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])";
private static final Pattern ANY_TIME = Pattern.compile(ANY_TIME_PATTERN);
private static final String ANY_DATE_TIME_PATTERN = "([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])";
private static final Pattern ANY_DATE_TIME = Pattern.compile(ANY_DATE_TIME_PATTERN);
private static final String TRUE_OR_FALSE_PATTERN = "(true|false)";
private static final Pattern TRUE_OR_FALSE = Pattern.compile(TRUE_OR_FALSE_PATTERN);
private ValueGeneratorConverter() {
}
static DslProperty<Object> convert(Generator generator,
BiFunction<Pattern, Object, DslProperty<Object>> dslPropertyProvider) {
Pattern pattern = null;
if (generator instanceof RandomIntGenerator) {
pattern = INTEGER;
}
else if (generator instanceof RandomDecimalGenerator) {
pattern = DECIMAL;
}
else if (generator instanceof RandomHexadecimalGenerator) {
pattern = HEX;
}
else if (generator instanceof RandomStringGenerator) {
pattern = ALPHA_NUMERIC;
}
else if (generator instanceof RegexGenerator) {
pattern = Pattern.compile(((RegexGenerator) generator).getRegex());
}
else if (generator instanceof UuidGenerator) {
pattern = UUID;
}
else if (generator instanceof DateGenerator) {
pattern = getDateTimePattern(((DateGenerator) generator).getFormat(), ANY_DATE);
}
else if (generator instanceof TimeGenerator) {
pattern = getDateTimePattern(((TimeGenerator) generator).getFormat(), ANY_TIME);
}
else if (generator instanceof DateTimeGenerator) {
pattern = getDateTimePattern(((DateTimeGenerator) generator).getFormat(), ANY_DATE_TIME);
}
else if (generator instanceof RandomBooleanGenerator) {
pattern = TRUE_OR_FALSE;
}
if (pattern == null) {
throw new UnsupportedOperationException(
"We currently don't support a generator of type " + generator.getClass().getSimpleName());
}
else {
Object generatedValue = generator.generate(new HashMap<>());
return dslPropertyProvider.apply(pattern, generatedValue);
}
}
private static Pattern getDateTimePattern(String format, Pattern defaultPattern) {
return StringUtils.isNotBlank(format) ? Pattern.compile(format) : defaultPattern;
}
static Generators extract(Body body, Function<DslProperty<?>, Object> dslPropertyValueProvider) {
Generators generators = new Generators();
traverse(body, dslPropertyValueProvider, "", generators, Category.BODY);
return generators;
}
static Generators extract(OutputMessage message, Function<DslProperty<?>, Object> dslPropertyValueProvider) {
Generators generators = new Generators();
traverse(message.getBody(), dslPropertyValueProvider, "", generators, Category.BODY);
return generators;
}
private static void traverse(Object value, Function<DslProperty<?>, Object> dslPropertyValueProvider, String path,
Generators generators, Category category) {
Object v = value;
if (v instanceof DslProperty) {
v = dslPropertyValueProvider.apply((DslProperty<?>) v);
}
if (v instanceof GString) {
v = ContentUtils.extractValue((GString) v, dslPropertyValueProvider);
}
if (v instanceof Map) {
((Map) v).forEach(
(key, val) -> traverse(val, dslPropertyValueProvider, path + "." + key, generators, category));
}
else if (v instanceof Collection) {
AtomicInteger index = new AtomicInteger();
((Collection<?>) v).forEach(val -> traverse(val, dslPropertyValueProvider,
path + "[" + index.getAndIncrement() + "]", generators, category));
}
else if (v instanceof DslProperty) {
traverse(v, dslPropertyValueProvider, path, generators, category);
}
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;
case DECIMAL_PATTERN:
generators.addGenerator(category, path, new RandomDecimalGenerator(10));
break;
case HEX_PATTERN:
generators.addGenerator(category, path, new RandomHexadecimalGenerator(10));
break;
case ALPHA_NUMERIC_PATTERN:
generators.addGenerator(category, path, new RandomStringGenerator(10));
break;
case UUID_PATTERN:
generators.addGenerator(category, path, UuidGenerator.INSTANCE);
break;
case ANY_DATE_PATTERN:
generators.addGenerator(category, path, new DateGenerator());
break;
case ANY_TIME_PATTERN:
generators.addGenerator(category, path, new TimeGenerator());
break;
case ANY_DATE_TIME_PATTERN:
generators.addGenerator(category, path, new DateTimeGenerator());
break;
case TRUE_OR_FALSE_PATTERN:
generators.addGenerator(category, path, RandomBooleanGenerator.INSTANCE);
break;
default:
generators.addGenerator(category, path, new RegexGenerator(regexProperty.pattern()));
break;
}
}
}
}

View File

@@ -42,7 +42,7 @@ class MethodBuilderSpec extends Specification {
}
}
}
File stubFile = new File("invalid-method:name.groovy")
File stubFile = new File("invalid-method;name.groovy")
ContractMetadata metadata = new ContractMetadata(stubFile.toPath(), false, 0, null, contractDsl)
SingleContractMetadata singleContractMetadata = new SingleContractMetadata(contractDsl, metadata)