Rewrite WireMockToDslConverter to Java (#1471)

* Rewrite WireMockToDslConverter to Java

* Fixes imports and code formatting

part of gh-1470
This commit is contained in:
Stessy Delcroix
2020-08-18 11:57:22 +02:00
committed by GitHub
parent 7e08c42e8f
commit fca9b69766
2 changed files with 321 additions and 252 deletions

View File

@@ -1,252 +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.wiremock
import java.nio.charset.StandardCharsets
import java.util.regex.Pattern
import groovy.io.FileType
import groovy.json.JsonOutput
import groovy.json.JsonParserType
import groovy.json.JsonSlurper
import groovy.transform.CompileDynamic
import groovy.xml.XmlUtil
import repackaged.nl.flotsam.xeger.Xeger
import org.springframework.cloud.contract.spec.internal.RegexProperty
import org.springframework.cloud.contract.verifier.util.ContentUtils
import org.springframework.cloud.contract.spec.Contract
import static org.apache.commons.text.StringEscapeUtils.escapeJava
/**
* Converts WireMock stubs into the DSL format
*
* @author Marcin Grzejszczak
* @author Konstantin Shevchuk
*
* @since 1.0.0
*/
@CompileDynamic
class WireMockToDslConverter {
/**
* @return the string content of the contract
*
* @param wireMockStringStub - string content of the WireMock JSON stub
*/
static String fromWireMockStub(String wireMockStringStub) {
return new WireMockToDslConverter().convertFromWireMockStub(wireMockStringStub)
}
private String convertFromWireMockStub(String wireMockStringStub) {
Object wireMockStub = parseStubDefinition(wireMockStringStub)
Integer priority = wireMockStub.priority
def request = wireMockStub.request
def response = wireMockStub.response
def bodyPatterns = request.bodyPatterns
String urlPattern = request.urlPattern
String urlPathPattern = request.urlPathPattern
return """\
${priority ? "priority ${priority}" : ''}
request {
${request.method ? "method \"\"\"$request.method\"\"\"" : ""}
${request.url ? "url \"\"\"$request.url\"\"\"" : ""}
${
urlPattern ? "url \$(consumer(regex('${escapeJava(urlPattern)}')), producer('${new Xeger(escapeJava(urlPattern)).generate()}'))" : ""
}
${
urlPathPattern ? "urlPath \$(consumer(regex('${escapeJava(urlPattern)}')), producer('${new Xeger(escapeJava(urlPattern)).generate()}'))" : ""
}
${request.urlPath ? "url \"\"\"$request.urlPath\"\"\"" : ""}
${
request.headers ? """headers {
${
request.headers.collect {
def assertion = it.value
String headerName = it.key as String
def entry = assertion.entrySet().first()
"""header(\"\"\"$headerName\"\"\", ${
buildRequestHeader(entry.key, entry.value)
})\n"""
}.join('')
}
}
""" : ""
}
${
bodyPatterns?.equalTo?.
every { it } ? "body('''${bodyPatterns.equalTo[0]}''')" : ''
}
${
bodyPatterns?.equalToJson?.
every { it } ? "body('''${bodyPatterns.equalToJson[0]}''')" : ''
}
${
bodyPatterns?.matches?.every {
it
} ? "body \$(consumer(regex('${escapeJava(bodyPatterns.matches[0])}')), producer('${new Xeger(escapeJava(bodyPatterns.matches[0])).generate()}'))" : ""
}
}
response {
${response.status ? "status $response.status" : ""}
${response.body ? "body( ${buildBody(response.body)})" : ""}
${
response.headers ? """headers {
${
response.headers.collect { "header('$it.key': '${it.value}')\n" }.join('')
}
}
""" : ""
}
}
"""
}
private Object parseStubDefinition(String wireMockStringStub) {
return new JsonSlurper().setType(JsonParserType.LAX).parseText(wireMockStringStub)
}
private String buildRequestHeader(String method, Object value) {
switch (method) {
case 'equalTo':
return wrapWithMultilineGString(value)
case 'contains':
String regex = '^.*' + value.toString() + '.*$'
String escapedRegex = escapeJava(regex)
String wrappedRegex = wrapWithMultilineGString(escapedRegex)
return 'c(regex(' + wrappedRegex + '))'
default:
String regex = value.toString()
String escapedRegex = escapeJava(regex)
String wrappedRegex = wrapWithMultilineGString(escapedRegex)
return 'c(regex(' + wrappedRegex + '))'
}
}
private Object buildBody(Map responseBody) {
return responseBody.entrySet().collectAll(withQuotedMapStringElements()).
inject([:], appendToIterable())
}
private Object buildBody(List responseBody) {
return responseBody.collectAll(withQuotedStringElements()).
inject([], appendToIterable())
}
private Object buildBody(Integer responseBody) {
return responseBody
}
private Object buildBody(String responseBody) {
try {
def json = new JsonSlurper().parseText(responseBody)
return wrapWithMultilineGString(JsonOutput.prettyPrint(responseBody))
}
catch (Exception jsonException) {
try {
def xml = ContentUtils.getXmlSlurperWithDefaultErrorHandler().parseText(responseBody)
return wrapWithMultilineGString(XmlUtil.serialize(responseBody))
}
catch (Exception xmlException) {
return wrapWithMultilineGString(responseBody)
}
}
}
private String wrapWithMultilineGString(String string) {
return """'''$string'''"""
}
private Closure withQuotedMapStringElements() {
return {
[(it.key): convert(it.value)]
}
}
private Closure withQuotedStringElements() {
return {
convert(it)
}
}
private Closure appendToIterable() {
return {
acc, el -> acc << el
}
}
private Object convert(Object element) {
return element
}
private Object convert(String element) {
return quoteString(element)
}
private String quoteString(String element) {
if (element =~ /^".*"$/) {
return element
}
return """\"\"\"${escapeJava(element)}\"\"\""""
}
private Object convert(List element) {
return element.collect {
convert(it)
}
}
private Object convert(Map element) {
return element.collectEntries {
[(it.key): convert(it.value)]
}
}
static void main(String[] args) {
String rootOfFolderWithStubs = args[0]
new File(rootOfFolderWithStubs).eachFileRecurse(FileType.FILES) {
try {
if (!it.name.endsWith('json')) {
return
}
String dslFromWireMockStub =
fromWireMockStub(it.getText(StandardCharsets.UTF_8.toString()))
String dslWrappedWithFactoryMethod =
wrapWithFactoryMethod(dslFromWireMockStub)
File newGroovyFile = new File(it.parent, it.name.
replaceAll('json', 'groovy'))
println("Creating new groovy file [$newGroovyFile.path]")
newGroovyFile.setText(dslWrappedWithFactoryMethod, StandardCharsets.UTF_8.
toString())
}
catch (Exception e) {
System.err.println(e)
}
}
}
static String wrapWithFactoryMethod(String dslFromWireMockStub) {
return """\
${Contract.name}.make {
$dslFromWireMockStub
}
"""
}
}

View File

@@ -0,0 +1,321 @@
/*
* 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.wiremock;
import java.io.IOException;
import java.util.ArrayList;
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 repackaged.nl.flotsam.xeger.Xeger;
import static org.apache.commons.text.StringEscapeUtils.escapeJava;
/**
* Converts WireMock stubs into the DSL format.
*
* @author Marcin Grzejszczak
* @author Konstantin Shevchuk
* @since 1.0.0
*/
public class WireMockToDslConverter {
private static final JsonPointer PRIORITY_POINTER = JsonPointer.compile("/priority");
private static final JsonPointer REQUEST_METHOD_POINTER = JsonPointer.compile("/request/method");
private static final JsonPointer REQUEST_URL_POINTER = JsonPointer.compile("/request/url");
private static final JsonPointer REQUEST_URL_PATH_POINTER = JsonPointer.compile("/request/urlPath");
private static final JsonPointer REQUEST_URL_PATTERN_POINTER = JsonPointer.compile("/request/urlPattern");
private static final JsonPointer REQUEST_URL_PATH_PATTERN_POINTER = JsonPointer.compile("/request/urlPathPattern");
private static final JsonPointer REQUEST_HEADERS_POINTER = JsonPointer.compile("/request/headers");
private static final JsonPointer REQUEST_BODY_POINTER = JsonPointer.compile("/request/bodyPatterns");
private static final JsonPointer RESPONSE_STATUS_POINTER = JsonPointer.compile("/response/status");
private static final JsonPointer RESPONSE_BODY_POINTER = JsonPointer.compile("/response/body");
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);
}
/**
* Returns the string content of the contract.
*
* @param wireMockStringStub - string content of the WireMock JSON stub
*/
public static String fromWireMockStub(String wireMockStringStub) {
return new WireMockToDslConverter().convertFromWireMockStub(wireMockStringStub);
}
private String convertFromWireMockStub(String wireMockStringStub) {
JsonNode wireMockStub = parseStubDefinition(wireMockStringStub);
return buildPriority(wireMockStub)
+ "request {\n"
+ buildRequestMethod(wireMockStub)
+ buildRequestUrl(wireMockStub)
+ buildRequestUrlPattern(wireMockStub)
+ buildRequestUrlPathPattern(wireMockStub)
+ buildRequestUrlPath(wireMockStub)
+ buildRequestHeaders(wireMockStub)
+ buildRequestBody(wireMockStub)
+ "}\n"
+ "response {\n"
+ buildResponseStatus(wireMockStub)
+ buildResponseBody(wireMockStub)
+ buildResponseHeaders(wireMockStub)
+ "}"
+ "\n";
}
private JsonNode parseStubDefinition(String wireMockStringStub) {
try {
return OBJECT_MAPPER.reader().readTree(wireMockStringStub);
}
catch (IOException 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) {
priority = "priority " + priorityNode.asInt() + "\n";
}
return priority;
}
private String buildRequestMethod(JsonNode wireMockStub) {
String requestMethod = "";
JsonNode requestMethodNode = wireMockStub.at(REQUEST_METHOD_POINTER);
if (requestMethodNode != null) {
requestMethod = "method '" + requestMethodNode.asText() + "'\n";
}
return requestMethod;
}
private String buildRequestUrl(JsonNode wireMockStub) {
String requestUrl = "";
JsonNode requestUrlNode = wireMockStub.at(REQUEST_URL_POINTER);
if (!requestUrlNode.isMissingNode()) {
requestUrl = "url '" + requestUrlNode.asText() + "'\n";
}
return requestUrl;
}
private String buildRequestUrlPath(JsonNode wireMockStub) {
String requestUrlPath = "";
JsonNode requestUrlPathNode = wireMockStub.at(REQUEST_URL_PATH_POINTER);
if (!requestUrlPathNode.isMissingNode()) {
requestUrlPath = "url '" + requestUrlPathNode.asText() + "'\n";
}
return requestUrlPath;
}
private String buildRequestUrlPattern(JsonNode wireMockStub) {
String requestUrlPattern = "";
JsonNode requestUrlPatternNode = wireMockStub.at(REQUEST_URL_PATTERN_POINTER);
if (!requestUrlPatternNode.isMissingNode()) {
String escapedRequestUrlPatternValue = escapeJava(requestUrlPatternNode.asText());
requestUrlPattern = "url $(consumer(regex('" + escapedRequestUrlPatternValue
+ "')), producer('"
+ new Xeger(escapedRequestUrlPatternValue).generate() + "'))\n";
}
return requestUrlPattern;
}
private String buildRequestUrlPathPattern(JsonNode wireMockStub) {
String requestUrlPathPattern = "";
JsonNode requestUrlPathPatternNode = wireMockStub.at(REQUEST_URL_PATH_PATTERN_POINTER);
if (!requestUrlPathPatternNode.isMissingNode()) {
String escapedRequestUrlPathPatternValue = escapeJava(requestUrlPathPatternNode.asText());
requestUrlPathPattern = "urlPath $(consumer(regex('"
+ escapedRequestUrlPathPatternValue + "')), producer('"
+ new Xeger(escapedRequestUrlPathPatternValue).generate() + "'))'\n";
}
return requestUrlPathPattern;
}
private String buildRequestHeaders(JsonNode wireMockStub) {
final StringBuilder requestHeadersBuilder = new StringBuilder();
JsonNode requestHeadersNode = wireMockStub.at(REQUEST_HEADERS_POINTER);
if (requestHeadersNode.isObject()) {
requestHeadersBuilder.append("headers {\n");
ObjectNode requestHeadersObjectNode = requestHeadersNode.deepCopy();
Iterator<Map.Entry<String, JsonNode>> fields = requestHeadersObjectNode.fields();
fields.forEachRemaining(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");
});
requestHeadersBuilder.append("}");
}
return requestHeadersBuilder.toString();
}
private String buildHeader(String method, String value) {
switch (method) {
case "equalTo":
return "'" + value + "'";
case "contains":
String regex = "^.*" + value + ".*$";
return "c(regex('" + escapeJava(regex) + "'))";
default:
return "c(regex('" + escapeJava(value) + "'))";
}
}
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;
List<Map.Entry<String, JsonNode>> requestBodyObjectNodes = new ArrayList<>();
StreamSupport.stream(iterableFields.spliterator(), false)
.filter(f -> f instanceof ObjectNode)
.map(f -> (ObjectNode) f)
.map(ObjectNode::fields)
.forEachOrdered(i -> i.forEachRemaining(requestBodyObjectNodes::add));
requestBodyObjectNodes.stream()
.filter(b -> b.getKey().equals("equalTo"))
.findFirst()
.ifPresent(b -> requestBody.append("body ('").append(b.getValue().asText()).append("')"));
requestBodyObjectNodes.stream()
.filter(b -> b.getKey().equals("equalToJson"))
.findFirst()
.ifPresent(b -> requestBody.append("body ('").append(b.getValue().asText()).append("')"));
requestBodyObjectNodes.stream().filter(b -> b.getKey().equals("matches"))
.findFirst()
.ifPresent(b -> requestBody.append("body $(consumer(regex('")
.append(escapeJava(b.getValue().asText()))
.append("')), producer('")
.append(new Xeger(escapeJava(b.getValue().asText())).generate())
.append("'))"));
}
return requestBody.toString();
}
private String buildResponseStatus(JsonNode wireMockStub) {
String responseStatus = "";
JsonNode responseStatusNode = wireMockStub.at(RESPONSE_STATUS_POINTER);
if (!responseStatusNode.isMissingNode()) {
int responseStatusValue = responseStatusNode.asInt();
responseStatus += "status " + responseStatusValue + "\n";
}
return responseStatus;
}
private String buildResponseBody(JsonNode wireMockStub) {
String responseBody = "";
JsonNode responseBodyNode = wireMockStub.at(RESPONSE_BODY_POINTER);
if (responseBodyNode.isInt()) {
responseBody += "body( " + escapeJava(buildPrettyPrintResponseBody((IntNode) responseBodyNode)) + ")\n";
}
if (responseBodyNode.isTextual()) {
responseBody += "body( \"" + escapeJava(buildPrettyPrintResponseBody((TextNode) responseBodyNode)) + "\")\n";
}
return responseBody;
}
private String buildPrettyPrintResponseBody(IntNode node) {
return node.asText();
}
private String buildPrettyPrintResponseBody(TextNode node) {
try {
String textNode = node.asText();
Object intermediateObjectForPrettyPrinting = OBJECT_MAPPER.reader()
.readValue(textNode, Object.class);
DefaultIndenter customIndenter = new DefaultIndenter(" ", "\n");
return OBJECT_MAPPER
.writer(new PrivatePrettyPrinter()
.withArrayIndenter(customIndenter)
.withObjectIndenter(customIndenter))
.writeValueAsString(intermediateObjectForPrettyPrinting);
}
catch (IOException e) {
throw new RuntimeException("WireMock response body could not be pretty printed");
}
}
private String buildResponseHeaders(JsonNode wireMockStub) {
final StringBuilder responseHeadersBuilder = new StringBuilder();
JsonNode requestHeadersNode = wireMockStub.at(RESPONSE_HEADERS_POINTER);
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('")
.append(c.getKey())
.append("',")
.append("'")
.append(c.getValue().asText())
.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;
}
}
}