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
2 changes: 2 additions & 0 deletions CedarJava/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ dependencies {
implementation 'com.fizzed:jne:4.3.0'
implementation 'com.google.guava:guava:33.4.0-jre'
compileOnly 'com.github.spotbugs:spotbugs-annotations:4.8.6'
compileOnly 'org.projectlombok:lombok:1.18.30'
annotationProcessor 'org.projectlombok:lombok:1.18.30'
testImplementation 'net.jqwik:jqwik:1.9.2'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.4'
testImplementation 'org.skyscreamer:jsonassert:2.0-rc1'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.cedarpolicy.value.CedarMap;
import com.cedarpolicy.value.DateTime;
import com.cedarpolicy.value.Decimal;
import com.cedarpolicy.value.Duration;
import com.cedarpolicy.value.EntityIdentifier;
import com.cedarpolicy.value.EntityTypeName;
import com.cedarpolicy.value.EntityUID;
Expand All @@ -31,6 +32,7 @@
import com.cedarpolicy.value.PrimString;
import com.cedarpolicy.value.Unknown;
import com.cedarpolicy.value.Value;
import com.cedarpolicy.value.functions.Offset;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
Expand All @@ -40,12 +42,22 @@
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/** Deserialize Json to Value. This is mostly an implementation detail, but you may need to modify it if you extend the
* `Value` class. */
public class ValueDeserializer extends JsonDeserializer<Value> {
private static final String ENTITY_ESCAPE_SEQ = "__entity";
private static final String EXTENSION_ESCAPE_SEQ = "__extn";
private static final String FN_OFFSET = "offset";
private static final String FN_IP = "ip";
private static final String FN_DECIMAL = "decimal";
private static final String FN_UNKNOWN = "unknown";
private static final String FN_DATETIME = "datetime";
private static final String FN_DURATION = "duration";

private static final Set<String> MULTI_ARG_FN = Set.of(FN_OFFSET);
private static final Set<String> SINGLE_ARG_FN = Set.of(FN_IP, FN_DECIMAL, FN_UNKNOWN, FN_DATETIME, FN_DURATION);

private enum EscapeType {
ENTITY,
Expand Down Expand Up @@ -116,19 +128,13 @@ public Value deserialize(JsonParser parser, DeserializationContext context) thro
throw new InvalidValueDeserializationException(parser,
"Not textual node: " + fn.toString(), node.asToken(), Map.class);
}
JsonNode arg = val.get("arg");
if (!arg.isTextual()) {
throw new InvalidValueDeserializationException(parser,
"Not textual node: " + arg.toString(), node.asToken(), Map.class);
}
if (fn.textValue().equals("ip")) {
return new IpAddress(arg.textValue());
} else if (fn.textValue().equals("decimal")) {
return new Decimal(arg.textValue());
} else if (fn.textValue().equals("unknown")) {
return new Unknown(arg.textValue());
} else if (fn.textValue().equals("datetime")) {
return new DateTime(arg.textValue());

String fnName = fn.textValue();

if (MULTI_ARG_FN.contains(fnName)) {
return deserializeMultiArgFunction(fnName, val, mapper, parser, node);
} else if (SINGLE_ARG_FN.contains(fnName)) {
return deserializeSingleArgFunction(fnName, val, parser, node);
} else {
throw new InvalidValueDeserializationException(parser,
"Invalid function type: " + fn.toString(), node.asToken(), Map.class);
Expand All @@ -153,4 +159,79 @@ public Value deserialize(JsonParser parser, DeserializationContext context) thro
throw new DeserializationRecursionDepthException("Stack overflow while deserializing value. " + e.toString());
}
}

private Value deserializeMultiArgFunction(String fnName, JsonNode val, ObjectMapper mapper, JsonParser parser,
JsonNode node) throws IOException {
JsonNode args = val.get("args");
if (args == null || !args.isArray()) {
throw new InvalidValueDeserializationException(parser,
"Expected args to be an array" + (args != null ? ", got: " + args.getNodeType() : ""),
node.asToken(), Map.class);
}

switch (fnName) {
case FN_OFFSET:
return deserializeOffset(args, mapper, parser, node);
default:
throw new InvalidValueDeserializationException(parser, "Invalid function type: " + fnName,
node.asToken(), Map.class);
}
}

private Value deserializeSingleArgFunction(String fnName, JsonNode val, JsonParser parser, JsonNode node)
throws IOException {
JsonNode arg = val.get("arg");
if (arg == null || !arg.isTextual()) {
throw new InvalidValueDeserializationException(parser, "Not textual node: " + fnName, node.asToken(),
Map.class);
}

String argValue = arg.textValue();
switch (fnName) {
case FN_IP:
return new IpAddress(argValue);
case FN_DECIMAL:
return new Decimal(argValue);
case FN_UNKNOWN:
return new Unknown(argValue);
case FN_DATETIME:
return new DateTime(argValue);
case FN_DURATION:
return new Duration(argValue);
default:
throw new InvalidValueDeserializationException(parser,
"Invalid function type: " + fnName, node.asToken(), Map.class);
}
}

private Offset deserializeOffset(JsonNode args, ObjectMapper mapper, JsonParser parser, JsonNode node)
throws IOException {
if (args.size() != 2) {
throw new InvalidValueDeserializationException(parser,
"Offset requires exactly two arguments but got: " + args.size(), node.asToken(), Offset.class);
}

try {
Value dateTimeValue = mapper.treeToValue(args.get(0), Value.class);
Value durationValue = mapper.treeToValue(args.get(1), Value.class);

if (!(dateTimeValue instanceof DateTime)) {
throw new InvalidValueDeserializationException(parser,
"Offset first argument must be DateTime but got: " + dateTimeValue.getClass().getSimpleName(),
node.asToken(), Offset.class);
}

if (!(durationValue instanceof Duration)) {
throw new InvalidValueDeserializationException(parser,
"Offset second argument must be Duration but got: " + durationValue.getClass().getSimpleName(),
node.asToken(), Offset.class);
}

return new Offset((DateTime) dateTimeValue, (Duration) durationValue);

} catch (IOException e) {
throw new InvalidValueDeserializationException(parser,
"Failed to deserialize Offset arguments: " + e.getMessage(), node.asToken(), Offset.class);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
import com.cedarpolicy.value.CedarMap;
import com.cedarpolicy.value.DateTime;
import com.cedarpolicy.value.Decimal;
import com.cedarpolicy.value.Duration;
import com.cedarpolicy.value.EntityUID;
import com.cedarpolicy.value.IpAddress;
import com.cedarpolicy.value.PrimBool;
import com.cedarpolicy.value.PrimLong;
import com.cedarpolicy.value.PrimString;
import com.cedarpolicy.value.Unknown;
import com.cedarpolicy.value.Value;
import com.cedarpolicy.value.functions.Offset;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
Expand Down Expand Up @@ -113,6 +115,30 @@ public void serialize(
jsonGenerator.writeString(value.toString());
jsonGenerator.writeEndObject();
jsonGenerator.writeEndObject();
} else if (value instanceof Duration) {
jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName(EXTENSION_ESCAPE_SEQ);
jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName("fn");
jsonGenerator.writeString("duration");
jsonGenerator.writeFieldName("arg");
jsonGenerator.writeString(value.toString());
jsonGenerator.writeEndObject();
jsonGenerator.writeEndObject();
} else if (value instanceof Offset) {
Offset offsetValue = (Offset) value;
jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName(EXTENSION_ESCAPE_SEQ);
jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName("fn");
jsonGenerator.writeString("offset");
jsonGenerator.writeFieldName("args");
CedarList args = new CedarList();
args.add(offsetValue.getDateTime());
args.add(offsetValue.getOffsetDuration());
jsonGenerator.writeObject(args);
jsonGenerator.writeEndObject();
jsonGenerator.writeEndObject();
} else {
// It is recommended that you extend the Value classes in
// main.java.com.cedarpolicy.model.value or that you convert your class to a CedarMap
Expand Down
2 changes: 1 addition & 1 deletion CedarJava/src/main/java/com/cedarpolicy/value/Decimal.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
public class Decimal extends Value {

private static class DecimalValidator {
private static final Pattern DECIMAL_PATTERN = Pattern.compile("^([0-9])*(\\.)([0-9]{0,4})$");
private static final Pattern DECIMAL_PATTERN = Pattern.compile("^-?([0-9])*(\\.)([0-9]{0,4})$");

public static boolean validDecimal(String d) {
if (d == null || d.isEmpty()) {
Expand Down
Loading