Skip to content

Commit dcd6780

Browse files
committed
Adds jsonPath to error messages, so errors can be pinpointed.
The jsonPath can be used to provide better error messages to users trying to create valid jsonLogic expressions Also: Fixes some exception messages to use the right operator. ("none", "or"). Aligns behavior to reference implementation for sums of unparseable strings, as well as for between comparisons of > and >=, and for comparison operators with arrays of more than three values.
1 parent b0f2395 commit dcd6780

31 files changed

+255
-135
lines changed

src/main/java/io/github/jamsesso/jsonlogic/JsonLogic.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public JsonLogic() {
5757
public JsonLogic addOperation(String name, Function<Object[], Object> function) {
5858
return addOperation(new PreEvaluatedArgumentsExpression() {
5959
@Override
60-
public Object evaluate(List arguments, Object data) {
60+
public Object evaluate(List arguments, Object data, String jsonPath) {
6161
return function.apply(arguments.toArray());
6262
}
6363

@@ -84,7 +84,7 @@ public Object apply(String json, Object data) throws JsonLogicException {
8484
evaluator = new JsonLogicEvaluator(expressions);
8585
}
8686

87-
return evaluator.evaluate(parseCache.get(json), data);
87+
return evaluator.evaluate(parseCache.get(json), data, "$");
8888
}
8989

9090
public static boolean truthy(Object value) {
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
package io.github.jamsesso.jsonlogic;
22

33
public class JsonLogicException extends Exception {
4+
5+
private String jsonPath;
6+
47
private JsonLogicException() {
58
// The default constructor should not be called for exceptions. A reason must be provided.
69
}
710

8-
public JsonLogicException(String msg) {
11+
public JsonLogicException(String msg, String jsonPath) {
912
super(msg);
13+
this.jsonPath = jsonPath;
1014
}
1115

12-
public JsonLogicException(Throwable cause) {
16+
public JsonLogicException(Throwable cause, String jsonPath) {
1317
super(cause);
18+
this.jsonPath = jsonPath;
1419
}
1520

16-
public JsonLogicException(String msg, Throwable cause) {
17-
super(msg, cause);
21+
public String getJsonPath() {
22+
return jsonPath;
1823
}
1924
}

src/main/java/io/github/jamsesso/jsonlogic/ast/JsonLogicParseException.java

+4-8
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,11 @@
33
import io.github.jamsesso.jsonlogic.JsonLogicException;
44

55
public class JsonLogicParseException extends JsonLogicException {
6-
public JsonLogicParseException(String msg) {
7-
super(msg);
6+
public JsonLogicParseException(String msg, String jsonPath) {
7+
super(msg, jsonPath);
88
}
99

10-
public JsonLogicParseException(Throwable cause) {
11-
super(cause);
12-
}
13-
14-
public JsonLogicParseException(String msg, Throwable cause) {
15-
super(msg, cause);
10+
public JsonLogicParseException(Throwable cause, String jsonPath) {
11+
super(cause, jsonPath);
1612
}
1713
}

src/main/java/io/github/jamsesso/jsonlogic/ast/JsonLogicParser.java

+8-4
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,14 @@ public static JsonLogicNode parse(String json) throws JsonLogicParseException {
1818
return parse(PARSER.parse(json));
1919
}
2020
catch (JsonSyntaxException e) {
21-
throw new JsonLogicParseException(e);
21+
throw new JsonLogicParseException(e, "$");
2222
}
2323
}
2424

2525
private static JsonLogicNode parse(JsonElement root) throws JsonLogicParseException {
26+
return parse(root, "$");
27+
}
28+
private static JsonLogicNode parse(JsonElement root, String jsonPath) throws JsonLogicParseException {
2629
// Handle null
2730
if (root.isJsonNull()) {
2831
return JsonLogicNull.NULL;
@@ -53,8 +56,9 @@ private static JsonLogicNode parse(JsonElement root) throws JsonLogicParseExcept
5356
JsonArray array = root.getAsJsonArray();
5457
List<JsonLogicNode> elements = new ArrayList<>(array.size());
5558

59+
int index = 0;
5660
for (JsonElement element : array) {
57-
elements.add(parse(element));
61+
elements.add(parse(element, String.format("%s[%d]", jsonPath, index++)));
5862
}
5963

6064
return new JsonLogicArray(elements);
@@ -64,11 +68,11 @@ private static JsonLogicNode parse(JsonElement root) throws JsonLogicParseExcept
6468
JsonObject object = root.getAsJsonObject();
6569

6670
if (object.keySet().size() != 1) {
67-
throw new JsonLogicParseException("objects must have exactly 1 key defined, found " + object.keySet().size());
71+
throw new JsonLogicParseException("objects must have exactly 1 key defined, found " + object.keySet().size(), jsonPath);
6872
}
6973

7074
String key = object.keySet().stream().findAny().get();
71-
JsonLogicNode argumentNode = parse(object.get(key));
75+
JsonLogicNode argumentNode = parse(object.get(key), String.format("%s.%s", jsonPath, key));
7276
JsonLogicArray arguments;
7377

7478
// Always coerce single-argument operations into a JsonLogicArray with a single element.

src/main/java/io/github/jamsesso/jsonlogic/evaluator/JsonLogicEvaluationException.java

+4-8
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,11 @@
33
import io.github.jamsesso.jsonlogic.JsonLogicException;
44

55
public class JsonLogicEvaluationException extends JsonLogicException {
6-
public JsonLogicEvaluationException(String msg) {
7-
super(msg);
6+
public JsonLogicEvaluationException(String msg, String jsonPath) {
7+
super(msg, jsonPath);
88
}
99

10-
public JsonLogicEvaluationException(Throwable cause) {
11-
super(cause);
12-
}
13-
14-
public JsonLogicEvaluationException(String msg, Throwable cause) {
15-
super(msg, cause);
10+
public JsonLogicEvaluationException(Throwable cause, String jsonPath) {
11+
super(cause, jsonPath);
1612
}
1713
}

src/main/java/io/github/jamsesso/jsonlogic/evaluator/JsonLogicEvaluator.java

+22-20
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ public JsonLogicEvaluator(Map<String, JsonLogicExpression> expressions) {
2020
this.expressions = Collections.unmodifiableMap(expressions);
2121
}
2222

23-
public Object evaluate(JsonLogicNode node, Object data) throws JsonLogicEvaluationException {
23+
public Object evaluate(JsonLogicNode node, Object data, String jsonPath) throws JsonLogicEvaluationException {
2424
switch (node.getType()) {
2525
case PRIMITIVE: return evaluate((JsonLogicPrimitive) node);
26-
case VARIABLE: return evaluate((JsonLogicVariable) node, data);
27-
case ARRAY: return evaluate((JsonLogicArray) node, data);
28-
default: return evaluate((JsonLogicOperation) node, data);
26+
case VARIABLE: return evaluate((JsonLogicVariable) node, data, jsonPath + ".var");
27+
case ARRAY: return evaluate((JsonLogicArray) node, data, jsonPath);
28+
default: return evaluate((JsonLogicOperation) node, data, jsonPath);
2929
}
3030
}
3131

@@ -38,19 +38,20 @@ public Object evaluate(JsonLogicPrimitive<?> primitive) {
3838
}
3939
}
4040

41-
public Object evaluate(JsonLogicVariable variable, Object data) throws JsonLogicEvaluationException {
42-
Object defaultValue = evaluate(variable.getDefaultValue(), null);
41+
public Object evaluate(JsonLogicVariable variable, Object data, String jsonPath)
42+
throws JsonLogicEvaluationException {
43+
Object defaultValue = evaluate(variable.getDefaultValue(), null, jsonPath + "[1]");
4344

4445
if (data == null) {
4546
return defaultValue;
4647
}
4748

48-
Object key = evaluate(variable.getKey(), data);
49+
Object key = evaluate(variable.getKey(), data, jsonPath + "[0]");
4950

5051
if (key == null) {
5152
return Optional.of(data)
5253
.map(JsonLogicEvaluator::transform)
53-
.orElse(evaluate(variable.getDefaultValue(), null));
54+
.orElse(evaluate(variable.getDefaultValue(), null, jsonPath + "[1]"));
5455
}
5556

5657
if (key instanceof Number) {
@@ -78,21 +79,21 @@ public Object evaluate(JsonLogicVariable variable, Object data) throws JsonLogic
7879
String[] keys = name.split("\\.");
7980
Object result = data;
8081

81-
for(String partial : keys) {
82-
result = evaluatePartialVariable(partial, result);
82+
for (String partial : keys) {
83+
result = evaluatePartialVariable(partial, result, jsonPath + "[0]");
8384

84-
if(result == null) {
85+
if (result == null) {
8586
return defaultValue;
8687
}
8788
}
8889

8990
return result;
9091
}
9192

92-
throw new JsonLogicEvaluationException("var first argument must be null, number, or string");
93+
throw new JsonLogicEvaluationException("var first argument must be null, number, or string", jsonPath + "[0]");
9394
}
9495

95-
private Object evaluatePartialVariable(String key, Object data) throws JsonLogicEvaluationException {
96+
private Object evaluatePartialVariable(String key, Object data, String jsonPath) throws JsonLogicEvaluationException {
9697
if (ArrayLike.isEligible(data)) {
9798
ArrayLike list = new ArrayLike(data);
9899
int index;
@@ -101,7 +102,7 @@ private Object evaluatePartialVariable(String key, Object data) throws JsonLogic
101102
index = Integer.parseInt(key);
102103
}
103104
catch (NumberFormatException e) {
104-
throw new JsonLogicEvaluationException(e);
105+
throw new JsonLogicEvaluationException(e, jsonPath);
105106
}
106107

107108
if (index < 0 || index >= list.size()) {
@@ -118,24 +119,25 @@ private Object evaluatePartialVariable(String key, Object data) throws JsonLogic
118119
return null;
119120
}
120121

121-
public List<Object> evaluate(JsonLogicArray array, Object data) throws JsonLogicEvaluationException {
122+
public List<Object> evaluate(JsonLogicArray array, Object data, String jsonPath) throws JsonLogicEvaluationException {
122123
List<Object> values = new ArrayList<>(array.size());
123124

125+
int index = 0;
124126
for(JsonLogicNode element : array) {
125-
values.add(evaluate(element, data));
127+
values.add(evaluate(element, data, String.format("%s[%d]", jsonPath, index++)));
126128
}
127129

128130
return values;
129131
}
130132

131-
public Object evaluate(JsonLogicOperation operation, Object data) throws JsonLogicEvaluationException {
133+
public Object evaluate(JsonLogicOperation operation, Object data, String jsonPath) throws JsonLogicEvaluationException {
132134
JsonLogicExpression handler = expressions.get(operation.getOperator());
133135

134136
if (handler == null) {
135-
throw new JsonLogicEvaluationException("Undefined operation '" + operation.getOperator() + "'");
137+
throw new JsonLogicEvaluationException("Undefined operation '" + operation.getOperator() + "'", jsonPath);
136138
}
137139

138-
return handler.evaluate(this, operation.getArguments(), data);
140+
return handler.evaluate(this, operation.getArguments(), data, String.format("%s.%s", jsonPath, operation.getOperator()));
139141
}
140142

141143
public static Object transform(Object value) {
@@ -145,4 +147,4 @@ public static Object transform(Object value) {
145147

146148
return value;
147149
}
148-
}
150+
}

src/main/java/io/github/jamsesso/jsonlogic/evaluator/JsonLogicExpression.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
public interface JsonLogicExpression {
66
String key();
77

8-
Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data)
8+
Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data, String jsonPath)
99
throws JsonLogicEvaluationException;
1010
}

src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/AllExpression.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,20 @@ public String key() {
2020
}
2121

2222
@Override
23-
public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data)
23+
public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data, String jsonPath)
2424
throws JsonLogicEvaluationException {
2525
if (arguments.size() != 2) {
26-
throw new JsonLogicEvaluationException("all expects exactly 2 arguments");
26+
throw new JsonLogicEvaluationException("all expects exactly 2 arguments", jsonPath);
2727
}
2828

29-
Object maybeArray = evaluator.evaluate(arguments.get(0), data);
29+
Object maybeArray = evaluator.evaluate(arguments.get(0), data, jsonPath + "[0]");
3030

3131
if (maybeArray == null) {
3232
return false;
3333
}
3434

3535
if (!ArrayLike.isEligible(maybeArray)) {
36-
throw new JsonLogicEvaluationException("first argument to all must be a valid array");
36+
throw new JsonLogicEvaluationException("first argument to all must be a valid array", jsonPath);
3737
}
3838

3939
ArrayLike array = new ArrayLike(maybeArray);
@@ -42,8 +42,9 @@ public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, O
4242
return false;
4343
}
4444

45+
int index = 1;
4546
for (Object item : array) {
46-
if(!JsonLogic.truthy(evaluator.evaluate(arguments.get(1), item))) {
47+
if(!JsonLogic.truthy(evaluator.evaluate(arguments.get(1), item, String.format("%s[%d]", jsonPath, index)))) {
4748
return false;
4849
}
4950
}

src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/ArrayHasExpression.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ public String key() {
2323
}
2424

2525
@Override
26-
public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data)
26+
public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data, String jsonPath)
2727
throws JsonLogicEvaluationException {
2828
if (arguments.size() != 2) {
29-
throw new JsonLogicEvaluationException("some expects exactly 2 arguments");
29+
throw new JsonLogicEvaluationException(key() + " expects exactly 2 arguments", jsonPath);
3030
}
3131

32-
Object maybeArray = evaluator.evaluate(arguments.get(0), data);
32+
Object maybeArray = evaluator.evaluate(arguments.get(0), data, jsonPath + "[0]");
3333

3434
// Array objects can have null values according to http://jsonlogic.com/
3535
if (maybeArray == null) {
@@ -41,15 +41,15 @@ public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, O
4141
}
4242

4343
if (!ArrayLike.isEligible(maybeArray)) {
44-
throw new JsonLogicEvaluationException("first argument to some must be a valid array");
44+
throw new JsonLogicEvaluationException("first argument to " + key() + " must be a valid array", jsonPath + "[0]");
4545
}
4646

4747
for (Object item : new ArrayLike(maybeArray)) {
48-
if(JsonLogic.truthy(evaluator.evaluate(arguments.get(1), item))) {
48+
if(JsonLogic.truthy(evaluator.evaluate(arguments.get(1), item, jsonPath + "[1]"))) {
4949
return isSome;
5050
}
5151
}
5252

5353
return !isSome;
5454
}
55-
}
55+
}

src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/ConcatenateExpression.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public String key() {
1818
}
1919

2020
@Override
21-
public Object evaluate(List arguments, Object data) throws JsonLogicEvaluationException {
21+
public Object evaluate(List arguments, Object data, String jsonPath) throws JsonLogicEvaluationException {
2222
return arguments.stream()
2323
.map(obj -> {
2424
if (obj instanceof Double && obj.toString().endsWith(".0")) {

src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/EqualityExpression.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ public String key() {
1818
}
1919

2020
@Override
21-
public Object evaluate(List arguments, Object data) throws JsonLogicEvaluationException {
21+
public Object evaluate(List arguments, Object data, String jsonPath) throws JsonLogicEvaluationException {
2222
if (arguments.size() != 2) {
23-
throw new JsonLogicEvaluationException("equality expressions expect exactly 2 arguments");
23+
throw new JsonLogicEvaluationException("equality expressions expect exactly 2 arguments", jsonPath);
2424
}
2525

2626
Object left = arguments.get(0);

src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/FilterExpression.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,22 @@ public String key() {
2323
}
2424

2525
@Override
26-
public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data)
26+
public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data, String jsonPath)
2727
throws JsonLogicEvaluationException {
2828
if (arguments.size() != 2) {
29-
throw new JsonLogicEvaluationException("filter expects exactly 2 arguments");
29+
throw new JsonLogicEvaluationException("filter expects exactly 2 arguments", jsonPath);
3030
}
3131

32-
Object maybeArray = evaluator.evaluate(arguments.get(0), data);
32+
Object maybeArray = evaluator.evaluate(arguments.get(0), data, jsonPath + "[0]");
3333

3434
if (!ArrayLike.isEligible(maybeArray)) {
35-
throw new JsonLogicEvaluationException("first argument to filter must be a valid array");
35+
throw new JsonLogicEvaluationException("first argument to filter must be a valid array", jsonPath + "[0]");
3636
}
3737

3838
List<Object> result = new ArrayList<>();
3939

4040
for (Object item : new ArrayLike(maybeArray)) {
41-
if(JsonLogic.truthy(evaluator.evaluate(arguments.get(1), item))) {
41+
if(JsonLogic.truthy(evaluator.evaluate(arguments.get(1), item, jsonPath + "[1]"))) {
4242
result.add(item);
4343
}
4444
}

0 commit comments

Comments
 (0)