diff --git a/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java b/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java index 52f0f1594..2e4d980c7 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java +++ b/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java @@ -35,7 +35,7 @@ */ @AutoValue @Immutable -public abstract class ProtoMessageLiteValue extends StructValue { +public abstract class ProtoMessageLiteValue extends StructValue { @Override public abstract MessageLite value(); diff --git a/common/src/main/java/dev/cel/common/values/ProtoMessageValue.java b/common/src/main/java/dev/cel/common/values/ProtoMessageValue.java index 12d47c253..627bd2c1d 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoMessageValue.java +++ b/common/src/main/java/dev/cel/common/values/ProtoMessageValue.java @@ -28,7 +28,7 @@ /** ProtoMessageValue is a struct value with protobuf support. */ @AutoValue @Immutable -public abstract class ProtoMessageValue extends StructValue { +public abstract class ProtoMessageValue extends StructValue { @Override public abstract Message value(); diff --git a/common/src/main/java/dev/cel/common/values/StructValue.java b/common/src/main/java/dev/cel/common/values/StructValue.java index 8775ef5c4..aa44ec420 100644 --- a/common/src/main/java/dev/cel/common/values/StructValue.java +++ b/common/src/main/java/dev/cel/common/values/StructValue.java @@ -19,13 +19,21 @@ /** * StructValue is a representation of a structured object with typed properties. * - *

Users may extend from this class to provide a custom struct that CEL can understand (ex: - * POJOs). Custom struct implementations must provide all functionalities denoted in the CEL - * specification, such as field selection, presence testing and new object creation. + *

Users may extend from this class to provide a custom struct that CEL can understand by + * wrapping a native Java object (e.g., a POJO or a Map). Custom struct implementations must provide + * all functionalities denoted in the CEL specification, such as field selection, presence testing + * and new object creation. * *

For an expression `e` selecting a field `f`, `e.f` must throw an exception if `f` does not * exist in the struct (i.e: hasField returns false). If the field exists but is not set, the * implementation should return an appropriate default value based on the struct's semantics. + * + * @param The type of the field identifier. Only {@code String} is supported for now, but we may + * extend support to other types in the future. + * @param The type of the wrapped native object. */ @Immutable -public abstract class StructValue extends CelValue implements SelectableValue {} +public abstract class StructValue extends CelValue implements SelectableValue { + @Override + public abstract V value(); +} diff --git a/common/src/test/java/dev/cel/common/values/OptionalValueTest.java b/common/src/test/java/dev/cel/common/values/OptionalValueTest.java index 24b3ea30b..f00954e3d 100644 --- a/common/src/test/java/dev/cel/common/values/OptionalValueTest.java +++ b/common/src/test/java/dev/cel/common/values/OptionalValueTest.java @@ -141,7 +141,7 @@ public void celTypeTest() { } @SuppressWarnings("Immutable") // Test only - private static class CelCustomStruct extends StructValue { + private static class CelCustomStruct extends StructValue { private final long data; @Override diff --git a/common/src/test/java/dev/cel/common/values/StructValueTest.java b/common/src/test/java/dev/cel/common/values/StructValueTest.java index b8d6371a8..f25db8e87 100644 --- a/common/src/test/java/dev/cel/common/values/StructValueTest.java +++ b/common/src/test/java/dev/cel/common/values/StructValueTest.java @@ -59,18 +59,34 @@ public Optional findType(String typeName) { }; private static final CelValueProvider CUSTOM_STRUCT_VALUE_PROVIDER = - (structType, fields) -> { - if (structType.equals(CUSTOM_STRUCT_TYPE.name())) { - return Optional.of(new CelCustomStructValue(fields)); + new CelValueProvider() { + @Override + public Optional newValue(String structType, Map fields) { + if (structType.equals(CUSTOM_STRUCT_TYPE.name())) { + return Optional.of(new CelCustomStructValue(fields)); + } + return Optional.empty(); + } + + @Override + public CelValueConverter celValueConverter() { + return new CelValueConverter() { + @Override + public Object toRuntimeValue(Object value) { + if (value instanceof CustomPojo) { + return new CelCustomStructValue((CustomPojo) value); + } + return super.toRuntimeValue(value); + } + }; } - return Optional.empty(); }; @Test public void emptyStruct() { CelCustomStructValue celCustomStruct = new CelCustomStructValue(0); - assertThat(celCustomStruct.value()).isEqualTo(celCustomStruct); + assertThat(celCustomStruct.value().getData()).isEqualTo(0L); assertThat(celCustomStruct.isZeroValue()).isTrue(); } @@ -78,7 +94,7 @@ public void emptyStruct() { public void constructStruct() { CelCustomStructValue celCustomStruct = new CelCustomStructValue(5); - assertThat(celCustomStruct.value()).isEqualTo(celCustomStruct); + assertThat(celCustomStruct.value().getData()).isEqualTo(5L); assertThat(celCustomStruct.isZeroValue()).isFalse(); } @@ -115,41 +131,41 @@ public void celTypeTest() { @Test public void evaluate_usingCustomClass_createNewStruct() throws Exception { Cel cel = - CelFactory.standardCelBuilder() - .setOptions(CelOptions.current().enableCelValue(true).build()) + CelFactory.plannerCelBuilder() + .setOptions(CelOptions.current().enableHeterogeneousNumericComparisons(true).build()) .setTypeProvider(CUSTOM_STRUCT_TYPE_PROVIDER) .setValueProvider(CUSTOM_STRUCT_VALUE_PROVIDER) .build(); CelAbstractSyntaxTree ast = cel.compile("custom_struct{data: 50}").getAst(); - CelCustomStructValue result = (CelCustomStructValue) cel.createProgram(ast).eval(); + CustomPojo result = (CustomPojo) cel.createProgram(ast).eval(); - assertThat(result.data).isEqualTo(50); + assertThat(result.getData()).isEqualTo(50); } @Test public void evaluate_usingCustomClass_asVariable() throws Exception { Cel cel = - CelFactory.standardCelBuilder() - .setOptions(CelOptions.current().enableCelValue(true).build()) + CelFactory.plannerCelBuilder() + .setOptions(CelOptions.current().enableHeterogeneousNumericComparisons(true).build()) .addVar("a", CUSTOM_STRUCT_TYPE) .setTypeProvider(CUSTOM_STRUCT_TYPE_PROVIDER) .setValueProvider(CUSTOM_STRUCT_VALUE_PROVIDER) .build(); CelAbstractSyntaxTree ast = cel.compile("a").getAst(); - CelCustomStructValue result = - (CelCustomStructValue) + CustomPojo result = + (CustomPojo) cel.createProgram(ast).eval(ImmutableMap.of("a", new CelCustomStructValue(10))); - assertThat(result.data).isEqualTo(10); + assertThat(result.getData()).isEqualTo(10); } @Test public void evaluate_usingCustomClass_asVariableSelectField() throws Exception { Cel cel = - CelFactory.standardCelBuilder() - .setOptions(CelOptions.current().enableCelValue(true).build()) + CelFactory.plannerCelBuilder() + .setOptions(CelOptions.current().enableHeterogeneousNumericComparisons(true).build()) .addVar("a", CUSTOM_STRUCT_TYPE) .setTypeProvider(CUSTOM_STRUCT_TYPE_PROVIDER) .setValueProvider(CUSTOM_STRUCT_VALUE_PROVIDER) @@ -163,8 +179,8 @@ public void evaluate_usingCustomClass_asVariableSelectField() throws Exception { @Test public void evaluate_usingCustomClass_selectField() throws Exception { Cel cel = - CelFactory.standardCelBuilder() - .setOptions(CelOptions.current().enableCelValue(true).build()) + CelFactory.plannerCelBuilder() + .setOptions(CelOptions.current().enableHeterogeneousNumericComparisons(true).build()) .setTypeProvider(CUSTOM_STRUCT_TYPE_PROVIDER) .setValueProvider(CUSTOM_STRUCT_VALUE_PROVIDER) .build(); @@ -178,8 +194,8 @@ public void evaluate_usingCustomClass_selectField() throws Exception { @Test public void evaluate_usingMultipleProviders_selectFieldFromCustomClass() throws Exception { Cel cel = - CelFactory.standardCelBuilder() - .setOptions(CelOptions.current().enableCelValue(true).build()) + CelFactory.plannerCelBuilder() + .setOptions(CelOptions.current().enableHeterogeneousNumericComparisons(true).build()) .setTypeProvider(CUSTOM_STRUCT_TYPE_PROVIDER) .setValueProvider( CombinedCelValueProvider.combine( @@ -197,19 +213,31 @@ public void evaluate_usingMultipleProviders_selectFieldFromCustomClass() throws // TODO: Bring back evaluate_usingMultipleProviders_selectFieldFromProtobufMessage // once planner is exposed from factory + private static class CustomPojo { + private final long data; + + CustomPojo(long data) { + this.data = data; + } + + long getData() { + return data; + } + } + @SuppressWarnings("Immutable") // Test only - private static class CelCustomStructValue extends StructValue { + private static class CelCustomStructValue extends StructValue { - private final long data; + private final CustomPojo pojo; @Override - public CelCustomStructValue value() { - return this; + public CustomPojo value() { + return pojo; } @Override public boolean isZeroValue() { - return data == 0; + return pojo.getData() == 0; } @Override @@ -226,7 +254,7 @@ public Object select(String field) { @Override public Optional find(String field) { if (field.equals("data")) { - return Optional.of(value().data); + return Optional.of(pojo.getData()); } return Optional.empty(); @@ -237,7 +265,11 @@ private CelCustomStructValue(Map fields) { } private CelCustomStructValue(long data) { - this.data = data; + this.pojo = new CustomPojo(data); + } + + private CelCustomStructValue(CustomPojo pojo) { + this.pojo = pojo; } } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java index 36485d5be..a2e8a9da6 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java @@ -87,9 +87,8 @@ Object evalInternal(GlobalResolver resolver, ExecutionFrame frame) { .newValue(structType.name(), Collections.unmodifiableMap(fieldValues)) .orElseThrow( () -> new IllegalArgumentException("Type name not found: " + structType.name())); - if (value instanceof StructValue) { - return ((StructValue) value).value(); + return ((StructValue) value).value(); } return value;