Skip to content
Draft
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
8 changes: 7 additions & 1 deletion src/main/java/io/github/jamsesso/jsonlogic/JsonLogic.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.github.jamsesso.jsonlogic.ast.JsonLogicNode;
import io.github.jamsesso.jsonlogic.ast.JsonLogicParser;
import io.github.jamsesso.jsonlogic.evaluator.JsonLogicEvaluationException;
import io.github.jamsesso.jsonlogic.evaluator.JsonLogicEvaluator;
import io.github.jamsesso.jsonlogic.evaluator.JsonLogicExpression;
import io.github.jamsesso.jsonlogic.evaluator.expressions.*;
Expand Down Expand Up @@ -84,7 +85,12 @@ public Object apply(String json, Object data) throws JsonLogicException {
evaluator = new JsonLogicEvaluator(expressions);
}

return evaluator.evaluate(parseCache.get(json), data, "$");
try {
return evaluator.evaluate(parseCache.get(json), data, "");
} catch (JsonLogicException e) {
e.prependPartialJsonPath("$");
throw e;
}
}

public static boolean truthy(Object value) {
Expand Down
35 changes: 27 additions & 8 deletions src/main/java/io/github/jamsesso/jsonlogic/JsonLogicException.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,47 @@

public class JsonLogicException extends Exception {

private String jsonPath;
private final StringBuilder jsonPath = new StringBuilder();

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

public JsonLogicException(String msg) {
super(msg);
}

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

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

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

public JsonLogicException(String msg, Throwable cause) {
super(msg, cause);
}

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

public String getJsonPath() {
return jsonPath;
public String getJsonPath() {
return jsonPath.toString();
}

public void prependPartialJsonPath(String partialPath) {
if (partialPath == null) {
return;
}
jsonPath.insert(0, partialPath);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@ public JsonLogicParseException(Throwable cause, String jsonPath) {
public JsonLogicParseException(String msg, Throwable cause, String jsonPath) {
super(msg, cause, jsonPath);
}

public JsonLogicParseException(String msg) {
super(msg);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@ public static JsonLogicNode parse(String json) throws JsonLogicParseException {
try {
return parse(PARSER.parse(json));
}
catch (JsonLogicParseException e) {
e.prependPartialJsonPath("$");
throw e;
}
catch (JsonSyntaxException 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 @@ -56,9 +57,16 @@ private static JsonLogicNode parse(JsonElement root, String jsonPath) throws Jso
JsonArray array = root.getAsJsonArray();
List<JsonLogicNode> elements = new ArrayList<>(array.size());

int index = 0;
for (JsonElement element : array) {
elements.add(parse(element, String.format("%s[%d]", jsonPath, index++)));
for (int index = 0; index < array.size(); index++) {
JsonElement element = array.get(index);
JsonLogicNode arrayNode;
try {
arrayNode = parse(element);
} catch (JsonLogicParseException e) {
e.prependPartialJsonPath("[" + (index) + "]");
throw e;
}
elements.add(arrayNode);
}

return new JsonLogicArray(elements);
Expand All @@ -68,14 +76,21 @@ private static JsonLogicNode parse(JsonElement root, String jsonPath) throws Jso
JsonObject object = root.getAsJsonObject();

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

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

// Always coerce single-argument operations into a JsonLogicArray with a single element.
try {
argumentNode = parse(object.get(key));
} catch (JsonLogicParseException e) {
e.prependPartialJsonPath("." + key);
throw e;
}

// Always coerce single-argument operations into a JsonLogicArray with a single element.
if (argumentNode instanceof JsonLogicArray) {
arguments = (JsonLogicArray) argumentNode;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,25 @@
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(Throwable cause, String jsonPath) {
super(cause, jsonPath);
}

public JsonLogicEvaluationException(String msg, Throwable cause) {
super(msg, cause);
}

public JsonLogicEvaluationException(String msg, Throwable cause, String jsonPath) {
super(msg, cause, jsonPath);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.jamsesso.jsonlogic.evaluator;

import io.github.jamsesso.jsonlogic.JsonLogicException;
import io.github.jamsesso.jsonlogic.ast.*;
import io.github.jamsesso.jsonlogic.utils.ArrayLike;

Expand All @@ -24,15 +25,31 @@ public JsonLogicEvaluator(Map<String, JsonLogicExpression> expressions) {
this.expressions = Collections.unmodifiableMap(expressions);
}

@Deprecated
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, jsonPath + ".var");
case ARRAY: return evaluate((JsonLogicArray) node, data, jsonPath);
default: return evaluate((JsonLogicOperation) node, data, jsonPath);
}
try {
return evaluate(node, data);
} catch (JsonLogicEvaluationException e) {
e.prependPartialJsonPath(jsonPath);
throw e;
}
}

public Object evaluate(JsonLogicNode node, Object data) throws JsonLogicEvaluationException {
switch (node.getType()) {
case PRIMITIVE: return evaluate((JsonLogicPrimitive) node);
case VARIABLE:
try {
return evaluate((JsonLogicVariable) node, data);
} catch (JsonLogicEvaluationException e) {
e.prependPartialJsonPath(".var");
throw e;
}
case ARRAY: return evaluate((JsonLogicArray) node, data);
default: return evaluate((JsonLogicOperation) node, data);
}
}

public Object evaluate(JsonLogicPrimitive<?> primitive) {
switch (primitive.getPrimitiveType()) {
case NUMBER: return ((JsonLogicNumber) primitive).getValue();
Expand All @@ -42,20 +59,53 @@ public Object evaluate(JsonLogicPrimitive<?> primitive) {
}
}

public Object evaluate(JsonLogicVariable variable, Object data, String jsonPath)
@Deprecated
public Object evaluate(JsonLogicVariable variable, Object data, String jsonPath)
throws JsonLogicEvaluationException {
try {
return evaluate(variable, data);
} catch (JsonLogicEvaluationException e) {
e.prependPartialJsonPath(jsonPath);
throw e;
}
}

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

try {
defaultValue = evaluate(variable.getDefaultValue(), null, "");
} catch (JsonLogicEvaluationException e) {
e.prependPartialJsonPath("[1]");
throw e;
}

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

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

try {
key = evaluate(variable.getKey(), data, "");
} catch (JsonLogicEvaluationException e) {
e.prependPartialJsonPath("[0]");
throw e;
}

if (key == null) {
return Optional.of(data)
if (key == null) {
Object varValue;
try {
varValue = evaluate(variable.getDefaultValue(), null, "");
} catch (JsonLogicEvaluationException e) {
e.prependPartialJsonPath("[0]");
throw e;
}

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

if (key instanceof Number) {
Expand Down Expand Up @@ -84,9 +134,14 @@ public Object evaluate(JsonLogicVariable variable, Object data, String jsonPath)
Object result = data;

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

if (result == MISSING) {
try {
result = evaluatePartialVariable(partial, result);
} catch (JsonLogicEvaluationException e) {
e.prependPartialJsonPath("[0]");
throw e;
}

if (result == MISSING) {
return defaultValue;
} else if (result == null) {
return null;
Expand All @@ -96,10 +151,10 @@ public Object evaluate(JsonLogicVariable variable, Object data, String jsonPath)
return result;
}

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

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

if (index < 0 || index >= list.size()) {
Expand All @@ -130,25 +185,55 @@ private Object evaluatePartialVariable(String key, Object data, String jsonPath)
return null;
}

public List<Object> evaluate(JsonLogicArray array, Object data, String jsonPath) throws JsonLogicEvaluationException {
@Deprecated
public List<Object> evaluate(JsonLogicArray array, Object data, String jsonPath) throws JsonLogicEvaluationException {
try {
return evaluate(array, data);
} catch (JsonLogicEvaluationException e) {
e.prependPartialJsonPath(jsonPath);
throw e;
}
}

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

int index = 0;
for(JsonLogicNode element : array) {
values.add(evaluate(element, data, String.format("%s[%d]", jsonPath, index++)));
for(int index = 0; index < array.size(); index++) {
JsonLogicNode element = array.get(index);
try {
values.add(evaluate(element, data, ""));
} catch (JsonLogicEvaluationException e) {
e.prependPartialJsonPath("[" + index + "]");
throw e;
}
}

return values;
}

@Deprecated
public Object evaluate(JsonLogicOperation operation, Object data, String jsonPath) throws JsonLogicEvaluationException {
try {
return evaluate(operation, data);
} catch (JsonLogicEvaluationException e) {
e.prependPartialJsonPath(jsonPath);
throw e;
}
}

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

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

return handler.evaluate(this, operation.getArguments(), data, String.format("%s.%s", jsonPath, operation.getOperator()));
try {
return handler.evaluate(this, operation.getArguments(), data, "");
} catch (JsonLogicException e) {
e.prependPartialJsonPath("." + operation.getOperator());
throw e;
}
}

public static Object transform(Object value) {
Expand Down
Loading