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
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
*/
@AutoValue
@Immutable
public abstract class ProtoMessageLiteValue extends StructValue<String> {
public abstract class ProtoMessageLiteValue extends StructValue<String, MessageLite> {

@Override
public abstract MessageLite value();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
/** ProtoMessageValue is a struct value with protobuf support. */
@AutoValue
@Immutable
public abstract class ProtoMessageValue extends StructValue<String> {
public abstract class ProtoMessageValue extends StructValue<String, Message> {

@Override
public abstract Message value();
Expand Down
16 changes: 12 additions & 4 deletions common/src/main/java/dev/cel/common/values/StructValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,21 @@
/**
* StructValue is a representation of a structured object with typed properties.
*
* <p>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.
* <p>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.
*
* <p>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 <F> 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 <V> The type of the wrapped native object.
*/
@Immutable
public abstract class StructValue<T> extends CelValue implements SelectableValue<T> {}
public abstract class StructValue<F, V> extends CelValue implements SelectableValue<F> {
@Override
public abstract V value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ public void celTypeTest() {
}

@SuppressWarnings("Immutable") // Test only
private static class CelCustomStruct extends StructValue<String> {
private static class CelCustomStruct extends StructValue<String, Long> {
private final long data;

@Override
Expand Down
88 changes: 60 additions & 28 deletions common/src/test/java/dev/cel/common/values/StructValueTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,26 +59,42 @@ public Optional<CelType> 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<Object> newValue(String structType, Map<String, Object> 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();
}

@Test
public void constructStruct() {
CelCustomStructValue celCustomStruct = new CelCustomStructValue(5);

assertThat(celCustomStruct.value()).isEqualTo(celCustomStruct);
assertThat(celCustomStruct.value().getData()).isEqualTo(5L);
assertThat(celCustomStruct.isZeroValue()).isFalse();
}

Expand Down Expand Up @@ -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)
Expand All @@ -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();
Expand All @@ -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(
Expand All @@ -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<String> {
private static class CelCustomStructValue extends StructValue<String, CustomPojo> {

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
Expand All @@ -226,7 +254,7 @@ public Object select(String field) {
@Override
public Optional<Object> find(String field) {
if (field.equals("data")) {
return Optional.of(value().data);
return Optional.of(pojo.getData());
}

return Optional.empty();
Expand All @@ -237,7 +265,11 @@ private CelCustomStructValue(Map<String, Object> fields) {
}

private CelCustomStructValue(long data) {
this.data = data;
this.pojo = new CustomPojo(data);
}

private CelCustomStructValue(CustomPojo pojo) {
this.pojo = pojo;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading