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
1 change: 1 addition & 0 deletions bundle/src/main/java/dev/cel/bundle/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 12 additions & 0 deletions bundle/src/main/java/dev/cel/bundle/CelBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -185,6 +186,17 @@ public interface CelBuilder {
@CanIgnoreReturnValue
CelBuilder setTypeFactory(Function<String, Message.Builder> 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.
*
* <p>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.
*
Expand Down
7 changes: 7 additions & 0 deletions bundle/src/main/java/dev/cel/bundle/CelImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -254,6 +255,12 @@ public Builder setTypeFactory(Function<String, Message.Builder> typeFactory) {
return this;
}

@Override
public Builder setValueProvider(CelValueProvider celValueProvider) {
runtimeBuilder.setValueProvider(celValueProvider);
return this;
}

@Override
@Deprecated
public Builder setTypeProvider(TypeProvider typeProvider) {
Expand Down
1 change: 1 addition & 0 deletions common/src/test/java/dev/cel/common/values/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ java_library(
srcs = glob(["*.java"]),
deps = [
"//:java_truth",
"//bundle:cel",
"//common",
"//common:options",
"//common:runtime_exception",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}

Expand Down
149 changes: 120 additions & 29 deletions common/src/test/java/dev/cel/common/values/StructValueTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<CelType> types() {
return ImmutableList.of(CUSTOM_STRUCT_TYPE);
}

@Override
public Optional<CelType> 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")));
Expand All @@ -67,46 +100,100 @@ 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);
}

@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<StringValue> {
private final UserDefinedClass userDefinedClass;
private static class CelCustomStructValue extends StructValue<StringValue> {

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
Expand All @@ -124,8 +211,12 @@ public Optional<CelValue> find(StringValue field) {
return Optional.empty();
}

private CelCustomStruct(UserDefinedClass value) {
this.userDefinedClass = value;
private CelCustomStructValue(Map<String, Object> fields) {
this((long) fields.get("data"));
}

private CelCustomStructValue(long data) {
this.data = data;
}
}
}
8 changes: 7 additions & 1 deletion runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,13 @@ public interface CelRuntimeBuilder {
@CanIgnoreReturnValue
CelRuntimeBuilder setTypeFactory(Function<String, Message.Builder> 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.
*
* <p>Note {@link CelOptions#enableCelValue()} must be enabled or this method will be a no-op.
*/
@CanIgnoreReturnValue
CelRuntimeBuilder setValueProvider(CelValueProvider celValueProvider);

Expand Down