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
113 changes: 74 additions & 39 deletions karate-core/src/main/java/com/intuit/karate/Match.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/
package com.intuit.karate;

import com.intuit.karate.MatchOperator.CoreOperator;
import com.intuit.karate.graal.JsEngine;
import java.lang.reflect.Array;
import java.util.ArrayList;
Expand All @@ -38,31 +39,70 @@
import java.util.regex.Pattern;
import org.w3c.dom.Node;

import static com.intuit.karate.Match.MatchOperatorFactory.*;

/**
*
* @author pthomas3
*/
public class Match {


// Enum constant with that value should never be returned by Match.macroToMatchType.
private static final int TYPE_DOES_NOT_SUPPORT_MACRO_SHORTCUT = -1;

interface MatchOperatorFactory {
MatchOperator create(boolean matchEachEmptyAllowed);

static MatchOperatorFactory not(CoreOperatorFactory delegateFactory, String failureMessage) {
return matchEachEmptyAllowed -> new MatchOperator.NotOperator(delegateFactory.create(matchEachEmptyAllowed), failureMessage);
}

static MatchOperatorFactory deep(CoreOperatorFactory delegateFactory) {
return matchEachEmptyAllowed -> delegateFactory.create(matchEachEmptyAllowed).deep();
}

static MatchOperatorFactory each(MatchOperatorFactory delegateFactory) {
return matchEachEmptyAllowed -> new MatchOperator.EachOperator(delegateFactory.create(matchEachEmptyAllowed), matchEachEmptyAllowed);
}
}

interface CoreOperatorFactory extends MatchOperatorFactory {
@Override
CoreOperator create(boolean matchEachEmptyAllowed);
}

public static enum Type {

EQUALS,
NOT_EQUALS,
CONTAINS,
NOT_CONTAINS,
CONTAINS_ONLY,
CONTAINS_ANY,
CONTAINS_DEEP,
CONTAINS_ONLY_DEEP,
CONTAINS_ANY_DEEP,
EACH_EQUALS,
EACH_NOT_EQUALS,
EACH_CONTAINS,
EACH_NOT_CONTAINS,
EACH_CONTAINS_ONLY,
EACH_CONTAINS_ANY,
EACH_CONTAINS_DEEP
EQUALS(CoreOperator::equalsOperator, 0),
NOT_EQUALS(not(CoreOperator::equalsOperator, "equals"), 2),
CONTAINS(CoreOperator::containsOperator, 1),
NOT_CONTAINS(not(CoreOperator::containsOperator, "actual contains expected"), 2),
CONTAINS_ONLY(CoreOperator::containsOnlyOperator, 2),
CONTAINS_ANY(CoreOperator::containsAnyOperator, 2),
CONTAINS_DEEP(deep(CoreOperator::containsOperator), 2),
CONTAINS_ONLY_DEEP(deep(CoreOperator::containsOnlyOperator), TYPE_DOES_NOT_SUPPORT_MACRO_SHORTCUT),
CONTAINS_ANY_DEEP(deep(CoreOperator::containsAnyOperator), TYPE_DOES_NOT_SUPPORT_MACRO_SHORTCUT),
EACH_EQUALS(each(EQUALS.operatorFactory), 0),
EACH_NOT_EQUALS(each(NOT_EQUALS.operatorFactory), 2),
EACH_CONTAINS(each(CONTAINS.operatorFactory), 1),
EACH_NOT_CONTAINS(each(NOT_CONTAINS.operatorFactory), 2),
EACH_CONTAINS_ONLY(each(CONTAINS_ONLY.operatorFactory), 2),
EACH_CONTAINS_ANY(each(CONTAINS_ANY.operatorFactory), 2),
EACH_CONTAINS_DEEP(each(CONTAINS_DEEP.operatorFactory), 2);


final MatchOperatorFactory operatorFactory;
final int shortcutLength;

Type(MatchOperatorFactory operatorFactory, int shortcutLength) {
this.operatorFactory = operatorFactory;
this.shortcutLength = shortcutLength;
}

MatchOperator operator(boolean matchEachEmptyAllowed) {
return operatorFactory.create(matchEachEmptyAllowed);
}
}

static final Result PASS = new Result(true, null);
Expand Down Expand Up @@ -96,7 +136,7 @@ public Result apply(Value v) {

}

static final Map<String, Validator> VALIDATORS = new HashMap(11);
static final Map<String, Validator> VALIDATORS = new HashMap<>(11);

static {
VALIDATORS.put("array", v -> v.isList() ? PASS : fail("not an array or list"));
Expand Down Expand Up @@ -138,7 +178,7 @@ public String toString() {
}

public Map<String, Object> toMap() {
Map<String, Object> map = new HashMap(2);
Map<String, Object> map = new HashMap<>(2);
map.put("pass", pass);
map.put("message", message);
return map;
Expand All @@ -155,35 +195,33 @@ static class Context {
final String path;
final String name;
final int index;
final boolean matchEachEmptyAllowed;

Context(JsEngine js, MatchOperation root, boolean xml, int depth, String path, String name, int index, boolean matchEachEmptyAllowed) {
Context(JsEngine js, MatchOperation root, boolean xml, int depth, String path, String name, int index) {
this.JS = js;
this.root = root;
this.xml = xml;
this.depth = depth;
this.path = path;
this.name = name;
this.index = index;
this.matchEachEmptyAllowed = matchEachEmptyAllowed;
}

Context descend(String name) {
if (xml) {
String childPath = path.endsWith("/@") ? path + name : (depth == 0 ? "" : path) + "/" + name;
return new Context(JS, root, xml, depth + 1, childPath, name, -1, matchEachEmptyAllowed);
return new Context(JS, root, xml, depth + 1, childPath, name, -1);
} else {
boolean needsQuotes = name.indexOf('-') != -1 || name.indexOf(' ') != -1 || name.indexOf('.') != -1;
String childPath = needsQuotes ? path + "['" + name + "']" : path + '.' + name;
return new Context(JS, root, xml, depth + 1, childPath, name, -1, matchEachEmptyAllowed);
return new Context(JS, root, xml, depth + 1, childPath, name, -1);
}
}

Context descend(int index) {
if (xml) {
return new Context(JS, root, xml, depth + 1, path + "[" + (index + 1) + "]", name, index, matchEachEmptyAllowed);
return new Context(JS, root, xml, depth + 1, path + "[" + (index + 1) + "]", name, index);
} else {
return new Context(JS, root, xml, depth + 1, path + "[" + index + "]", name, index, matchEachEmptyAllowed);
return new Context(JS, root, xml, depth + 1, path + "[" + index + "]", name, index);
}
}

Expand Down Expand Up @@ -213,11 +251,11 @@ public static class Value {
}

Value(Object value, boolean exceptionOnMatchFailure) {
if (value instanceof Set) {
value = new ArrayList((Set) value);
if (value instanceof Set<?> set) {
value = new ArrayList<Object>(set);
} else if (value != null && value.getClass().isArray()) {
int length = Array.getLength(value);
List list = new ArrayList(length);
List<Object> list = new ArrayList<>(length);
for (int i = 0; i < length; i++) {
list.add(Array.get(value, i));
}
Expand Down Expand Up @@ -338,8 +376,8 @@ Value getSortedLike(Value other) {
if (isMap() && other.isMap()) {
Map<String, Object> reference = other.getValue();
Map<String, Object> source = getValue();
Set<String> remainder = new LinkedHashSet(source.keySet());
Map<String, Object> result = new LinkedHashMap(source.size());
Set<String> remainder = new LinkedHashSet<>(source.keySet());
Map<String, Object> result = new LinkedHashMap<>(source.size());
reference.keySet().forEach(key -> {
if (source.containsKey(key)) {
result.put(key, source.get(key));
Expand All @@ -357,15 +395,13 @@ Value getSortedLike(Value other) {

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[type: ").append(type);
sb.append(", value: ").append(value);
sb.append("]");
return sb.toString();
return "[type: " + type +
", value: " + value +
"]";
}

public Result is(Type matchType, Object expected) {
MatchOperation mo = new MatchOperation(matchType, this, new Value(parseIfJsonOrXmlString(expected), exceptionOnMatchFailure), false);
MatchOperation mo = new MatchOperation(matchType.operator(false), this, new Value(parseIfJsonOrXmlString(expected), exceptionOnMatchFailure));
mo.execute();
if (mo.pass) {
return Match.PASS;
Expand Down Expand Up @@ -442,7 +478,7 @@ public Result isEachContainingAny(Object expected) {
}

public static Result execute(JsEngine js, Type matchType, Object actual, Object expected, boolean matchEachEmptyAllowed) {
MatchOperation mo = new MatchOperation(js, matchType, new Value(actual), new Value(expected), matchEachEmptyAllowed);
MatchOperation mo = new MatchOperation(js, matchType.operator(matchEachEmptyAllowed), new Value(actual), new Value(expected));
mo.execute();
if (mo.pass) {
return PASS;
Expand All @@ -452,8 +488,7 @@ public static Result execute(JsEngine js, Type matchType, Object actual, Object
}

public static Object parseIfJsonOrXmlString(Object o) {
if (o instanceof String) {
String s = (String) o;
if (o instanceof String s) {
if (s.isEmpty()) {
return o;
} else if (JsonUtils.isJson(s)) {
Expand Down
Loading