Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds jsonPath to error messages, so errors can be pinpointed. #53

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions src/main/java/io/github/jamsesso/jsonlogic/JsonLogic.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public JsonLogic() {
public JsonLogic addOperation(String name, Function<Object[], Object> function) {
return addOperation(new PreEvaluatedArgumentsExpression() {
@Override
public Object evaluate(List arguments, Object data) {
public Object evaluate(List arguments, Object data, String jsonPath) {
return function.apply(arguments.toArray());
}

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

return evaluator.evaluate(parseCache.get(json), data);
return evaluator.evaluate(parseCache.get(json), data, "$");
}

public static boolean truthy(Object value) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
package io.github.jamsesso.jsonlogic;

public class JsonLogicException extends Exception {

private String jsonPath;

private JsonLogicException() {
// The default constructor should not be called for exceptions. A reason must be provided.
}

public JsonLogicException(String msg) {
public JsonLogicException(String msg, String jsonPath) {
super(msg);
this.jsonPath = jsonPath;
}

public JsonLogicException(Throwable cause) {
public JsonLogicException(Throwable cause, String jsonPath) {
super(cause);
this.jsonPath = jsonPath;
}

public JsonLogicException(String msg, Throwable cause) {
super(msg, cause);
public String getJsonPath() {
return jsonPath;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@
import io.github.jamsesso.jsonlogic.JsonLogicException;

public class JsonLogicParseException extends JsonLogicException {
public JsonLogicParseException(String msg) {
super(msg);
public JsonLogicParseException(String msg, String jsonPath) {
super(msg, jsonPath);
}

public JsonLogicParseException(Throwable cause) {
super(cause);
}

public JsonLogicParseException(String msg, Throwable cause) {
super(msg, cause);
public JsonLogicParseException(Throwable cause, String jsonPath) {
super(cause, jsonPath);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ public static JsonLogicNode parse(String json) throws JsonLogicParseException {
return parse(PARSER.parse(json));
}
catch (JsonSyntaxException e) {
throw new JsonLogicParseException(e);
throw new JsonLogicParseException(e, "$");
}
}

private static JsonLogicNode parse(JsonElement root) throws JsonLogicParseException {
return parse(root, "$");
}
private static JsonLogicNode parse(JsonElement root, String jsonPath) throws JsonLogicParseException {
// Handle null
if (root.isJsonNull()) {
return JsonLogicNull.NULL;
Expand Down Expand Up @@ -53,8 +56,9 @@ private static JsonLogicNode parse(JsonElement root) throws JsonLogicParseExcept
JsonArray array = root.getAsJsonArray();
List<JsonLogicNode> elements = new ArrayList<>(array.size());

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

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

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

String key = object.keySet().stream().findAny().get();
JsonLogicNode argumentNode = parse(object.get(key));
JsonLogicNode argumentNode = parse(object.get(key), String.format("%s.%s", jsonPath, key));
JsonLogicArray arguments;

// Always coerce single-argument operations into a JsonLogicArray with a single element.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@
import io.github.jamsesso.jsonlogic.JsonLogicException;

public class JsonLogicEvaluationException extends JsonLogicException {
public JsonLogicEvaluationException(String msg) {
super(msg);
public JsonLogicEvaluationException(String msg, String jsonPath) {
super(msg, jsonPath);
}

public JsonLogicEvaluationException(Throwable cause) {
super(cause);
}

public JsonLogicEvaluationException(String msg, Throwable cause) {
super(msg, cause);
public JsonLogicEvaluationException(Throwable cause, String jsonPath) {
super(cause, jsonPath);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ public JsonLogicEvaluator(Map<String, JsonLogicExpression> expressions) {
this.expressions = Collections.unmodifiableMap(expressions);
}

public Object evaluate(JsonLogicNode node, Object data) throws JsonLogicEvaluationException {
public Object evaluate(JsonLogicNode node, Object data, String jsonPath) throws JsonLogicEvaluationException {
switch (node.getType()) {
case PRIMITIVE: return evaluate((JsonLogicPrimitive) node);
case VARIABLE: return evaluate((JsonLogicVariable) node, data);
case ARRAY: return evaluate((JsonLogicArray) node, data);
default: return evaluate((JsonLogicOperation) node, data);
case VARIABLE: return evaluate((JsonLogicVariable) node, data, jsonPath + ".var");
case ARRAY: return evaluate((JsonLogicArray) node, data, jsonPath);
default: return evaluate((JsonLogicOperation) node, data, jsonPath);
}
}

Expand All @@ -38,19 +38,20 @@ public Object evaluate(JsonLogicPrimitive<?> primitive) {
}
}

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

if (data == null) {
return defaultValue;
}

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

if (key == null) {
return Optional.of(data)
.map(JsonLogicEvaluator::transform)
.orElse(evaluate(variable.getDefaultValue(), null));
.orElse(evaluate(variable.getDefaultValue(), null, jsonPath + "[1]"));
}

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

for(String partial : keys) {
result = evaluatePartialVariable(partial, result);
for (String partial : keys) {
result = evaluatePartialVariable(partial, result, jsonPath + "[0]");

if(result == null) {
if (result == null) {
return defaultValue;
}
}

return result;
}

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

private Object evaluatePartialVariable(String key, Object data) throws JsonLogicEvaluationException {
private Object evaluatePartialVariable(String key, Object data, String jsonPath) throws JsonLogicEvaluationException {
if (ArrayLike.isEligible(data)) {
ArrayLike list = new ArrayLike(data);
int index;
Expand All @@ -101,7 +102,7 @@ private Object evaluatePartialVariable(String key, Object data) throws JsonLogic
index = Integer.parseInt(key);
}
catch (NumberFormatException e) {
throw new JsonLogicEvaluationException(e);
throw new JsonLogicEvaluationException(e, jsonPath);
}

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

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

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

return values;
}

public Object evaluate(JsonLogicOperation operation, Object data) throws JsonLogicEvaluationException {
public Object evaluate(JsonLogicOperation operation, Object data, String jsonPath) throws JsonLogicEvaluationException {
JsonLogicExpression handler = expressions.get(operation.getOperator());

if (handler == null) {
throw new JsonLogicEvaluationException("Undefined operation '" + operation.getOperator() + "'");
throw new JsonLogicEvaluationException("Undefined operation '" + operation.getOperator() + "'", jsonPath);
}

return handler.evaluate(this, operation.getArguments(), data);
return handler.evaluate(this, operation.getArguments(), data, String.format("%s.%s", jsonPath, operation.getOperator()));
}

public static Object transform(Object value) {
Expand All @@ -145,4 +147,4 @@ public static Object transform(Object value) {

return value;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
public interface JsonLogicExpression {
String key();

Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data)
Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data, String jsonPath)
throws JsonLogicEvaluationException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,20 @@ public String key() {
}

@Override
public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data)
public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data, String jsonPath)
throws JsonLogicEvaluationException {
if (arguments.size() != 2) {
throw new JsonLogicEvaluationException("all expects exactly 2 arguments");
throw new JsonLogicEvaluationException("all expects exactly 2 arguments", jsonPath);
}

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

if (maybeArray == null) {
return false;
}

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

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

int index = 1;
for (Object item : array) {
if(!JsonLogic.truthy(evaluator.evaluate(arguments.get(1), item))) {
if(!JsonLogic.truthy(evaluator.evaluate(arguments.get(1), item, String.format("%s[%d]", jsonPath, index)))) {
return false;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ public String key() {
}

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

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

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

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

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

return !isSome;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public String key() {
}

@Override
public Object evaluate(List arguments, Object data) throws JsonLogicEvaluationException {
public Object evaluate(List arguments, Object data, String jsonPath) throws JsonLogicEvaluationException {
return arguments.stream()
.map(obj -> {
if (obj instanceof Double && obj.toString().endsWith(".0")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ public String key() {
}

@Override
public Object evaluate(List arguments, Object data) throws JsonLogicEvaluationException {
public Object evaluate(List arguments, Object data, String jsonPath) throws JsonLogicEvaluationException {
if (arguments.size() != 2) {
throw new JsonLogicEvaluationException("equality expressions expect exactly 2 arguments");
throw new JsonLogicEvaluationException("equality expressions expect exactly 2 arguments", jsonPath);
}

Object left = arguments.get(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,22 @@ public String key() {
}

@Override
public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data)
public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data, String jsonPath)
throws JsonLogicEvaluationException {
if (arguments.size() != 2) {
throw new JsonLogicEvaluationException("filter expects exactly 2 arguments");
throw new JsonLogicEvaluationException("filter expects exactly 2 arguments", jsonPath);
}

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

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

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

for (Object item : new ArrayLike(maybeArray)) {
if(JsonLogic.truthy(evaluator.evaluate(arguments.get(1), item))) {
if(JsonLogic.truthy(evaluator.evaluate(arguments.get(1), item, jsonPath + "[1]"))) {
result.add(item);
}
}
Expand Down
Loading