Skip to content

Commit

Permalink
Restricted JsonUnit implementation of EqualToJson to JRE8, since this…
Browse files Browse the repository at this point in the history
… is the minimum supported version for the library.
  • Loading branch information
tomakehurst committed Jan 31, 2020
1 parent 0cdca58 commit ef4cf34
Show file tree
Hide file tree
Showing 7 changed files with 511 additions and 72 deletions.
19 changes: 17 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ allprojects {
compile("com.github.jknack:handlebars-helpers:$versions.handlebars") {
exclude group: 'org.mozilla', module: 'rhino'
}
compile 'net.javacrumbs.json-unit:json-unit-core:2.12.0'

compile 'com.flipkart.zjsonpatch:zjsonpatch:0.4.4', {
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-databind'
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-core'
}

compile 'commons-fileupload:commons-fileupload:1.4'

Expand Down Expand Up @@ -175,7 +179,18 @@ subprojects {
apply plugin: 'signing'

sourceSets {
main.java.srcDir project(':').sourceSets.main.java
// def isJava7 = project.name in ['wiremock', 'java7']
// println "$project.name"
// if (isJava7) {
// println "$project.name: Using Java 7 EqualToJsonPattern"
// main.java.srcDir project(':').sourceSets.main.java
// } else {
// println "$project.name: Excluding Java 7 EqualToJsonPattern"
// main.java.srcDir(project(':').sourceSets.main.java.exclude('com/github/tomakehurst/wiremock/matching/EqualToJsonPattern*'))
// }

main.java.srcDir project(':').sourceSets.main.java.exclude('com/github/tomakehurst/wiremock/matching/EqualToJsonPattern*')

test.java.srcDir project(':').sourceSets.test.java
test.scala.srcDir project(':').sourceSets.test.scala
}
Expand Down
6 changes: 5 additions & 1 deletion docs-v2/_docs/request-matching.md
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,11 @@ would match a request with a JSON body of:
```

It's also possible to use placeholders that constrain the expected value by type or regular expression.
See [the JsonUnit placeholders documentation](https://github.com/lukas-krecan/JsonUnit#typeplc) for the full syntax.
See [the JsonUnit placeholders documentation](https://github.com/lukas-krecan/JsonUnit#typeplc) for the full syntax.

> **note**
>
> Placeholders are only available in the `jre8` WireMock JARs, as the JsonUnit library requires at least Java 8.
### JSON Path

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/*
* Copyright (C) 2011 Thomas Akehurst
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.tomakehurst.wiremock.matching;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.flipkart.zjsonpatch.DiffFlags;
import com.flipkart.zjsonpatch.JsonDiff;
import com.github.tomakehurst.wiremock.common.Json;
import com.google.common.base.Function;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

import java.util.EnumSet;
import java.util.List;
import java.util.Objects;

import static com.flipkart.zjsonpatch.DiffFlags.OMIT_COPY_OPERATION;
import static com.flipkart.zjsonpatch.DiffFlags.OMIT_MOVE_OPERATION;
import static com.github.tomakehurst.wiremock.common.Json.deepSize;
import static com.github.tomakehurst.wiremock.common.Json.maxDeepSize;
import static com.google.common.collect.Iterables.getLast;
import static org.apache.commons.lang3.math.NumberUtils.isNumber;

public class EqualToJsonPattern extends StringValuePattern {

private final JsonNode expected;
private final Boolean ignoreArrayOrder;
private final Boolean ignoreExtraElements;
private final Boolean serializeAsString;

public EqualToJsonPattern(@JsonProperty("equalToJson") String json,
@JsonProperty("ignoreArrayOrder") Boolean ignoreArrayOrder,
@JsonProperty("ignoreExtraElements") Boolean ignoreExtraElements) {
super(json);
expected = Json.read(json, JsonNode.class);
this.ignoreArrayOrder = ignoreArrayOrder;
this.ignoreExtraElements = ignoreExtraElements;
this.serializeAsString = true;
}

public EqualToJsonPattern(JsonNode jsonNode,
Boolean ignoreArrayOrder,
Boolean ignoreExtraElements) {
super(Json.write(jsonNode));
expected = jsonNode;
this.ignoreArrayOrder = ignoreArrayOrder;
this.ignoreExtraElements = ignoreExtraElements;
this.serializeAsString = false;
}

@JsonProperty("equalToJson")
public Object getSerializedEqualToJson() {
return serializeAsString ? getValue() : expected;
}

public String getEqualToJson() {
return expectedValue;
}

private boolean shouldIgnoreArrayOrder() {
return ignoreArrayOrder != null && ignoreArrayOrder;
}

public Boolean isIgnoreArrayOrder() {
return ignoreArrayOrder;
}

private boolean shouldIgnoreExtraElements() {
return ignoreExtraElements != null && ignoreExtraElements;
}

public Boolean isIgnoreExtraElements() {
return ignoreExtraElements;
}

@Override
public String getExpected() {
return Json.prettyPrint(getValue());
}

@Override
public MatchResult match(String value) {
try {
final JsonNode actual = Json.read(value, JsonNode.class);

return new MatchResult() {
@Override
public boolean isExactMatch() {
// Try to do it the fast way first, then fall back to doing the full diff
if (!shouldIgnoreArrayOrder() && !shouldIgnoreExtraElements()) {
return Objects.equals(actual, expected);
}

return getDistance() == 0.0;
}

@Override
public double getDistance() {
EnumSet<DiffFlags> flags = EnumSet.of(OMIT_COPY_OPERATION);
ArrayNode diff = (ArrayNode) JsonDiff.asJson(expected, actual, flags);

double maxNodes = maxDeepSize(expected, actual);
return diffSize(diff) / maxNodes;
}
};
} catch (Exception e) {
return MatchResult.noMatch();
}
}

private int diffSize(ArrayNode diff) {
int acc = 0;
for (JsonNode child: diff) {
String operation = child.findValue("op").textValue();
JsonNode pathString = getFromPathString(operation, child);
List<String> path = getPath(pathString.textValue());
if (!arrayOrderIgnoredAndIsArrayMove(operation, path) && !extraElementsIgnoredAndIsAddition(operation)) {
JsonNode valueNode = operation.equals("remove") ? null : child.findValue("value");
JsonNode referencedExpectedNode = getNodeAtPath(expected, pathString);
if (valueNode == null) {
acc += deepSize(referencedExpectedNode);
} else {
acc += maxDeepSize(referencedExpectedNode, valueNode);
}
}
}

return acc;
}

private static JsonNode getFromPathString(String operation, JsonNode node) {
if (operation.equals("move")) {
return node.findValue("from");
}

return node.findValue("path");
}

private boolean extraElementsIgnoredAndIsAddition(String operation) {
return operation.equals("add") && shouldIgnoreExtraElements();
}

private boolean arrayOrderIgnoredAndIsArrayMove(String operation, List<String> path) {
return operation.equals("move") && isNumber(getLast(path)) && shouldIgnoreArrayOrder();
}

public static JsonNode getNodeAtPath(JsonNode rootNode, JsonNode path) {
String pathString = path.toString().equals("\"/\"") ? "\"\"" : path.toString();
return getNode(rootNode, getPath(pathString), 1);
}

private static JsonNode getNode(JsonNode ret, List<String> path, int pos) {
if (pos >= path.size()) {
return ret;
}

if (ret == null) {
return null;
}

String key = path.get(pos);
if (ret.isArray()) {
int keyInt = Integer.parseInt(key.replaceAll("\"", ""));
return getNode(ret.get(keyInt), path, ++pos);
} else if (ret.isObject()) {
if (ret.has(key)) {
return getNode(ret.get(key), path, ++pos);
}
return null;
} else {
return ret;
}
}

private static List<String> getPath(String path) {
List<String> paths = Splitter.on('/').splitToList(path.replaceAll("\"", ""));
return Lists.newArrayList(Iterables.transform(paths, new DecodePathFunction()));
}

private final static class DecodePathFunction implements Function<String, String> {

@Override
public String apply(String path) {
return path.replaceAll("~1", "/").replaceAll("~0", "~"); // see http://tools.ietf.org/html/rfc6901#section-4
}
}

}
6 changes: 2 additions & 4 deletions java8/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ dependencies {
compile "org.eclipse.jetty:jetty-alpn-server:$jettyVersion"
compile "org.eclipse.jetty:jetty-alpn-conscrypt-server:$jettyVersion"
compile "org.eclipse.jetty:jetty-alpn-conscrypt-client:$jettyVersion"
compile 'net.javacrumbs.json-unit:json-unit-core:2.12.0'

testCompile "org.eclipse.jetty:jetty-client:$jettyVersion"
testCompile "org.eclipse.jetty.http2:http2-http-client-transport:$jettyVersion"
}

def getAlpnJarPath() {
project.configurations.compile.find { it.name.startsWith("alpn-boot-") }
}
Loading

0 comments on commit ef4cf34

Please sign in to comment.