Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions docs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<name>Spring Cloud Contract Docs</name>
<description>Spring Cloud Contract Docs</description>
<properties>
<jackson-module-jsonSchema.version>2.14.3</jackson-module-jsonSchema.version>
<jackson-module-jsonSchema.version>2.20.0</jackson-module-jsonSchema.version>
<docs.main>spring-cloud-contract</docs.main>
<main.basedir>${basedir}/..</main.basedir>
<configprops.inclusionPattern>spring.cloud.*</configprops.inclusionPattern>
Expand Down Expand Up @@ -113,10 +113,14 @@
<goal>exec</goal>
</goals>
<configuration>
<workingDirectory>${maven.multiModuleProjectDirectory}/docker/spring-cloud-contract-docker</workingDirectory>
<workingDirectory>
${maven.multiModuleProjectDirectory}/docker/spring-cloud-contract-docker
</workingDirectory>
<executable>./build_adocs.sh</executable>
<arguments>
<argument>${maven.multiModuleProjectDirectory}/docs/modules/ROOT/partials/</argument>
<argument>
${maven.multiModuleProjectDirectory}/docs/modules/ROOT/partials/
</argument>
</arguments>
</configuration>
</execution>
Expand All @@ -127,9 +131,13 @@
<goal>java</goal>
</goals>
<configuration>
<mainClass>org.springframework.cloud.contract.docs.Main</mainClass>
<mainClass>
org.springframework.cloud.contract.docs.Main
</mainClass>
<arguments>
<argument>${maven.multiModuleProjectDirectory}/docs</argument>
<argument>
${maven.multiModuleProjectDirectory}/docs
</argument>
</arguments>
</configuration>
</execution>
Expand Down Expand Up @@ -185,11 +193,18 @@
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-amqp</artifactId>
</dependency>
<!-- TODO: remove when replacements with Jackson 3 integration become available -->
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jsonSchema</artifactId>
<version>${jackson-module-jsonSchema.version}</version>
</dependency>
<!-- TODO: switch to Jackson 3 when jsonSchema replacements with Jackson 3 integration become available -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson-module-jsonSchema.version}</version>
</dependency>
</dependencies>
</profile>
</profiles>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tomakehurst.wiremock.common.Metadata;
import com.github.tomakehurst.wiremock.extension.Parameters;
import com.github.tomakehurst.wiremock.extension.PostServeActionDefinition;
Expand All @@ -30,12 +28,14 @@
import com.github.tomakehurst.wiremock.http.Fault;
import com.github.tomakehurst.wiremock.http.ResponseDefinition;
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.json.JsonMapper;

import org.springframework.cloud.contract.spec.Contract;

class DefaultWireMockStubPostProcessor implements WireMockStubPostProcessor {

private final ObjectMapper objectMapper = new ObjectMapper();
private final JsonMapper objectMapper = new JsonMapper();

@Override
public StubMapping postProcess(StubMapping stubMapping, Contract contract) {
Expand Down Expand Up @@ -143,7 +143,7 @@ else if (wiremock instanceof Map) {
try {
return StubMapping.buildFrom(this.objectMapper.writeValueAsString(wiremock));
}
catch (JsonProcessingException e) {
catch (JacksonException e) {
throw new IllegalStateException("Failed to build StubMapping for map [" + wiremock + "]", e);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,29 @@

package org.springframework.cloud.contract.verifier.wiremock;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.StreamSupport;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonPointer;
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.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.IntNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import java.util.Set;

import repackaged.nl.flotsam.xeger.Xeger;
import tools.jackson.core.JsonPointer;
import tools.jackson.core.util.DefaultIndenter;
import tools.jackson.core.util.DefaultPrettyPrinter;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.node.ArrayNode;
import tools.jackson.databind.node.IntNode;
import tools.jackson.databind.node.ObjectNode;
import tools.jackson.databind.node.StringNode;

import static org.apache.commons.text.StringEscapeUtils.escapeJava;
import static tools.jackson.core.json.JsonReadFeature.ALLOW_JAVA_COMMENTS;
import static tools.jackson.core.json.JsonReadFeature.ALLOW_SINGLE_QUOTES;
import static tools.jackson.core.json.JsonReadFeature.ALLOW_UNQUOTED_PROPERTY_NAMES;
import static tools.jackson.core.json.JsonReadFeature.ALLOW_YAML_COMMENTS;

/**
* Converts WireMock stubs into the DSL format.
Expand Down Expand Up @@ -69,14 +71,12 @@ public class WireMockToDslConverter {

private static final JsonPointer RESPONSE_HEADERS_POINTER = JsonPointer.compile("/response/headers");

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);
}
private static final JsonMapper OBJECT_MAPPER = JsonMapper.builder()
.configure(ALLOW_JAVA_COMMENTS, true)
.configure(ALLOW_YAML_COMMENTS, true)
.configure(ALLOW_UNQUOTED_PROPERTY_NAMES, true)
.configure(ALLOW_SINGLE_QUOTES, true)
.build();

/**
* Returns the string content of the contract.
Expand All @@ -100,15 +100,15 @@ private JsonNode parseStubDefinition(String wireMockStringStub) {
try {
return OBJECT_MAPPER.reader().readTree(wireMockStringStub);
}
catch (IOException e) {
catch (Exception e) {
throw new RuntimeException("WireMock string stub could not be read", e);
}
}

private String buildPriority(JsonNode wireMockStub) {
String priority = "";
JsonNode priorityNode = wireMockStub.at(PRIORITY_POINTER);
if (priorityNode != null && priorityNode.asInt() > 0) {
if (!priorityNode.isMissingNode() && priorityNode.asInt() > 0) {
priority = "priority " + priorityNode.asInt() + "\n";
}
return priority;
Expand All @@ -118,7 +118,7 @@ private String buildRequestMethod(JsonNode wireMockStub) {
String requestMethod = "";
JsonNode requestMethodNode = wireMockStub.at(REQUEST_METHOD_POINTER);
if (requestMethodNode != null) {
requestMethod = "method '" + requestMethodNode.asText() + "'\n";
requestMethod = "method '" + requestMethodNode.asString() + "'\n";
}
return requestMethod;
}
Expand All @@ -127,7 +127,7 @@ private String buildRequestUrl(JsonNode wireMockStub) {
String requestUrl = "";
JsonNode requestUrlNode = wireMockStub.at(REQUEST_URL_POINTER);
if (!requestUrlNode.isMissingNode()) {
requestUrl = "url '" + requestUrlNode.asText() + "'\n";
requestUrl = "url '" + requestUrlNode.asString() + "'\n";
}
return requestUrl;
}
Expand All @@ -136,7 +136,7 @@ private String buildRequestUrlPath(JsonNode wireMockStub) {
String requestUrlPath = "";
JsonNode requestUrlPathNode = wireMockStub.at(REQUEST_URL_PATH_POINTER);
if (!requestUrlPathNode.isMissingNode()) {
requestUrlPath = "url '" + requestUrlPathNode.asText() + "'\n";
requestUrlPath = "url '" + requestUrlPathNode.asString() + "'\n";
}
return requestUrlPath;
}
Expand All @@ -145,7 +145,7 @@ private String buildRequestUrlPattern(JsonNode wireMockStub) {
String requestUrlPattern = "";
JsonNode requestUrlPatternNode = wireMockStub.at(REQUEST_URL_PATTERN_POINTER);
if (!requestUrlPatternNode.isMissingNode()) {
String escapedRequestUrlPatternValue = escapeJava(requestUrlPatternNode.asText());
String escapedRequestUrlPatternValue = escapeJava(requestUrlPatternNode.asString());
requestUrlPattern = "url $(consumer(regex('" + escapedRequestUrlPatternValue + "')), producer('"
+ new Xeger(escapedRequestUrlPatternValue).generate() + "'))\n";
}
Expand All @@ -156,7 +156,7 @@ private String buildRequestUrlPathPattern(JsonNode wireMockStub) {
String requestUrlPathPattern = "";
JsonNode requestUrlPathPatternNode = wireMockStub.at(REQUEST_URL_PATH_PATTERN_POINTER);
if (!requestUrlPathPatternNode.isMissingNode()) {
String escapedRequestUrlPathPatternValue = escapeJava(requestUrlPathPatternNode.asText());
String escapedRequestUrlPathPatternValue = escapeJava(requestUrlPathPatternNode.asString());
requestUrlPathPattern = "urlPath $(consumer(regex('" + escapedRequestUrlPathPatternValue + "')), producer('"
+ new Xeger(escapedRequestUrlPathPatternValue).generate() + "'))'\n";
}
Expand All @@ -169,13 +169,17 @@ private String buildRequestHeaders(JsonNode wireMockStub) {

if (requestHeadersNode.isObject()) {
requestHeadersBuilder.append("headers {\n");
ObjectNode requestHeadersObjectNode = requestHeadersNode.deepCopy();
Iterator<Map.Entry<String, JsonNode>> fields = requestHeadersObjectNode.fields();
fields.forEachRemaining(c -> {
JsonNode requestHeadersObjectNode = requestHeadersNode.deepCopy();
Set<Map.Entry<String, JsonNode>> fields = requestHeadersObjectNode.properties();
fields.forEach(c -> {
requestHeadersBuilder.append("header('").append(c.getKey()).append("',");
Map.Entry<String, JsonNode> headerValue = c.getValue().deepCopy().fields().next();
String header = buildHeader(headerValue.getKey(), headerValue.getValue().asText());
requestHeadersBuilder.append(header).append(")").append("\n");
ObjectNode headersNode = (ObjectNode) c.getValue().deepCopy();
Iterator<Map.Entry<String, JsonNode>> headersNodeIterator = headersNode.properties().iterator();
if (headersNodeIterator.hasNext()) {
Map.Entry<String, JsonNode> headerValue = headersNodeIterator.next();
String header = buildHeader(headerValue.getKey(), headerValue.getValue().asString());
requestHeadersBuilder.append(header).append(")").append("\n");
}
});
requestHeadersBuilder.append("}");
}
Expand All @@ -198,30 +202,30 @@ private String buildRequestBody(JsonNode wireMockStub) {
final StringBuilder requestBody = new StringBuilder();
JsonNode requestBodyNode = wireMockStub.at(REQUEST_BODY_POINTER);
if (requestBodyNode.isArray()) {
ArrayNode requestBodyArrayNode = requestBodyNode.deepCopy();
Iterator<JsonNode> elements = requestBodyArrayNode.elements();
Iterable<JsonNode> iterableFields = () -> elements;
ArrayNode requestBodyArrayNode = (ArrayNode) requestBodyNode.deepCopy();
Collection<JsonNode> elements = requestBodyArrayNode.elements();
List<Map.Entry<String, JsonNode>> requestBodyObjectNodes = new ArrayList<>();
StreamSupport.stream(iterableFields.spliterator(), false)

elements.stream()
.filter(f -> f instanceof ObjectNode)
.map(f -> (ObjectNode) f)
.map(ObjectNode::fields)
.forEachOrdered(i -> i.forEachRemaining(requestBodyObjectNodes::add));
.map(ObjectNode::properties)
.forEachOrdered(requestBodyObjectNodes::addAll);
requestBodyObjectNodes.stream()
.filter(b -> b.getKey().equals("equalTo"))
.findFirst()
.ifPresent(b -> requestBody.append("body ('").append(b.getValue().asText()).append("')"));
.ifPresent(b -> requestBody.append("body ('").append(b.getValue().asString()).append("')"));
requestBodyObjectNodes.stream()
.filter(b -> b.getKey().equals("equalToJson"))
.findFirst()
.ifPresent(b -> requestBody.append("body ('").append(b.getValue().asText()).append("')"));
.ifPresent(b -> requestBody.append("body ('").append(b.getValue().asString()).append("')"));
requestBodyObjectNodes.stream()
.filter(b -> b.getKey().equals("matches"))
.findFirst()
.ifPresent(b -> requestBody.append("body $(consumer(regex('")
.append(escapeJava(b.getValue().asText()))
.append(escapeJava(b.getValue().asString()))
.append("')), producer('")
.append(new Xeger(escapeJava(b.getValue().asText())).generate())
.append(new Xeger(escapeJava(b.getValue().asString())).generate())
.append("'))"));
}
return requestBody.toString();
Expand All @@ -243,28 +247,29 @@ private String buildResponseBody(JsonNode wireMockStub) {
if (responseBodyNode.isInt()) {
responseBody += "body( " + escapeJava(buildPrettyPrintResponseBody((IntNode) responseBodyNode)) + ")\n";
}
if (responseBodyNode.isTextual()) {
responseBody += "body( \"" + escapeJava(buildPrettyPrintResponseBody((TextNode) responseBodyNode))
if (responseBodyNode.isString()) {
responseBody += "body( \"" + escapeJava(buildPrettyPrintResponseBody((StringNode) responseBodyNode))
+ "\")\n";
}
return responseBody;
}

private String buildPrettyPrintResponseBody(IntNode node) {
return node.asText();
return node.asString();
}

private String buildPrettyPrintResponseBody(TextNode node) {
private String buildPrettyPrintResponseBody(StringNode node) {
try {
String textNode = node.asText();
Object intermediateObjectForPrettyPrinting = OBJECT_MAPPER.reader().readValue(textNode, Object.class);
String stringNode = node.asString();
Object intermediateObjectForPrettyPrinting = OBJECT_MAPPER.readerFor(Object.class).readValue(stringNode);

DefaultIndenter customIndenter = new DefaultIndenter(" ", "\n");
return OBJECT_MAPPER
.writer(new PrivatePrettyPrinter().withArrayIndenter(customIndenter).withObjectIndenter(customIndenter))
return OBJECT_MAPPER.writer()
.with(new DefaultPrettyPrinter().withArrayIndenter(customIndenter).withObjectIndenter(customIndenter))
.writeValueAsString(intermediateObjectForPrettyPrinting);
}
catch (IOException e) {
throw new RuntimeException("WireMock response body could not be pretty printed");
catch (Exception e) {
throw new RuntimeException("WireMock response body could not be pretty printed", e);
}
}

Expand All @@ -274,33 +279,17 @@ private String buildResponseHeaders(JsonNode wireMockStub) {

if (requestHeadersNode.isObject()) {
responseHeadersBuilder.append("headers {\n");
ObjectNode responseHeadersObjectNode = requestHeadersNode.deepCopy();
Iterator<Map.Entry<String, JsonNode>> fields = responseHeadersObjectNode.fields();
fields.forEachRemaining(c -> responseHeadersBuilder.append("header('")
JsonNode responseHeadersObjectNode = requestHeadersNode.deepCopy();
Set<Map.Entry<String, JsonNode>> fields = responseHeadersObjectNode.properties();
fields.forEach(c -> responseHeadersBuilder.append("header('")
.append(c.getKey())
.append("',")
.append("'")
.append(c.getValue().asText())
.append(c.getValue().asString())
.append("')\n"));
responseHeadersBuilder.append("}");
}
return responseHeadersBuilder.toString();
}

private static class PrivatePrettyPrinter extends DefaultPrettyPrinter {

@Override
public DefaultPrettyPrinter createInstance() {
return new PrivatePrettyPrinter();
}

@Override
public DefaultPrettyPrinter withSeparators(Separators separators) {
_separators = separators;
_objectFieldValueSeparatorWithSpaces = separators.getObjectFieldValueSeparator() + " ";
return this;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,10 @@
import java.util.Map;
import java.util.stream.Collectors;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tomakehurst.wiremock.extension.PostServeActionDefinition;
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
import org.junit.jupiter.api.Test;
import tools.jackson.databind.json.JsonMapper;

import org.springframework.cloud.contract.spec.Contract;

Expand Down Expand Up @@ -119,10 +118,10 @@ void should_merge_stub_mappings_when_stub_mapping_is_stub_mapping() {
}

@Test
void should_merge_stub_mappings_when_stub_mapping_is_map() throws JsonProcessingException {
void should_merge_stub_mappings_when_stub_mapping_is_map() {
Contract contract = new Contract();
Map<String, Object> map = new HashMap<>();
map.put("stubMapping", new ObjectMapper().readValue(POST_SERVE_ACTION, HashMap.class));
map.put("stubMapping", new JsonMapper().readValue(POST_SERVE_ACTION, HashMap.class));
contract.getMetadata().put("wiremock", map);
StubMapping stubMapping = StubMapping.buildFrom(STUB_MAPPING);

Expand Down
Loading
Loading