Skip to content

Commit 311ca62

Browse files
authored
Adds jsonPath to error messages, so errors can be pinpointed. (#53)
* 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. * Handles edge cases of + and * the way jsonlogic.com does When presented with an array parameter, jsonlogic.com uses the first value for + and * expressions. * Uses fixtures for the error tests. Also fixes a bug in the jsonPath calculation for and/or. * Bump version to 1.1.0-SNAPSHOT
1 parent b0f2395 commit 311ca62

34 files changed

+583
-142
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ plugins {
55
}
66

77
group "io.github.jamsesso"
8-
version "1.0.10-SNAPSHOT"
8+
version "1.1.0-SNAPSHOT"
99

1010
sourceCompatibility = 1.8
1111
targetCompatibility = 1.8

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

Lines changed: 2 additions & 2 deletions
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) {
Lines changed: 9 additions & 4 deletions
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

Lines changed: 4 additions & 8 deletions
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

Lines changed: 8 additions & 4 deletions
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

Lines changed: 4 additions & 8 deletions
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

Lines changed: 22 additions & 20 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 6 additions & 5 deletions
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

Lines changed: 6 additions & 6 deletions
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+
}

0 commit comments

Comments
 (0)