From 7625796641ab8f4f056723513bfae6fbb45cd4de Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 11 Dec 2023 15:19:59 -0800 Subject: [PATCH] Add an example demonstrating how to bind a POJO in the runtime PiperOrigin-RevId: 589967899 --- .../src/main/java/dev/cel/bundle/BUILD.bazel | 1 + .../main/java/dev/cel/bundle/CelBuilder.java | 12 ++ .../src/main/java/dev/cel/bundle/CelImpl.java | 7 + .../java/dev/cel/common/values/BUILD.bazel | 1 + .../common/values/ImmutableMapValueTest.java | 1 - .../cel/common/values/StructValueTest.java | 149 ++++++++++++++---- .../dev/cel/runtime/CelRuntimeBuilder.java | 8 +- 7 files changed, 148 insertions(+), 31 deletions(-) diff --git a/bundle/src/main/java/dev/cel/bundle/BUILD.bazel b/bundle/src/main/java/dev/cel/bundle/BUILD.bazel index f85381631..c2c7d3f10 100644 --- a/bundle/src/main/java/dev/cel/bundle/BUILD.bazel +++ b/bundle/src/main/java/dev/cel/bundle/BUILD.bazel @@ -30,6 +30,7 @@ java_library( "//common/internal:file_descriptor_converter", "//common/types:cel_types", "//common/types:type_providers", + "//common/values:cel_value_provider", "//compiler", "//compiler:compiler_builder", "//parser", diff --git a/bundle/src/main/java/dev/cel/bundle/CelBuilder.java b/bundle/src/main/java/dev/cel/bundle/CelBuilder.java index 7857d4140..eb54c2ff6 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelBuilder.java +++ b/bundle/src/main/java/dev/cel/bundle/CelBuilder.java @@ -29,6 +29,7 @@ import dev.cel.common.CelVarDecl; import dev.cel.common.types.CelType; import dev.cel.common.types.CelTypeProvider; +import dev.cel.common.values.CelValueProvider; import dev.cel.compiler.CelCompilerLibrary; import dev.cel.parser.CelMacro; import dev.cel.parser.CelStandardMacro; @@ -185,6 +186,17 @@ public interface CelBuilder { @CanIgnoreReturnValue CelBuilder setTypeFactory(Function typeFactory); + /** + * Sets the {@code celValueProvider} for resolving values during evaluation. The provided value + * provider will be used first before falling back to the built-in {@link + * dev.cel.common.values.ProtoMessageValueProvider} for resolving protobuf messages. + * + *

Note that {@link CelOptions#enableCelValue()} must be enabled or this method will be a + * no-op. + */ + @CanIgnoreReturnValue + CelBuilder setValueProvider(CelValueProvider celValueProvider); + /** * Set the {@code typeProvider} for use with type-checking expressions. * diff --git a/bundle/src/main/java/dev/cel/bundle/CelImpl.java b/bundle/src/main/java/dev/cel/bundle/CelImpl.java index 3d65ada89..6a0f63589 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelImpl.java +++ b/bundle/src/main/java/dev/cel/bundle/CelImpl.java @@ -42,6 +42,7 @@ import dev.cel.common.types.CelType; import dev.cel.common.types.CelTypeProvider; import dev.cel.common.types.CelTypes; +import dev.cel.common.values.CelValueProvider; import dev.cel.compiler.CelCompiler; import dev.cel.compiler.CelCompilerBuilder; import dev.cel.compiler.CelCompilerImpl; @@ -254,6 +255,12 @@ public Builder setTypeFactory(Function typeFactory) { return this; } + @Override + public Builder setValueProvider(CelValueProvider celValueProvider) { + runtimeBuilder.setValueProvider(celValueProvider); + return this; + } + @Override @Deprecated public Builder setTypeProvider(TypeProvider typeProvider) { diff --git a/common/src/test/java/dev/cel/common/values/BUILD.bazel b/common/src/test/java/dev/cel/common/values/BUILD.bazel index 02016fa03..5b3ac349c 100644 --- a/common/src/test/java/dev/cel/common/values/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/values/BUILD.bazel @@ -8,6 +8,7 @@ java_library( srcs = glob(["*.java"]), deps = [ "//:java_truth", + "//bundle:cel", "//common", "//common:options", "//common:runtime_exception", diff --git a/common/src/test/java/dev/cel/common/values/ImmutableMapValueTest.java b/common/src/test/java/dev/cel/common/values/ImmutableMapValueTest.java index 4517a203c..ee91712ec 100644 --- a/common/src/test/java/dev/cel/common/values/ImmutableMapValueTest.java +++ b/common/src/test/java/dev/cel/common/values/ImmutableMapValueTest.java @@ -68,7 +68,6 @@ public void get_nonExistentKey_throws() { CelRuntimeException exception = assertThrows(CelRuntimeException.class, () -> mapValue.get(IntValue.create(100L))); - assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(exception).hasMessageThat().contains("key '100' is not present in map."); } 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 f2974992d..bd4e820b8 100644 --- a/common/src/test/java/dev/cel/common/values/StructValueTest.java +++ b/common/src/test/java/dev/cel/common/values/StructValueTest.java @@ -17,47 +17,80 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; +import dev.cel.bundle.Cel; +import dev.cel.bundle.CelFactory; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelOptions; import dev.cel.common.types.CelType; -import dev.cel.common.types.StructTypeReference; +import dev.cel.common.types.CelTypeProvider; +import dev.cel.common.types.SimpleType; +import dev.cel.common.types.StructType; +import java.util.Map; import java.util.Optional; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(TestParameterInjector.class) public final class StructValueTest { + private static final StructType CUSTOM_STRUCT_TYPE = + StructType.create( + "custom_struct", + ImmutableSet.of("data"), + fieldName -> fieldName.equals("data") ? Optional.of(SimpleType.INT) : Optional.empty()); + private static final CelTypeProvider CUSTOM_STRUCT_TYPE_PROVIDER = + new CelTypeProvider() { + @Override + public ImmutableList types() { + return ImmutableList.of(CUSTOM_STRUCT_TYPE); + } + + @Override + public Optional findType(String typeName) { + return typeName.equals(CUSTOM_STRUCT_TYPE.name()) + ? Optional.of(CUSTOM_STRUCT_TYPE) + : Optional.empty(); + } + }; + + private static final CelValueProvider CUSTOM_STRUCT_VALUE_PROVIDER = + (structType, fields) -> { + if (structType.equals(CUSTOM_STRUCT_TYPE.name())) { + return Optional.of(new CelCustomStructValue(fields)); + } + return Optional.empty(); + }; @Test public void emptyStruct() { - UserDefinedClass userDefinedPojo = new UserDefinedClass(0); - CelCustomStruct celCustomStruct = new CelCustomStruct(userDefinedPojo); + CelCustomStructValue celCustomStruct = new CelCustomStructValue(0); - assertThat(celCustomStruct.value()).isEqualTo(userDefinedPojo); + assertThat(celCustomStruct.value()).isEqualTo(celCustomStruct); assertThat(celCustomStruct.isZeroValue()).isTrue(); } @Test public void constructStruct() { - UserDefinedClass userDefinedPojo = new UserDefinedClass(5); - CelCustomStruct celCustomStruct = new CelCustomStruct(userDefinedPojo); + CelCustomStructValue celCustomStruct = new CelCustomStructValue(5); - assertThat(celCustomStruct.value()).isEqualTo(userDefinedPojo); + assertThat(celCustomStruct.value()).isEqualTo(celCustomStruct); assertThat(celCustomStruct.isZeroValue()).isFalse(); } @Test public void selectField_success() { - UserDefinedClass userDefinedPojo = new UserDefinedClass(5); - CelCustomStruct celCustomStruct = new CelCustomStruct(userDefinedPojo); + CelCustomStructValue celCustomStruct = new CelCustomStructValue(5); assertThat(celCustomStruct.select(StringValue.create("data"))).isEqualTo(IntValue.create(5L)); } @Test public void selectField_nonExistentField_throws() { - UserDefinedClass userDefinedPojo = new UserDefinedClass(5); - CelCustomStruct celCustomStruct = new CelCustomStruct(userDefinedPojo); + CelCustomStructValue celCustomStruct = new CelCustomStructValue(5); assertThrows( IllegalArgumentException.class, () -> celCustomStruct.select(StringValue.create("bogus"))); @@ -67,8 +100,7 @@ public void selectField_nonExistentField_throws() { @TestParameters("{fieldName: 'data', expectedResult: true}") @TestParameters("{fieldName: 'bogus', expectedResult: false}") public void findField_success(String fieldName, boolean expectedResult) { - UserDefinedClass userDefinedPojo = new UserDefinedClass(5); - CelCustomStruct celCustomStruct = new CelCustomStruct(userDefinedPojo); + CelCustomStructValue celCustomStruct = new CelCustomStructValue(5); assertThat(celCustomStruct.find(StringValue.create(fieldName)).isPresent()) .isEqualTo(expectedResult); @@ -76,37 +108,92 @@ public void findField_success(String fieldName, boolean expectedResult) { @Test public void celTypeTest() { - UserDefinedClass userDefinedPojo = new UserDefinedClass(0); - CelCustomStruct value = new CelCustomStruct(userDefinedPojo); + CelCustomStructValue value = new CelCustomStructValue(0); - assertThat(value.celType()).isEqualTo(StructTypeReference.create("customStruct")); + assertThat(value.celType()).isEqualTo(CUSTOM_STRUCT_TYPE); } - private static class UserDefinedClass { - private final long data; + @Test + public void evaluate_usingCustomClass_createNewStruct() throws Exception { + Cel cel = + CelFactory.standardCelBuilder() + .setOptions(CelOptions.current().enableCelValue(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(); + + assertThat(result.data).isEqualTo(50); + } - private UserDefinedClass(long data) { - this.data = data; - } + @Test + public void evaluate_usingCustomClass_asVariable() throws Exception { + Cel cel = + CelFactory.standardCelBuilder() + .setOptions(CelOptions.current().enableCelValue(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) + cel.createProgram(ast).eval(ImmutableMap.of("a", new CelCustomStructValue(10))); + + assertThat(result.data).isEqualTo(10); + } + + @Test + public void evaluate_usingCustomClass_asVariableSelectField() throws Exception { + Cel cel = + CelFactory.standardCelBuilder() + .setOptions(CelOptions.current().enableCelValue(true).build()) + .addVar("a", CUSTOM_STRUCT_TYPE) + .setTypeProvider(CUSTOM_STRUCT_TYPE_PROVIDER) + .setValueProvider(CUSTOM_STRUCT_VALUE_PROVIDER) + .build(); + CelAbstractSyntaxTree ast = cel.compile("a.data").getAst(); + + assertThat(cel.createProgram(ast).eval(ImmutableMap.of("a", new CelCustomStructValue(20)))) + .isEqualTo(20L); + } + + @Test + public void evaluate_usingCustomClass_selectField() throws Exception { + Cel cel = + CelFactory.standardCelBuilder() + .setOptions(CelOptions.current().enableCelValue(true).build()) + .setTypeProvider(CUSTOM_STRUCT_TYPE_PROVIDER) + .setValueProvider(CUSTOM_STRUCT_VALUE_PROVIDER) + .build(); + CelAbstractSyntaxTree ast = cel.compile("custom_struct{data: 5}.data").getAst(); + + Object result = cel.createProgram(ast).eval(); + + assertThat(result).isEqualTo(5L); } @SuppressWarnings("Immutable") // Test only - private static class CelCustomStruct extends StructValue { - private final UserDefinedClass userDefinedClass; + private static class CelCustomStructValue extends StructValue { + + private final long data; @Override - public UserDefinedClass value() { - return userDefinedClass; + public CelCustomStructValue value() { + return this; } @Override public boolean isZeroValue() { - return userDefinedClass.data == 0; + return data == 0; } @Override public CelType celType() { - return StructTypeReference.create("customStruct"); + return CUSTOM_STRUCT_TYPE; } @Override @@ -124,8 +211,12 @@ public Optional find(StringValue field) { return Optional.empty(); } - private CelCustomStruct(UserDefinedClass value) { - this.userDefinedClass = value; + private CelCustomStructValue(Map fields) { + this((long) fields.get("data")); + } + + private CelCustomStructValue(long data) { + this.data = data; } } } diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java index 2090e6bfa..47187e4d3 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java @@ -139,7 +139,13 @@ public interface CelRuntimeBuilder { @CanIgnoreReturnValue CelRuntimeBuilder setTypeFactory(Function typeFactory); - /** Sets the {@code celValueProvider} for resolving values during evaluation. */ + /** + * Sets the {@code celValueProvider} for resolving values during evaluation. The provided value + * provider will be used first before falling back to the built-in {@link + * dev.cel.common.values.ProtoMessageValueProvider} for resolving protobuf messages. + * + *

Note {@link CelOptions#enableCelValue()} must be enabled or this method will be a no-op. + */ @CanIgnoreReturnValue CelRuntimeBuilder setValueProvider(CelValueProvider celValueProvider);