diff --git a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java
index 260f13bf2..4cb3a75e7 100644
--- a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java
+++ b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java
@@ -526,6 +526,24 @@ public void program_withVars() throws Exception {
assertThat(program.eval(ImmutableMap.of("variable", "hello"))).isEqualTo(true);
}
+ @Test
+ public void program_withCelValue() throws Exception {
+ Cel cel =
+ standardCelBuilderWithMacros()
+ .setOptions(CelOptions.current().enableCelValue(true).build())
+ .addDeclarations(
+ Decl.newBuilder()
+ .setName("variable")
+ .setIdent(IdentDecl.newBuilder().setType(CelTypes.STRING))
+ .build())
+ .setResultType(SimpleType.BOOL)
+ .build();
+
+ CelRuntime.Program program = cel.createProgram(cel.compile("variable == 'hello'").getAst());
+
+ assertThat(program.eval(ImmutableMap.of("variable", "hello"))).isEqualTo(true);
+ }
+
@Test
public void program_withProtoVars() throws Exception {
Cel cel =
@@ -1285,6 +1303,26 @@ public void programAdvanceEvaluation_nestedSelect() throws Exception {
.isEqualTo(CelUnknownSet.create(CelAttribute.fromQualifiedIdentifier("com.google.a")));
}
+ @Test
+ public void programAdvanceEvaluation_nestedSelect_withCelValue() throws Exception {
+ Cel cel =
+ standardCelBuilderWithMacros()
+ .setOptions(
+ CelOptions.current().enableUnknownTracking(true).enableCelValue(true).build())
+ .addVar("com", MapType.create(SimpleType.STRING, SimpleType.DYN))
+ .addFunctionBindings()
+ .setResultType(SimpleType.BOOL)
+ .build();
+ CelRuntime.Program program = cel.createProgram(cel.compile("com.google.a || false").getAst());
+
+ assertThat(
+ program.advanceEvaluation(
+ UnknownContext.create(
+ fromMap(ImmutableMap.of()),
+ ImmutableList.of(CelAttributePattern.fromQualifiedIdentifier("com.google.a")))))
+ .isEqualTo(CelUnknownSet.create(CelAttribute.fromQualifiedIdentifier("com.google.a")));
+ }
+
@Test
public void programAdvanceEvaluation_argumentMergeErrorPriority() throws Exception {
Cel cel =
diff --git a/common/src/main/java/dev/cel/common/CelOptions.java b/common/src/main/java/dev/cel/common/CelOptions.java
index b4a944daa..eaa812e2b 100644
--- a/common/src/main/java/dev/cel/common/CelOptions.java
+++ b/common/src/main/java/dev/cel/common/CelOptions.java
@@ -87,6 +87,8 @@ public abstract class CelOptions {
public abstract boolean enableUnknownTracking();
+ public abstract boolean enableCelValue();
+
public abstract int comprehensionMaxIterations();
public abstract Builder toBuilder();
@@ -176,6 +178,7 @@ public static Builder newBuilder() {
.errorOnDuplicateMapKeys(false)
.resolveTypeDependencies(true)
.enableUnknownTracking(false)
+ .enableCelValue(false)
.comprehensionMaxIterations(-1);
}
@@ -428,6 +431,15 @@ public abstract static class Builder {
*/
public abstract Builder enableUnknownTracking(boolean value);
+ /**
+ * Enables the usage of {@code CelValue} for the runtime. It is a native value representation of
+ * CEL that wraps Java native objects, and comes with extended capabilities, such as allowing
+ * value constructs not understood by CEL (ex: POJOs).
+ *
+ *
Warning: This option is experimental.
+ */
+ public abstract Builder enableCelValue(boolean value);
+
/**
* Limit the total number of iterations permitted within comprehension loops.
*
diff --git a/common/src/main/java/dev/cel/common/types/TypeType.java b/common/src/main/java/dev/cel/common/types/TypeType.java
index 797284986..44905bbc7 100644
--- a/common/src/main/java/dev/cel/common/types/TypeType.java
+++ b/common/src/main/java/dev/cel/common/types/TypeType.java
@@ -26,8 +26,6 @@
@Immutable
public abstract class TypeType extends CelType {
- static final TypeType TYPE = create(SimpleType.DYN);
-
@Override
public CelKind kind() {
return CelKind.TYPE;
@@ -38,6 +36,16 @@ public String name() {
return "type";
}
+ /** Retrieves the underlying type name of the type-kind held. */
+ public String containingTypeName() {
+ CelType containingType = type();
+ if (containingType.kind() == CelKind.DYN) {
+ return "type";
+ }
+
+ return containingType.name();
+ }
+
@Override
public abstract ImmutableList parameters();
diff --git a/common/src/main/java/dev/cel/common/values/CelValueProvider.java b/common/src/main/java/dev/cel/common/values/CelValueProvider.java
index 782960b0c..0e896e7ac 100644
--- a/common/src/main/java/dev/cel/common/values/CelValueProvider.java
+++ b/common/src/main/java/dev/cel/common/values/CelValueProvider.java
@@ -41,7 +41,7 @@ public interface CelValueProvider {
final class CombinedCelValueProvider implements CelValueProvider {
private final ImmutableList celValueProviders;
- CombinedCelValueProvider(CelValueProvider first, CelValueProvider second) {
+ public CombinedCelValueProvider(CelValueProvider first, CelValueProvider second) {
Preconditions.checkNotNull(first);
Preconditions.checkNotNull(second);
celValueProviders = ImmutableList.of(first, second);
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 ee91712ec..4517a203c 100644
--- a/common/src/test/java/dev/cel/common/values/ImmutableMapValueTest.java
+++ b/common/src/test/java/dev/cel/common/values/ImmutableMapValueTest.java
@@ -68,6 +68,7 @@ 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/runtime/BUILD.bazel b/runtime/BUILD.bazel
index d9f3020e1..b08b341f8 100644
--- a/runtime/BUILD.bazel
+++ b/runtime/BUILD.bazel
@@ -45,3 +45,9 @@ java_library(
name = "evaluation_listener",
exports = ["//runtime/src/main/java/dev/cel/runtime:evaluation_listener"],
)
+
+java_library(
+ name = "runtime_type_provider_legacy",
+ visibility = ["//visibility:public"],
+ exports = ["//runtime/src/main/java/dev/cel/runtime:runtime_type_provider_legacy"],
+)
diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel
index d1b4da86c..d713b5113 100644
--- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel
+++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel
@@ -147,9 +147,8 @@ java_library(
tags = [
],
deps = [
- ":base",
":evaluation_listener",
- ":interpreter",
+ ":runtime_type_provider_legacy",
":unknown_attributes",
"//:auto_value",
"//common",
@@ -161,6 +160,9 @@ java_library(
"//common/internal:dynamic_proto",
"//common/internal:proto_message_factory",
"//common/types:cel_types",
+ "//common/values:cel_value_provider",
+ "//common/values:proto_message_value_provider",
+ "//runtime:interpreter",
"@maven//:com_google_code_findbugs_annotations",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
@@ -212,6 +214,29 @@ java_library(
],
)
+java_library(
+ name = "runtime_type_provider_legacy",
+ srcs = ["RuntimeTypeProviderLegacyImpl.java"],
+ deps = [
+ ":unknown_attributes",
+ "//common:options",
+ "//common/annotations",
+ "//common/internal:cel_descriptor_pools",
+ "//common/internal:dynamic_proto",
+ "//common/types",
+ "//common/types:type_providers",
+ "//common/values",
+ "//common/values:cel_value",
+ "//common/values:cel_value_provider",
+ "//common/values:proto_message_value",
+ "//runtime:interpreter",
+ "@cel_spec//proto/cel/expr:expr_java_proto",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:com_google_guava_guava",
+ "@maven//:org_jspecify_jspecify",
+ ],
+)
+
java_library(
name = "interpreter_util",
srcs = ["InterpreterUtil.java"],
diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java
index 1408a2069..2090e6bfa 100644
--- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java
+++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java
@@ -22,6 +22,7 @@
import com.google.protobuf.ExtensionRegistry;
import com.google.protobuf.Message;
import dev.cel.common.CelOptions;
+import dev.cel.common.values.CelValueProvider;
import java.util.function.Function;
/** Interface for building an instance of CelRuntime */
@@ -138,6 +139,10 @@ public interface CelRuntimeBuilder {
@CanIgnoreReturnValue
CelRuntimeBuilder setTypeFactory(Function typeFactory);
+ /** Sets the {@code celValueProvider} for resolving values during evaluation. */
+ @CanIgnoreReturnValue
+ CelRuntimeBuilder setValueProvider(CelValueProvider celValueProvider);
+
/** Enable or disable the standard CEL library functions and variables. */
@CanIgnoreReturnValue
CelRuntimeBuilder setStandardEnvironmentEnabled(boolean value);
diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java
index 549977846..a98ed056f 100644
--- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java
+++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java
@@ -40,6 +40,8 @@
// CEL-Internal-3
import dev.cel.common.internal.ProtoMessageFactory;
import dev.cel.common.types.CelTypes;
+import dev.cel.common.values.CelValueProvider;
+import dev.cel.common.values.ProtoMessageValueProvider;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.Function;
@@ -82,6 +84,7 @@ public static final class Builder implements CelRuntimeBuilder {
private boolean standardEnvironmentEnabled;
private Function customTypeFactory;
private ExtensionRegistry extensionRegistry;
+ private CelValueProvider celValueProvider;
@Override
@CanIgnoreReturnValue
@@ -142,6 +145,13 @@ public Builder setTypeFactory(Function typeFactory) {
return this;
}
+ @Override
+ @CanIgnoreReturnValue
+ public CelRuntimeBuilder setValueProvider(CelValueProvider celValueProvider) {
+ this.celValueProvider = celValueProvider;
+ return this;
+ }
+
@Override
@CanIgnoreReturnValue
public Builder setStandardEnvironmentEnabled(boolean value) {
@@ -179,11 +189,14 @@ public CelRuntimeLegacyImpl build() {
// Add libraries, such as extensions
celRuntimeLibraries.build().forEach(celLibrary -> celLibrary.setRuntimeOptions(this));
+ CelDescriptors celDescriptors =
+ CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(
+ fileTypes.build(), options.resolveTypeDependencies());
+
CelDescriptorPool celDescriptorPool =
newDescriptorPool(
- fileTypes.build(),
- extensionRegistry,
- options);
+ celDescriptors,
+ extensionRegistry);
@SuppressWarnings("Immutable")
ProtoMessageFactory runtimeTypeFactory =
@@ -222,20 +235,30 @@ public CelRuntimeLegacyImpl build() {
}
}));
+ RuntimeTypeProvider runtimeTypeProvider;
+
+ if (options.enableCelValue()) {
+ CelValueProvider messageValueProvider =
+ ProtoMessageValueProvider.newInstance(dynamicProto, options);
+ if (celValueProvider != null) {
+ messageValueProvider =
+ new CelValueProvider.CombinedCelValueProvider(celValueProvider, messageValueProvider);
+ }
+
+ runtimeTypeProvider =
+ new RuntimeTypeProviderLegacyImpl(
+ options, messageValueProvider, celDescriptorPool, dynamicProto);
+ } else {
+ runtimeTypeProvider = new DescriptorMessageProvider(runtimeTypeFactory, options);
+ }
+
return new CelRuntimeLegacyImpl(
- new DefaultInterpreter(
- new DescriptorMessageProvider(runtimeTypeFactory, options), dispatcher, options),
- options);
+ new DefaultInterpreter(runtimeTypeProvider, dispatcher, options), options);
}
private static CelDescriptorPool newDescriptorPool(
- ImmutableSet fileTypeSet,
- ExtensionRegistry extensionRegistry,
- CelOptions celOptions) {
- CelDescriptors celDescriptors =
- CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(
- fileTypeSet, celOptions.resolveTypeDependencies());
-
+ CelDescriptors celDescriptors,
+ ExtensionRegistry extensionRegistry) {
ImmutableList.Builder descriptorPools = new ImmutableList.Builder<>();
descriptorPools.add(DefaultDescriptorPool.create(celDescriptors, extensionRegistry));
diff --git a/runtime/src/main/java/dev/cel/runtime/RuntimeTypeProviderLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/RuntimeTypeProviderLegacyImpl.java
new file mode 100644
index 000000000..841d702a2
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/RuntimeTypeProviderLegacyImpl.java
@@ -0,0 +1,130 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dev.cel.runtime;
+
+import dev.cel.expr.Type;
+import dev.cel.expr.Value;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.errorprone.annotations.Immutable;
+import dev.cel.common.CelOptions;
+import dev.cel.common.annotations.Internal;
+import dev.cel.common.internal.CelDescriptorPool;
+import dev.cel.common.internal.DynamicProto;
+import dev.cel.common.types.CelType;
+import dev.cel.common.types.TypeType;
+import dev.cel.common.values.CelValue;
+import dev.cel.common.values.CelValueProvider;
+import dev.cel.common.values.ProtoCelValueConverter;
+import dev.cel.common.values.SelectableValue;
+import dev.cel.common.values.StringValue;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import org.jspecify.nullness.Nullable;
+
+/** Bridge between the old RuntimeTypeProvider and CelValueProvider APIs. */
+@Internal
+@Immutable
+public final class RuntimeTypeProviderLegacyImpl implements RuntimeTypeProvider {
+
+ private final CelValueProvider valueProvider;
+ private final ProtoCelValueConverter protoCelValueConverter;
+ private final TypeResolver standardTypeResolver;
+
+ @VisibleForTesting
+ public RuntimeTypeProviderLegacyImpl(
+ CelOptions celOptions,
+ CelValueProvider valueProvider,
+ CelDescriptorPool celDescriptorPool,
+ DynamicProto dynamicProto) {
+ this.valueProvider = valueProvider;
+ this.protoCelValueConverter =
+ ProtoCelValueConverter.newInstance(celOptions, celDescriptorPool, dynamicProto);
+ this.standardTypeResolver = StandardTypeResolver.getInstance(celOptions);
+ }
+
+ @Override
+ public Object createMessage(String messageName, Map values) {
+ return unwrapCelValue(
+ valueProvider
+ .newValue(messageName, values)
+ .orElseThrow(
+ () ->
+ new NoSuchElementException(
+ "Could not generate a new value for message name: " + messageName)));
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Object selectField(Object message, String fieldName) {
+ SelectableValue selectableValue =
+ (SelectableValue) protoCelValueConverter.fromJavaObjectToCelValue(message);
+
+ return unwrapCelValue(selectableValue.select(StringValue.create(fieldName)));
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Object hasField(Object message, String fieldName) {
+ SelectableValue selectableValue =
+ (SelectableValue) protoCelValueConverter.fromJavaObjectToCelValue(message);
+
+ return selectableValue.find(StringValue.create(fieldName)).isPresent();
+ }
+
+ @Override
+ public Object adapt(Object message) {
+ if (message instanceof CelUnknownSet) {
+ return message; // CelUnknownSet is handled specially for iterative evaluation. No need to
+ // adapt to CelValue.
+ }
+ return unwrapCelValue(protoCelValueConverter.fromJavaObjectToCelValue(message));
+ }
+
+ @Override
+ public Value resolveObjectType(Object obj, Value checkedTypeValue) {
+ // Presently, Java only supports evaluation of checked-only expressions.
+ Preconditions.checkNotNull(checkedTypeValue);
+ return standardTypeResolver.resolveObjectType(obj, checkedTypeValue);
+ }
+
+ @Override
+ public Value adaptType(CelType type) {
+ Preconditions.checkNotNull(type);
+ if (type instanceof TypeType) {
+ return createTypeValue(((TypeType) type).containingTypeName());
+ }
+
+ return createTypeValue(type.name());
+ }
+
+ @Override
+ public @Nullable Value adaptType(@Nullable Type type) {
+ throw new UnsupportedOperationException("This should only be called with native CelType.");
+ }
+
+ /**
+ * DefaultInterpreter cannot handle CelValue and instead expects plain Java objects.
+ *
+ *
This will become unnecessary once we introduce a rewrite of a Cel runtime.
+ */
+ private Object unwrapCelValue(CelValue object) {
+ return protoCelValueConverter.fromCelValueToJavaObject(object);
+ }
+
+ private static Value createTypeValue(String name) {
+ return Value.newBuilder().setTypeValue(name).build();
+ }
+}
diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel
index 52c200358..fea8d6e43 100644
--- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel
+++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel
@@ -8,6 +8,7 @@ java_library(
srcs = glob(
["*.java"],
exclude = [
+ "CelValueInterpreterTest.java",
"InterpreterTest.java",
],
),
@@ -70,6 +71,22 @@ java_library(
],
)
+java_library(
+ name = "cel_value_interpreter_test",
+ testonly = 1,
+ srcs = [
+ "CelValueInterpreterTest.java",
+ ],
+ deps = [
+ # "//java/com/google/testing/testsize:annotations",
+ "//common:options",
+ "//testing:base_interpreter_test",
+ "//testing:cel_value_sync",
+ "//testing:eval",
+ "@maven//:junit_junit",
+ ],
+)
+
junit4_test_suites(
name = "test_suites",
shard_count = 4,
@@ -79,6 +96,7 @@ junit4_test_suites(
],
src_dir = "src/test/java",
deps = [
+ ":cel_value_interpreter_test",
":interpreter_test",
":tests",
],
diff --git a/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java b/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java
index 72c318e37..61794aea0 100644
--- a/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java
+++ b/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java
@@ -2,7 +2,7 @@
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
+// You may obtain a copy of the License aj
//
// https://www.apache.org/licenses/LICENSE-2.0
//
diff --git a/runtime/src/test/java/dev/cel/runtime/CelValueInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/CelValueInterpreterTest.java
new file mode 100644
index 000000000..4c657a0c8
--- /dev/null
+++ b/runtime/src/test/java/dev/cel/runtime/CelValueInterpreterTest.java
@@ -0,0 +1,88 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dev.cel.runtime;
+
+// import com.google.testing.testsize.MediumTest;
+import dev.cel.common.CelOptions;
+import dev.cel.testing.BaseInterpreterTest;
+import dev.cel.testing.Eval;
+import dev.cel.testing.EvalCelValueSync;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Tests for {@link Interpreter} and related functionality using {@code CelValue}. */
+// @MediumTest
+@RunWith(Parameterized.class)
+public class CelValueInterpreterTest extends BaseInterpreterTest {
+
+ private static final CelOptions SIGNED_UINT_TEST_OPTIONS =
+ CelOptions.current()
+ .enableTimestampEpoch(true)
+ .enableHeterogeneousNumericComparisons(true)
+ .enableCelValue(true)
+ .comprehensionMaxIterations(1_000)
+ .build();
+
+ public CelValueInterpreterTest(boolean declareWithCelType, Eval eval) {
+ super(declareWithCelType, eval);
+ }
+
+ /** Test relies on PartialMessage, which is deprecated and not supported for CelValue. */
+ @Override
+ @Test
+ public void unknownField() {
+ skipBaselineVerification();
+ }
+
+ /** Test relies on PartialMessage, which is deprecated and not supported for CelValue. */
+ @Override
+ @Test
+ public void unknownResultSet() {
+ skipBaselineVerification();
+ }
+
+ @Parameters
+ public static List