of("id", overloadSelector.id()));
}
}
+
+ private final class RepresentFeatureFlag implements Represent {
+
+ @Override
+ public Node representData(Object data) {
+ CelEnvironment.FeatureFlag featureFlag = (CelEnvironment.FeatureFlag) data;
+ return represent(
+ ImmutableMap.builder()
+ .put("name", featureFlag.name())
+ .put("enabled", featureFlag.enabled())
+ .buildOrThrow());
+ }
+ }
+
+ private final class RepresentLimit implements Represent {
+
+ @Override
+ public Node representData(Object data) {
+ CelEnvironment.Limit limit = (CelEnvironment.Limit) data;
+ return represent(
+ ImmutableMap.builder()
+ .put("name", limit.name())
+ .put("value", limit.value() < 0 ? -1 : limit.value())
+ .buildOrThrow());
+ }
+ }
}
diff --git a/bundle/src/main/java/dev/cel/bundle/CelFactory.java b/bundle/src/main/java/dev/cel/bundle/CelFactory.java
index 6cc6d8192..ac589cfe6 100644
--- a/bundle/src/main/java/dev/cel/bundle/CelFactory.java
+++ b/bundle/src/main/java/dev/cel/bundle/CelFactory.java
@@ -20,6 +20,7 @@
import dev.cel.compiler.CelCompilerImpl;
import dev.cel.parser.CelParserImpl;
import dev.cel.runtime.CelRuntime;
+import dev.cel.runtime.CelRuntimeImpl;
import dev.cel.runtime.CelRuntimeLegacyImpl;
/** Helper class to configure the entire CEL stack in a common interface. */
@@ -44,6 +45,30 @@ public static CelBuilder standardCelBuilder() {
.setStandardEnvironmentEnabled(true);
}
+ /**
+ * Creates a builder for configuring CEL for the parsing, optional type-checking, and evaluation
+ * of expressions using the Program Planner.
+ *
+ * The {@code ProgramPlanner} architecture provides key benefits over the {@link
+ * #standardCelBuilder()}:
+ *
+ *
+ * Performance: Programs can be cached for improving evaluation speed.
+ * Parsed-only expression evaluation: Unlike the traditional stack which required
+ * supplying type-checked expressions, this architecture handles both parsed-only and
+ * type-checked expressions.
+ *
+ */
+ public static CelBuilder plannerCelBuilder() {
+ return CelImpl.newBuilder(
+ CelCompilerImpl.newBuilder(
+ CelParserImpl.newBuilder(),
+ CelCheckerLegacyImpl.newBuilder().setStandardEnvironmentEnabled(true)),
+ CelRuntimeImpl.newBuilder())
+ // CEL-Internal-2
+ .setOptions(CelOptions.current().enableHeterogeneousNumericComparisons(true).build());
+ }
+
/** Combines a prebuilt {@link CelCompiler} and {@link CelRuntime} into {@link Cel}. */
public static Cel combine(CelCompiler celCompiler, CelRuntime celRuntime) {
return CelImpl.combine(celCompiler, celRuntime);
diff --git a/bundle/src/main/java/dev/cel/bundle/CelImpl.java b/bundle/src/main/java/dev/cel/bundle/CelImpl.java
index bc92cca7a..f0db128c1 100644
--- a/bundle/src/main/java/dev/cel/bundle/CelImpl.java
+++ b/bundle/src/main/java/dev/cel/bundle/CelImpl.java
@@ -54,6 +54,7 @@
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.CelRuntime;
import dev.cel.runtime.CelRuntimeBuilder;
+import dev.cel.runtime.CelRuntimeImpl;
import dev.cel.runtime.CelRuntimeLibrary;
import dev.cel.runtime.CelStandardFunctions;
import java.util.Arrays;
@@ -142,6 +143,8 @@ static CelImpl combine(CelCompiler compiler, CelRuntime runtime) {
* Create a new builder for constructing a {@code CelImpl} instance.
*
* By default, {@link CelOptions#DEFAULT} are enabled, as is the CEL standard environment.
+ *
+ *
CEL Library Internals. Do Not Use. Consumers should use {@code CelFactory} instead.
*/
static CelBuilder newBuilder(
CelCompilerBuilder compilerBuilder, CelRuntimeBuilder celRuntimeBuilder) {
@@ -199,6 +202,10 @@ public CelContainer container() {
@Override
public CelBuilder setContainer(CelContainer container) {
compilerBuilder.setContainer(container);
+ if (runtimeBuilder instanceof CelRuntimeImpl.Builder) {
+ runtimeBuilder.setContainer(container);
+ }
+
return this;
}
@@ -274,6 +281,18 @@ public CelBuilder addFunctionBindings(Iterable lateBoundFunctionNames) {
+ runtimeBuilder.addLateBoundFunctions(lateBoundFunctionNames);
+ return this;
+ }
+
@Override
public CelBuilder setResultType(CelType resultType) {
checkNotNull(resultType);
@@ -308,6 +327,9 @@ public Builder setTypeProvider(TypeProvider typeProvider) {
@Override
public CelBuilder setTypeProvider(CelTypeProvider celTypeProvider) {
compilerBuilder.setTypeProvider(celTypeProvider);
+ if (runtimeBuilder instanceof CelRuntimeImpl.Builder) {
+ runtimeBuilder.setTypeProvider(celTypeProvider);
+ }
return this;
}
diff --git a/bundle/src/test/java/dev/cel/bundle/BUILD.bazel b/bundle/src/test/java/dev/cel/bundle/BUILD.bazel
index cd33dd67d..548b4483d 100644
--- a/bundle/src/test/java/dev/cel/bundle/BUILD.bazel
+++ b/bundle/src/test/java/dev/cel/bundle/BUILD.bazel
@@ -1,9 +1,11 @@
load("@rules_java//java:defs.bzl", "java_library")
load("//:testing.bzl", "junit4_test_suites")
-package(default_applicable_licenses = [
- "//:license",
-])
+package(
+ default_applicable_licenses = [
+ "//:license",
+ ],
+)
java_library(
name = "tests",
@@ -17,6 +19,7 @@ java_library(
deps = [
"//:java_truth",
"//bundle:cel",
+ "//bundle:cel_impl",
"//bundle:environment",
"//bundle:environment_exception",
"//bundle:environment_exporter",
@@ -54,6 +57,8 @@ java_library(
"//runtime:evaluation_listener",
"//runtime:function_binding",
"//runtime:unknown_attributes",
+ "//testing:cel_runtime_flavor",
+ "//testing/protos:single_file_extension_java_proto",
"//testing/protos:single_file_java_proto",
"@cel_spec//proto/cel/expr:checked_java_proto",
"@cel_spec//proto/cel/expr:syntax_java_proto",
diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentExporterTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentExporterTest.java
index d6608a9d4..ae0de2c18 100644
--- a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentExporterTest.java
+++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentExporterTest.java
@@ -36,8 +36,10 @@
import dev.cel.common.CelOptions;
import dev.cel.common.CelOverloadDecl;
import dev.cel.common.CelVarDecl;
+import dev.cel.common.types.ListType;
import dev.cel.common.types.OpaqueType;
import dev.cel.common.types.SimpleType;
+import dev.cel.common.types.TypeParamType;
import dev.cel.extensions.CelExtensions;
import java.net.URL;
import java.util.HashSet;
@@ -176,6 +178,20 @@ public void customFunctions() {
"math.isFinite",
CelOverloadDecl.newGlobalOverload(
"math_isFinite_int64", SimpleType.BOOL, SimpleType.INT)),
+ CelFunctionDecl.newFunctionDeclaration(
+ "zipGeneric",
+ CelOverloadDecl.newGlobalOverload(
+ "zip_list_list",
+ ListType.create(ListType.create(TypeParamType.create("T"))),
+ ListType.create(TypeParamType.create("T")),
+ ListType.create(TypeParamType.create("T")))),
+ CelFunctionDecl.newFunctionDeclaration(
+ "zip",
+ CelOverloadDecl.newGlobalOverload(
+ "zip_list_int_list_int",
+ ListType.create(ListType.create(SimpleType.INT)),
+ ListType.create(SimpleType.INT),
+ ListType.create(SimpleType.INT))),
CelFunctionDecl.newFunctionDeclaration(
"addWeeks",
CelOverloadDecl.newMemberOverload(
@@ -207,6 +223,68 @@ public void customFunctions() {
.setTarget(TypeDecl.create("google.protobuf.Timestamp"))
.setArguments(ImmutableList.of(TypeDecl.create("int")))
.setReturnType(TypeDecl.create("bool"))
+ .build())),
+ FunctionDecl.create(
+ "zipGeneric",
+ ImmutableSet.of(
+ OverloadDecl.newBuilder()
+ .setId("zip_list_list")
+ .setArguments(
+ ImmutableList.of(
+ TypeDecl.newBuilder()
+ .setName("list")
+ .addParams(
+ TypeDecl.newBuilder()
+ .setName("T")
+ .setIsTypeParam(true)
+ .build())
+ .build(),
+ TypeDecl.newBuilder()
+ .setName("list")
+ .addParams(
+ TypeDecl.newBuilder()
+ .setName("T")
+ .setIsTypeParam(true)
+ .build())
+ .build()))
+ .setReturnType(
+ TypeDecl.newBuilder()
+ .setName("list")
+ .addParams(
+ TypeDecl.newBuilder()
+ .setName("list")
+ .addParams(
+ TypeDecl.newBuilder()
+ .setName("T")
+ .setIsTypeParam(true)
+ .build())
+ .build())
+ .build())
+ .build())),
+ FunctionDecl.create(
+ "zip",
+ ImmutableSet.of(
+ OverloadDecl.newBuilder()
+ .setId("zip_list_int_list_int")
+ .setArguments(
+ ImmutableList.of(
+ TypeDecl.newBuilder()
+ .setName("list")
+ .addParams(TypeDecl.create("int"))
+ .build(),
+ TypeDecl.newBuilder()
+ .setName("list")
+ .addParams(TypeDecl.create("int"))
+ .build()))
+ .setReturnType(
+ TypeDecl.newBuilder()
+ .setName("list")
+ .addParams(
+ TypeDecl.newBuilder()
+ .setName("list")
+ .addParams(TypeDecl.create("int"))
+ .build())
+ .build())
.build())));
// Random-check some standard functions: we don't want to see them explicitly defined.
@@ -255,10 +333,38 @@ public void container() {
CelEnvironmentExporter exporter = CelEnvironmentExporter.newBuilder().build();
CelEnvironment celEnvironment = exporter.export(cel);
- CelContainer container = celEnvironment.container();
+ CelContainer container = celEnvironment.container().get();
assertThat(container.name()).isEqualTo("cntnr");
assertThat(container.abbreviations()).containsExactly("foo.Bar", "baz.Qux").inOrder();
assertThat(container.aliases()).containsAtLeast("nm", "user.name", "id", "user.id").inOrder();
}
-}
+ @Test
+ public void options() {
+ Cel cel =
+ CelFactory.standardCelBuilder()
+ .setOptions(
+ CelOptions.current()
+ .maxExpressionCodePointSize(100)
+ .maxParseErrorRecoveryLimit(10)
+ .maxParseRecursionDepth(10)
+ .enableQuotedIdentifierSyntax(true)
+ .enableHeterogeneousNumericComparisons(true)
+ .populateMacroCalls(true)
+ .build())
+ .build();
+
+ CelEnvironmentExporter exporter = CelEnvironmentExporter.newBuilder().build();
+ CelEnvironment celEnvironment = exporter.export(cel);
+ assertThat(celEnvironment.features())
+ .containsExactly(
+ CelEnvironment.FeatureFlag.create("cel.feature.backtick_escape_syntax", true),
+ CelEnvironment.FeatureFlag.create("cel.feature.cross_type_numeric_comparisons", true),
+ CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true));
+ assertThat(celEnvironment.limits())
+ .containsExactly(
+ CelEnvironment.Limit.create("cel.limit.expression_code_points", 100),
+ CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
+ CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10));
+ }
+}
diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java
index 6bc84a48f..a5a2f3e6d 100644
--- a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java
+++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java
@@ -28,6 +28,10 @@
import dev.cel.common.CelOptions;
import dev.cel.common.CelValidationException;
import dev.cel.common.CelValidationResult;
+import dev.cel.common.types.CelType;
+import dev.cel.common.types.CelTypeProvider;
+import dev.cel.common.types.SimpleType;
+import dev.cel.common.types.TypeType;
import dev.cel.compiler.CelCompiler;
import dev.cel.compiler.CelCompilerFactory;
import dev.cel.parser.CelStandardMacro;
@@ -44,9 +48,7 @@ public void newBuilder_defaults() {
assertThat(environment.source()).isEmpty();
assertThat(environment.name()).isEmpty();
assertThat(environment.description()).isEmpty();
- assertThat(environment.container().name()).isEmpty();
- assertThat(environment.container().abbreviations()).isEmpty();
- assertThat(environment.container().aliases()).isEmpty();
+ assertThat(environment.container()).isEmpty();
assertThat(environment.extensions()).isEmpty();
assertThat(environment.variables()).isEmpty();
assertThat(environment.functions()).isEmpty();
@@ -65,10 +67,10 @@ public void container() {
.build())
.build();
- assertThat(environment.container().name()).isEqualTo("cntr");
- assertThat(environment.container().abbreviations()).containsExactly("foo.Bar", "baz.Qux");
- assertThat(environment.container().aliases())
- .containsExactly("nm", "user.name", "id", "user.id");
+ CelContainer container = environment.container().get();
+ assertThat(container.name()).isEqualTo("cntr");
+ assertThat(container.abbreviations()).containsExactly("foo.Bar", "baz.Qux");
+ assertThat(container.aliases()).containsExactly("nm", "user.name", "id", "user.id");
}
@Test
@@ -81,9 +83,10 @@ public void extend_allExtensions() throws Exception {
ExtensionConfig.latest("math"),
ExtensionConfig.latest("optional"),
ExtensionConfig.latest("protos"),
+ ExtensionConfig.latest("regex"),
ExtensionConfig.latest("sets"),
ExtensionConfig.latest("strings"),
- ExtensionConfig.latest("comprehensions"));
+ ExtensionConfig.latest("two-var-comprehensions"));
CelEnvironment environment =
CelEnvironment.newBuilder().addExtensions(extensionConfigs).build();
@@ -100,6 +103,99 @@ public void extend_allExtensions() throws Exception {
assertThat(result).isTrue();
}
+ @Test
+ public void extend_allFeatureFlags() throws Exception {
+ CelEnvironment environment =
+ CelEnvironment.newBuilder()
+ .setFeatures(
+ CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true),
+ CelEnvironment.FeatureFlag.create("cel.feature.backtick_escape_syntax", true),
+ CelEnvironment.FeatureFlag.create(
+ "cel.feature.cross_type_numeric_comparisons", true))
+ .build();
+
+ Cel cel =
+ environment.extend(
+ CelFactory.standardCelBuilder()
+ .setStandardMacros(CelStandardMacro.STANDARD_MACROS)
+ .build(),
+ CelOptions.DEFAULT);
+ CelAbstractSyntaxTree ast =
+ cel.compile("[{'foo.bar': 1}, {'foo.bar': 2}].all(e, e.`foo.bar` < 2.5)").getAst();
+ assertThat(ast.getSource().getMacroCalls()).hasSize(1);
+ boolean result = (boolean) cel.createProgram(ast).eval();
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void extend_allLimits() throws Exception {
+ CelEnvironment environment =
+ CelEnvironment.newBuilder()
+ .setLimits(
+ CelEnvironment.Limit.create("cel.limit.expression_code_points", 20),
+ CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
+ CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10))
+ .build();
+
+ Cel cel =
+ environment.extend(
+ CelFactory.standardCelBuilder()
+ .setStandardMacros(CelStandardMacro.STANDARD_MACROS)
+ .build(),
+ CelOptions.DEFAULT);
+ CelOptions checkerOptions = cel.toCheckerBuilder().options();
+ assertThat(checkerOptions.maxExpressionCodePointSize()).isEqualTo(20);
+ assertThat(checkerOptions.maxParseErrorRecoveryLimit()).isEqualTo(10);
+ assertThat(checkerOptions.maxParseRecursionDepth()).isEqualTo(10);
+
+ CelAbstractSyntaxTree ast = cel.compile("1 + 2 + 3 + 4 + 5").getAst();
+ Long result = (Long) cel.createProgram(ast).eval();
+ assertThat(result).isEqualTo(15L);
+
+ CelValidationResult validationResult = cel.compile("1 + 2 + 3 + 4 + 5 + 6");
+ assertThat(validationResult.hasError()).isTrue();
+ assertThat(validationResult.getErrorString())
+ .contains("expression code point size exceeds limit: size: 21, limit 20");
+ }
+
+ @Test
+ public void extend_unsupportedFeatureFlag_throws() throws Exception {
+ CelEnvironment environment =
+ CelEnvironment.newBuilder()
+ .setFeatures(CelEnvironment.FeatureFlag.create("unknown.feature", true))
+ .build();
+
+ IllegalArgumentException e =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ environment.extend(
+ CelFactory.standardCelBuilder()
+ .setStandardMacros(CelStandardMacro.STANDARD_MACROS)
+ .build(),
+ CelOptions.DEFAULT));
+ assertThat(e).hasMessageThat().contains("Unknown feature flag: unknown.feature");
+ }
+
+ @Test
+ public void extend_unsupportedLimit_throws() throws Exception {
+ CelEnvironment environment =
+ CelEnvironment.newBuilder()
+ .setLimits(CelEnvironment.Limit.create("unknown.limit", 5))
+ .build();
+
+ IllegalArgumentException e =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ environment.extend(
+ CelFactory.standardCelBuilder()
+ .setStandardMacros(CelStandardMacro.STANDARD_MACROS)
+ .build(),
+ CelOptions.DEFAULT));
+ assertThat(e).hasMessageThat().contains("Unknown limit: unknown.limit");
+ }
+
@Test
public void extensionVersion_specific() throws Exception {
CelEnvironment environment =
@@ -342,4 +438,30 @@ public void stdlibSubset_functionOverloadExcluded() throws Exception {
result = extendedCompiler.compile("1 == 1 && 1 != 1 + 1");
assertThat(result.getErrorString()).contains("found no matching overload for '_+_'");
}
+
+ @Test
+ public void typeDecl_toCelType_type() {
+ CelTypeProvider typeProvider =
+ CelCompilerFactory.standardCelCompilerBuilder().build().getTypeProvider();
+ CelEnvironment.TypeDecl typeDecl =
+ CelEnvironment.TypeDecl.newBuilder()
+ .setName("type")
+ .addParams(CelEnvironment.TypeDecl.create("int"))
+ .build();
+
+ CelType celType = typeDecl.toCelType(typeProvider);
+
+ assertThat(celType).isEqualTo(TypeType.create(SimpleType.INT));
+ }
+
+ @Test
+ public void typeDecl_toCelType_type_wrongParamCount_throws() {
+ CelTypeProvider typeProvider =
+ CelCompilerFactory.standardCelCompilerBuilder().build().getTypeProvider();
+ CelEnvironment.TypeDecl typeDecl = CelEnvironment.TypeDecl.newBuilder().setName("type").build();
+
+ IllegalStateException e =
+ assertThrows(IllegalStateException.class, () -> typeDecl.toCelType(typeProvider));
+ assertThat(e).hasMessageThat().contains("Expected 1 parameter for type, got 0");
+ }
}
diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java
index d69d0517b..043664e8e 100644
--- a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java
+++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java
@@ -40,8 +40,8 @@
import dev.cel.common.types.SimpleType;
import dev.cel.parser.CelUnparserFactory;
import dev.cel.runtime.CelEvaluationListener;
-import dev.cel.runtime.CelLateFunctionBindings;
import dev.cel.runtime.CelFunctionBinding;
+import dev.cel.runtime.CelLateFunctionBindings;
import java.io.IOException;
import java.net.URL;
import java.util.Optional;
@@ -81,6 +81,62 @@ public void environment_setBasicProperties() throws Exception {
.build());
}
+ @Test
+ public void environment_setFeatures() throws Exception {
+ String yamlConfig =
+ "name: hello\n"
+ + "description: empty\n"
+ + "features:\n"
+ + " - name: 'cel.feature.macro_call_tracking'\n"
+ + " enabled: true\n"
+ + " - name: 'cel.feature.backtick_escape_syntax'\n"
+ + " enabled: false";
+
+ CelEnvironment environment = ENVIRONMENT_PARSER.parse(yamlConfig);
+
+ assertThat(environment)
+ .isEqualTo(
+ CelEnvironment.newBuilder()
+ .setSource(environment.source().get())
+ .setName("hello")
+ .setDescription("empty")
+ .setFeatures(
+ ImmutableSet.of(
+ CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true),
+ CelEnvironment.FeatureFlag.create(
+ "cel.feature.backtick_escape_syntax", false)))
+ .build());
+ }
+
+ @Test
+ public void environment_setLimits() throws Exception {
+ String yamlConfig =
+ "name: hello\n"
+ + "description: empty\n"
+ + "limits:\n"
+ + " - name: 'cel.limit.expression_code_points'\n"
+ + " value: 1000\n"
+ + " - name: 'cel.limit.parse_error_recovery'\n"
+ + " value: 10\n"
+ + " - name: 'cel.limit.parse_recursion_depth'\n"
+ + " value: 7";
+
+ CelEnvironment environment = ENVIRONMENT_PARSER.parse(yamlConfig);
+
+ assertThat(environment)
+ .isEqualTo(
+ CelEnvironment.newBuilder()
+ .setSource(environment.source().get())
+ .setName("hello")
+ .setDescription("empty")
+ .setLimits(
+ ImmutableSet.of(
+ CelEnvironment.Limit.create("cel.limit.expression_code_points", 1000),
+ CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
+ CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 7)))
+ .build());
+ }
+
@Test
public void environment_setExtensions() throws Exception {
String yamlConfig =
@@ -619,9 +675,7 @@ private enum EnvironmentParseErrorTestcase {
+ " | - version: 0\n"
+ " | ..^"),
ILLEGAL_LIBRARY_SUBSET_TAG(
- "name: 'test_suite_name'\n"
- + "stdlib:\n"
- + " unknown_tag: 'test_value'\n",
+ "name: 'test_suite_name'\n" + "stdlib:\n" + " unknown_tag: 'test_value'\n",
"ERROR: :3:3: Unsupported library subset tag: unknown_tag\n"
+ " | unknown_tag: 'test_value'\n"
+ " | ..^"),
@@ -672,6 +726,40 @@ private enum EnvironmentParseErrorTestcase {
"ERROR: :6:7: Unsupported alias tag: unknown_tag\n"
+ " | unknown_tag: 'test_value'\n"
+ " | ......^"),
+ UNSUPPORTED_LIMIT_TAG(
+ "limits:\n"
+ + " - name: 'test_limit'\n"
+ + " unknown_tag: 'test_value'\n"
+ + " value: 100\n",
+ "ERROR: :3:5: Unsupported limits tag: unknown_tag\n"
+ + " | unknown_tag: 'test_value'\n"
+ + " | ....^"),
+ MISSING_LIMIT_NAME(
+ "limits:\n" + " - value: 100\n",
+ "ERROR: :2:5: Missing required attribute(s): name\n"
+ + " | - value: 100\n"
+ + " | ....^"),
+ MISSING_LIMIT_VALUE(
+ "limits:\n" + " - name: 'test_limit'\n",
+ "ERROR: :2:5: Missing required attribute(s): value\n"
+ + " | - name: 'test_limit'\n"
+ + " | ....^"),
+ ILLEGAL_LIMIT_VALUE(
+ "limits:\n" + " - cel.limit.foo: 'not_a_number'\n",
+ "ERROR: :2:21: Got yaml node type tag:yaml.org,2002:str, wanted type(s)"
+ + " [tag:yaml.org,2002:int]\n"
+ + " | - cel.limit.foo: 'not_a_number'\n"
+ + " | ....................^"),
+ ILLEGAL_FEATURE_TAG(
+ "features:\n" + " - name: 'test_feature'\n" + " unknown_tag: 'test_value'\n",
+ "ERROR: :3:5: Unsupported feature tag: unknown_tag\n"
+ + " | unknown_tag: 'test_value'\n"
+ + " | ....^"),
+ MISSING_FEATURE_NAME(
+ "features:\n" + " - enabled: true\n",
+ "ERROR: :2:5: Missing required attribute(s): name\n"
+ + " | - enabled: true\n"
+ + " | ....^"),
;
private final String yamlConfig;
@@ -769,30 +857,45 @@ private enum EnvironmentYamlResourceTestCase {
.setVariables(
VariableDecl.newBuilder()
.setName("msg")
+ .setDescription(
+ "msg represents all possible type permutation which CEL understands from a"
+ + " proto perspective")
.setType(TypeDecl.create("cel.expr.conformance.proto3.TestAllTypes"))
.build())
.setFunctions(
- FunctionDecl.create(
- "isEmpty",
- ImmutableSet.of(
- OverloadDecl.newBuilder()
- .setId("wrapper_string_isEmpty")
- .setTarget(TypeDecl.create("google.protobuf.StringValue"))
- .setReturnType(TypeDecl.create("bool"))
- .build(),
- OverloadDecl.newBuilder()
- .setId("list_isEmpty")
- .setTarget(
- TypeDecl.newBuilder()
- .setName("list")
- .addParams(
- TypeDecl.newBuilder()
- .setName("T")
- .setIsTypeParam(true)
- .build())
- .build())
- .setReturnType(TypeDecl.create("bool"))
- .build())))
+ FunctionDecl.newBuilder()
+ .setName("isEmpty")
+ .setDescription(
+ "determines whether a list is empty,\nor a string has no characters")
+ .setOverloads(
+ ImmutableSet.of(
+ OverloadDecl.newBuilder()
+ .setId("wrapper_string_isEmpty")
+ .setTarget(TypeDecl.create("google.protobuf.StringValue"))
+ .addExamples("''.isEmpty() // true")
+ .setReturnType(TypeDecl.create("bool"))
+ .build(),
+ OverloadDecl.newBuilder()
+ .setId("list_isEmpty")
+ .addExamples("[].isEmpty() // true")
+ .addExamples("[1].isEmpty() // false")
+ .setTarget(
+ TypeDecl.newBuilder()
+ .setName("list")
+ .addParams(
+ TypeDecl.newBuilder()
+ .setName("T")
+ .setIsTypeParam(true)
+ .build())
+ .build())
+ .setReturnType(TypeDecl.create("bool"))
+ .build()))
+ .build())
+ .setFeatures(CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true))
+ .setLimits(
+ ImmutableSet.of(
+ CelEnvironment.Limit.create("cel.limit.expression_code_points", 1000),
+ CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 7)))
.build()),
LIBRARY_SUBSET_ENV(
diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlSerializerTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlSerializerTest.java
index 7e4be0912..aad72a578 100644
--- a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlSerializerTest.java
+++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlSerializerTest.java
@@ -106,6 +106,68 @@ public void toYaml_success() throws Exception {
.setReturnType(
TypeDecl.newBuilder().setName("V").setIsTypeParam(true).build())
.build())),
+ FunctionDecl.create(
+ "zip",
+ ImmutableSet.of(
+ OverloadDecl.newBuilder()
+ .setId("zip_list_int_list_int")
+ .setArguments(
+ ImmutableList.of(
+ TypeDecl.newBuilder()
+ .setName("list")
+ .addParams(TypeDecl.create("int"))
+ .build(),
+ TypeDecl.newBuilder()
+ .setName("list")
+ .addParams(TypeDecl.create("int"))
+ .build()))
+ .setReturnType(
+ TypeDecl.newBuilder()
+ .setName("list")
+ .addParams(
+ TypeDecl.newBuilder()
+ .setName("list")
+ .addParams(TypeDecl.create("int"))
+ .build())
+ .build())
+ .build())),
+ FunctionDecl.create(
+ "zipGeneric",
+ ImmutableSet.of(
+ OverloadDecl.newBuilder()
+ .setId("zip_list_list")
+ .setArguments(
+ ImmutableList.of(
+ TypeDecl.newBuilder()
+ .setName("list")
+ .addParams(
+ TypeDecl.newBuilder()
+ .setName("T")
+ .setIsTypeParam(true)
+ .build())
+ .build(),
+ TypeDecl.newBuilder()
+ .setName("list")
+ .addParams(
+ TypeDecl.newBuilder()
+ .setName("T")
+ .setIsTypeParam(true)
+ .build())
+ .build()))
+ .setReturnType(
+ TypeDecl.newBuilder()
+ .setName("list")
+ .addParams(
+ TypeDecl.newBuilder()
+ .setName("list")
+ .addParams(
+ TypeDecl.newBuilder()
+ .setName("T")
+ .setIsTypeParam(true)
+ .build())
+ .build())
+ .build())
+ .build())),
FunctionDecl.create(
"coalesce",
ImmutableSet.of(
@@ -126,6 +188,13 @@ public void toYaml_success() throws Exception {
FunctionSelector.create(
"_+_", ImmutableSet.of("add_bytes", "add_list"))))
.build())
+ .setFeatures(
+ CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true),
+ CelEnvironment.FeatureFlag.create("cel.feature.backtick_escape_syntax", false))
+ .setLimits(
+ CelEnvironment.Limit.create("cel.limit.expression_code_points", 1000),
+ CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10),
+ CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 7))
.build();
String yamlOutput = CelEnvironmentYamlSerializer.toYaml(environment);
diff --git a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java
index 0708b37c3..a3ad60d40 100644
--- a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java
+++ b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java
@@ -98,6 +98,7 @@
import dev.cel.expr.conformance.proto2.Proto2ExtensionScopedMessage;
import dev.cel.expr.conformance.proto2.TestAllTypesExtensions;
import dev.cel.expr.conformance.proto3.TestAllTypes;
+import dev.cel.extensions.CelExtensions;
import dev.cel.parser.CelParserImpl;
import dev.cel.parser.CelStandardMacro;
import dev.cel.runtime.CelAttribute;
@@ -113,7 +114,9 @@
import dev.cel.runtime.CelUnknownSet;
import dev.cel.runtime.CelVariableResolver;
import dev.cel.runtime.UnknownContext;
-import dev.cel.testing.testdata.SingleFileProto.SingleFile;
+import dev.cel.testing.CelRuntimeFlavor;
+import dev.cel.testing.testdata.SingleFile;
+import dev.cel.testing.testdata.SingleFileExtensionsProto;
import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum;
import java.time.Instant;
import java.util.ArrayList;
@@ -1991,59 +1994,6 @@ public void program_nativeTypeUnknownsEnabled_asCallArguments() throws Exception
assertThat(result.attributes()).isEmpty();
}
- @Test
- @TestParameters("{expression: 'string(123)'}")
- @TestParameters("{expression: 'string(123u)'}")
- @TestParameters("{expression: 'string(1.5)'}")
- @TestParameters("{expression: 'string(\"foo\")'}")
- @TestParameters("{expression: 'string(b\"foo\")'}")
- @TestParameters("{expression: 'string(timestamp(100))'}")
- @TestParameters("{expression: 'string(duration(\"1h\"))'}")
- public void program_stringConversionDisabled_throws(String expression) throws Exception {
- Cel cel =
- CelFactory.standardCelBuilder()
- .setOptions(
- CelOptions.current()
- .enableTimestampEpoch(true)
- .enableStringConversion(false)
- .build())
- .build();
- CelAbstractSyntaxTree ast = cel.compile(expression).getAst();
-
- CelEvaluationException e =
- assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast).eval());
- assertThat(e).hasMessageThat().contains("No matching overload for function 'string'");
- assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.OVERLOAD_NOT_FOUND);
- }
-
- @Test
- public void program_stringConcatenationDisabled_throws() throws Exception {
- Cel cel =
- CelFactory.standardCelBuilder()
- .setOptions(CelOptions.current().enableStringConcatenation(false).build())
- .build();
- CelAbstractSyntaxTree ast = cel.compile("'foo' + 'bar'").getAst();
-
- CelEvaluationException e =
- assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast).eval());
- assertThat(e).hasMessageThat().contains("No matching overload for function '_+_'");
- assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.OVERLOAD_NOT_FOUND);
- }
-
- @Test
- public void program_listConcatenationDisabled_throws() throws Exception {
- Cel cel =
- CelFactory.standardCelBuilder()
- .setOptions(CelOptions.current().enableListConcatenation(false).build())
- .build();
- CelAbstractSyntaxTree ast = cel.compile("[1] + [2]").getAst();
-
- CelEvaluationException e =
- assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast).eval());
- assertThat(e).hasMessageThat().contains("No matching overload for function '_+_'");
- assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.OVERLOAD_NOT_FOUND);
- }
-
@Test
public void program_comprehensionDisabled_throws() throws Exception {
Cel cel =
@@ -2162,7 +2112,6 @@ public void program_fdsContainsWktDependency_descriptorInstancesMatch() throws E
standardCelBuilderWithMacros()
.addMessageTypes(descriptors)
// CEL-Internal-2
- .setOptions(CelOptions.current().enableTimestampEpoch(true).build())
.setContainer(CelContainer.ofName("cel.expr.conformance.proto3"))
.build();
CelAbstractSyntaxTree ast =
@@ -2196,20 +2145,93 @@ public void toBuilder_isImmutable() {
}
@Test
- public void eval_withJsonFieldName() throws Exception {
- Cel cel =
- standardCelBuilderWithMacros()
- .addVar("file", StructTypeReference.create(SingleFile.getDescriptor().getFullName()))
- .addMessageTypes(SingleFile.getDescriptor())
- .setOptions(CelOptions.current().enableJsonFieldNames(true).build())
- .build();
- CelAbstractSyntaxTree ast = cel.compile("file.camelCased").getAst();
+ public void eval_withJsonFieldName(@TestParameter CelRuntimeFlavor runtimeFlavor)
+ throws Exception {
+ Cel cel = setupEnv(runtimeFlavor.builder());
+ CelAbstractSyntaxTree ast =
+ cel.compile(
+ "file.int32_snake_case_json_name == 1 && "
+ + "file.int64CamelCaseJsonName == 2 && "
+ + "file.uint32DefaultJsonName == 3u && "
+ + "file.`uint64-custom-json-name` == 4u && "
+ + "file.single_string == 'shadows' && "
+ + "file.singleString == 'shadowed'")
+ .getAst();
+
+ boolean result =
+ (boolean)
+ cel.createProgram(ast)
+ .eval(
+ ImmutableMap.of(
+ "file",
+ SingleFile.newBuilder()
+ .setInt32SnakeCaseJsonName(1)
+ .setInt64CamelCaseJsonName(2L)
+ .setUint32DefaultJsonName(3)
+ .setUint64CustomJsonName(4)
+ .setStringJsonNameShadows("shadows")
+ .setSingleString("shadowed")
+ .setExtension(SingleFileExtensionsProto.int64CamelCaseJsonName, 5L)
+ .build()));
+
+ assertThat(result).isTrue();
+ }
- Object result =
- cel.createProgram(ast)
- .eval(ImmutableMap.of("file", SingleFile.newBuilder().setSnakeCased("foo").build()));
+ @Test
+ public void eval_withJsonFieldName_fieldsFallBack(@TestParameter CelRuntimeFlavor runtimeFlavor)
+ throws Exception {
+ Cel cel = setupEnv(runtimeFlavor.builder());
+ CelAbstractSyntaxTree ast =
+ cel.compile(
+ "dyn(file).int32_snake_case_json_name == 1 && "
+ + "dyn(file).`uint64-custom-json-name` == 4u && "
+ + "dyn(file).single_string == 'shadows' && "
+ + "dyn(file).string_json_name_shadows == 'shadows' && "
+ + "dyn(file).singleString == 'shadowed'")
+ .getAst();
+
+ boolean result =
+ (boolean)
+ cel.createProgram(ast)
+ .eval(
+ ImmutableMap.of(
+ "file",
+ SingleFile.newBuilder()
+ .setInt32SnakeCaseJsonName(1)
+ .setInt64CamelCaseJsonName(2L)
+ .setUint32DefaultJsonName(3)
+ .setUint64CustomJsonName(4)
+ .setStringJsonNameShadows("shadows")
+ .setSingleString("shadowed")
+ .build()));
- assertThat(result).isEqualTo("foo");
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void eval_withJsonFieldName_extensionFields(@TestParameter CelRuntimeFlavor runtimeFlavor)
+ throws Exception {
+ Cel cel = setupEnv(runtimeFlavor.builder());
+ CelAbstractSyntaxTree ast =
+ cel.compile(
+ "proto.getExt(file, dev.cel.testing.testdata.int64CamelCaseJsonName) == 5 &&"
+ + " proto.getExt(file, dev.cel.testing.testdata.single_string) == 'foo'")
+ .getAst();
+
+ boolean result =
+ (boolean)
+ cel.createProgram(ast)
+ .eval(
+ ImmutableMap.of(
+ "file",
+ SingleFile.newBuilder()
+ .setInt64CamelCaseJsonName(2L)
+ .setExtension(SingleFileExtensionsProto.int64CamelCaseJsonName, 5L)
+ .setSingleString("This should not be used")
+ .setExtension(SingleFileExtensionsProto.singleString, "foo")
+ .build()));
+
+ assertThat(result).isTrue();
}
@Test
@@ -2225,7 +2247,7 @@ public void eval_withJsonFieldName_runtimeOptionDisabled_throws() throws Excepti
.addMessageTypes(SingleFile.getDescriptor())
.setOptions(CelOptions.current().enableJsonFieldNames(false).build())
.build();
- CelAbstractSyntaxTree ast = celCompiler.compile("file.camelCased").getAst();
+ CelAbstractSyntaxTree ast = celCompiler.compile("file.int64CamelCaseJsonName").getAst();
CelEvaluationException e =
assertThrows(
@@ -2237,7 +2259,8 @@ public void eval_withJsonFieldName_runtimeOptionDisabled_throws() throws Excepti
assertThat(e)
.hasMessageThat()
.contains(
- "field 'camelCased' is not declared in message 'dev.cel.testing.testdata.SingleFile");
+ "field 'int64CamelCaseJsonName' is not declared in message"
+ + " 'dev.cel.testing.testdata.SingleFile");
}
@Test
@@ -2248,7 +2271,7 @@ public void compile_withJsonFieldName_astTagged() throws Exception {
.addMessageTypes(SingleFile.getDescriptor())
.setOptions(CelOptions.current().enableJsonFieldNames(true).build())
.build();
- CelAbstractSyntaxTree ast = cel.compile("file.camelCased").getAst();
+ CelAbstractSyntaxTree ast = cel.compile("file.int64CamelCaseJsonName").getAst();
assertThat(ast.getSource().getExtensions())
.contains(
@@ -2297,4 +2320,22 @@ private static TypeProvider aliasingProvider(ImmutableMap typeAlia
}
};
}
+
+ private static Cel setupEnv(CelBuilder celBuilder) {
+ ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance();
+ SingleFileExtensionsProto.registerAllExtensions(extensionRegistry);
+ return celBuilder
+ .addVar("file", StructTypeReference.create(SingleFile.getDescriptor().getFullName()))
+ .addMessageTypes(SingleFile.getDescriptor())
+ .addFileTypes(SingleFileExtensionsProto.getDescriptor())
+ .addCompilerLibraries(CelExtensions.protos())
+ .setExtensionRegistry(extensionRegistry)
+ .setOptions(
+ CelOptions.current()
+ .enableJsonFieldNames(true)
+ .enableHeterogeneousNumericComparisons(true)
+ .enableQuotedIdentifierSyntax(true)
+ .build())
+ .build();
+ }
}
diff --git a/cel_android_rules.bzl b/cel_android_rules.bzl
index 5a94a7ef5..9bd2fd8bc 100644
--- a/cel_android_rules.bzl
+++ b/cel_android_rules.bzl
@@ -33,11 +33,13 @@ def cel_android_library(name, **kwargs):
# By default, set visibility to android_allow_list, unless if overridden at the call site.
provided_visibility_or_default = kwargs.get("visibility", ["//:android_allow_list"])
- filtered_kwargs = {k: v for k, v in kwargs.items() if k != "visibility"}
+ provided_compatible_with_or_default = kwargs.get("compatible_with", [])
+ filtered_kwargs = {k: v for k, v in kwargs.items() if k not in ["visibility", "compatible_with"]}
android_library(
name = name,
visibility = provided_visibility_or_default,
+ compatible_with = provided_compatible_with_or_default,
javacopts = all_javacopts,
**filtered_kwargs
)
diff --git a/checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java b/checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java
index e19cf5b70..a7d531f88 100644
--- a/checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java
+++ b/checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java
@@ -35,6 +35,9 @@ public interface CelCheckerBuilder {
@CanIgnoreReturnValue
CelCheckerBuilder setOptions(CelOptions options);
+ /** Retrieves the currently configured {@link CelOptions} in the builder. */
+ CelOptions options();
+
/**
* Set the {@link CelContainer} to use as the namespace for resolving CEL expression variables and
* functions.
diff --git a/checker/src/main/java/dev/cel/checker/CelCheckerLegacyImpl.java b/checker/src/main/java/dev/cel/checker/CelCheckerLegacyImpl.java
index df8a82f43..ceab0fa93 100644
--- a/checker/src/main/java/dev/cel/checker/CelCheckerLegacyImpl.java
+++ b/checker/src/main/java/dev/cel/checker/CelCheckerLegacyImpl.java
@@ -202,6 +202,11 @@ public CelCheckerBuilder setOptions(CelOptions celOptions) {
return this;
}
+ @Override
+ public CelOptions options() {
+ return this.celOptions;
+ }
+
@Override
public CelCheckerBuilder setContainer(CelContainer container) {
checkNotNull(container);
@@ -421,11 +426,6 @@ CelStandardDeclarations standardDeclarations() {
return this.standardDeclarations;
}
- @VisibleForTesting
- CelOptions options() {
- return this.celOptions;
- }
-
@VisibleForTesting
CelTypeProvider celTypeProvider() {
return this.celTypeProvider;
diff --git a/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java b/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java
index 12ad47c62..53615604f 100644
--- a/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java
+++ b/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java
@@ -31,6 +31,7 @@
import dev.cel.common.types.SimpleType;
import dev.cel.common.types.TypeParamType;
import dev.cel.common.types.TypeType;
+import java.util.Optional;
/**
* Standard declarations for CEL.
@@ -1474,6 +1475,16 @@ public boolean isHeterogeneousComparison() {
public CelOverloadDecl celOverloadDecl() {
return this.celOverloadDecl;
}
+
+ /** Finds a Comparison by its overload ID. */
+ public static Optional fromOverloadId(String overloadId) {
+ for (Comparison c : values()) {
+ if (c.celOverloadDecl().overloadId().equals(overloadId)) {
+ return Optional.of(c);
+ }
+ }
+ return Optional.empty();
+ }
}
private Overload() {}
diff --git a/checker/src/test/java/dev/cel/checker/BUILD.bazel b/checker/src/test/java/dev/cel/checker/BUILD.bazel
index 1821a5d85..22b70210d 100644
--- a/checker/src/test/java/dev/cel/checker/BUILD.bazel
+++ b/checker/src/test/java/dev/cel/checker/BUILD.bazel
@@ -1,9 +1,11 @@
load("@rules_java//java:defs.bzl", "java_library")
load("//:testing.bzl", "junit4_test_suites")
-package(default_applicable_licenses = [
- "//:license",
-])
+package(
+ default_applicable_licenses = [
+ "//:license",
+ ],
+)
java_library(
name = "tests",
@@ -11,8 +13,6 @@ java_library(
srcs = glob(["*Test.java"]),
resources = ["//checker/src/test/resources:baselines"],
deps = [
- # "//java/com/google/testing/testsize:annotations",
- "//:auto_value",
"//checker",
"//checker:cel_ident_decl",
"//checker:checker_builder",
@@ -42,9 +42,11 @@ java_library(
"//common/types:type_providers",
"//compiler",
"//compiler:compiler_builder",
+ # "//java/com/google/testing/testsize:annotations",
"//parser:macro",
"//testing:adorner",
"//testing:cel_baseline_test_case",
+ "//:auto_value",
"@maven//:junit_junit",
"@maven//:com_google_testparameterinjector_test_parameter_injector",
"//:java_truth",
diff --git a/checker/src/test/java/dev/cel/checker/CelCheckerLegacyImplTest.java b/checker/src/test/java/dev/cel/checker/CelCheckerLegacyImplTest.java
index c0c54381d..92a70c2d6 100644
--- a/checker/src/test/java/dev/cel/checker/CelCheckerLegacyImplTest.java
+++ b/checker/src/test/java/dev/cel/checker/CelCheckerLegacyImplTest.java
@@ -63,7 +63,7 @@ public void toCheckerBuilder_isImmutable() {
public void toCheckerBuilder_singularFields_copied() {
CelStandardDeclarations subsetDecls =
CelStandardDeclarations.newBuilder().includeFunctions(StandardFunction.BOOL).build();
- CelOptions celOptions = CelOptions.current().enableTimestampEpoch(true).build();
+ CelOptions celOptions = CelOptions.current().build();
CelContainer celContainer = CelContainer.ofName("foo");
CelType expectedResultType = SimpleType.BOOL;
CelTypeProvider customTypeProvider =
diff --git a/checker/src/test/java/dev/cel/checker/ExprCheckerTest.java b/checker/src/test/java/dev/cel/checker/ExprCheckerTest.java
index d5d5d9a3a..846201d32 100644
--- a/checker/src/test/java/dev/cel/checker/ExprCheckerTest.java
+++ b/checker/src/test/java/dev/cel/checker/ExprCheckerTest.java
@@ -517,6 +517,71 @@ public void jsonType() throws Exception {
runTest();
}
+ @Test
+ public void jsonTypeNullConstruction() throws Exception {
+ // Ok
+ source = "google.protobuf.Value{null_value: google.protobuf.NullValue.NULL_VALUE}";
+ runTest();
+
+ // Error
+ source = "google.protobuf.Value{null_value: null}";
+ runTest();
+
+ // Ok
+ source = "cel.expr.conformance.proto3.TestAllTypes{single_value: null}";
+ runTest();
+
+ // Ok but not expected (int coerced to double/json number 0.0)
+ source =
+ "cel.expr.conformance.proto3.TestAllTypes{single_value:"
+ + " google.protobuf.NullValue.NULL_VALUE}";
+ runTest();
+
+ // Error
+ source = "cel.expr.conformance.proto3.TestAllTypes{null_value: null}";
+ runTest();
+
+ // Ok
+ source =
+ "cel.expr.conformance.proto3.TestAllTypes{null_value:"
+ + " google.protobuf.NullValue.NULL_VALUE}";
+ runTest();
+ }
+
+ @Test
+ public void jsonTypeNullAccess() throws Exception {
+ source = "google.protobuf.Value{null_value: google.protobuf.NullValue.NULL_VALUE} == null";
+ runTest();
+
+ source = "cel.expr.conformance.proto3.TestAllTypes{single_value: null}.single_value == null";
+ runTest();
+
+ source =
+ "cel.expr.conformance.proto3.TestAllTypes{single_value:"
+ + " google.protobuf.NullValue.NULL_VALUE}.single_value == null";
+ runTest();
+
+ // Error
+ source =
+ "cel.expr.conformance.proto3.TestAllTypes{null_value:"
+ + " google.protobuf.NullValue.NULL_VALUE}.null_value == null";
+ runTest();
+
+ // Ok
+ source =
+ "cel.expr.conformance.proto3.TestAllTypes{null_value:"
+ + " google.protobuf.NullValue.NULL_VALUE}.null_value == 0";
+ runTest();
+
+ // Error
+ source = "google.protobuf.NullValue.NULL_VALUE == null";
+ runTest();
+
+ // Ok
+ source = "google.protobuf.NullValue.NULL_VALUE == 0";
+ runTest();
+ }
+
// Call Style and User Functions
// =============================
diff --git a/checker/src/test/resources/jsonTypeNullAccess.baseline b/checker/src/test/resources/jsonTypeNullAccess.baseline
new file mode 100644
index 000000000..834b8fde8
--- /dev/null
+++ b/checker/src/test/resources/jsonTypeNullAccess.baseline
@@ -0,0 +1,54 @@
+Source: google.protobuf.Value{null_value: google.protobuf.NullValue.NULL_VALUE} == null
+=====>
+_==_(
+ google.protobuf.Value{
+ null_value:google.protobuf.NullValue.NULL_VALUE~int^google.protobuf.NullValue.NULL_VALUE
+ }~dyn^google.protobuf.Value,
+ null~null
+)~bool^equals
+
+Source: cel.expr.conformance.proto3.TestAllTypes{single_value: null}.single_value == null
+=====>
+_==_(
+ cel.expr.conformance.proto3.TestAllTypes{
+ single_value:null~null
+ }~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes.single_value~dyn,
+ null~null
+)~bool^equals
+
+Source: cel.expr.conformance.proto3.TestAllTypes{single_value: google.protobuf.NullValue.NULL_VALUE}.single_value == null
+=====>
+_==_(
+ cel.expr.conformance.proto3.TestAllTypes{
+ single_value:google.protobuf.NullValue.NULL_VALUE~int^google.protobuf.NullValue.NULL_VALUE
+ }~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes.single_value~dyn,
+ null~null
+)~bool^equals
+
+Source: cel.expr.conformance.proto3.TestAllTypes{null_value: google.protobuf.NullValue.NULL_VALUE}.null_value == null
+=====>
+ERROR: test_location:1:103: found no matching overload for '_==_' applied to '(int, null)' (candidates: (%A0, %A0))
+ | cel.expr.conformance.proto3.TestAllTypes{null_value: google.protobuf.NullValue.NULL_VALUE}.null_value == null
+ | ......................................................................................................^
+
+Source: cel.expr.conformance.proto3.TestAllTypes{null_value: google.protobuf.NullValue.NULL_VALUE}.null_value == 0
+=====>
+_==_(
+ cel.expr.conformance.proto3.TestAllTypes{
+ null_value:google.protobuf.NullValue.NULL_VALUE~int^google.protobuf.NullValue.NULL_VALUE
+ }~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes.null_value~int,
+ 0~int
+)~bool^equals
+
+Source: google.protobuf.NullValue.NULL_VALUE == null
+=====>
+ERROR: test_location:1:38: found no matching overload for '_==_' applied to '(int, null)' (candidates: (%A0, %A0))
+ | google.protobuf.NullValue.NULL_VALUE == null
+ | .....................................^
+
+Source: google.protobuf.NullValue.NULL_VALUE == 0
+=====>
+_==_(
+ google.protobuf.NullValue.NULL_VALUE~int^google.protobuf.NullValue.NULL_VALUE,
+ 0~int
+)~bool^equals
\ No newline at end of file
diff --git a/checker/src/test/resources/jsonTypeNullConstruction.baseline b/checker/src/test/resources/jsonTypeNullConstruction.baseline
new file mode 100644
index 000000000..5b9b211a8
--- /dev/null
+++ b/checker/src/test/resources/jsonTypeNullConstruction.baseline
@@ -0,0 +1,35 @@
+Source: google.protobuf.Value{null_value: google.protobuf.NullValue.NULL_VALUE}
+=====>
+google.protobuf.Value{
+ null_value:google.protobuf.NullValue.NULL_VALUE~int^google.protobuf.NullValue.NULL_VALUE
+}~dyn^google.protobuf.Value
+
+Source: google.protobuf.Value{null_value: null}
+=====>
+ERROR: test_location:1:33: expected type of field 'null_value' is 'int' but provided type is 'null'
+ | google.protobuf.Value{null_value: null}
+ | ................................^
+
+Source: cel.expr.conformance.proto3.TestAllTypes{single_value: null}
+=====>
+cel.expr.conformance.proto3.TestAllTypes{
+ single_value:null~null
+}~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes
+
+Source: cel.expr.conformance.proto3.TestAllTypes{single_value: google.protobuf.NullValue.NULL_VALUE}
+=====>
+cel.expr.conformance.proto3.TestAllTypes{
+ single_value:google.protobuf.NullValue.NULL_VALUE~int^google.protobuf.NullValue.NULL_VALUE
+}~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes
+
+Source: cel.expr.conformance.proto3.TestAllTypes{null_value: null}
+=====>
+ERROR: test_location:1:52: expected type of field 'null_value' is 'int' but provided type is 'null'
+ | cel.expr.conformance.proto3.TestAllTypes{null_value: null}
+ | ...................................................^
+
+Source: cel.expr.conformance.proto3.TestAllTypes{null_value: google.protobuf.NullValue.NULL_VALUE}
+=====>
+cel.expr.conformance.proto3.TestAllTypes{
+ null_value:google.protobuf.NullValue.NULL_VALUE~int^google.protobuf.NullValue.NULL_VALUE
+}~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes
\ No newline at end of file
diff --git a/codelab/README.md b/codelab/README.md
index f7d248b13..83257d186 100644
--- a/codelab/README.md
+++ b/codelab/README.md
@@ -50,7 +50,7 @@ The code for this codelab lives in the `codelab` folder of the cel-java repo. Th
Clone and cd into the repo:
```
-git clone git@github.com:google/cel-java.git
+git clone git@github.com:cel-expr/cel-java.git
cd cel-java
```
@@ -74,10 +74,10 @@ Tests run: 5, Failures: 5
Each exercise is laid out as `ExerciseN.java` and is accompanied by failing tests. Throughout this codelab, we will modify the main exercise code to make these tests pass.
-- Codelab code: https://github.com/google/cel-java/tree/main/codelab/src/main/codelab
-- Test code for the main codelab: https://github.com/google/cel-java/tree/main/codelab/src/test/codelab
-- Codelab solution code: https://github.com/google/cel-java/tree/main/codelab/src/main/codelab/solutions
-- Test code for the solution: https://github.com/google/cel-java/tree/main/codelab/src/test/codelab/solutions
+- Codelab code: https://github.com/cel-expr/cel-java/tree/main/codelab/src/main/codelab
+- Test code for the main codelab: https://github.com/cel-expr/cel-java/tree/main/codelab/src/test/codelab
+- Codelab solution code: https://github.com/cel-expr/cel-java/tree/main/codelab/src/main/codelab/solutions
+- Test code for the solution: https://github.com/cel-expr/cel-java/tree/main/codelab/src/test/codelab/solutions
We will also be using `google.rpc.context.AttributeContext` in [attribute_context.proto](https://github.com/googleapis/googleapis/blob/master/google/rpc/context/attribute_context.proto) to help with defining inputs for exercises.
diff --git a/common/BUILD.bazel b/common/BUILD.bazel
index 21a124565..4e0d7485c 100644
--- a/common/BUILD.bazel
+++ b/common/BUILD.bazel
@@ -79,6 +79,12 @@ cel_android_library(
exports = ["//common/src/main/java/dev/cel/common:cel_source_android"],
)
+java_library(
+ name = "cel_source_helper",
+ visibility = ["//:internal"],
+ exports = ["//common/src/main/java/dev/cel/common:cel_source_helper"],
+)
+
java_library(
name = "cel_ast",
exports = ["//common/src/main/java/dev/cel/common:cel_ast"],
@@ -108,9 +114,6 @@ java_library(
visibility = [
"//:internal",
# TODO: Remove references to the following clients
- "//java/com/google/abuse/admin/notebook/compiler/checkedtypes:__pkg__",
- "//java/com/google/paymentfraud/v2/util/featurereplay/common/risklogrecordio:__pkg__",
- "//java/com/google/payments/consumer/growth/treatmentconfig/management/backend/service/config/utils:__pkg__",
],
exports = ["//common/src/main/java/dev/cel/common:cel_descriptor_util"],
)
diff --git a/common/internal/BUILD.bazel b/common/internal/BUILD.bazel
index c69f26b3e..7c33e56b9 100644
--- a/common/internal/BUILD.bazel
+++ b/common/internal/BUILD.bazel
@@ -11,6 +11,11 @@ java_library(
exports = ["//common/src/main/java/dev/cel/common/internal"],
)
+java_library(
+ name = "code_point_stream",
+ exports = ["//common/src/main/java/dev/cel/common/internal:code_point_stream"],
+)
+
java_library(
name = "comparison_functions",
exports = ["//common/src/main/java/dev/cel/common/internal:comparison_functions"],
@@ -123,11 +128,6 @@ cel_android_library(
exports = ["//common/src/main/java/dev/cel/common/internal:internal_android"],
)
-java_library(
- name = "proto_java_qualified_names",
- exports = ["//common/src/main/java/dev/cel/common/internal:proto_java_qualified_names"],
-)
-
java_library(
name = "proto_time_utils",
exports = ["//common/src/main/java/dev/cel/common/internal:proto_time_utils"],
@@ -147,3 +147,8 @@ cel_android_library(
name = "date_time_helpers_android",
exports = ["//common/src/main/java/dev/cel/common/internal:date_time_helpers_android"],
)
+
+java_library(
+ name = "reflection_util",
+ exports = ["//common/src/main/java/dev/cel/common/internal:reflection_util"],
+)
diff --git a/common/src/main/java/dev/cel/common/BUILD.bazel b/common/src/main/java/dev/cel/common/BUILD.bazel
index 62ba8db78..38548744c 100644
--- a/common/src/main/java/dev/cel/common/BUILD.bazel
+++ b/common/src/main/java/dev/cel/common/BUILD.bazel
@@ -21,7 +21,6 @@ COMPILER_COMMON_SOURCES = [
# keep sorted
SOURCE_SOURCES = [
- "CelSourceHelper.java",
"Source.java",
]
@@ -234,6 +233,7 @@ java_library(
tags = [
],
deps = [
+ ":cel_source_helper",
":source",
":source_location",
"//:auto_value",
@@ -250,6 +250,7 @@ cel_android_library(
tags = [
],
deps = [
+ ":cel_source_helper_android",
":source_android",
":source_location_android",
"//:auto_value",
@@ -260,6 +261,30 @@ cel_android_library(
],
)
+java_library(
+ name = "cel_source_helper",
+ srcs = ["CelSourceHelper.java"],
+ tags = [
+ ],
+ deps = [
+ ":source_location",
+ "//common/annotations",
+ "//common/internal",
+ "@maven//:com_google_guava_guava",
+ ],
+)
+
+cel_android_library(
+ name = "cel_source_helper_android",
+ srcs = ["CelSourceHelper.java"],
+ deps = [
+ ":source_location_android",
+ "//common/annotations",
+ "//common/internal:internal_android",
+ "@maven_android//:com_google_guava_guava",
+ ],
+)
+
java_library(
name = "cel_ast",
srcs = ["CelAbstractSyntaxTree.java"],
@@ -300,7 +325,6 @@ java_library(
tags = [
],
deps = [
- ":source_location",
"//common/annotations",
"//common/internal",
"@maven//:com_google_guava_guava",
@@ -312,7 +336,6 @@ cel_android_library(
srcs = SOURCE_SOURCES,
visibility = ["//visibility:private"],
deps = [
- ":source_location_android",
"//common/annotations",
"//common/internal:internal_android",
"@maven_android//:com_google_guava_guava",
diff --git a/common/src/main/java/dev/cel/common/CelAbstractSyntaxTree.java b/common/src/main/java/dev/cel/common/CelAbstractSyntaxTree.java
index 6b3b6a74f..b79c67e79 100644
--- a/common/src/main/java/dev/cel/common/CelAbstractSyntaxTree.java
+++ b/common/src/main/java/dev/cel/common/CelAbstractSyntaxTree.java
@@ -103,6 +103,11 @@ public Optional getType(long exprId) {
return Optional.ofNullable(types().get(exprId));
}
+ public CelType getTypeOrThrow(long exprId) {
+ return getType(exprId)
+ .orElseThrow(() -> new NoSuchElementException("Type not found for expr id: " + exprId));
+ }
+
public ImmutableMap getTypeMap() {
return types();
}
diff --git a/common/src/main/java/dev/cel/common/CelOptions.java b/common/src/main/java/dev/cel/common/CelOptions.java
index d0b020697..d9c2dd818 100644
--- a/common/src/main/java/dev/cel/common/CelOptions.java
+++ b/common/src/main/java/dev/cel/common/CelOptions.java
@@ -17,7 +17,6 @@
import com.google.auto.value.AutoValue;
import com.google.errorprone.annotations.CheckReturnValue;
import com.google.errorprone.annotations.Immutable;
-import dev.cel.common.annotations.Beta;
/**
* Options to configure how the CEL parser, type-checker, and evaluator behave.
@@ -117,12 +116,6 @@ public enum ProtoUnsetFieldOptions {
public abstract ProtoUnsetFieldOptions fromProtoUnsetFieldOption();
- public abstract boolean enableStringConversion();
-
- public abstract boolean enableStringConcatenation();
-
- public abstract boolean enableListConcatenation();
-
public abstract boolean enableComprehension();
public abstract int maxRegexProgramSize();
@@ -145,7 +138,7 @@ public static Builder newBuilder() {
.retainRepeatedUnaryOperators(false)
.retainUnbalancedLogicalExpressions(false)
.enableHiddenAccumulatorVar(true)
- .enableQuotedIdentifierSyntax(false)
+ .enableQuotedIdentifierSyntax(true)
// Type-Checker options
.enableCompileTimeOverloadResolution(false)
.enableHomogeneousLiterals(false)
@@ -169,9 +162,6 @@ public static Builder newBuilder() {
.comprehensionMaxIterations(-1)
.unwrapWellKnownTypesOnFunctionDispatch(true)
.fromProtoUnsetFieldOption(ProtoUnsetFieldOptions.BIND_DEFAULT)
- .enableStringConversion(true)
- .enableStringConcatenation(true)
- .enableListConcatenation(true)
.enableComprehension(true)
.maxRegexProgramSize(-1);
}
@@ -186,6 +176,7 @@ public static Builder current() {
.enableUnsignedComparisonAndArithmeticIsUnsigned(true)
.enableUnsignedLongs(true)
.enableRegexPartialMatch(true)
+ .enableTimestampEpoch(true)
.errorOnDuplicateMapKeys(true)
.evaluateCanonicalTypesToNativeValues(true)
.errorOnIntWrap(true)
@@ -301,14 +292,20 @@ public abstract static class Builder {
public abstract Builder enableHomogeneousLiterals(boolean value);
/**
- * Enable the {@code int64_to_timestamp} overload which creates a timestamp from Uxix epoch
+ * Enable the {@code int64_to_timestamp} overload which creates a timestamp from Unix epoch
* seconds.
*
- * This option will be automatically enabled after a sufficient period of time has elapsed to
- * ensure that all runtimes support the implementation.
+ *
Historically used to opt-in to this feature, this option is now enabled by default across
+ * all runtimes.
*
*
TODO: Remove this feature once it has been auto-enabled.
+ *
+ * @deprecated This option is now enabled by default. If you are passing {@code true}, simply
+ * remove this method call. If you are passing {@code false} to disable this feature, subset
+ * the environment instead using {@code dev.cel.checker.CelStandardDeclarations} and {@code
+ * dev.cel.runtime.CelStandardFunctions}.
*/
+ @Deprecated
public abstract Builder enableTimestampEpoch(boolean value);
/**
@@ -436,13 +433,10 @@ 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.
+ * @deprecated Do not use, this flag will be removed in the future. Use the planner based
+ * runtime instead, which supports CelValue by default.
*/
- @Beta
+ @Deprecated
public abstract Builder enableCelValue(boolean value);
/**
@@ -494,24 +488,6 @@ public abstract static class Builder {
*/
public abstract Builder fromProtoUnsetFieldOption(ProtoUnsetFieldOptions value);
- /**
- * Enables string() overloads for the runtime. This option exists to maintain parity with
- * cel-cpp interpreter options.
- */
- public abstract Builder enableStringConversion(boolean value);
-
- /**
- * Enables string concatenation overload for the runtime. This option exists to maintain parity
- * with cel-cpp interpreter options.
- */
- public abstract Builder enableStringConcatenation(boolean value);
-
- /**
- * Enables list concatenation overload for the runtime. This option exists to maintain parity
- * with cel-cpp interpreter options.
- */
- public abstract Builder enableListConcatenation(boolean value);
-
/**
* Enables comprehension (macros) for the runtime. Setting false has the same effect with
* assigning 0 for {@link #comprehensionMaxIterations()}. This option exists to maintain parity
diff --git a/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java b/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java
index 6204fd2fe..d805c9cf4 100644
--- a/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java
+++ b/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java
@@ -39,6 +39,11 @@ public static CelAttributeNotFoundException forFieldResolution(Collection attributes) {
+ return new CelAttributeNotFoundException(
+ "No such attribute(s): " + String.join(", ", attributes));
+ }
+
private static String formatErrorMessage(Collection fields) {
String maybePlural = "";
if (fields.size() > 1) {
diff --git a/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java b/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java
index 41358bb79..6a2b4ab72 100644
--- a/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java
+++ b/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java
@@ -24,4 +24,8 @@ public final class CelInvalidArgumentException extends CelRuntimeException {
public CelInvalidArgumentException(Throwable cause) {
super(cause, CelErrorCode.INVALID_ARGUMENT);
}
+
+ public CelInvalidArgumentException(String message) {
+ super(message, CelErrorCode.INVALID_ARGUMENT);
+ }
}
diff --git a/common/src/main/java/dev/cel/common/formats/BUILD.bazel b/common/src/main/java/dev/cel/common/formats/BUILD.bazel
index 16918cb3a..e906c8ba2 100644
--- a/common/src/main/java/dev/cel/common/formats/BUILD.bazel
+++ b/common/src/main/java/dev/cel/common/formats/BUILD.bazel
@@ -71,6 +71,7 @@ java_library(
],
deps = [
"//:auto_value",
+ "//common:cel_source_helper",
"//common:source",
"//common:source_location",
"//common/internal",
diff --git a/common/src/main/java/dev/cel/common/formats/ParserContext.java b/common/src/main/java/dev/cel/common/formats/ParserContext.java
index 0bdfdb299..17eff473f 100644
--- a/common/src/main/java/dev/cel/common/formats/ParserContext.java
+++ b/common/src/main/java/dev/cel/common/formats/ParserContext.java
@@ -42,6 +42,32 @@ public interface ParserContext {
Map getIdToOffsetMap();
- /** NewString creates a new ValueString from the YAML node. */
- ValueString newValueString(T node);
+ /**
+ * @deprecated Use {@link #newSourceString} instead.
+ */
+ @Deprecated
+ default ValueString newValueString(T node) {
+ return newSourceString(node);
+ }
+
+ /**
+ * NewYamlString creates a new ValueString from the YAML node, evaluated according to standard
+ * YAML parsing rules.
+ *
+ * This respects the whitespace folding semantics defined by the node's scalar style (e.g.,
+ * folded string {@code >} versus literal string {@code |}). Use this method for general string
+ * fields such as {@code description}, {@code name}, or {@code id}.
+ */
+ ValueString newYamlString(T node);
+
+ /**
+ * NewRawString creates a new ValueString from the YAML node, preserving formatting for accurate
+ * source mapping.
+ *
+ *
This extracts the verbatim text directly from the source file, preserving raw block
+ * indentation and unmodified newlines. Use this method when the string represents code or a CEL
+ * expression where precise character-level offsets must be maintained for accurate diagnostic
+ * error reporting.
+ */
+ ValueString newSourceString(T node);
}
diff --git a/common/src/main/java/dev/cel/common/formats/YamlHelper.java b/common/src/main/java/dev/cel/common/formats/YamlHelper.java
index e0780b01f..c16126f95 100644
--- a/common/src/main/java/dev/cel/common/formats/YamlHelper.java
+++ b/common/src/main/java/dev/cel/common/formats/YamlHelper.java
@@ -136,7 +136,7 @@ public static boolean newBoolean(ParserContext ctx, Node node) {
}
public static String newString(ParserContext ctx, Node node) {
- return ctx.newValueString(node).value();
+ return ctx.newYamlString(node).value();
}
private YamlHelper() {}
diff --git a/common/src/main/java/dev/cel/common/formats/YamlParserContextImpl.java b/common/src/main/java/dev/cel/common/formats/YamlParserContextImpl.java
index 456872803..9f6077562 100644
--- a/common/src/main/java/dev/cel/common/formats/YamlParserContextImpl.java
+++ b/common/src/main/java/dev/cel/common/formats/YamlParserContextImpl.java
@@ -62,7 +62,18 @@ public Map getIdToOffsetMap() {
}
@Override
- public ValueString newValueString(Node node) {
+ public ValueString newYamlString(Node node) {
+ long id = collectMetadata(node);
+ if (!assertYamlType(this, id, node, YamlNodeType.STRING, YamlNodeType.TEXT)) {
+ return ValueString.of(id, ERROR);
+ }
+
+ ScalarNode scalarNode = (ScalarNode) node;
+ return ValueString.of(id, scalarNode.getValue());
+ }
+
+ @Override
+ public ValueString newSourceString(Node node) {
long id = collectMetadata(node);
if (!assertYamlType(this, id, node, YamlNodeType.STRING, YamlNodeType.TEXT)) {
return ValueString.of(id, ERROR);
diff --git a/common/src/main/java/dev/cel/common/internal/BUILD.bazel b/common/src/main/java/dev/cel/common/internal/BUILD.bazel
index a0e564788..58b15b103 100644
--- a/common/src/main/java/dev/cel/common/internal/BUILD.bazel
+++ b/common/src/main/java/dev/cel/common/internal/BUILD.bazel
@@ -7,6 +7,7 @@ package(
],
default_visibility = [
"//common/internal:__pkg__",
+ "//publish:__pkg__",
],
)
@@ -14,7 +15,6 @@ package(
INTERNAL_SOURCES = [
"BasicCodePointArray.java",
"CelCodePointArray.java",
- "CodePointStream.java",
"Constants.java",
"EmptyCodePointArray.java",
"Latin1CodePointArray.java",
@@ -34,6 +34,19 @@ CEL_DESCRIPTOR_POOL_SOURCES = [
"DefaultDescriptorPool.java",
]
+java_library(
+ name = "code_point_stream",
+ srcs = ["CodePointStream.java"],
+ tags = [
+ ],
+ deps = [
+ ":internal",
+ "//common/annotations",
+ "@maven//:com_google_guava_guava",
+ "@maven//:org_antlr_antlr4_runtime",
+ ],
+)
+
java_library(
name = "internal",
srcs = INTERNAL_SOURCES,
@@ -48,7 +61,6 @@ java_library(
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:com_google_protobuf_protobuf_java",
- "@maven//:org_antlr_antlr4_runtime",
],
)
@@ -65,7 +77,6 @@ cel_android_library(
"//common/values:values_android",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
- "@maven//:org_antlr_antlr4_runtime",
"@maven_android//:com_google_guava_guava",
"@maven_android//:com_google_protobuf_protobuf_javalite",
],
@@ -142,7 +153,6 @@ java_library(
tags = [
],
deps = [
- ":proto_java_qualified_names",
":reflection_util",
"//common/annotations",
"@maven//:com_google_guava_guava",
@@ -386,22 +396,13 @@ java_library(
)
java_library(
- name = "proto_java_qualified_names",
- srcs = ["ProtoJavaQualifiedNames.java"],
+ name = "reflection_util",
+ srcs = ["ReflectionUtil.java"],
tags = [
],
deps = [
"//common/annotations",
"@maven//:com_google_guava_guava",
- "@maven//:com_google_protobuf_protobuf_java",
- ],
-)
-
-java_library(
- name = "reflection_util",
- srcs = ["ReflectionUtil.java"],
- deps = [
- "//common/annotations",
],
)
diff --git a/common/src/main/java/dev/cel/common/internal/Constants.java b/common/src/main/java/dev/cel/common/internal/Constants.java
index d2c0719ec..49bca7489 100644
--- a/common/src/main/java/dev/cel/common/internal/Constants.java
+++ b/common/src/main/java/dev/cel/common/internal/Constants.java
@@ -207,6 +207,9 @@ private static void decodeString(
continue;
}
skipNewline = false;
+ if (codePoint >= MIN_SURROGATE && codePoint <= MAX_SURROGATE) {
+ throw new ParseException("Invalid unicode code point", seqOffset);
+ }
buffer.appendCodePoint(codePoint);
} else {
// Normalize '\r' and '\r\n' to '\n'.
@@ -231,6 +234,9 @@ private static void decodeString(
// For raw literals, all escapes are valid and those characters come through literally in
// the string.
buffer.appendCodePoint('\\');
+ if (codePoint >= MIN_SURROGATE && codePoint <= MAX_SURROGATE) {
+ throw new ParseException("Invalid unicode code point", seqOffset);
+ }
buffer.appendCodePoint(codePoint);
continue;
}
diff --git a/common/src/main/java/dev/cel/common/internal/DefaultInstanceMessageFactory.java b/common/src/main/java/dev/cel/common/internal/DefaultInstanceMessageFactory.java
index fcb0e7056..163d0273e 100644
--- a/common/src/main/java/dev/cel/common/internal/DefaultInstanceMessageFactory.java
+++ b/common/src/main/java/dev/cel/common/internal/DefaultInstanceMessageFactory.java
@@ -15,6 +15,7 @@
package dev.cel.common.internal;
import com.google.protobuf.Descriptors.Descriptor;
+import com.google.protobuf.GeneratorNames;
import com.google.protobuf.Message;
import com.google.protobuf.MessageLite;
import dev.cel.common.annotations.Internal;
@@ -45,9 +46,7 @@ public static DefaultInstanceMessageFactory getInstance() {
public Optional getPrototype(Descriptor descriptor) {
MessageLite defaultInstance =
DefaultInstanceMessageLiteFactory.getInstance()
- .getPrototype(
- descriptor.getFullName(),
- ProtoJavaQualifiedNames.getFullyQualifiedJavaClassName(descriptor))
+ .getPrototype(descriptor.getFullName(), GeneratorNames.getBytecodeClassName(descriptor))
.orElse(null);
if (defaultInstance == null) {
return Optional.empty();
diff --git a/common/src/main/java/dev/cel/common/internal/DefaultMessageFactory.java b/common/src/main/java/dev/cel/common/internal/DefaultMessageFactory.java
index 4a021cd90..68d05e127 100644
--- a/common/src/main/java/dev/cel/common/internal/DefaultMessageFactory.java
+++ b/common/src/main/java/dev/cel/common/internal/DefaultMessageFactory.java
@@ -52,7 +52,7 @@ public Optional newBuilder(String messageName) {
DefaultInstanceMessageFactory.getInstance().getPrototype(descriptor.get());
if (message.isPresent()) {
- return message.map(Message::toBuilder);
+ return message.map(Message::newBuilderForType);
}
return Optional.of(DynamicMessage.newBuilder(descriptor.get()));
diff --git a/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java b/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java
index 962a9d2e9..7e3910433 100644
--- a/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java
+++ b/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java
@@ -204,8 +204,27 @@ public Optional adaptFieldToValue(FieldDescriptor fieldDescriptor, Objec
@SuppressWarnings({"unchecked", "rawtypes"})
public Optional adaptValueToFieldType(
FieldDescriptor fieldDescriptor, Object fieldValue) {
- if (isWrapperType(fieldDescriptor) && fieldValue.equals(NullValue.NULL_VALUE)) {
- return Optional.empty();
+ if (fieldValue instanceof NullValue) {
+ // `null` assignment to fields indicate that the field would not be set
+ // in a protobuf message (e.g: Message{msg_field: null} -> Message{})
+ //
+ // We explicitly check below for invalid null assignments, such as repeated
+ // or map fields. (e.g: Message{repeated_field: null} -> Error)
+ if (fieldDescriptor.isMapField()
+ || fieldDescriptor.isRepeated()
+ || fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE
+ || WellKnownProto.JSON_STRUCT_VALUE
+ .typeName()
+ .equals(fieldDescriptor.getMessageType().getFullName())
+ || WellKnownProto.JSON_LIST_VALUE
+ .typeName()
+ .equals(fieldDescriptor.getMessageType().getFullName())) {
+ throw new IllegalArgumentException("Unsupported field type");
+ }
+
+ if (!isFieldAnyOrJson(fieldDescriptor)) {
+ return Optional.empty();
+ }
}
if (fieldDescriptor.isMapField()) {
Descriptor entryDescriptor = fieldDescriptor.getMessageType();
@@ -221,7 +240,11 @@ public Optional adaptValueToFieldType(
getDefaultValueForMaybeMessage(keyDescriptor),
valueDescriptor.getLiteType(),
getDefaultValueForMaybeMessage(valueDescriptor));
+ boolean isValueAnyOrJson = isFieldAnyOrJson(valueDescriptor);
for (Map.Entry entry : ((Map, ?>) fieldValue).entrySet()) {
+ if (!isValueAnyOrJson && entry.getValue() instanceof NullValue) {
+ continue;
+ }
mapEntries.add(
protoMapEntry.toBuilder()
.setKey(keyConverter.backwardConverter().convert(entry.getKey()))
@@ -231,15 +254,54 @@ public Optional adaptValueToFieldType(
return Optional.of(mapEntries);
}
if (fieldDescriptor.isRepeated()) {
+ List> listValue = (List>) fieldValue;
+
+ if (!isFieldAnyOrJson(fieldDescriptor)) {
+ listValue = filterOutNullValues(listValue);
+ }
+
return Optional.of(
- AdaptingTypes.adaptingList(
- (List>) fieldValue, fieldToValueConverter(fieldDescriptor).reverse()));
+ AdaptingTypes.adaptingList(listValue, fieldToValueConverter(fieldDescriptor).reverse()));
}
return Optional.of(
fieldToValueConverter(fieldDescriptor).backwardConverter().convert(fieldValue));
}
+ private static List> filterOutNullValues(List> originalList) {
+ List filteredList = null;
+
+ for (int i = 0; i < originalList.size(); i++) {
+ Object elem = originalList.get(i);
+
+ if (elem instanceof NullValue) {
+ if (filteredList == null) {
+ filteredList = new ArrayList<>(originalList.size() - 1);
+ if (i > 0) {
+ filteredList.addAll(originalList.subList(0, i));
+ }
+ }
+ } else if (filteredList != null) {
+ filteredList.add(elem);
+ }
+ }
+
+ // Return the original list if no nulls were found to avoid unnecessary allocations
+ return filteredList != null ? filteredList : originalList;
+ }
+
+ private static boolean isFieldAnyOrJson(FieldDescriptor fieldDescriptor) {
+ if (!fieldDescriptor.getType().equals(FieldDescriptor.Type.MESSAGE)) {
+ return false;
+ }
+
+ String typeFullName = fieldDescriptor.getMessageType().getFullName();
+
+ return WellKnownProto.getByTypeName(typeFullName)
+ .map(wkp -> wkp.equals(WellKnownProto.ANY_VALUE) || wkp.equals(WellKnownProto.JSON_VALUE))
+ .orElse(false);
+ }
+
@SuppressWarnings("rawtypes")
private BidiConverter fieldToValueConverter(FieldDescriptor fieldDescriptor) {
switch (fieldDescriptor.getType()) {
@@ -370,14 +432,6 @@ private static String typeName(Descriptor protoType) {
return protoType.getFullName();
}
- private static boolean isWrapperType(FieldDescriptor fieldDescriptor) {
- if (fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE) {
- return false;
- }
- String fieldTypeName = fieldDescriptor.getMessageType().getFullName();
- return WellKnownProto.isWrapperType(fieldTypeName);
- }
-
private static int intCheckedCast(long value) {
try {
return Ints.checkedCast(value);
diff --git a/common/src/main/java/dev/cel/common/internal/ProtoJavaQualifiedNames.java b/common/src/main/java/dev/cel/common/internal/ProtoJavaQualifiedNames.java
deleted file mode 100644
index f27181a50..000000000
--- a/common/src/main/java/dev/cel/common/internal/ProtoJavaQualifiedNames.java
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2025 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.common.internal;
-
-import com.google.protobuf.Descriptors.Descriptor;
-import com.google.protobuf.Descriptors.FileDescriptor;
-import com.google.protobuf.GeneratorNames;
-import dev.cel.common.annotations.Internal;
-
-/**
- * Helper class for constructing a fully qualified Java class name from a protobuf descriptor.
- *
- * CEL Library Internals. Do Not Use.
- */
-@Internal
-public final class ProtoJavaQualifiedNames {
- /**
- * Retrieves the full Java class name from the given descriptor
- *
- * @return fully qualified class name.
- *
Example 1: dev.cel.expr.Value
- *
Example 2: com.google.rpc.context.AttributeContext$Resource (Nested classes)
- *
Example 3: com.google.api.expr.cel.internal.testdata$SingleFileProto$SingleFile$Path
- * (Nested class with java multiple files disabled)
- */
- public static String getFullyQualifiedJavaClassName(Descriptor descriptor) {
- return GeneratorNames.getBytecodeClassName(descriptor);
- }
-
- /**
- * Gets the java package name from the descriptor. See
- * https://developers.google.com/protocol-buffers/docs/reference/java-generated#package for rules
- * on package name generation
- */
- public static String getJavaPackageName(FileDescriptor fileDescriptor) {
- return GeneratorNames.getFileJavaPackage(fileDescriptor.toProto());
- }
-
- private ProtoJavaQualifiedNames() {}
-}
diff --git a/common/src/main/java/dev/cel/common/internal/ReflectionUtil.java b/common/src/main/java/dev/cel/common/internal/ReflectionUtil.java
index e513a446b..97bed650f 100644
--- a/common/src/main/java/dev/cel/common/internal/ReflectionUtil.java
+++ b/common/src/main/java/dev/cel/common/internal/ReflectionUtil.java
@@ -14,9 +14,11 @@
package dev.cel.common.internal;
+import com.google.common.reflect.TypeToken;
import dev.cel.common.annotations.Internal;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.lang.reflect.Type;
/**
* Utility class for invoking Java reflection.
@@ -48,5 +50,18 @@ public static Object invoke(Method method, Object object, Object... params) {
}
}
+ /** Resolves a generic parameter of a base class from a type token. */
+ public static Type resolveGenericParameter(TypeToken> token, Class> baseClass, int index) {
+ return token.resolveType(baseClass.getTypeParameters()[index]).getType();
+ }
+
+ /**
+ * Extracts the raw Class from a Type. Handles Class, ParameterizedType, and WildcardType (returns
+ * upper bound). Returns Object.class as fallback.
+ */
+ public static Class> getRawType(Type type) {
+ return TypeToken.of(type).getRawType();
+ }
+
private ReflectionUtil() {}
}
diff --git a/common/src/main/java/dev/cel/common/types/BUILD.bazel b/common/src/main/java/dev/cel/common/types/BUILD.bazel
index f758b8e86..de65d0b1f 100644
--- a/common/src/main/java/dev/cel/common/types/BUILD.bazel
+++ b/common/src/main/java/dev/cel/common/types/BUILD.bazel
@@ -7,6 +7,7 @@ package(
],
default_visibility = [
"//common/types:__pkg__",
+ "//publish:__pkg__",
],
)
diff --git a/common/src/main/java/dev/cel/common/types/SimpleType.java b/common/src/main/java/dev/cel/common/types/SimpleType.java
index 6c43ab53f..93bd5326d 100644
--- a/common/src/main/java/dev/cel/common/types/SimpleType.java
+++ b/common/src/main/java/dev/cel/common/types/SimpleType.java
@@ -46,7 +46,6 @@ public abstract class SimpleType extends CelType {
public static final ImmutableMap TYPE_MAP =
ImmutableMap.of(
- DYN.name(), DYN,
BOOL.name(), BOOL,
BYTES.name(), BYTES,
DOUBLE.name(), DOUBLE,
diff --git a/common/src/main/java/dev/cel/common/values/BUILD.bazel b/common/src/main/java/dev/cel/common/values/BUILD.bazel
index 3a78091bf..5ccc498fd 100644
--- a/common/src/main/java/dev/cel/common/values/BUILD.bazel
+++ b/common/src/main/java/dev/cel/common/values/BUILD.bazel
@@ -7,6 +7,7 @@ package(
],
default_visibility = [
"//common/values:__pkg__",
+ "//publish:__pkg__",
],
)
@@ -59,7 +60,6 @@ java_library(
deps = [
"//common/values",
"@maven//:com_google_errorprone_error_prone_annotations",
- "@maven//:com_google_guava_guava",
],
)
@@ -71,16 +71,19 @@ cel_android_library(
deps = [
"//common/values:values_android",
"@maven//:com_google_errorprone_error_prone_annotations",
- "@maven_android//:com_google_guava_guava",
],
)
java_library(
name = "combined_cel_value_provider",
- srcs = ["CombinedCelValueProvider.java"],
+ srcs = [
+ "CombinedCelValueProvider.java",
+ ],
tags = [
],
deps = [
+ ":combined_cel_value_converter",
+ ":values",
"//common/values:cel_value_provider",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
@@ -89,16 +92,70 @@ java_library(
cel_android_library(
name = "combined_cel_value_provider_android",
- srcs = ["CombinedCelValueProvider.java"],
+ srcs = [
+ "CombinedCelValueProvider.java",
+ ],
tags = [
],
deps = [
+ ":combined_cel_value_converter_android",
+ ":values_android",
"//common/values:cel_value_provider_android",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven_android//:com_google_guava_guava",
],
)
+java_library(
+ name = "combined_cel_value_converter",
+ srcs = [
+ "CombinedCelValueConverter.java",
+ ],
+ tags = [
+ ],
+ deps = [
+ ":values",
+ "//common/annotations",
+ "@maven//:com_google_guava_guava",
+ "@maven//:org_jspecify_jspecify",
+ ],
+)
+
+cel_android_library(
+ name = "combined_cel_value_converter_android",
+ srcs = [
+ "CombinedCelValueConverter.java",
+ ],
+ tags = [
+ ],
+ deps = [
+ ":values_android",
+ "//common/annotations",
+ "@maven//:org_jspecify_jspecify",
+ "@maven_android//:com_google_guava_guava",
+ ],
+)
+
+java_library(
+ name = "preadapted_list",
+ srcs = [
+ "CelPreAdaptedList.java",
+ ],
+ tags = [
+ ],
+ deps = ["//common/annotations"],
+)
+
+cel_android_library(
+ name = "preadapted_list_android",
+ srcs = [
+ "CelPreAdaptedList.java",
+ ],
+ tags = [
+ ],
+ deps = ["//common/annotations"],
+)
+
java_library(
name = "values",
srcs = CEL_VALUES_SOURCES,
@@ -107,6 +164,7 @@ java_library(
deps = [
":cel_byte_string",
":cel_value",
+ ":preadapted_list",
"//:auto_value",
"//common/annotations",
"//common/types",
@@ -117,6 +175,38 @@ java_library(
],
)
+java_library(
+ name = "mutable_map_value",
+ srcs = ["MutableMapValue.java"],
+ tags = [
+ ],
+ deps = [
+ "//common/annotations",
+ "//common/exceptions:attribute_not_found",
+ "//common/types",
+ "//common/types:type_providers",
+ "//common/values",
+ "//common/values:cel_value",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ ],
+)
+
+cel_android_library(
+ name = "mutable_map_value_android",
+ srcs = ["MutableMapValue.java"],
+ tags = [
+ ],
+ deps = [
+ ":cel_value_android",
+ "//common/annotations",
+ "//common/exceptions:attribute_not_found",
+ "//common/types:type_providers_android",
+ "//common/types:types_android",
+ "//common/values:values_android",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ ],
+)
+
cel_android_library(
name = "values_android",
srcs = CEL_VALUES_SOURCES,
@@ -125,6 +215,7 @@ cel_android_library(
deps = [
":cel_byte_string",
":cel_value_android",
+ ":preadapted_list_android",
"//:auto_value",
"//common/annotations",
"//common/types:type_providers_android",
@@ -153,7 +244,6 @@ java_library(
],
deps = [
":cel_byte_string",
- ":values",
"//common/annotations",
"//common/internal:proto_time_utils",
"//common/internal:well_known_proto",
@@ -188,8 +278,10 @@ java_library(
],
deps = [
":base_proto_cel_value_converter",
+ ":preadapted_list",
":values",
"//:auto_value",
+ "//common:options",
"//common/annotations",
"//common/internal:cel_descriptor_pools",
"//common/internal:dynamic_proto",
@@ -199,7 +291,6 @@ java_library(
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:com_google_protobuf_protobuf_java",
- "@maven//:org_jspecify_jspecify",
],
)
@@ -242,8 +333,6 @@ java_library(
"//protobuf:cel_lite_descriptor",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
- "@maven//:com_google_protobuf_protobuf_java",
- "@maven//:org_jspecify_jspecify",
"@maven_android//:com_google_protobuf_protobuf_javalite",
],
)
@@ -269,7 +358,6 @@ cel_android_library(
"//protobuf:cel_lite_descriptor",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
- "@maven//:org_jspecify_jspecify",
"@maven_android//:com_google_guava_guava",
"@maven_android//:com_google_protobuf_protobuf_javalite",
],
diff --git a/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java b/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java
index b05a21e24..9fc218abe 100644
--- a/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java
+++ b/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java
@@ -98,6 +98,8 @@ protected Object fromWellKnownProto(MessageLiteOrBuilder message, WellKnownProto
return UnsignedLong.valueOf(((UInt32Value) message).getValue());
case UINT64_VALUE:
return UnsignedLong.fromLongBits(((UInt64Value) message).getValue());
+ case EMPTY:
+ return ImmutableMap.of();
default:
throw new UnsupportedOperationException(
"Unsupported well known proto conversion - " + wellKnownProto);
diff --git a/common/src/main/java/dev/cel/common/values/CelPreAdaptedList.java b/common/src/main/java/dev/cel/common/values/CelPreAdaptedList.java
new file mode 100644
index 000000000..c0ff25e45
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/values/CelPreAdaptedList.java
@@ -0,0 +1,49 @@
+// Copyright 2026 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.common.values;
+
+import dev.cel.common.annotations.Internal;
+import java.util.AbstractList;
+import java.util.List;
+import java.util.RandomAccess;
+
+/**
+ * A zero-allocation view over a list we know is already adapted.
+ *
+ * This class purely exists as an optimization scheme to avoid redundant collection traversals in
+ * {@link CelValueConverter}, and is not intended for general use.
+ */
+@Internal
+final class CelPreAdaptedList extends AbstractList implements RandomAccess {
+ private final List delegate;
+
+ private CelPreAdaptedList(List delegate) {
+ this.delegate = delegate;
+ }
+
+ static CelPreAdaptedList wrap(List safeList) {
+ return new CelPreAdaptedList<>(safeList);
+ }
+
+ @Override
+ public E get(int index) {
+ return delegate.get(index);
+ }
+
+ @Override
+ public int size() {
+ return delegate.size();
+ }
+}
diff --git a/common/src/main/java/dev/cel/common/values/CelValueConverter.java b/common/src/main/java/dev/cel/common/values/CelValueConverter.java
index ae0b40ef7..20deef1d3 100644
--- a/common/src/main/java/dev/cel/common/values/CelValueConverter.java
+++ b/common/src/main/java/dev/cel/common/values/CelValueConverter.java
@@ -20,9 +20,12 @@
import com.google.errorprone.annotations.Immutable;
import dev.cel.common.annotations.Internal;
import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Optional;
+import java.util.RandomAccess;
+import java.util.function.Function;
/**
* {@code CelValueConverter} handles bidirectional conversion between native Java objects to {@link
@@ -37,45 +40,121 @@ public class CelValueConverter {
private static final CelValueConverter DEFAULT_INSTANCE = new CelValueConverter();
+ @SuppressWarnings("Immutable") // Method reference is immutable
+ private final Function maybeUnwrapFunction;
+
+ @SuppressWarnings("Immutable") // Method reference is immutable
+ private final Function toRuntimeValueFunction;
+
public static CelValueConverter getDefaultInstance() {
return DEFAULT_INSTANCE;
}
- /** Adapts a {@link CelValue} to a plain old Java Object. */
- public Object unwrap(CelValue celValue) {
- Preconditions.checkNotNull(celValue);
+ /**
+ * Unwraps the {@code value} into its plain old Java Object representation.
+ *
+ * The value may be a {@link CelValue}, a {@link Collection} or a {@link Map}.
+ */
+ public Object maybeUnwrap(Object value) {
+ if (value instanceof CelValue || value instanceof CelPreAdaptedList) {
+ return value instanceof CelValue ? unwrap((CelValue) value) : value;
+ }
- if (celValue instanceof OptionalValue) {
- OptionalValue optionalValue = (OptionalValue) celValue;
- if (optionalValue.isZeroValue()) {
- return Optional.empty();
+ return mapContainer(value, maybeUnwrapFunction);
+ }
+
+ /**
+ * Maps a container (Collection or Map) by applying the provided mapper function to its elements.
+ * Returns the original value if it's not a supported container.
+ */
+ protected Object mapContainer(Object value, Function mapper) {
+
+ // Zero allocation path for standard lists that support O(1) indexing
+ // Generally, protobuf lists (backed by arrays) fall into this category
+ if (value instanceof List && value instanceof RandomAccess) {
+ List list = (List) value;
+ for (int i = 0; i < list.size(); i++) {
+ Object element = list.get(i);
+ Object mapped = mapper.apply(element);
+
+ if (mapped != element) {
+ ImmutableList.Builder builder =
+ ImmutableList.builderWithExpectedSize(list.size());
+ for (int j = 0; j < i; j++) {
+ builder.add(list.get(j));
+ }
+ builder.add(mapped);
+ for (int j = i + 1; j < list.size(); j++) {
+ builder.add(mapper.apply(list.get(j)));
+ }
+ return builder.build();
+ }
}
- return Optional.of(optionalValue.value());
+ // Zero allocations if unmodified
+ return value;
}
- return celValue.value();
+ // Fallback for lists that are unordered
+ if (value instanceof Collection) {
+ Collection collection = (Collection) value;
+ ImmutableList.Builder builder =
+ ImmutableList.builderWithExpectedSize(collection.size());
+ for (Object element : collection) {
+ builder.add(mapper.apply(element));
+ }
+ return builder.build();
+ }
+
+ if (value instanceof Map) {
+ Map map = (Map) value;
+ Iterator> iterator = map.entrySet().iterator();
+
+ while (iterator.hasNext()) {
+ Map.Entry entry = iterator.next();
+ Object mappedKey = mapper.apply(entry.getKey());
+ Object mappedValue = mapper.apply(entry.getValue());
+
+ if (mappedKey != entry.getKey() || mappedValue != entry.getValue()) {
+ ImmutableMap.Builder builder =
+ ImmutableMap.builderWithExpectedSize(map.size());
+
+ for (Map.Entry prevEntry : map.entrySet()) {
+ if (prevEntry.getKey() == entry.getKey()) {
+ break;
+ }
+ builder.put(mapper.apply(prevEntry.getKey()), mapper.apply(prevEntry.getValue()));
+ }
+ builder.put(mappedKey, mappedValue);
+ while (iterator.hasNext()) {
+ Map.Entry nextEntry = iterator.next();
+ builder.put(mapper.apply(nextEntry.getKey()), mapper.apply(nextEntry.getValue()));
+ }
+ return builder.buildOrThrow();
+ }
+ }
+ return value;
+ }
+
+ return value;
}
- /**
- * Canonicalizes an inbound {@code value} into a suitable Java object representation for
- * evaluation.
- */
public Object toRuntimeValue(Object value) {
Preconditions.checkNotNull(value);
- if (value instanceof CelValue) {
+ if (value instanceof CelValue || value instanceof CelPreAdaptedList) {
return value;
}
- if (value instanceof Collection) {
- return toListValue((Collection) value);
- } else if (value instanceof Map) {
- return toMapValue((Map) value);
- } else if (value instanceof Optional) {
+ Object mapped = mapContainer(value, toRuntimeValueFunction);
+ if (mapped != value) {
+ return mapped;
+ }
+
+ if (value instanceof Optional) {
Optional optionalValue = (Optional) value;
return optionalValue
- .map(this::toRuntimeValue)
+ .map(toRuntimeValueFunction)
.map(OptionalValue::create)
.orElse(OptionalValue.EMPTY);
}
@@ -97,31 +176,28 @@ protected Object normalizePrimitive(Object value) {
return value;
}
- private ImmutableList toListValue(Collection iterable) {
- Preconditions.checkNotNull(iterable);
-
- ImmutableList.Builder listBuilder =
- ImmutableList.builderWithExpectedSize(iterable.size());
- for (Object entry : iterable) {
- listBuilder.add(toRuntimeValue(entry));
- }
+ /** Adapts a {@link CelValue} to a plain old Java Object. */
+ private Object unwrap(CelValue celValue) {
+ Preconditions.checkNotNull(celValue);
- return listBuilder.build();
- }
+ if (celValue instanceof OptionalValue) {
+ OptionalValue optionalValue = (OptionalValue) celValue;
+ if (optionalValue.isZeroValue()) {
+ return Optional.empty();
+ }
- private ImmutableMap toMapValue(Map map) {
- Preconditions.checkNotNull(map);
+ return Optional.of(maybeUnwrap(optionalValue.value()));
+ }
- ImmutableMap.Builder mapBuilder =
- ImmutableMap.builderWithExpectedSize(map.size());
- for (Entry entry : map.entrySet()) {
- Object mapKey = toRuntimeValue(entry.getKey());
- Object mapValue = toRuntimeValue(entry.getValue());
- mapBuilder.put(mapKey, mapValue);
+ if (celValue instanceof ErrorValue) {
+ return celValue;
}
- return mapBuilder.buildOrThrow();
+ return celValue.value();
}
- protected CelValueConverter() {}
+ protected CelValueConverter() {
+ this.maybeUnwrapFunction = this::maybeUnwrap;
+ this.toRuntimeValueFunction = this::toRuntimeValue;
+ }
}
diff --git a/common/src/main/java/dev/cel/common/values/CombinedCelValueConverter.java b/common/src/main/java/dev/cel/common/values/CombinedCelValueConverter.java
new file mode 100644
index 000000000..46e5fc3f1
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/values/CombinedCelValueConverter.java
@@ -0,0 +1,84 @@
+// Copyright 2026 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.common.values;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.collect.ImmutableList;
+import dev.cel.common.annotations.Internal;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * {@code CombinedCelValueConverter} delegates value conversion to a list of underlying {@link
+ * CelValueConverter}s.
+ */
+@Internal
+public final class CombinedCelValueConverter extends CelValueConverter {
+ private final ImmutableList converters;
+
+ public static CombinedCelValueConverter combine(ImmutableList converters) {
+ return new CombinedCelValueConverter(converters);
+ }
+
+ private CombinedCelValueConverter(ImmutableList converters) {
+ this.converters = checkNotNull(converters);
+ }
+
+ @Override
+ public @Nullable Object toRuntimeValue(Object value) {
+ if (value == null) {
+ return null;
+ }
+
+ // Let the base class handle CelValues, Optionals, Collections, Maps, and primitives.
+ Object baseResult = super.toRuntimeValue(value);
+ if (baseResult != value) {
+ return baseResult;
+ }
+
+ // If the base class left the object unchanged (e.g. a raw POJO), try the delegates.
+ for (CelValueConverter converter : converters) {
+ Object result = converter.toRuntimeValue(value);
+ if (result != value) {
+ return result;
+ }
+ }
+
+ return value;
+ }
+
+ @Override
+ public @Nullable Object maybeUnwrap(Object value) {
+ if (value == null) {
+ return null;
+ }
+
+ // Let the base class handle standard unwrapping and container unrolling.
+ Object baseResult = super.maybeUnwrap(value);
+ if (baseResult != value) {
+ return baseResult;
+ }
+
+ // Try delegates for specialized unwrapping.
+ for (CelValueConverter converter : converters) {
+ Object result = converter.maybeUnwrap(value);
+ if (result != value) {
+ return result;
+ }
+ }
+
+ return value;
+ }
+}
diff --git a/common/src/main/java/dev/cel/common/values/CombinedCelValueProvider.java b/common/src/main/java/dev/cel/common/values/CombinedCelValueProvider.java
index 8fe62cb7b..d51c3afce 100644
--- a/common/src/main/java/dev/cel/common/values/CombinedCelValueProvider.java
+++ b/common/src/main/java/dev/cel/common/values/CombinedCelValueProvider.java
@@ -16,6 +16,7 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.Immutable;
@@ -49,6 +50,14 @@ public Optional newValue(String structType, Map fields)
return Optional.empty();
}
+ @Override
+ public CelValueConverter celValueConverter() {
+ return CombinedCelValueConverter.combine(
+ celValueProviders.stream()
+ .map(CelValueProvider::celValueConverter)
+ .collect(toImmutableList()));
+ }
+
/** Returns the underlying {@link CelValueProvider}s in order. */
public ImmutableList valueProviders() {
return celValueProviders;
diff --git a/common/src/main/java/dev/cel/common/values/MutableMapValue.java b/common/src/main/java/dev/cel/common/values/MutableMapValue.java
new file mode 100644
index 000000000..706436b2e
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/values/MutableMapValue.java
@@ -0,0 +1,146 @@
+// Copyright 2026 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.common.values;
+
+import com.google.errorprone.annotations.Immutable;
+import dev.cel.common.annotations.Internal;
+import dev.cel.common.exceptions.CelAttributeNotFoundException;
+import dev.cel.common.types.CelType;
+import dev.cel.common.types.MapType;
+import dev.cel.common.types.SimpleType;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * A custom CelValue implementation that allows O(1) insertions for maps during comprehension.
+ *
+ * CEL Library Internals. Do Not Use.
+ */
+@Internal
+@Immutable
+@SuppressWarnings("Immutable") // Intentionally mutable for performance reasons
+public final class MutableMapValue extends CelValue
+ implements SelectableValue, Map {
+ private final Map internalMap;
+ private final CelType celType;
+
+ public static MutableMapValue create(Map, ?> map) {
+ return new MutableMapValue(map);
+ }
+
+ @Override
+ public int size() {
+ return internalMap.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return internalMap.isEmpty();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return internalMap.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ return internalMap.containsValue(value);
+ }
+
+ @Override
+ public Object get(Object key) {
+ return internalMap.get(key);
+ }
+
+ @Override
+ public Object put(Object key, Object value) {
+ return internalMap.put(key, value);
+ }
+
+ @Override
+ public Object remove(Object key) {
+ return internalMap.remove(key);
+ }
+
+ @Override
+ public void putAll(Map, ?> m) {
+ internalMap.putAll(m);
+ }
+
+ @Override
+ public void clear() {
+ internalMap.clear();
+ }
+
+ @Override
+ public Set keySet() {
+ return internalMap.keySet();
+ }
+
+ @Override
+ public Collection values() {
+ return internalMap.values();
+ }
+
+ @Override
+ public Set> entrySet() {
+ return internalMap.entrySet();
+ }
+
+ @Override
+ public Object select(Object field) {
+ Object val = internalMap.get(field);
+ if (val != null) {
+ return val;
+ }
+ if (!internalMap.containsKey(field)) {
+ throw CelAttributeNotFoundException.forMissingMapKey(field.toString());
+ }
+ throw CelAttributeNotFoundException.of(
+ String.format("Map value cannot be null for key: %s", field));
+ }
+
+ @Override
+ public Optional> find(Object field) {
+ if (internalMap.containsKey(field)) {
+ return Optional.ofNullable(internalMap.get(field));
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public Object value() {
+ return this;
+ }
+
+ @Override
+ public boolean isZeroValue() {
+ return internalMap.isEmpty();
+ }
+
+ @Override
+ public CelType celType() {
+ return celType;
+ }
+
+ private MutableMapValue(Map, ?> map) {
+ this.internalMap = new LinkedHashMap<>(map);
+ this.celType = MapType.create(SimpleType.DYN, SimpleType.DYN);
+ }
+}
diff --git a/common/src/main/java/dev/cel/common/values/OpaqueValue.java b/common/src/main/java/dev/cel/common/values/OpaqueValue.java
index 3350d05d4..8b3ac4574 100644
--- a/common/src/main/java/dev/cel/common/values/OpaqueValue.java
+++ b/common/src/main/java/dev/cel/common/values/OpaqueValue.java
@@ -14,13 +14,32 @@
package dev.cel.common.values;
+import static com.google.common.base.Preconditions.checkNotNull;
+
import com.google.auto.value.AutoValue;
+import com.google.errorprone.annotations.Immutable;
import dev.cel.common.types.OpaqueType;
-/** OpaqueValue is the value representation of OpaqueType. */
-@AutoValue
-@AutoValue.CopyAnnotations
-@SuppressWarnings("Immutable") // Java Object is mutable.
+/**
+ * OpaqueValue is the value representation of an {@link OpaqueType}.
+ *
+ * Users may provide a custom opaque type that CEL can understand. Note that this is only
+ * supported for the Planner runtime. There are two primary modes of extending this class:
+ *
+ *
+ * Direct Extension (Recommended): A domain object directly extends {@code OpaqueValue}
+ * and returns {@code this} for its {@link #value()} method. This approach allows the CEL
+ * engine to evaluate the object natively without stripping its type information, eliminating
+ * the need to register a custom {@link CelValueConverter}.
+ * Wrapping: A domain object is wrapped into an {@code OpaqueValue} via the {@link
+ * #create(String, Object)} factory method. This is required when users cannot modify their
+ * existing POJOs to extend {@code OpaqueValue}. However, because the CEL runtime aggressively
+ * unwraps objects during evaluation, this mode necessitates implementing and registering a
+ * custom {@code CelValueConverter} that maps the unwrapped native Java object back into its
+ * corresponding {@code OpaqueValue}.
+ *
+ */
+@Immutable
public abstract class OpaqueValue extends CelValue {
@Override
@@ -31,7 +50,30 @@ public boolean isZeroValue() {
@Override
public abstract OpaqueType celType();
+ /**
+ * Creates an {@code OpaqueValue} by wrapping a domain object.
+ *
+ * This method should only be used for the "Wrapping" extension mode (see class Javadoc) when
+ * users cannot modify their POJOs to directly extend {@code OpaqueValue}. Using this method
+ * necessitates implementing and registering a custom {@link CelValueConverter}.
+ *
+ * @param name The name of the opaque type.
+ * @param value The raw Java object to wrap.
+ */
public static OpaqueValue create(String name, Object value) {
- return new AutoValue_OpaqueValue(value, OpaqueType.create(name));
+ return new AutoValue_OpaqueValue_OpaqueValueWrapper(
+ checkNotNull(value), OpaqueType.create(name));
+ }
+
+ @AutoValue
+ @AutoValue.CopyAnnotations
+ @Immutable
+ @SuppressWarnings("Immutable")
+ abstract static class OpaqueValueWrapper extends OpaqueValue {
+ @Override
+ public abstract Object value();
+
+ @Override
+ public abstract OpaqueType celType();
}
}
diff --git a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java
index 14ff87f1b..a4280e1ea 100644
--- a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java
+++ b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java
@@ -26,6 +26,7 @@
import com.google.protobuf.Message;
import com.google.protobuf.MessageLiteOrBuilder;
import com.google.protobuf.MessageOrBuilder;
+import dev.cel.common.CelOptions;
import dev.cel.common.annotations.Internal;
import dev.cel.common.internal.CelDescriptorPool;
import dev.cel.common.internal.DynamicProto;
@@ -49,11 +50,12 @@
public final class ProtoCelValueConverter extends BaseProtoCelValueConverter {
private final CelDescriptorPool celDescriptorPool;
private final DynamicProto dynamicProto;
+ private final CelOptions celOptions;
/** Constructs a new instance of ProtoCelValueConverter. */
public static ProtoCelValueConverter newInstance(
- CelDescriptorPool celDescriptorPool, DynamicProto dynamicProto) {
- return new ProtoCelValueConverter(celDescriptorPool, dynamicProto);
+ CelDescriptorPool celDescriptorPool, DynamicProto dynamicProto, CelOptions celOptions) {
+ return new ProtoCelValueConverter(celDescriptorPool, dynamicProto, celOptions);
}
@Override
@@ -65,10 +67,13 @@ protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wel
try {
unpackedMessage = dynamicProto.unpack((Any) message);
} catch (InvalidProtocolBufferException e) {
- throw new IllegalStateException(
+ throw new IllegalArgumentException(
"Unpacking failed for message: " + message.getDescriptorForType().getFullName(), e);
}
return toRuntimeValue(unpackedMessage);
+ case FIELD_MASK:
+ return ProtoMessageValue.create(
+ (Message) message, celDescriptorPool, this, celOptions.enableJsonFieldNames());
default:
return super.fromWellKnownProto(message, wellKnownProto);
}
@@ -97,7 +102,8 @@ public Object toRuntimeValue(Object value) {
WellKnownProto wellKnownProto =
WellKnownProto.getByTypeName(message.getDescriptorForType().getFullName()).orElse(null);
if (wellKnownProto == null) {
- return ProtoMessageValue.create((Message) message, celDescriptorPool, this);
+ return ProtoMessageValue.create(
+ message, celDescriptorPool, this, celOptions.enableJsonFieldNames());
}
return fromWellKnownProto(message, wellKnownProto);
@@ -151,20 +157,43 @@ public Object fromProtoMessageFieldToCelValue(Message message, FieldDescriptor f
return toRuntimeValue(result);
case UINT32:
- return UnsignedLong.valueOf((int) result);
+ case FIXED32:
+ if (!fieldDescriptor.isRepeated()) {
+ return UnsignedLong.valueOf((int) result);
+ }
+ break;
case UINT64:
- return UnsignedLong.fromLongBits((long) result);
+ case FIXED64:
+ if (!fieldDescriptor.isRepeated()) {
+ return UnsignedLong.fromLongBits((long) result);
+ }
+ break;
default:
break;
}
+ if (fieldDescriptor.isRepeated()) {
+ switch (fieldDescriptor.getType()) {
+ case INT64:
+ case BOOL:
+ case STRING:
+ case DOUBLE:
+ return CelPreAdaptedList.wrap((List>) result);
+ default:
+ break;
+ }
+ }
+
return toRuntimeValue(result);
}
- private ProtoCelValueConverter(CelDescriptorPool celDescriptorPool, DynamicProto dynamicProto) {
+ private ProtoCelValueConverter(
+ CelDescriptorPool celDescriptorPool, DynamicProto dynamicProto, CelOptions celOptions) {
Preconditions.checkNotNull(celDescriptorPool);
Preconditions.checkNotNull(dynamicProto);
+ Preconditions.checkNotNull(celOptions);
this.celDescriptorPool = celDescriptorPool;
this.dynamicProto = dynamicProto;
+ this.celOptions = celOptions;
}
}
diff --git a/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java b/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java
index 3fbb0ad75..64d6ec1d4 100644
--- a/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java
+++ b/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java
@@ -28,6 +28,7 @@
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.ExtensionRegistryLite;
import com.google.protobuf.MessageLite;
+import com.google.protobuf.MessageLiteOrBuilder;
import com.google.protobuf.WireFormat;
import dev.cel.common.annotations.Internal;
import dev.cel.common.internal.CelLiteDescriptorPool;
@@ -178,12 +179,27 @@ public Object toRuntimeValue(Object value) {
return ProtoMessageLiteValue.create(msg, descriptor.getProtoTypeName(), this);
}
- return super.fromWellKnownProto(msg, wellKnownProto);
+ return fromWellKnownProto(msg, wellKnownProto);
}
return super.toRuntimeValue(value);
}
+ @Override
+ protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wellKnownProto) {
+ if (wellKnownProto == WellKnownProto.FIELD_MASK) {
+ MessageLite message = (MessageLite) msg;
+ MessageLiteDescriptor descriptor =
+ descriptorPool
+ .findDescriptor(message)
+ .orElseThrow(
+ () -> new NoSuchElementException("Could not find a descriptor for: " + message));
+ return ProtoMessageLiteValue.create(message, descriptor.getProtoTypeName(), this);
+ }
+
+ return super.fromWellKnownProto(msg, wellKnownProto);
+ }
+
private Object getDefaultValue(FieldLiteDescriptor fieldDescriptor) {
EncodingType encodingType = fieldDescriptor.getEncodingType();
switch (encodingType) {
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 3e809975e..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();
@@ -40,6 +40,8 @@ public abstract class ProtoMessageValue extends StructValue {
abstract ProtoCelValueConverter protoCelValueConverter();
+ abstract boolean enableJsonFieldNames();
+
@Override
public boolean isZeroValue() {
return value().getDefaultInstanceForType().equals(value());
@@ -75,7 +77,8 @@ public Optional find(String field) {
public static ProtoMessageValue create(
Message value,
CelDescriptorPool celDescriptorPool,
- ProtoCelValueConverter protoCelValueConverter) {
+ ProtoCelValueConverter protoCelValueConverter,
+ boolean enableJsonFieldNames) {
Preconditions.checkNotNull(value);
Preconditions.checkNotNull(celDescriptorPool);
Preconditions.checkNotNull(protoCelValueConverter);
@@ -83,11 +86,20 @@ public static ProtoMessageValue create(
value,
StructTypeReference.create(value.getDescriptorForType().getFullName()),
celDescriptorPool,
- protoCelValueConverter);
+ protoCelValueConverter,
+ enableJsonFieldNames);
}
private FieldDescriptor findField(
CelDescriptorPool celDescriptorPool, Descriptor descriptor, String fieldName) {
+ if (enableJsonFieldNames()) {
+ for (FieldDescriptor fd : descriptor.getFields()) {
+ if (fd.getJsonName().equals(fieldName)) {
+ return fd;
+ }
+ }
+ }
+
FieldDescriptor fieldDescriptor = descriptor.findFieldByName(fieldName);
if (fieldDescriptor != null) {
return fieldDescriptor;
diff --git a/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java b/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java
index a05658c8f..7beb40c61 100644
--- a/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java
+++ b/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java
@@ -38,6 +38,7 @@ public class ProtoMessageValueProvider implements CelValueProvider {
private final ProtoAdapter protoAdapter;
private final ProtoMessageFactory protoMessageFactory;
private final ProtoCelValueConverter protoCelValueConverter;
+ private final CelOptions celOptions;
@Override
public CelValueConverter celValueConverter() {
@@ -67,6 +68,14 @@ public Optional newValue(String structType, Map fields)
}
private FieldDescriptor findField(Descriptor descriptor, String fieldName) {
+ if (celOptions.enableJsonFieldNames()) {
+ for (FieldDescriptor fd : descriptor.getFields()) {
+ if (fd.getJsonName().equals(fieldName)) {
+ return fd;
+ }
+ }
+ }
+
FieldDescriptor fieldDescriptor = descriptor.findFieldByName(fieldName);
if (fieldDescriptor != null) {
return fieldDescriptor;
@@ -91,7 +100,9 @@ public static ProtoMessageValueProvider newInstance(
private ProtoMessageValueProvider(CelOptions celOptions, DynamicProto dynamicProto) {
this.protoMessageFactory = dynamicProto.getProtoMessageFactory();
this.protoCelValueConverter =
- ProtoCelValueConverter.newInstance(protoMessageFactory.getDescriptorPool(), dynamicProto);
+ ProtoCelValueConverter.newInstance(
+ protoMessageFactory.getDescriptorPool(), dynamicProto, celOptions);
this.protoAdapter = new ProtoAdapter(dynamicProto, celOptions);
+ this.celOptions = celOptions;
}
}
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/BUILD.bazel b/common/src/test/java/dev/cel/common/BUILD.bazel
index 8dcb60b92..98be87d17 100644
--- a/common/src/test/java/dev/cel/common/BUILD.bazel
+++ b/common/src/test/java/dev/cel/common/BUILD.bazel
@@ -1,9 +1,11 @@
load("@rules_java//java:defs.bzl", "java_library")
load("//:testing.bzl", "junit4_test_suites")
-package(default_applicable_licenses = [
- "//:license",
-])
+package(
+ default_applicable_licenses = [
+ "//:license",
+ ],
+)
java_library(
name = "tests",
@@ -25,6 +27,7 @@ java_library(
"//common:source_location",
"//common/ast",
"//common/internal",
+ "//common/internal:code_point_stream",
"//common/types",
"//common/types:cel_proto_types",
"//common/types:cel_v1alpha1_types",
diff --git a/common/src/test/java/dev/cel/common/ast/BUILD.bazel b/common/src/test/java/dev/cel/common/ast/BUILD.bazel
index 19726aa21..e4aac277b 100644
--- a/common/src/test/java/dev/cel/common/ast/BUILD.bazel
+++ b/common/src/test/java/dev/cel/common/ast/BUILD.bazel
@@ -1,9 +1,11 @@
load("@rules_java//java:defs.bzl", "java_library")
load("//:testing.bzl", "junit4_test_suites")
-package(default_applicable_licenses = [
- "//:license",
-])
+package(
+ default_applicable_licenses = [
+ "//:license",
+ ],
+)
java_library(
name = "tests",
diff --git a/common/src/test/java/dev/cel/common/internal/BUILD.bazel b/common/src/test/java/dev/cel/common/internal/BUILD.bazel
index 33127ce16..a70489fb4 100644
--- a/common/src/test/java/dev/cel/common/internal/BUILD.bazel
+++ b/common/src/test/java/dev/cel/common/internal/BUILD.bazel
@@ -1,9 +1,11 @@
load("@rules_java//java:defs.bzl", "java_library")
load("//:testing.bzl", "junit4_test_suites")
-package(default_applicable_licenses = [
- "//:license",
-])
+package(
+ default_applicable_licenses = [
+ "//:license",
+ ],
+)
java_library(
name = "tests",
diff --git a/common/src/test/java/dev/cel/common/internal/DynamicProtoTest.java b/common/src/test/java/dev/cel/common/internal/DynamicProtoTest.java
index cc5ba5632..7be994391 100644
--- a/common/src/test/java/dev/cel/common/internal/DynamicProtoTest.java
+++ b/common/src/test/java/dev/cel/common/internal/DynamicProtoTest.java
@@ -37,7 +37,7 @@
import dev.cel.common.CelDescriptorUtil;
import dev.cel.common.CelDescriptors;
import dev.cel.testing.testdata.MultiFile;
-import dev.cel.testing.testdata.SingleFileProto.SingleFile;
+import dev.cel.testing.testdata.SingleFile;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/common/src/test/java/dev/cel/common/internal/ProtoAdapterTest.java b/common/src/test/java/dev/cel/common/internal/ProtoAdapterTest.java
index 61c71e4a6..91e0e22db 100644
--- a/common/src/test/java/dev/cel/common/internal/ProtoAdapterTest.java
+++ b/common/src/test/java/dev/cel/common/internal/ProtoAdapterTest.java
@@ -150,10 +150,7 @@ public static List data() {
@Test
public void adaptValueToProto_bidirectionalConversion() {
DynamicProto dynamicProto = DynamicProto.create(DefaultMessageFactory.INSTANCE);
- ProtoAdapter protoAdapter =
- new ProtoAdapter(
- dynamicProto,
- CelOptions.current().build());
+ ProtoAdapter protoAdapter = new ProtoAdapter(dynamicProto, CelOptions.current().build());
assertThat(protoAdapter.adaptValueToProto(value, proto.getDescriptorForType().getFullName()))
.isEqualTo(proto);
assertThat(protoAdapter.adaptProtoToValue(proto)).isEqualTo(value);
@@ -181,6 +178,18 @@ public void adaptAnyValue_hermeticTypes_bidirectionalConversion() {
@RunWith(JUnit4.class)
public static class AsymmetricConversionTest {
+
+ @Test
+ public void unpackAny_celNullValue() throws Exception {
+ ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);
+ Any any =
+ (Any)
+ protoAdapter.adaptValueToProto(
+ dev.cel.common.values.NullValue.NULL_VALUE, "google.protobuf.Any");
+ Object unpacked = protoAdapter.adaptProtoToValue(any);
+ assertThat(unpacked).isEqualTo(dev.cel.common.values.NullValue.NULL_VALUE);
+ }
+
@Test
public void adaptValueToProto_asymmetricFloatConversion() {
ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);
diff --git a/common/src/test/java/dev/cel/common/navigation/BUILD.bazel b/common/src/test/java/dev/cel/common/navigation/BUILD.bazel
index 74ec3e080..f8b2b988b 100644
--- a/common/src/test/java/dev/cel/common/navigation/BUILD.bazel
+++ b/common/src/test/java/dev/cel/common/navigation/BUILD.bazel
@@ -1,7 +1,9 @@
load("@rules_java//java:defs.bzl", "java_library")
load("//:testing.bzl", "junit4_test_suites")
-package(default_applicable_licenses = ["//:license"])
+package(
+ default_applicable_licenses = ["//:license"],
+)
java_library(
name = "tests",
diff --git a/common/src/test/java/dev/cel/common/types/BUILD.bazel b/common/src/test/java/dev/cel/common/types/BUILD.bazel
index 0c8121bbd..64f555547 100644
--- a/common/src/test/java/dev/cel/common/types/BUILD.bazel
+++ b/common/src/test/java/dev/cel/common/types/BUILD.bazel
@@ -1,7 +1,9 @@
load("@rules_java//java:defs.bzl", "java_library")
load("//:testing.bzl", "junit4_test_suites")
-package(default_applicable_licenses = ["//:license"])
+package(
+ default_applicable_licenses = ["//:license"],
+)
java_library(
name = "tests",
diff --git a/common/src/test/java/dev/cel/common/types/ProtoMessageTypeProviderTest.java b/common/src/test/java/dev/cel/common/types/ProtoMessageTypeProviderTest.java
index 16797b714..c9f9d9e21 100644
--- a/common/src/test/java/dev/cel/common/types/ProtoMessageTypeProviderTest.java
+++ b/common/src/test/java/dev/cel/common/types/ProtoMessageTypeProviderTest.java
@@ -23,7 +23,7 @@
import dev.cel.common.types.StructType.Field;
import dev.cel.expr.conformance.proto2.TestAllTypes;
import dev.cel.expr.conformance.proto2.TestAllTypesExtensions;
-import dev.cel.testing.testdata.SingleFileProto.SingleFile;
+import dev.cel.testing.testdata.SingleFile;
import java.util.Optional;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -269,8 +269,8 @@ public void findField_withJsonNameOption() {
(ProtoMessageType) typeProvider.findType(SingleFile.getDescriptor().getFullName()).get();
// Note that these are the same fields, with json_name option set
- Optional snakeCasedField = msgType.findField("snake_cased");
- Optional jsonNameField = msgType.findField("camelCased");
+ Optional snakeCasedField = msgType.findField("int64_camel_case_json_name");
+ Optional jsonNameField = msgType.findField("int64CamelCaseJsonName");
assertThat(snakeCasedField).isEmpty();
assertThat(jsonNameField).isPresent();
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 ab7eae8dd..76c761567 100644
--- a/common/src/test/java/dev/cel/common/values/BUILD.bazel
+++ b/common/src/test/java/dev/cel/common/values/BUILD.bazel
@@ -1,7 +1,9 @@
load("@rules_java//java:defs.bzl", "java_library")
load("//:testing.bzl", "junit4_test_suites")
-package(default_applicable_licenses = ["//:license"])
+package(
+ default_applicable_licenses = ["//:license"],
+)
java_library(
name = "tests",
@@ -24,6 +26,7 @@ java_library(
"//common/values",
"//common/values:cel_byte_string",
"//common/values:cel_value_provider",
+ "//common/values:combined_cel_value_converter",
"//common/values:combined_cel_value_provider",
"//common/values:proto_message_lite_value",
"//common/values:proto_message_lite_value_provider",
@@ -32,6 +35,7 @@ java_library(
"//testing/protos:test_all_types_cel_java_proto3",
"@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto",
"@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto",
+ "@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:com_google_guava_guava_testlib",
"@maven//:com_google_protobuf_protobuf_java",
diff --git a/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java b/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java
index 308d7b510..ccb8e605f 100644
--- a/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java
+++ b/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java
@@ -37,7 +37,8 @@ public void toRuntimeValue_optionalValue() {
@Test
@SuppressWarnings("unchecked") // Test only
public void unwrap_optionalValue() {
- Optional result = (Optional) CEL_VALUE_CONVERTER.unwrap(OptionalValue.create(2L));
+ Optional result =
+ (Optional) CEL_VALUE_CONVERTER.maybeUnwrap(OptionalValue.create(2L));
assertThat(result).isEqualTo(Optional.of(2L));
}
@@ -45,7 +46,7 @@ public void unwrap_optionalValue() {
@Test
@SuppressWarnings("unchecked") // Test only
public void unwrap_emptyOptionalValue() {
- Optional result = (Optional) CEL_VALUE_CONVERTER.unwrap(OptionalValue.EMPTY);
+ Optional result = (Optional) CEL_VALUE_CONVERTER.maybeUnwrap(OptionalValue.EMPTY);
assertThat(result).isEqualTo(Optional.empty());
}
diff --git a/common/src/test/java/dev/cel/common/values/CombinedCelValueConverterTest.java b/common/src/test/java/dev/cel/common/values/CombinedCelValueConverterTest.java
new file mode 100644
index 000000000..8574587bc
--- /dev/null
+++ b/common/src/test/java/dev/cel/common/values/CombinedCelValueConverterTest.java
@@ -0,0 +1,112 @@
+// Copyright 2026 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.common.values;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Map;
+import java.util.Optional;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class CombinedCelValueConverterTest {
+
+ @Test
+ public void toRuntimeValue_delegatesToUnderlyingConverters() {
+ CustomConverter converter1 = new CustomConverter("target1", "replacement1");
+ CustomConverter converter2 = new CustomConverter("target2", "replacement2");
+ CelValueConverter combined =
+ CombinedCelValueConverter.combine(ImmutableList.of(converter1, converter2));
+
+ assertThat(combined.toRuntimeValue("target1")).isEqualTo("replacement1");
+ assertThat(combined.toRuntimeValue("target2")).isEqualTo("replacement2");
+ assertThat(combined.toRuntimeValue("unhandled")).isEqualTo("unhandled");
+ }
+
+ @Test
+ public void maybeUnwrap_delegatesToUnderlyingConverters() {
+ CustomConverter converter1 = new CustomConverter("target1", "replacement1");
+ CustomConverter converter2 = new CustomConverter("target2", "replacement2");
+ CelValueConverter combined =
+ CombinedCelValueConverter.combine(ImmutableList.of(converter1, converter2));
+
+ assertThat(combined.maybeUnwrap("replacement1")).isEqualTo("target1");
+ assertThat(combined.maybeUnwrap("replacement2")).isEqualTo("target2");
+ assertThat(combined.maybeUnwrap("unhandled")).isEqualTo("unhandled");
+ }
+
+ @Test
+ public void combinedCelValueProvider_returnsCombinedConverter() {
+ CustomConverter converter1 = new CustomConverter("target1", "replacement1");
+ CustomConverter converter2 = new CustomConverter("target2", "replacement2");
+ CustomProvider provider1 = new CustomProvider(converter1);
+ CustomProvider provider2 = new CustomProvider(converter2);
+
+ CombinedCelValueProvider combinedProvider =
+ CombinedCelValueProvider.combine(provider1, provider2);
+ CelValueConverter combinedConverter = combinedProvider.celValueConverter();
+
+ assertThat(combinedConverter).isInstanceOf(CombinedCelValueConverter.class);
+ assertThat(combinedConverter.toRuntimeValue("target1")).isEqualTo("replacement1");
+ assertThat(combinedConverter.toRuntimeValue("target2")).isEqualTo("replacement2");
+ }
+
+ private static class CustomConverter extends CelValueConverter {
+ private final String target;
+ private final String replacement;
+
+ private CustomConverter(String target, String replacement) {
+ this.target = target;
+ this.replacement = replacement;
+ }
+
+ @Override
+ public Object toRuntimeValue(Object value) {
+ if (value.equals(target)) {
+ return replacement;
+ }
+ return value;
+ }
+
+ @Override
+ public Object maybeUnwrap(Object value) {
+ if (value.equals(replacement)) {
+ return target;
+ }
+ return value;
+ }
+ }
+
+ private static class CustomProvider implements CelValueProvider {
+ private final CelValueConverter converter;
+
+ private CustomProvider(CelValueConverter converter) {
+ this.converter = converter;
+ }
+
+ @Override
+ public Optional newValue(String structType, Map fields) {
+ return Optional.empty();
+ }
+
+ @Override
+ public CelValueConverter celValueConverter() {
+ return converter;
+ }
+ }
+}
diff --git a/common/src/test/java/dev/cel/common/values/OpaqueValueTest.java b/common/src/test/java/dev/cel/common/values/OpaqueValueTest.java
index d97bcd28a..326572842 100644
--- a/common/src/test/java/dev/cel/common/values/OpaqueValueTest.java
+++ b/common/src/test/java/dev/cel/common/values/OpaqueValueTest.java
@@ -17,7 +17,17 @@
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.errorprone.annotations.Immutable;
+import dev.cel.bundle.Cel;
+import dev.cel.bundle.CelFactory;
+import dev.cel.common.CelAbstractSyntaxTree;
+import dev.cel.common.types.CelType;
+import dev.cel.common.types.CelTypeProvider;
import dev.cel.common.types.OpaqueType;
+import java.util.Map;
+import java.util.Optional;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -37,4 +47,177 @@ public void opaqueValue_construct() {
public void create_nullValue_throws() {
assertThrows(NullPointerException.class, () -> OpaqueValue.create("opaque_type_name", null));
}
+
+ private static final OpaqueType CUSTOM_OPAQUE_TYPE = OpaqueType.create("custom_opaque_type");
+
+ private static final CelTypeProvider CUSTOM_OPAQUE_TYPE_PROVIDER =
+ new CelTypeProvider() {
+ @Override
+ public ImmutableList types() {
+ return ImmutableList.of(CUSTOM_OPAQUE_TYPE);
+ }
+
+ @Override
+ public Optional findType(String typeName) {
+ return typeName.equals(CUSTOM_OPAQUE_TYPE.name())
+ ? Optional.of(CUSTOM_OPAQUE_TYPE)
+ : Optional.empty();
+ }
+ };
+
+ private static final CelValueProvider CUSTOM_OPAQUE_VALUE_PROVIDER =
+ new CelValueProvider() {
+ @Override
+ public Optional newValue(String structType, Map fields) {
+ return Optional.empty();
+ }
+
+ @Override
+ public CelValueConverter celValueConverter() {
+ return new CelValueConverter() {
+ @Override
+ public Object toRuntimeValue(Object value) {
+ if (value instanceof CustomOpaqueObject) {
+ CustomOpaqueObject customOpaqueObject = (CustomOpaqueObject) value;
+ return new CelCustomOpaqueValue(customOpaqueObject);
+ }
+ return super.toRuntimeValue(value);
+ }
+ };
+ }
+ };
+
+ private static final CelValueProvider WRAPPED_CUSTOM_OPAQUE_VALUE_PROVIDER =
+ new CelValueProvider() {
+ @Override
+ public Optional newValue(String structType, Map fields) {
+ return Optional.empty();
+ }
+
+ @Override
+ public CelValueConverter celValueConverter() {
+ return new CelValueConverter() {
+ @Override
+ public Object toRuntimeValue(Object value) {
+ if (value instanceof CustomOpaqueObject) {
+ CustomOpaqueObject customOpaqueObject = (CustomOpaqueObject) value;
+ return OpaqueValue.create(CUSTOM_OPAQUE_TYPE.name(), customOpaqueObject);
+ }
+ return super.toRuntimeValue(value);
+ }
+ };
+ }
+ };
+
+ @Immutable
+ private static class CustomOpaqueObject {
+ private final String value;
+
+ CustomOpaqueObject(String value) {
+ this.value = value;
+ }
+
+ String getValue() {
+ return value;
+ }
+ }
+
+ @Immutable
+ private static class CelCustomOpaqueValue extends OpaqueValue {
+ private final CustomOpaqueObject obj;
+
+ CelCustomOpaqueValue(CustomOpaqueObject obj) {
+ this.obj = obj;
+ }
+
+ @Override
+ public CustomOpaqueObject value() {
+ return obj;
+ }
+
+ @Override
+ public OpaqueType celType() {
+ return CUSTOM_OPAQUE_TYPE;
+ }
+ }
+
+ @Test
+ public void evaluate_customOpaqueValue_asVariable() throws Exception {
+ Cel cel =
+ CelFactory.plannerCelBuilder()
+ .addVar("opaque_var", CUSTOM_OPAQUE_TYPE)
+ .setTypeProvider(CUSTOM_OPAQUE_TYPE_PROVIDER)
+ .setValueProvider(CUSTOM_OPAQUE_VALUE_PROVIDER)
+ .build();
+ CelAbstractSyntaxTree ast = cel.compile("opaque_var").getAst();
+
+ CustomOpaqueObject rawValue = new CustomOpaqueObject("hello");
+ Object result = cel.createProgram(ast).eval(ImmutableMap.of("opaque_var", rawValue));
+
+ assertThat(result).isInstanceOf(CustomOpaqueObject.class);
+ assertThat(((CustomOpaqueObject) result).getValue()).isEqualTo("hello");
+ }
+
+ @Test
+ public void evaluate_typeOfCustomOpaqueValue() throws Exception {
+ Cel cel =
+ CelFactory.plannerCelBuilder()
+ .addVar("opaque_var", CUSTOM_OPAQUE_TYPE)
+ .setTypeProvider(CUSTOM_OPAQUE_TYPE_PROVIDER)
+ .setValueProvider(CUSTOM_OPAQUE_VALUE_PROVIDER)
+ .build();
+ CelAbstractSyntaxTree ast = cel.compile("type(opaque_var) == custom_opaque_type").getAst();
+
+ CustomOpaqueObject rawValue = new CustomOpaqueObject("hello");
+ Object result = cel.createProgram(ast).eval(ImmutableMap.of("opaque_var", rawValue));
+
+ assertThat(result).isEqualTo(true);
+ }
+
+ @Test
+ public void evaluate_typeOfCustomOpaqueValue_wrapped() throws Exception {
+ Cel cel =
+ CelFactory.plannerCelBuilder()
+ .addVar("opaque_var", CUSTOM_OPAQUE_TYPE)
+ .setTypeProvider(CUSTOM_OPAQUE_TYPE_PROVIDER)
+ .setValueProvider(WRAPPED_CUSTOM_OPAQUE_VALUE_PROVIDER)
+ .build();
+ CelAbstractSyntaxTree ast = cel.compile("type(opaque_var) == custom_opaque_type").getAst();
+
+ CustomOpaqueObject rawValue = new CustomOpaqueObject("hello");
+ Object result = cel.createProgram(ast).eval(ImmutableMap.of("opaque_var", rawValue));
+
+ assertThat(result).isEqualTo(true);
+ }
+
+ @Immutable
+ private static class SelfReturningOpaqueObject extends OpaqueValue {
+ SelfReturningOpaqueObject() {}
+
+ @Override
+ public Object value() {
+ return this;
+ }
+
+ @Override
+ public OpaqueType celType() {
+ return CUSTOM_OPAQUE_TYPE;
+ }
+ }
+
+ @Test
+ public void evaluate_selfReturningOpaqueValue_noConverter() throws Exception {
+ Cel cel =
+ CelFactory.plannerCelBuilder()
+ .addVar("opaque_var", CUSTOM_OPAQUE_TYPE)
+ .setTypeProvider(CUSTOM_OPAQUE_TYPE_PROVIDER)
+ .build();
+ CelAbstractSyntaxTree ast = cel.compile("type(opaque_var) == custom_opaque_type").getAst();
+
+ SelfReturningOpaqueObject rawValue = new SelfReturningOpaqueObject();
+ Object result = cel.createProgram(ast).eval(ImmutableMap.of("opaque_var", rawValue));
+
+ assertThat(result).isEqualTo(true);
+ }
}
+
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/ProtoCelValueConverterTest.java b/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java
index 61a05cf89..17c012db7 100644
--- a/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java
+++ b/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java
@@ -16,6 +16,7 @@
import static com.google.common.truth.Truth.assertThat;
+import dev.cel.common.CelOptions;
import dev.cel.common.internal.DefaultDescriptorPool;
import dev.cel.common.internal.DefaultMessageFactory;
import dev.cel.common.internal.DynamicProto;
@@ -28,11 +29,13 @@ public class ProtoCelValueConverterTest {
private static final ProtoCelValueConverter PROTO_CEL_VALUE_CONVERTER =
ProtoCelValueConverter.newInstance(
- DefaultDescriptorPool.INSTANCE, DynamicProto.create(DefaultMessageFactory.INSTANCE));
+ DefaultDescriptorPool.INSTANCE,
+ DynamicProto.create(DefaultMessageFactory.INSTANCE),
+ CelOptions.DEFAULT);
@Test
public void unwrap_nullValue() {
- NullValue nullValue = (NullValue) PROTO_CEL_VALUE_CONVERTER.unwrap(NullValue.NULL_VALUE);
+ NullValue nullValue = (NullValue) PROTO_CEL_VALUE_CONVERTER.maybeUnwrap(NullValue.NULL_VALUE);
// Note: No conversion is attempted. We're using dev.cel.common.values.NullValue.NULL_VALUE as
// the
diff --git a/common/src/test/java/dev/cel/common/values/ProtoLiteCelValueConverterTest.java b/common/src/test/java/dev/cel/common/values/ProtoLiteCelValueConverterTest.java
index cec3e0fbf..3b66171e4 100644
--- a/common/src/test/java/dev/cel/common/values/ProtoLiteCelValueConverterTest.java
+++ b/common/src/test/java/dev/cel/common/values/ProtoLiteCelValueConverterTest.java
@@ -27,6 +27,7 @@
import com.google.protobuf.DoubleValue;
import com.google.protobuf.Duration;
import com.google.protobuf.ExtensionRegistryLite;
+import com.google.protobuf.FieldMask;
import com.google.protobuf.FloatValue;
import com.google.protobuf.Int32Value;
import com.google.protobuf.Int64Value;
@@ -104,6 +105,17 @@ public void fromProtoMessageToCelValue_withWellKnownProto_convertsToPrimitivesFr
assertThat(adaptedValue).isEqualTo(testCase.value);
}
+ @Test
+ public void fromProtoMessageToCelValue_fieldMask_returnsProtoMessageLiteValue() {
+ FieldMask fieldMask = FieldMask.newBuilder().addPaths("foo").addPaths("bar").build();
+
+ Object adaptedValue = PROTO_LITE_CEL_VALUE_CONVERTER.toRuntimeValue(fieldMask);
+
+ assertThat(adaptedValue).isInstanceOf(ProtoMessageLiteValue.class);
+ assertThat(((ProtoMessageLiteValue) adaptedValue).select("paths"))
+ .isEqualTo(ImmutableList.of("foo", "bar"));
+ }
+
/** Test cases for repeated_int64: 1L,2L,3L */
@SuppressWarnings("ImmutableEnumChecker") // Test only
private enum RepeatedFieldBytesTestCase {
diff --git a/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java b/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java
index ad61f0c4b..b5c29129b 100644
--- a/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java
+++ b/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java
@@ -23,6 +23,7 @@
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import com.google.protobuf.DynamicMessage;
+import com.google.protobuf.FieldMask;
import com.google.protobuf.FloatValue;
import com.google.protobuf.Int32Value;
import com.google.protobuf.Int64Value;
@@ -35,6 +36,7 @@
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import com.google.testing.junit.testparameterinjector.TestParameters;
import dev.cel.common.CelDescriptorUtil;
+import dev.cel.common.CelOptions;
import dev.cel.common.internal.CelDescriptorPool;
import dev.cel.common.internal.DefaultDescriptorPool;
import dev.cel.common.internal.DefaultMessageFactory;
@@ -54,7 +56,9 @@ public final class ProtoMessageValueTest {
private static final ProtoCelValueConverter PROTO_CEL_VALUE_CONVERTER =
ProtoCelValueConverter.newInstance(
- DefaultDescriptorPool.INSTANCE, DynamicProto.create(DefaultMessageFactory.INSTANCE));
+ DefaultDescriptorPool.INSTANCE,
+ DynamicProto.create(DefaultMessageFactory.INSTANCE),
+ CelOptions.DEFAULT);
@Test
public void emptyProtoMessage() {
@@ -62,7 +66,8 @@ public void emptyProtoMessage() {
ProtoMessageValue.create(
TestAllTypes.getDefaultInstance(),
DefaultDescriptorPool.INSTANCE,
- PROTO_CEL_VALUE_CONVERTER);
+ PROTO_CEL_VALUE_CONVERTER,
+ /* enableJsonFieldNames= */ false);
assertThat(protoMessageValue.value()).isEqualTo(TestAllTypes.getDefaultInstance());
assertThat(protoMessageValue.isZeroValue()).isTrue();
@@ -74,7 +79,7 @@ public void constructProtoMessage() {
TestAllTypes.newBuilder().setSingleBool(true).setSingleInt64(5L).build();
ProtoMessageValue protoMessageValue =
ProtoMessageValue.create(
- testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER);
+ testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false);
assertThat(protoMessageValue.value()).isEqualTo(testAllTypes);
assertThat(protoMessageValue.isZeroValue()).isFalse();
@@ -90,7 +95,7 @@ public void findField_fieldIsSet_fieldExists() {
.build();
ProtoMessageValue protoMessageValue =
ProtoMessageValue.create(
- testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER);
+ testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false);
assertThat(protoMessageValue.find("single_bool")).isPresent();
assertThat(protoMessageValue.find("single_int64")).isPresent();
@@ -103,7 +108,8 @@ public void findField_fieldIsUnset_fieldDoesNotExist() {
ProtoMessageValue.create(
TestAllTypes.getDefaultInstance(),
DefaultDescriptorPool.INSTANCE,
- PROTO_CEL_VALUE_CONVERTER);
+ PROTO_CEL_VALUE_CONVERTER,
+ /* enableJsonFieldNames= */ false);
assertThat(protoMessageValue.find("single_int32")).isEmpty();
assertThat(protoMessageValue.find("single_uint64")).isEmpty();
@@ -116,7 +122,8 @@ public void findField_fieldIsUndeclared_throwsException() {
ProtoMessageValue.create(
TestAllTypes.getDefaultInstance(),
DefaultDescriptorPool.INSTANCE,
- PROTO_CEL_VALUE_CONVERTER);
+ PROTO_CEL_VALUE_CONVERTER,
+ /* enableJsonFieldNames= */ false);
IllegalArgumentException exception =
assertThrows(IllegalArgumentException.class, () -> protoMessageValue.select("bogus"));
@@ -137,7 +144,7 @@ public void findField_extensionField_success() {
TestAllTypes.newBuilder().setExtension(TestAllTypesExtensions.int32Ext, 1).build();
ProtoMessageValue protoMessageValue =
- ProtoMessageValue.create(proto2Message, descriptorPool, PROTO_CEL_VALUE_CONVERTER);
+ ProtoMessageValue.create(proto2Message, descriptorPool, PROTO_CEL_VALUE_CONVERTER, false);
assertThat(protoMessageValue.find("cel.expr.conformance.proto2.int32_ext")).isPresent();
}
@@ -149,7 +156,7 @@ public void findField_extensionField_throwsWhenDescriptorMissing() {
ProtoMessageValue protoMessageValue =
ProtoMessageValue.create(
- proto2Message, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER);
+ proto2Message, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false);
IllegalArgumentException exception =
assertThrows(
@@ -193,7 +200,8 @@ private enum SelectFieldTestCase {
ProtoMessageValue.create(
NestedMessage.getDefaultInstance(),
DefaultDescriptorPool.INSTANCE,
- PROTO_CEL_VALUE_CONVERTER)),
+ PROTO_CEL_VALUE_CONVERTER,
+ /* enableJsonFieldNames= */ false)),
NESTED_ENUM("standalone_enum", 1L);
private final String fieldName;
@@ -239,7 +247,7 @@ public void selectField_success(@TestParameter SelectFieldTestCase testCase) {
ProtoMessageValue protoMessageValue =
ProtoMessageValue.create(
- testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER);
+ testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false);
assertThat(protoMessageValue.select(testCase.fieldName)).isEqualTo(testCase.value);
}
@@ -253,7 +261,8 @@ public void selectField_dynamicMessage_success() {
ProtoMessageValue.create(
DynamicMessage.newBuilder(testAllTypes).build(),
DefaultDescriptorPool.INSTANCE,
- PROTO_CEL_VALUE_CONVERTER);
+ PROTO_CEL_VALUE_CONVERTER,
+ /* enableJsonFieldNames= */ false);
assertThat(protoMessageValue.select("single_int32_wrapper")).isEqualTo(5);
}
@@ -269,7 +278,7 @@ public void selectField_timestampNanosOutOfRange_success(int nanos) {
ProtoMessageValue protoMessageValue =
ProtoMessageValue.create(
- testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER);
+ testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false);
assertThat(protoMessageValue.select("single_timestamp"))
.isEqualTo(Instant.ofEpochSecond(0, nanos));
@@ -289,12 +298,52 @@ public void selectField_durationOutOfRange_success(int seconds, int nanos) {
ProtoMessageValue protoMessageValue =
ProtoMessageValue.create(
- testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER);
+ testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false);
assertThat(protoMessageValue.select("single_duration"))
.isEqualTo(Duration.ofSeconds(seconds, nanos));
}
+ @Test
+ public void selectField_fieldMask_returnsProtoMessageValue() {
+ TestAllTypes testAllTypes =
+ TestAllTypes.newBuilder()
+ .setFieldMask(FieldMask.newBuilder().addPaths("foo").addPaths("bar"))
+ .build();
+
+ ProtoMessageValue protoMessageValue =
+ ProtoMessageValue.create(
+ testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false);
+
+ Object selected = protoMessageValue.select("field_mask");
+ assertThat(selected).isInstanceOf(ProtoMessageValue.class);
+ assertThat(((ProtoMessageValue) selected).select("paths"))
+ .isEqualTo(ImmutableList.of("foo", "bar"));
+ }
+
+ @Test
+ public void selectField_fixed32_returnsUnsignedLong() {
+ TestAllTypes testAllTypes = TestAllTypes.newBuilder().setSingleFixed32(1).build();
+
+ ProtoMessageValue protoMessageValue =
+ ProtoMessageValue.create(
+ testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false);
+
+ assertThat(protoMessageValue.select("single_fixed32")).isEqualTo(UnsignedLong.valueOf(1L));
+ }
+
+ @Test
+ public void selectField_fixed64_returnsUnsignedLong() {
+ TestAllTypes testAllTypes =
+ TestAllTypes.newBuilder().setSingleFixed64(UnsignedLong.MAX_VALUE.longValue()).build();
+
+ ProtoMessageValue protoMessageValue =
+ ProtoMessageValue.create(
+ testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false);
+
+ assertThat(protoMessageValue.select("single_fixed64")).isEqualTo(UnsignedLong.MAX_VALUE);
+ }
+
@SuppressWarnings("ImmutableEnumChecker") // Test only
private enum SelectFieldJsonValueTestCase {
NULL(Value.newBuilder().build(), NullValue.NULL_VALUE),
@@ -334,7 +383,7 @@ public void selectField_jsonValue(@TestParameter SelectFieldJsonValueTestCase te
ProtoMessageValue protoMessageValue =
ProtoMessageValue.create(
- testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER);
+ testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false);
assertThat(protoMessageValue.select("single_value")).isEqualTo(testCase.value);
}
@@ -351,7 +400,7 @@ public void selectField_jsonStruct() {
ProtoMessageValue protoMessageValue =
ProtoMessageValue.create(
- testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER);
+ testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false);
assertThat(protoMessageValue.select("single_struct")).isEqualTo(ImmutableMap.of("a", false));
}
@@ -368,7 +417,7 @@ public void selectField_jsonList() {
ProtoMessageValue protoMessageValue =
ProtoMessageValue.create(
- testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER);
+ testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false);
assertThat(protoMessageValue.select("list_value")).isEqualTo(ImmutableList.of(false));
}
@@ -379,7 +428,8 @@ public void selectField_wrapperFieldUnset_returnsNull() {
ProtoMessageValue.create(
TestAllTypes.getDefaultInstance(),
DefaultDescriptorPool.INSTANCE,
- PROTO_CEL_VALUE_CONVERTER);
+ PROTO_CEL_VALUE_CONVERTER,
+ /* enableJsonFieldNames= */ false);
assertThat(protoMessageValue.select("single_int64_wrapper")).isEqualTo(NullValue.NULL_VALUE);
}
@@ -390,9 +440,21 @@ public void celTypeTest() {
ProtoMessageValue.create(
TestAllTypes.getDefaultInstance(),
DefaultDescriptorPool.INSTANCE,
- PROTO_CEL_VALUE_CONVERTER);
+ PROTO_CEL_VALUE_CONVERTER,
+ /* enableJsonFieldNames= */ false);
assertThat(protoMessageValue.celType())
.isEqualTo(StructTypeReference.create(TestAllTypes.getDescriptor().getFullName()));
}
+
+ @Test
+ public void findField_jsonName_success() {
+ TestAllTypes testAllTypes = TestAllTypes.newBuilder().setSingleInt32(42).build();
+
+ ProtoMessageValue protoMessageValue =
+ ProtoMessageValue.create(
+ testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, true);
+
+ assertThat(protoMessageValue.find("singleInt32")).isPresent();
+ }
}
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..978222869 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();
}
@@ -112,44 +128,60 @@ public void celTypeTest() {
assertThat(value.celType()).isEqualTo(CUSTOM_STRUCT_TYPE);
}
+ @Test
+ public void evaluate_typeOfCustomStruct() throws Exception {
+ Cel cel =
+ 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("type(a) == custom_struct").getAst();
+
+ Object result = cel.createProgram(ast).eval(ImmutableMap.of("a", new CelCustomStructValue(20)));
+
+ assertThat(result).isEqualTo(true);
+ }
+
@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 +195,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 +210,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 +229,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 +270,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 +281,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/common/values/BUILD.bazel b/common/values/BUILD.bazel
index f1fa107b6..9853289a9 100644
--- a/common/values/BUILD.bazel
+++ b/common/values/BUILD.bazel
@@ -37,6 +37,18 @@ cel_android_library(
exports = ["//common/src/main/java/dev/cel/common/values:combined_cel_value_provider_android"],
)
+java_library(
+ name = "combined_cel_value_converter",
+ visibility = ["//:internal"],
+ exports = ["//common/src/main/java/dev/cel/common/values:combined_cel_value_converter"],
+)
+
+cel_android_library(
+ name = "combined_cel_value_converter_android",
+ visibility = ["//:internal"],
+ exports = ["//common/src/main/java/dev/cel/common/values:combined_cel_value_converter_android"],
+)
+
java_library(
name = "values",
exports = ["//common/src/main/java/dev/cel/common/values"],
@@ -47,6 +59,18 @@ cel_android_library(
exports = ["//common/src/main/java/dev/cel/common/values:values_android"],
)
+java_library(
+ name = "mutable_map_value",
+ visibility = ["//:internal"],
+ exports = ["//common/src/main/java/dev/cel/common/values:mutable_map_value"],
+)
+
+cel_android_library(
+ name = "mutable_map_value_android",
+ visibility = ["//:internal"],
+ exports = ["//common/src/main/java/dev/cel/common/values:mutable_map_value_android"],
+)
+
java_library(
name = "base_proto_cel_value_converter",
exports = ["//common/src/main/java/dev/cel/common/values:base_proto_cel_value_converter"],
diff --git a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel
index 248bc7fe2..e9ed58642 100644
--- a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel
+++ b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel
@@ -29,6 +29,7 @@ java_library(
"//parser:parser_builder",
"//parser:parser_factory",
"//runtime",
+ "//runtime:runtime_planner_impl",
"//testing:expr_value_utils",
"@cel_spec//proto/cel/expr:expr_java_proto",
"@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto",
@@ -41,7 +42,13 @@ java_library(
],
)
-MAVEN_JAR_DEPS = ["@maven_conformance//:dev_cel_cel"]
+MAVEN_JAR_DEPS = [
+ "@maven_conformance//:dev_cel_compiler",
+ "@maven_conformance//:dev_cel_common",
+ "@maven_conformance//:dev_cel_runtime",
+ "@maven_conformance//:dev_cel_protobuf",
+ "@maven_conformance//:dev_cel_cel",
+]
java_library(
name = "run_maven_jar",
@@ -51,6 +58,8 @@ java_library(
deps = MAVEN_JAR_DEPS + [
"//:java_truth",
"//compiler:compiler_builder",
+ "//parser:parser_factory",
+ "//runtime:runtime_planner_impl",
"//testing:expr_value_utils",
"@cel_spec//proto/cel/expr:expr_java_proto",
"@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto",
@@ -94,14 +103,8 @@ _ALL_TESTS = [
"@cel_spec//tests/simple:testdata/wrappers.textproto",
]
-_TESTS_TO_SKIP = [
- # Tests which require spec changes.
- # TODO: Deprecate Duration.get_milliseconds
- "timestamps/duration_converters/get_milliseconds",
-
+_TESTS_TO_SKIP_LEGACY = [
# Broken test cases which should be supported.
- # TODO: Invalid bytes to string conversion should error.
- "conversions/string/bytes_invalid",
# TODO: Support setting / getting enum values out of the defined enum value range.
"enums/legacy_proto2/select_big,select_neg",
"enums/legacy_proto2/assign_standalone_int_big,assign_standalone_int_neg",
@@ -110,26 +113,16 @@ _TESTS_TO_SKIP = [
# TODO: Ensure overflow occurs on conversions of double values which might not work properly on all platforms.
"conversions/int/double_int_min_range",
# TODO: Duration and timestamp operations should error on overflow.
- "timestamps/duration_range/from_string_under,from_string_over",
"timestamps/timestamp_range/sub_time_duration_over,sub_time_duration_under",
# TODO: Ensure adding negative duration values is appropriately supported.
"timestamps/timestamp_arithmetic/add_time_to_duration_nanos_negative",
# Skip until fixed.
"fields/qualified_identifier_resolution/map_value_repeat_key_heterogeneous",
- # TODO: Add strings.format and strings.quote.
- "string_ext/quote",
+ # TODO: Add strings.format.quote.
"string_ext/format",
"string_ext/format_errors",
- # TODO: Fix null assignment to a field
- "proto2/set_null/single_message",
- "proto2/set_null/single_duration",
- "proto2/set_null/single_timestamp",
- "proto3/set_null/single_message",
- "proto3/set_null/single_duration",
- "proto3/set_null/single_timestamp",
-
# Future features for CEL 1.0
# TODO: Strong typing support for enums, specified but not implemented.
"enums/strong_proto2",
@@ -153,17 +146,53 @@ _TESTS_TO_SKIP = [
"type_deductions/legacy_nullable_types/null_assignable_to_timestamp_parameter_candidate",
]
+_TESTS_TO_SKIP_PLANNER = [
+ # TODO: Add strings.format.
+ "string_ext/format",
+ "string_ext/format_errors",
+
+ # TODO: Check behavior for go/cpp
+ "basic/functions/unbound_is_runtime_error",
+
+ # TODO: Ensure overflow occurs on conversions of double values which might not work properly on all platforms.
+ "conversions/int/double_int_min_range",
+ "enums/legacy_proto3/assign_standalone_int_too_big",
+ "enums/legacy_proto3/assign_standalone_int_too_neg",
+
+ # TODO: Duration and timestamp operations should error on overflow.
+ "timestamps/timestamp_range/sub_time_duration_over",
+ "timestamps/timestamp_range/sub_time_duration_under",
+
+ # Skip until fixed.
+ "parse/receiver_function_names",
+
+ # Type inference edgecases around null(able) assignability.
+ # These type check, but resolve to a different type.
+ # list(int), want list(wrapper(int))
+ "type_deductions/wrappers/wrapper_promotion",
+ # list(null), want list(Message)
+ "type_deductions/legacy_nullable_types/null_assignable_to_message_parameter_candidate",
+ "type_deductions/legacy_nullable_types/null_assignable_to_abstract_parameter_candidate",
+ "type_deductions/legacy_nullable_types/null_assignable_to_duration_parameter_candidate",
+ "type_deductions/legacy_nullable_types/null_assignable_to_timestamp_parameter_candidate",
+
+ # Future features for CEL 1.0
+ # TODO: Strong typing support for enums, specified but not implemented.
+ "enums/strong_proto2",
+ "enums/strong_proto3",
+]
+
conformance_test(
name = "conformance",
data = _ALL_TESTS,
- skip_tests = _TESTS_TO_SKIP,
+ skip_tests = _TESTS_TO_SKIP_LEGACY,
)
conformance_test(
name = "conformance_maven",
data = _ALL_TESTS,
mode = MODE.MAVEN_TEST,
- skip_tests = _TESTS_TO_SKIP,
+ skip_tests = _TESTS_TO_SKIP_LEGACY,
)
conformance_test(
@@ -171,3 +200,10 @@ conformance_test(
data = _ALL_TESTS,
mode = MODE.DASHBOARD,
)
+
+conformance_test(
+ name = "conformance_planner",
+ data = _ALL_TESTS,
+ skip_tests = _TESTS_TO_SKIP_PLANNER,
+ use_planner = True,
+)
diff --git a/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java b/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java
index dcd226fa5..db57ccb79 100644
--- a/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java
+++ b/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java
@@ -16,8 +16,6 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
-import static dev.cel.testing.utils.ExprValueUtils.DEFAULT_EXTENSION_REGISTRY;
-import static dev.cel.testing.utils.ExprValueUtils.DEFAULT_TYPE_REGISTRY;
import static dev.cel.testing.utils.ExprValueUtils.fromValue;
import static dev.cel.testing.utils.ExprValueUtils.toExprValue;
@@ -29,6 +27,8 @@
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.protobuf.ExtensionRegistry;
+import com.google.protobuf.TypeRegistry;
import dev.cel.checker.CelChecker;
import dev.cel.common.CelContainer;
import dev.cel.common.CelOptions;
@@ -45,7 +45,9 @@
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.CelRuntime;
import dev.cel.runtime.CelRuntime.Program;
+import dev.cel.runtime.CelRuntimeBuilder;
import dev.cel.runtime.CelRuntimeFactory;
+import dev.cel.runtime.CelRuntimeImpl;
import dev.cel.runtime.CelRuntimeLibrary;
import java.util.Map;
import org.junit.runners.model.Statement;
@@ -56,7 +58,6 @@ public final class ConformanceTest extends Statement {
private static final CelOptions OPTIONS =
CelOptions.current()
- .enableTimestampEpoch(true)
.enableHeterogeneousNumericComparisons(true)
.enableProtoDifferencerEquality(true)
.enableOptionalSyntax(true)
@@ -83,6 +84,21 @@ public final class ConformanceTest extends Statement {
CelExtensions.strings(),
CelOptionalLibrary.INSTANCE);
+ static final TypeRegistry CONFORMANCE_TYPE_REGISTRY =
+ TypeRegistry.newBuilder()
+ .add(dev.cel.expr.conformance.proto2.TestAllTypes.getDescriptor())
+ .add(dev.cel.expr.conformance.proto3.TestAllTypes.getDescriptor())
+ .build();
+
+ static final ExtensionRegistry CONFORMANCE_EXTENSION_REGISTRY =
+ createConformanceExtensionRegistry();
+
+ private static ExtensionRegistry createConformanceExtensionRegistry() {
+ ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance();
+ dev.cel.expr.conformance.proto2.TestAllTypesExtensions.registerAllExtensions(extensionRegistry);
+ return extensionRegistry;
+ }
+
private static final CelParser PARSER_WITH_MACROS =
CelParserFactory.standardCelParserBuilder()
.setOptions(OPTIONS)
@@ -105,7 +121,7 @@ private static CelChecker getChecker(SimpleTest test) throws Exception {
ImmutableList.Builder decls =
ImmutableList.builderWithExpectedSize(test.getTypeEnvCount());
for (dev.cel.expr.Decl decl : test.getTypeEnvList()) {
- decls.add(Decl.parseFrom(decl.toByteArray(), DEFAULT_EXTENSION_REGISTRY));
+ decls.add(Decl.parseFrom(decl.toByteArray(), CONFORMANCE_EXTENSION_REGISTRY));
}
return CelCompilerFactory.standardCelCheckerBuilder()
.setOptions(OPTIONS)
@@ -118,15 +134,25 @@ private static CelChecker getChecker(SimpleTest test) throws Exception {
.build();
}
- private static final CelRuntime RUNTIME =
- CelRuntimeFactory.standardCelRuntimeBuilder()
- .setOptions(OPTIONS)
- .addLibraries(CANONICAL_RUNTIME_EXTENSIONS)
- .setExtensionRegistry(DEFAULT_EXTENSION_REGISTRY)
- .addMessageTypes(dev.cel.expr.conformance.proto2.TestAllTypes.getDescriptor())
- .addMessageTypes(dev.cel.expr.conformance.proto3.TestAllTypes.getDescriptor())
- .addFileTypes(dev.cel.expr.conformance.proto2.TestAllTypesExtensions.getDescriptor())
- .build();
+ private static CelRuntime getRuntime(SimpleTest test, boolean usePlanner) {
+ CelRuntimeBuilder builder =
+ usePlanner ? CelRuntimeImpl.newBuilder() : CelRuntimeFactory.standardCelRuntimeBuilder();
+
+ builder
+ // CEL-Internal-2
+ .setOptions(OPTIONS)
+ .addLibraries(CANONICAL_RUNTIME_EXTENSIONS)
+ .setExtensionRegistry(CONFORMANCE_EXTENSION_REGISTRY)
+ .addMessageTypes(dev.cel.expr.conformance.proto2.TestAllTypes.getDescriptor())
+ .addMessageTypes(dev.cel.expr.conformance.proto3.TestAllTypes.getDescriptor())
+ .addFileTypes(dev.cel.expr.conformance.proto2.TestAllTypesExtensions.getDescriptor());
+
+ if (usePlanner) {
+ builder.setContainer(CelContainer.ofName(test.getContainer()));
+ }
+
+ return builder.build();
+ }
private static ImmutableMap getBindings(SimpleTest test) throws Exception {
ImmutableMap.Builder bindings =
@@ -140,7 +166,8 @@ private static ImmutableMap getBindings(SimpleTest test) throws
private static Object fromExprValue(ExprValue value) throws Exception {
switch (value.getKindCase()) {
case VALUE:
- return fromValue(value.getValue());
+ return fromValue(
+ value.getValue(), CONFORMANCE_TYPE_REGISTRY, CONFORMANCE_EXTENSION_REGISTRY);
default:
throw new IllegalArgumentException(
String.format("Unexpected binding value kind: %s", value.getKindCase()));
@@ -157,13 +184,15 @@ private static SimpleTest defaultTestMatcherToTrueIfUnset(SimpleTest test) {
private final String name;
private final SimpleTest test;
private final boolean skip;
+ private final boolean usePlanner;
- public ConformanceTest(String name, SimpleTest test, boolean skip) {
+ public ConformanceTest(String name, SimpleTest test, boolean skip, boolean usePlanner) {
this.name = Preconditions.checkNotNull(name);
this.test =
Preconditions.checkNotNull(
defaultTestMatcherToTrueIfUnset(Preconditions.checkNotNull(test)));
this.skip = skip;
+ this.usePlanner = usePlanner;
}
public String getName() {
@@ -178,7 +207,9 @@ public boolean shouldSkip() {
public void evaluate() throws Throwable {
CelValidationResult response = getParser(test).parse(test.getExpr(), test.getName());
assertThat(response.hasError()).isFalse();
- response = getChecker(test).check(response.getAst());
+ if (!test.getDisableCheck()) {
+ response = getChecker(test).check(response.getAst());
+ }
assertThat(response.hasError()).isFalse();
Type resultType = CelProtoTypes.celTypeToType(response.getAst().getResultType());
@@ -188,10 +219,16 @@ public void evaluate() throws Throwable {
return;
}
- Program program = RUNTIME.createProgram(response.getAst());
+ if (!usePlanner && test.getDisableCheck()) {
+ // Only planner supports parsed-only evaluation
+ return;
+ }
+
+ CelRuntime runtime = getRuntime(test, usePlanner);
ExprValue result = null;
CelEvaluationException error = null;
try {
+ Program program = runtime.createProgram(response.getAst());
result = toExprValue(program.eval(getBindings(test)), response.getAst().getResultType());
} catch (CelEvaluationException e) {
error = e;
@@ -203,7 +240,7 @@ public void evaluate() throws Throwable {
assertThat(result)
.ignoringRepeatedFieldOrderOfFieldDescriptors(
MapValue.getDescriptor().findFieldByName("entries"))
- .unpackingAnyUsing(DEFAULT_TYPE_REGISTRY, DEFAULT_EXTENSION_REGISTRY)
+ .unpackingAnyUsing(CONFORMANCE_TYPE_REGISTRY, CONFORMANCE_EXTENSION_REGISTRY)
.isEqualTo(ExprValue.newBuilder().setValue(test.getValue()).build());
break;
case EVAL_ERROR:
@@ -216,7 +253,7 @@ public void evaluate() throws Throwable {
assertThat(result)
.ignoringRepeatedFieldOrderOfFieldDescriptors(
MapValue.getDescriptor().findFieldByName("entries"))
- .unpackingAnyUsing(DEFAULT_TYPE_REGISTRY, DEFAULT_EXTENSION_REGISTRY)
+ .unpackingAnyUsing(CONFORMANCE_TYPE_REGISTRY, CONFORMANCE_EXTENSION_REGISTRY)
.isEqualTo(ExprValue.newBuilder().setValue(test.getTypedResult().getResult()).build());
assertThat(resultType).isEqualTo(test.getTypedResult().getDeducedType());
break;
diff --git a/conformance/src/test/java/dev/cel/conformance/ConformanceTestRunner.java b/conformance/src/test/java/dev/cel/conformance/ConformanceTestRunner.java
index 89598ed3e..4c3631d31 100644
--- a/conformance/src/test/java/dev/cel/conformance/ConformanceTestRunner.java
+++ b/conformance/src/test/java/dev/cel/conformance/ConformanceTestRunner.java
@@ -14,8 +14,7 @@
package dev.cel.conformance;
-import static dev.cel.testing.utils.ExprValueUtils.DEFAULT_EXTENSION_REGISTRY;
-import static dev.cel.testing.utils.ExprValueUtils.DEFAULT_TYPE_REGISTRY;
+import static dev.cel.conformance.ConformanceTest.CONFORMANCE_EXTENSION_REGISTRY;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
@@ -43,20 +42,23 @@ public final class ConformanceTestRunner extends ParentRunner {
private final ImmutableSortedMap testFiles;
private final ImmutableList testsToSkip;
+ private final boolean usePlanner;
private static ImmutableSortedMap loadTestFiles() {
List testPaths =
SPLITTER.splitToList(System.getProperty("dev.cel.conformance.ConformanceTests.tests"));
try {
TextFormat.Parser parser =
- TextFormat.Parser.newBuilder().setTypeRegistry(DEFAULT_TYPE_REGISTRY).build();
+ TextFormat.Parser.newBuilder()
+ .setTypeRegistry(ConformanceTest.CONFORMANCE_TYPE_REGISTRY)
+ .build();
ImmutableSortedMap.Builder testFiles =
ImmutableSortedMap.naturalOrder();
for (String testPath : testPaths) {
SimpleTestFile.Builder fileBuilder = SimpleTestFile.newBuilder();
try (BufferedReader input =
Files.newBufferedReader(Paths.get(testPath), StandardCharsets.UTF_8)) {
- parser.merge(input, DEFAULT_EXTENSION_REGISTRY, fileBuilder);
+ parser.merge(input, CONFORMANCE_EXTENSION_REGISTRY, fileBuilder);
}
SimpleTestFile testFile = fileBuilder.build();
testFiles.put(testFile.getName(), testFile);
@@ -75,6 +77,9 @@ public ConformanceTestRunner(Class> clazz) throws InitializationError {
ImmutableList.copyOf(
SPLITTER.splitToList(
System.getProperty("dev.cel.conformance.ConformanceTests.skip_tests")));
+ usePlanner =
+ Boolean.parseBoolean(
+ System.getProperty("dev.cel.conformance.ConformanceTests.use_planner", "false"));
}
private boolean shouldSkipTest(String name) {
@@ -97,8 +102,7 @@ protected List getChildren() {
for (SimpleTest test : testSection.getTestList()) {
String name =
String.format("%s/%s/%s", testFile.getName(), testSection.getName(), test.getName());
- tests.add(
- new ConformanceTest(name, test, test.getDisableCheck() || shouldSkipTest(name)));
+ tests.add(new ConformanceTest(name, test, shouldSkipTest(name), usePlanner));
}
}
}
diff --git a/conformance/src/test/java/dev/cel/conformance/conformance_test.bzl b/conformance/src/test/java/dev/cel/conformance/conformance_test.bzl
index f884a2a84..b91ce4a8b 100644
--- a/conformance/src/test/java/dev/cel/conformance/conformance_test.bzl
+++ b/conformance/src/test/java/dev/cel/conformance/conformance_test.bzl
@@ -38,11 +38,12 @@ def _expand_tests_to_skip(tests_to_skip):
result.append(test_to_skip[0:slash] + part)
return result
-def _conformance_test_args(data, skip_tests):
- args = []
- args.append("-Ddev.cel.conformance.ConformanceTests.skip_tests={}".format(",".join(_expand_tests_to_skip(skip_tests))))
- args.append("-Ddev.cel.conformance.ConformanceTests.tests={}".format(",".join(["$(location " + test + ")" for test in data])))
- return args
+def _conformance_test_args(data, skip_tests, use_planner):
+ return [
+ "-Ddev.cel.conformance.ConformanceTests.skip_tests={}".format(",".join(_expand_tests_to_skip(skip_tests))),
+ "-Ddev.cel.conformance.ConformanceTests.tests={}".format(",".join(["$(location {})".format(t) for t in data])),
+ "-Ddev.cel.conformance.ConformanceTests.use_planner={}".format("true" if use_planner else "false"),
+ ]
MODE = struct(
# Standard test execution against HEAD
@@ -53,7 +54,7 @@ MODE = struct(
DASHBOARD = "dashboard",
)
-def conformance_test(name, data, mode = MODE.TEST, skip_tests = []):
+def conformance_test(name, data, mode = MODE.TEST, skip_tests = [], use_planner = False):
"""Executes conformance tests
Args:
@@ -69,7 +70,7 @@ def conformance_test(name, data, mode = MODE.TEST, skip_tests = []):
if mode == MODE.DASHBOARD:
java_test(
name = "_" + name,
- jvm_flags = _conformance_test_args(data, skip_tests),
+ jvm_flags = _conformance_test_args(data, skip_tests, use_planner),
data = data,
size = "small",
test_class = "dev.cel.conformance.ConformanceTests",
@@ -95,7 +96,7 @@ def conformance_test(name, data, mode = MODE.TEST, skip_tests = []):
elif mode == MODE.TEST:
java_test(
name = name,
- jvm_flags = _conformance_test_args(data, skip_tests),
+ jvm_flags = _conformance_test_args(data, skip_tests, use_planner),
data = data,
size = "small",
test_class = "dev.cel.conformance.ConformanceTests",
@@ -104,7 +105,7 @@ def conformance_test(name, data, mode = MODE.TEST, skip_tests = []):
elif mode == MODE.MAVEN_TEST:
java_test(
name = name,
- jvm_flags = _conformance_test_args(data, skip_tests),
+ jvm_flags = _conformance_test_args(data, skip_tests, use_planner),
data = data,
size = "small",
test_class = "dev.cel.conformance.ConformanceTests",
diff --git a/conformance/src/test/java/dev/cel/conformance/policy/BUILD.bazel b/conformance/src/test/java/dev/cel/conformance/policy/BUILD.bazel
new file mode 100644
index 000000000..e4d80eccf
--- /dev/null
+++ b/conformance/src/test/java/dev/cel/conformance/policy/BUILD.bazel
@@ -0,0 +1,36 @@
+load("@rules_java//java:defs.bzl", "java_library")
+load(":cel_policy_conformance_test.bzl", "cel_policy_conformance_test_java")
+
+package(
+ default_applicable_licenses = ["//:license"],
+ default_testonly = True,
+)
+
+java_library(
+ name = "run",
+ srcs = glob(["*.java"]),
+ deps = [
+ "//:auto_value",
+ "//:java_truth",
+ "//bundle:cel",
+ "//policy:parser_factory",
+ "//policy:validation_exception",
+ "//policy/testing:k8s_test_tag_handler",
+ "//runtime:function_binding",
+ "//testing/testrunner:cel_expression_source",
+ "//testing/testrunner:cel_test_context",
+ "//testing/testrunner:cel_test_suite",
+ "//testing/testrunner:cel_test_suite_text_proto_parser",
+ "//testing/testrunner:cel_test_suite_yaml_parser",
+ "//testing/testrunner:test_runner_library",
+ "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto",
+ "@maven//:com_google_guava_guava",
+ "@maven//:com_google_protobuf_protobuf_java",
+ "@maven//:junit_junit",
+ ],
+)
+
+cel_policy_conformance_test_java(
+ name = "policy_conformance_tests",
+ testdata = "@cel_policy//conformance:testdata",
+)
diff --git a/conformance/src/test/java/dev/cel/conformance/policy/PolicyConformanceTest.java b/conformance/src/test/java/dev/cel/conformance/policy/PolicyConformanceTest.java
new file mode 100644
index 000000000..d7851bb72
--- /dev/null
+++ b/conformance/src/test/java/dev/cel/conformance/policy/PolicyConformanceTest.java
@@ -0,0 +1,114 @@
+// Copyright 2026 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.conformance.policy;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.protobuf.Struct;
+import dev.cel.bundle.Cel;
+import dev.cel.bundle.CelFactory;
+import dev.cel.expr.conformance.proto3.TestAllTypes;
+import dev.cel.policy.CelPolicyParserFactory;
+import dev.cel.policy.CelPolicyValidationException;
+import dev.cel.policy.testing.K8sTagHandler;
+import dev.cel.runtime.CelFunctionBinding;
+import dev.cel.testing.testrunner.CelExpressionSource;
+import dev.cel.testing.testrunner.CelTestContext;
+import dev.cel.testing.testrunner.CelTestSuite.CelTestSection.CelTestCase;
+import dev.cel.testing.testrunner.TestRunnerLibrary;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Locale;
+import org.junit.runners.model.Statement;
+
+/** Statement representing a single CEL policy conformance test case. */
+public final class PolicyConformanceTest extends Statement {
+
+ private static final Cel CEL =
+ CelFactory.standardCelBuilder()
+ .addFunctionBindings(
+ CelFunctionBinding.fromOverloads(
+ "locationCode",
+ CelFunctionBinding.from(
+ "locationCode_string",
+ String.class,
+ (ip) -> {
+ switch (ip) {
+ case "10.0.0.1":
+ return "us";
+ case "10.0.0.2":
+ return "de";
+ default:
+ return "ir";
+ }
+ })))
+ .build();
+
+ private final String name;
+ private final CelTestCase testCase;
+ private final String dirPath;
+
+ public PolicyConformanceTest(String name, CelTestCase testCase, String dirPath) {
+ this.name = name;
+ this.testCase = testCase;
+ this.dirPath = dirPath;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ String policyFile = Paths.get(dirPath, "policy.yaml").toString();
+
+ CelTestContext.Builder contextBuilder =
+ CelTestContext.newBuilder()
+ .setCelExpression(CelExpressionSource.fromSource(policyFile))
+ .setCel(CEL)
+ .addFileTypes(
+ TestAllTypes.getDescriptor().getFile(),
+ Struct.getDescriptor().getFile());
+
+ // Scopes the custom Kubernetes tag visitor exclusively to k8s tests to prevent non-standard
+ // grammar leakage.
+ if (name.startsWith("k8s/")) {
+ contextBuilder.setCelPolicyParser(
+ CelPolicyParserFactory.newYamlParserBuilder().addTagVisitor(new K8sTagHandler()).build());
+ }
+
+ Path yamlConfigPath = Paths.get(dirPath, "config.yaml");
+ Path textprotoConfigPath = Paths.get(dirPath, "config.textproto");
+
+ if (Files.exists(yamlConfigPath)) {
+ contextBuilder.setConfigFile(yamlConfigPath.toString());
+ } else if (Files.exists(textprotoConfigPath)) {
+ contextBuilder.setConfigFile(textprotoConfigPath.toString());
+ }
+
+ try {
+ TestRunnerLibrary.runTest(testCase, contextBuilder.build());
+ } catch (CelPolicyValidationException e) {
+ if (testCase.output().kind() == CelTestCase.Output.Kind.EVAL_ERROR) {
+ String expectedError = testCase.output().evalError().get(0).toString();
+ assertThat(e.getMessage().toLowerCase(Locale.US))
+ .contains(expectedError.toLowerCase(Locale.US));
+ } else {
+ throw e;
+ }
+ }
+ }
+}
diff --git a/conformance/src/test/java/dev/cel/conformance/policy/PolicyConformanceTestRunner.java b/conformance/src/test/java/dev/cel/conformance/policy/PolicyConformanceTestRunner.java
new file mode 100644
index 000000000..62812b124
--- /dev/null
+++ b/conformance/src/test/java/dev/cel/conformance/policy/PolicyConformanceTestRunner.java
@@ -0,0 +1,219 @@
+// Copyright 2026 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.conformance.policy;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Files;
+import com.google.protobuf.ListValue;
+import com.google.protobuf.Struct;
+import com.google.protobuf.TypeRegistry;
+import com.google.protobuf.Value;
+import dev.cel.testing.testrunner.CelTestSuite;
+import dev.cel.testing.testrunner.CelTestSuite.CelTestSection;
+import dev.cel.testing.testrunner.CelTestSuite.CelTestSection.CelTestCase;
+import dev.cel.testing.testrunner.CelTestSuiteTextProtoParser;
+import dev.cel.testing.testrunner.CelTestSuiteYamlParser;
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.runner.Description;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.ParentRunner;
+import org.junit.runners.model.InitializationError;
+
+/** Custom JUnit runner for CEL policy conformance tests. */
+public final class PolicyConformanceTestRunner extends ParentRunner {
+
+ private static final Splitter SPLITTER = Splitter.on(",").omitEmptyStrings();
+ private static final String TESTS_YAML_FILE_NAME = "tests.yaml";
+ private static final String TESTS_TEXTPROTO_FILE_NAME = "tests.textproto";
+ private static final String POLICY_YAML_FILE_NAME = "policy.yaml";
+ private static final TypeRegistry TYPE_REGISTRY =
+ TypeRegistry.newBuilder()
+ .add(Struct.getDescriptor())
+ .add(Value.getDescriptor())
+ .add(ListValue.getDescriptor())
+ .build();
+
+ private static final String TEST_DIRS_PROP =
+ System.getProperty("dev.cel.policy.conformance.tests");
+ private static final String TESTDATA_DIR =
+ System.getProperty("dev.cel.policy.conformance.testdata_dir", "testdata");
+ private static final String SKIP_TESTS_PROP =
+ System.getProperty("dev.cel.policy.conformance.skip_tests");
+
+ private static final ImmutableList TESTS_TO_SKIP =
+ Strings.isNullOrEmpty(SKIP_TESTS_PROP)
+ ? ImmutableList.of()
+ : ImmutableList.copyOf(SPLITTER.splitToList(SKIP_TESTS_PROP));
+
+ private static final ImmutableList TEST_DIRS =
+ Strings.isNullOrEmpty(TEST_DIRS_PROP)
+ ? discoverTestDirs(TESTDATA_DIR)
+ : ImmutableList.copyOf(SPLITTER.splitToList(TEST_DIRS_PROP));
+
+ private static ImmutableList discoverTestDirs(String testdataDir) {
+ File dir = new File(testdataDir);
+ if (!dir.exists() || !dir.isDirectory()) {
+ return ImmutableList.of();
+ }
+ File[] topLevelDirs = dir.listFiles(File::isDirectory);
+ if (topLevelDirs == null) {
+ return ImmutableList.of();
+ }
+
+ ImmutableList.Builder testDirsBuilder = ImmutableList.builder();
+ Arrays.sort(topLevelDirs);
+ for (File topLevelDir : topLevelDirs) {
+ if (hasTestSuite(topLevelDir)) {
+ testDirsBuilder.add(topLevelDir.getName());
+ continue;
+ }
+
+ // Check one level deeper to support nested tests like compile_errors/unreachable
+ File[] subDirs = topLevelDir.listFiles(File::isDirectory);
+ if (subDirs == null) {
+ continue;
+ }
+
+ Arrays.sort(subDirs);
+ for (File subDir : subDirs) {
+ if (hasTestSuite(subDir)) {
+ testDirsBuilder.add(topLevelDir.getName() + "/" + subDir.getName());
+ }
+ }
+ }
+
+ return testDirsBuilder.build();
+ }
+
+ private static boolean hasTestSuite(File dir) {
+ return (new File(dir, TESTS_YAML_FILE_NAME).exists()
+ || new File(dir, TESTS_TEXTPROTO_FILE_NAME).exists())
+ && new File(dir, POLICY_YAML_FILE_NAME).exists();
+ }
+
+ private final ImmutableList tests;
+
+ private ImmutableList loadTests() {
+ if (TEST_DIRS.isEmpty()) {
+ return ImmutableList.of();
+ }
+
+ ImmutableList.Builder testsBuilder = ImmutableList.builder();
+
+ for (String dir : TEST_DIRS) {
+ String fullDirPath = TESTDATA_DIR + "/" + dir;
+ try {
+ ImmutableList suites = readTestSuites(fullDirPath);
+ for (CelTestSuiteContext namedSuite : suites) {
+ for (CelTestSection section : namedSuite.testSuite().sections()) {
+ for (CelTestCase testCase : section.tests()) {
+ String baseName = String.format("%s/%s/%s", dir, section.name(), testCase.name());
+ String displayName = baseName + namedSuite.formatSuffix();
+ if (!shouldSkipTest(baseName, TESTS_TO_SKIP)) {
+ testsBuilder.add(new PolicyConformanceTest(displayName, testCase, fullDirPath));
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load test suite in " + fullDirPath, e);
+ }
+ }
+ return testsBuilder.build();
+ }
+
+ private static boolean shouldSkipTest(String name, List testsToSkip) {
+ for (String testToSkip : testsToSkip) {
+ if (name.startsWith(testToSkip)) {
+ String consumedName = name.substring(testToSkip.length());
+ if (consumedName.isEmpty() || consumedName.startsWith("/")) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static ImmutableList readTestSuites(String dirPath)
+ throws Exception {
+ File dir = new File(dirPath);
+ File yamlFile = new File(dir, TESTS_YAML_FILE_NAME);
+ File textprotoFile = new File(dir, TESTS_TEXTPROTO_FILE_NAME);
+
+ boolean bothExist = yamlFile.exists() && textprotoFile.exists();
+ ImmutableList.Builder suitesBuilder = ImmutableList.builder();
+
+ if (yamlFile.exists()) {
+ suitesBuilder.add(
+ CelTestSuiteContext.create(
+ CelTestSuiteYamlParser.newInstance()
+ .parse(Files.asCharSource(yamlFile, UTF_8).read()),
+ bothExist ? " (yaml)" : ""));
+ }
+ if (textprotoFile.exists()) {
+ suitesBuilder.add(
+ CelTestSuiteContext.create(
+ CelTestSuiteTextProtoParser.newInstance()
+ .parse(Files.asCharSource(textprotoFile, UTF_8).read(), TYPE_REGISTRY),
+ bothExist ? " (textproto)" : ""));
+ }
+
+ ImmutableList suites = suitesBuilder.build();
+ if (suites.isEmpty()) {
+ throw new IllegalArgumentException(
+ String.format(
+ "No %s or %s found in %s", TESTS_YAML_FILE_NAME, TESTS_TEXTPROTO_FILE_NAME, dirPath));
+ }
+ return suites;
+ }
+
+ @Override
+ protected ImmutableList getChildren() {
+ return tests;
+ }
+
+ @Override
+ protected Description describeChild(PolicyConformanceTest child) {
+ return Description.createTestDescription(getTestClass().getJavaClass(), child.getName());
+ }
+
+ @Override
+ protected void runChild(PolicyConformanceTest child, RunNotifier notifier) {
+ runLeaf(child, describeChild(child), notifier);
+ }
+
+ public PolicyConformanceTestRunner(Class> clazz) throws InitializationError {
+ super(clazz);
+ this.tests = loadTests();
+ }
+
+ @AutoValue
+ abstract static class CelTestSuiteContext {
+ abstract CelTestSuite testSuite();
+
+ abstract String formatSuffix();
+
+ static CelTestSuiteContext create(CelTestSuite testSuite, String formatSuffix) {
+ return new AutoValue_PolicyConformanceTestRunner_CelTestSuiteContext(testSuite, formatSuffix);
+ }
+ }
+}
diff --git a/conformance/src/test/java/dev/cel/conformance/policy/PolicyConformanceTests.java b/conformance/src/test/java/dev/cel/conformance/policy/PolicyConformanceTests.java
new file mode 100644
index 000000000..46596763e
--- /dev/null
+++ b/conformance/src/test/java/dev/cel/conformance/policy/PolicyConformanceTests.java
@@ -0,0 +1,21 @@
+// Copyright 2026 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.conformance.policy;
+
+import org.junit.runner.RunWith;
+
+/** Main test class for CEL policy conformance tests. */
+@RunWith(PolicyConformanceTestRunner.class)
+public class PolicyConformanceTests {}
diff --git a/conformance/src/test/java/dev/cel/conformance/policy/cel_policy_conformance_test.bzl b/conformance/src/test/java/dev/cel/conformance/policy/cel_policy_conformance_test.bzl
new file mode 100644
index 000000000..b53d982bb
--- /dev/null
+++ b/conformance/src/test/java/dev/cel/conformance/policy/cel_policy_conformance_test.bzl
@@ -0,0 +1,54 @@
+# Copyright 2026 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.
+
+"""Macro to run CEL policy conformance tests."""
+
+load("@rules_java//java:defs.bzl", "java_test")
+
+def cel_policy_conformance_test_java(
+ name,
+ testdata,
+ test_cases = [],
+ skip_tests = [],
+ **kwargs):
+ """Macro to run CEL policy conformance tests for Java.
+
+ Args:
+ name: The name of the test target.
+ testdata: Testdata filegroup target.
+ test_cases: (optional) List of test case names (directory names) to run.
+ skip_tests: (optional) List of test case names (directory names) to skip.
+ **kwargs: Other standard Bazel target attributes.
+ """
+
+ lbl = native.package_relative_label(testdata)
+
+ # Under Bzlmod, external repository runfiles are located in sibling directories
+ # named after their canonical repository name.
+ repo_prefix = "../" + lbl.workspace_name + "/" if lbl.workspace_name else ""
+ testdata_dir = repo_prefix + lbl.package + "/" + lbl.name
+
+ java_test(
+ name = name,
+ jvm_flags = [
+ "-Ddev.cel.policy.conformance.tests=" + ",".join(test_cases),
+ "-Ddev.cel.policy.conformance.testdata_dir=" + testdata_dir,
+ "-Ddev.cel.policy.conformance.skip_tests=" + ",".join(skip_tests),
+ ],
+ data = [testdata],
+ size = "small",
+ test_class = "dev.cel.conformance.policy.PolicyConformanceTests",
+ runtime_deps = [Label(":run")],
+ **kwargs
+ )
diff --git a/conformance/src/test/java/dev/cel/maven/BUILD.bazel b/conformance/src/test/java/dev/cel/maven/BUILD.bazel
new file mode 100644
index 000000000..895339521
--- /dev/null
+++ b/conformance/src/test/java/dev/cel/maven/BUILD.bazel
@@ -0,0 +1,47 @@
+load("@rules_java//java:defs.bzl", "java_test")
+
+package(default_applicable_licenses = [
+ "//:license",
+])
+
+# keep sorted
+MAVEN_COMPILER_JAR_DEPS = [
+ "@maven_conformance//:dev_cel_common",
+ "@maven_conformance//:dev_cel_compiler",
+]
+
+# keep sorted
+MAVEN_RUNTIME_JAR_DEPS = [
+ "@maven_conformance//:dev_cel_common",
+ "@maven_conformance//:dev_cel_protobuf",
+ "@maven_conformance//:dev_cel_runtime",
+]
+
+java_test(
+ name = "compiler_artifact_test",
+ srcs = ["CompilerArtifactTest.java"],
+ test_class = "dev.cel.maven.CompilerArtifactTest",
+ deps =
+ MAVEN_COMPILER_JAR_DEPS + [
+ "//:java_truth",
+ "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto",
+ "@maven//:com_google_testparameterinjector_test_parameter_injector",
+ "@maven//:junit_junit",
+ ],
+)
+
+java_test(
+ name = "runtime_artifact_test",
+ srcs = ["RuntimeArtifactTest.java"],
+ test_class = "dev.cel.maven.RuntimeArtifactTest",
+ deps =
+ MAVEN_RUNTIME_JAR_DEPS + [
+ "//:java_truth",
+ "@cel_spec//proto/cel/expr:checked_java_proto",
+ "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto",
+ "@maven//:com_google_guava_guava",
+ "@maven//:com_google_protobuf_protobuf_java",
+ "@maven//:com_google_testparameterinjector_test_parameter_injector",
+ "@maven//:junit_junit",
+ ],
+)
diff --git a/conformance/src/test/java/dev/cel/maven/CompilerArtifactTest.java b/conformance/src/test/java/dev/cel/maven/CompilerArtifactTest.java
new file mode 100644
index 000000000..dcdedb157
--- /dev/null
+++ b/conformance/src/test/java/dev/cel/maven/CompilerArtifactTest.java
@@ -0,0 +1,106 @@
+// Copyright 2026 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.maven;
+
+import static com.google.common.truth.Truth.assertThat;
+import static dev.cel.common.CelFunctionDecl.newFunctionDeclaration;
+import static dev.cel.common.CelOverloadDecl.newGlobalOverload;
+import static org.junit.Assert.assertThrows;
+
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+import dev.cel.checker.CelChecker;
+import dev.cel.common.CelAbstractSyntaxTree;
+import dev.cel.common.CelContainer;
+import dev.cel.common.CelOptions;
+import dev.cel.common.CelValidationException;
+import dev.cel.common.CelValidationResult;
+import dev.cel.common.ast.CelConstant;
+import dev.cel.common.ast.CelExpr;
+import dev.cel.common.types.ProtoMessageTypeProvider;
+import dev.cel.common.types.SimpleType;
+import dev.cel.common.types.StructTypeReference;
+import dev.cel.compiler.CelCompiler;
+import dev.cel.compiler.CelCompilerFactory;
+import dev.cel.expr.conformance.proto3.TestAllTypes;
+import dev.cel.parser.CelParser;
+import dev.cel.parser.CelParserFactory;
+import dev.cel.parser.CelStandardMacro;
+import dev.cel.parser.CelUnparser;
+import dev.cel.parser.CelUnparserFactory;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(TestParameterInjector.class)
+public class CompilerArtifactTest {
+
+ @Test
+ public void parse() throws Exception {
+ CelParser parser = CelParserFactory.standardCelParserBuilder().build();
+ CelUnparser unparser = CelUnparserFactory.newUnparser();
+
+ CelAbstractSyntaxTree ast = parser.parse("'Hello World'").getAst();
+
+ assertThat(ast.getExpr()).isEqualTo(CelExpr.ofConstant(1L, CelConstant.ofValue("Hello World")));
+ assertThat(unparser.unparse(ast)).isEqualTo("\"Hello World\"");
+ }
+
+ @Test
+ public void typeCheck() throws Exception {
+ CelParser parser = CelParserFactory.standardCelParserBuilder().build();
+ CelChecker checker = CelCompilerFactory.standardCelCheckerBuilder().build();
+
+ CelAbstractSyntaxTree ast = checker.check(parser.parse("'Hello World'").getAst()).getAst();
+
+ assertThat(ast.getResultType()).isEqualTo(SimpleType.STRING);
+ }
+
+ @Test
+ public void compile() throws Exception {
+ CelCompiler compiler =
+ CelCompilerFactory.standardCelCompilerBuilder()
+ .addFunctionDeclarations(
+ newFunctionDeclaration(
+ "getThree", newGlobalOverload("getThree_overload", SimpleType.INT)))
+ .setOptions(CelOptions.DEFAULT)
+ .setStandardMacros(CelStandardMacro.STANDARD_MACROS)
+ .setTypeProvider(
+ ProtoMessageTypeProvider.newBuilder()
+ .addFileDescriptors(TestAllTypes.getDescriptor().getFile())
+ .build())
+ .setContainer(CelContainer.ofName("cel.expr.conformance.proto3"))
+ .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName()))
+ .build();
+ CelUnparser unparser = CelUnparserFactory.newUnparser();
+
+ CelAbstractSyntaxTree ast =
+ compiler.compile("msg == TestAllTypes{} && 3 == getThree()").getAst();
+
+ assertThat(unparser.unparse(ast))
+ .isEqualTo("msg == cel.expr.conformance.proto3.TestAllTypes{} && 3 == getThree()");
+ assertThat(ast.getResultType()).isEqualTo(SimpleType.BOOL);
+ }
+
+ @Test
+ public void compile_error() {
+ CelCompiler compiler = CelCompilerFactory.standardCelCompilerBuilder().build();
+
+ CelValidationResult result = compiler.compile("'foo' + 1");
+
+ assertThat(result.hasError()).isTrue();
+ assertThat(assertThrows(CelValidationException.class, result::getAst))
+ .hasMessageThat()
+ .contains("found no matching overload for '_+_' applied to '(string, int)'");
+ }
+}
diff --git a/conformance/src/test/java/dev/cel/maven/RuntimeArtifactTest.java b/conformance/src/test/java/dev/cel/maven/RuntimeArtifactTest.java
new file mode 100644
index 000000000..e129b85d3
--- /dev/null
+++ b/conformance/src/test/java/dev/cel/maven/RuntimeArtifactTest.java
@@ -0,0 +1,413 @@
+// Copyright 2026 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.maven;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import dev.cel.expr.CheckedExpr;
+import com.google.common.collect.ImmutableList;
+import com.google.protobuf.TextFormat;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+import dev.cel.common.CelAbstractSyntaxTree;
+import dev.cel.common.CelOptions;
+import dev.cel.common.CelProtoAbstractSyntaxTree;
+import dev.cel.expr.conformance.proto3.TestAllTypes;
+import dev.cel.runtime.CelEvaluationException;
+import dev.cel.runtime.CelFunctionBinding;
+import dev.cel.runtime.CelRuntime;
+import dev.cel.runtime.CelRuntimeFactory;
+import dev.cel.runtime.CelStandardFunctions;
+import dev.cel.runtime.CelStandardFunctions.StandardFunction;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(TestParameterInjector.class)
+public class RuntimeArtifactTest {
+
+ // Serialized expr: [TestAllTypes{single_int64: 1}.single_int64, 2].exists(x, x == 2)
+ private static final String CHECKED_EXPR =
+ "reference_map {\n"
+ + " key: 2\n"
+ + " value {\n"
+ + " name: \"cel.expr.conformance.proto3.TestAllTypes\"\n"
+ + " }\n"
+ + "}\n"
+ + "reference_map {\n"
+ + " key: 7\n"
+ + " value {\n"
+ + " overload_id: \"getThree_overload\"\n"
+ + " }\n"
+ + "}\n"
+ + "reference_map {\n"
+ + " key: 10\n"
+ + " value {\n"
+ + " name: \"x\"\n"
+ + " }\n"
+ + "}\n"
+ + "reference_map {\n"
+ + " key: 11\n"
+ + " value {\n"
+ + " overload_id: \"greater_equals_int64\"\n"
+ + " }\n"
+ + "}\n"
+ + "reference_map {\n"
+ + " key: 14\n"
+ + " value {\n"
+ + " name: \"@result\"\n"
+ + " }\n"
+ + "}\n"
+ + "reference_map {\n"
+ + " key: 15\n"
+ + " value {\n"
+ + " overload_id: \"not_strictly_false\"\n"
+ + " }\n"
+ + "}\n"
+ + "reference_map {\n"
+ + " key: 16\n"
+ + " value {\n"
+ + " name: \"@result\"\n"
+ + " }\n"
+ + "}\n"
+ + "reference_map {\n"
+ + " key: 17\n"
+ + " value {\n"
+ + " overload_id: \"logical_and\"\n"
+ + " }\n"
+ + "}\n"
+ + "reference_map {\n"
+ + " key: 18\n"
+ + " value {\n"
+ + " name: \"@result\"\n"
+ + " }\n"
+ + "}\n"
+ + "type_map {\n"
+ + " key: 1\n"
+ + " value {\n"
+ + " list_type {\n"
+ + " elem_type {\n"
+ + " primitive: INT64\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + "}\n"
+ + "type_map {\n"
+ + " key: 2\n"
+ + " value {\n"
+ + " message_type: \"cel.expr.conformance.proto3.TestAllTypes\"\n"
+ + " }\n"
+ + "}\n"
+ + "type_map {\n"
+ + " key: 4\n"
+ + " value {\n"
+ + " primitive: INT64\n"
+ + " }\n"
+ + "}\n"
+ + "type_map {\n"
+ + " key: 5\n"
+ + " value {\n"
+ + " primitive: INT64\n"
+ + " }\n"
+ + "}\n"
+ + "type_map {\n"
+ + " key: 6\n"
+ + " value {\n"
+ + " primitive: INT64\n"
+ + " }\n"
+ + "}\n"
+ + "type_map {\n"
+ + " key: 7\n"
+ + " value {\n"
+ + " primitive: INT64\n"
+ + " }\n"
+ + "}\n"
+ + "type_map {\n"
+ + " key: 10\n"
+ + " value {\n"
+ + " primitive: INT64\n"
+ + " }\n"
+ + "}\n"
+ + "type_map {\n"
+ + " key: 11\n"
+ + " value {\n"
+ + " primitive: BOOL\n"
+ + " }\n"
+ + "}\n"
+ + "type_map {\n"
+ + " key: 12\n"
+ + " value {\n"
+ + " primitive: INT64\n"
+ + " }\n"
+ + "}\n"
+ + "type_map {\n"
+ + " key: 13\n"
+ + " value {\n"
+ + " primitive: BOOL\n"
+ + " }\n"
+ + "}\n"
+ + "type_map {\n"
+ + " key: 14\n"
+ + " value {\n"
+ + " primitive: BOOL\n"
+ + " }\n"
+ + "}\n"
+ + "type_map {\n"
+ + " key: 15\n"
+ + " value {\n"
+ + " primitive: BOOL\n"
+ + " }\n"
+ + "}\n"
+ + "type_map {\n"
+ + " key: 16\n"
+ + " value {\n"
+ + " primitive: BOOL\n"
+ + " }\n"
+ + "}\n"
+ + "type_map {\n"
+ + " key: 17\n"
+ + " value {\n"
+ + " primitive: BOOL\n"
+ + " }\n"
+ + "}\n"
+ + "type_map {\n"
+ + " key: 18\n"
+ + " value {\n"
+ + " primitive: BOOL\n"
+ + " }\n"
+ + "}\n"
+ + "type_map {\n"
+ + " key: 19\n"
+ + " value {\n"
+ + " primitive: BOOL\n"
+ + " }\n"
+ + "}\n"
+ + "expr {\n"
+ + " id: 19\n"
+ + " comprehension_expr {\n"
+ + " iter_var: \"x\"\n"
+ + " iter_range {\n"
+ + " id: 1\n"
+ + " list_expr {\n"
+ + " elements {\n"
+ + " id: 5\n"
+ + " select_expr {\n"
+ + " operand {\n"
+ + " id: 2\n"
+ + " struct_expr {\n"
+ + " message_name: \"cel.expr.conformance.proto3.TestAllTypes\"\n"
+ + " entries {\n"
+ + " id: 3\n"
+ + " field_key: \"single_int64\"\n"
+ + " value {\n"
+ + " id: 4\n"
+ + " const_expr {\n"
+ + " int64_value: 1\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + " field: \"single_int64\"\n"
+ + " }\n"
+ + " }\n"
+ + " elements {\n"
+ + " id: 6\n"
+ + " const_expr {\n"
+ + " int64_value: 2\n"
+ + " }\n"
+ + " }\n"
+ + " elements {\n"
+ + " id: 7\n"
+ + " call_expr {\n"
+ + " function: \"getThree\"\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + " accu_var: \"@result\"\n"
+ + " accu_init {\n"
+ + " id: 13\n"
+ + " const_expr {\n"
+ + " bool_value: true\n"
+ + " }\n"
+ + " }\n"
+ + " loop_condition {\n"
+ + " id: 15\n"
+ + " call_expr {\n"
+ + " function: \"@not_strictly_false\"\n"
+ + " args {\n"
+ + " id: 14\n"
+ + " ident_expr {\n"
+ + " name: \"@result\"\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + " loop_step {\n"
+ + " id: 17\n"
+ + " call_expr {\n"
+ + " function: \"_&&_\"\n"
+ + " args {\n"
+ + " id: 16\n"
+ + " ident_expr {\n"
+ + " name: \"@result\"\n"
+ + " }\n"
+ + " }\n"
+ + " args {\n"
+ + " id: 11\n"
+ + " call_expr {\n"
+ + " function: \"_>=_\"\n"
+ + " args {\n"
+ + " id: 10\n"
+ + " ident_expr {\n"
+ + " name: \"x\"\n"
+ + " }\n"
+ + " }\n"
+ + " args {\n"
+ + " id: 12\n"
+ + " const_expr {\n"
+ + " int64_value: 0\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + " result {\n"
+ + " id: 18\n"
+ + " ident_expr {\n"
+ + " name: \"@result\"\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + "}\n"
+ + "source_info {\n"
+ + " location: \" \"\n"
+ + " line_offsets: 75\n"
+ + " positions {\n"
+ + " key: 1\n"
+ + " value: 0\n"
+ + " }\n"
+ + " positions {\n"
+ + " key: 2\n"
+ + " value: 13\n"
+ + " }\n"
+ + " positions {\n"
+ + " key: 3\n"
+ + " value: 26\n"
+ + " }\n"
+ + " positions {\n"
+ + " key: 4\n"
+ + " value: 28\n"
+ + " }\n"
+ + " positions {\n"
+ + " key: 5\n"
+ + " value: 30\n"
+ + " }\n"
+ + " positions {\n"
+ + " key: 6\n"
+ + " value: 45\n"
+ + " }\n"
+ + " positions {\n"
+ + " key: 7\n"
+ + " value: 56\n"
+ + " }\n"
+ + " positions {\n"
+ + " key: 9\n"
+ + " value: 64\n"
+ + " }\n"
+ + " positions {\n"
+ + " key: 10\n"
+ + " value: 67\n"
+ + " }\n"
+ + " positions {\n"
+ + " key: 11\n"
+ + " value: 69\n"
+ + " }\n"
+ + " positions {\n"
+ + " key: 12\n"
+ + " value: 72\n"
+ + " }\n"
+ + " positions {\n"
+ + " key: 13\n"
+ + " value: 63\n"
+ + " }\n"
+ + " positions {\n"
+ + " key: 14\n"
+ + " value: 63\n"
+ + " }\n"
+ + " positions {\n"
+ + " key: 15\n"
+ + " value: 63\n"
+ + " }\n"
+ + " positions {\n"
+ + " key: 16\n"
+ + " value: 63\n"
+ + " }\n"
+ + " positions {\n"
+ + " key: 17\n"
+ + " value: 63\n"
+ + " }\n"
+ + " positions {\n"
+ + " key: 18\n"
+ + " value: 63\n"
+ + " }\n"
+ + " positions {\n"
+ + " key: 19\n"
+ + " value: 63\n"
+ + " }\n"
+ + "}\n";
+
+ @Test
+ public void eval() throws Exception {
+ CelRuntime runtime =
+ CelRuntimeFactory.standardCelRuntimeBuilder()
+ .setOptions(CelOptions.DEFAULT)
+ .setStandardEnvironmentEnabled(false)
+ .addFunctionBindings(
+ CelFunctionBinding.fromOverloads(
+ "getThree",
+ CelFunctionBinding.from("getThree_overload", ImmutableList.of(), arg -> 3L)))
+ .setStandardFunctions(
+ CelStandardFunctions.newBuilder()
+ .includeFunctions(
+ StandardFunction.GREATER_EQUALS,
+ StandardFunction.LOGICAL_NOT,
+ StandardFunction.NOT_STRICTLY_FALSE)
+ .build())
+ .addMessageTypes(TestAllTypes.getDescriptor())
+ .build();
+ CheckedExpr checkedExpr = TextFormat.parse(CHECKED_EXPR, CheckedExpr.class);
+ CelAbstractSyntaxTree ast = CelProtoAbstractSyntaxTree.fromCheckedExpr(checkedExpr).getAst();
+
+ boolean result = (boolean) runtime.createProgram(ast).eval();
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void eval_error() throws Exception {
+ CelRuntime runtime =
+ CelRuntimeFactory.standardCelRuntimeBuilder()
+ .addMessageTypes(TestAllTypes.getDescriptor())
+ .build();
+ CheckedExpr checkedExpr = TextFormat.parse(CHECKED_EXPR, CheckedExpr.class);
+ CelAbstractSyntaxTree ast = CelProtoAbstractSyntaxTree.fromCheckedExpr(checkedExpr).getAst();
+
+ assertThat(assertThrows(CelEvaluationException.class, () -> runtime.createProgram(ast).eval()))
+ .hasMessageThat()
+ .contains("No matching overload for function 'getThree'");
+ }
+}
diff --git a/extensions/BUILD.bazel b/extensions/BUILD.bazel
index c6a029106..dea4cd760 100644
--- a/extensions/BUILD.bazel
+++ b/extensions/BUILD.bazel
@@ -56,3 +56,8 @@ java_library(
name = "comprehensions",
exports = ["//extensions/src/main/java/dev/cel/extensions:comprehensions"],
)
+
+java_library(
+ name = "native",
+ exports = ["//extensions/src/main/java/dev/cel/extensions:native"],
+)
diff --git a/extensions/src/main/java/dev/cel/extensions/BUILD.bazel b/extensions/src/main/java/dev/cel/extensions/BUILD.bazel
index ed2d19d6f..b25fdf16d 100644
--- a/extensions/src/main/java/dev/cel/extensions/BUILD.bazel
+++ b/extensions/src/main/java/dev/cel/extensions/BUILD.bazel
@@ -34,6 +34,7 @@ java_library(
":encoders",
":lists",
":math",
+ ":native",
":optional_library",
":protos",
":regex",
@@ -42,6 +43,7 @@ java_library(
":strings",
"//common:options",
"//extensions:extension_library",
+ "@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
],
)
@@ -121,7 +123,6 @@ java_library(
":extension_library",
"//checker:checker_builder",
"//common:compiler_common",
- "//common:options",
"//common/ast",
"//common/exceptions:numeric_overflow",
"//common/internal:comparison_functions",
@@ -142,6 +143,7 @@ java_library(
deps = [
"//common:compiler_common",
"//common/ast",
+ "//common/types",
"//compiler:compiler_builder",
"//extensions:extension_library",
"//parser:macro",
@@ -184,6 +186,7 @@ java_library(
"//common/types",
"//common/values",
"//common/values:cel_byte_string",
+ "//common/values:cel_value",
"//compiler:compiler_builder",
"//extensions:extension_library",
"//parser:macro",
@@ -306,6 +309,7 @@ java_library(
"//common:options",
"//common/ast",
"//common/types",
+ "//common/values:mutable_map_value",
"//compiler:compiler_builder",
"//extensions:extension_library",
"//parser:macro",
@@ -316,3 +320,27 @@ java_library(
"@maven//:com_google_guava_guava",
],
)
+
+java_library(
+ name = "native",
+ srcs = ["CelNativeTypesExtensions.java"],
+ tags = [
+ ],
+ deps = [
+ "//checker:checker_builder",
+ "//common/exceptions:attribute_not_found",
+ "//common/exceptions:invalid_argument",
+ "//common/internal:reflection_util",
+ "//common/types",
+ "//common/types:type_providers",
+ "//common/values",
+ "//common/values:cel_byte_string",
+ "//common/values:cel_value",
+ "//common/values:cel_value_provider",
+ "//compiler:compiler_builder",
+ "//runtime",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:com_google_guava_guava",
+ "@maven//:org_jspecify_jspecify",
+ ],
+)
diff --git a/extensions/src/main/java/dev/cel/extensions/CelBindingsExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelBindingsExtensions.java
index 5eb2c2e8c..0e6537334 100644
--- a/extensions/src/main/java/dev/cel/extensions/CelBindingsExtensions.java
+++ b/extensions/src/main/java/dev/cel/extensions/CelBindingsExtensions.java
@@ -22,7 +22,11 @@
import com.google.errorprone.annotations.Immutable;
import dev.cel.common.CelFunctionDecl;
import dev.cel.common.CelIssue;
+import dev.cel.common.CelOverloadDecl;
import dev.cel.common.ast.CelExpr;
+import dev.cel.common.types.ListType;
+import dev.cel.common.types.SimpleType;
+import dev.cel.common.types.TypeParamType;
import dev.cel.compiler.CelCompilerLibrary;
import dev.cel.parser.CelMacro;
import dev.cel.parser.CelMacroExprFactory;
@@ -62,7 +66,15 @@ public int version() {
@Override
public ImmutableSet functions() {
- return ImmutableSet.of();
+ // TODO: Add bindings for block once decorator support is available.
+ return ImmutableSet.of(
+ CelFunctionDecl.newFunctionDeclaration(
+ "cel.@block",
+ CelOverloadDecl.newGlobalOverload(
+ "cel_block_list",
+ TypeParamType.create("T"),
+ ListType.create(SimpleType.DYN),
+ TypeParamType.create("T"))));
}
@Override
diff --git a/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java
index 23663f02e..14968099c 100644
--- a/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java
+++ b/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java
@@ -29,6 +29,7 @@
import dev.cel.common.ast.CelExpr;
import dev.cel.common.types.MapType;
import dev.cel.common.types.TypeParamType;
+import dev.cel.common.values.MutableMapValue;
import dev.cel.compiler.CelCompilerLibrary;
import dev.cel.parser.CelMacro;
import dev.cel.parser.CelMacroExprFactory;
@@ -118,29 +119,18 @@ public void setRuntimeOptions(CelRuntimeBuilder runtimeBuilder) {
@Override
public void setRuntimeOptions(
CelRuntimeBuilder runtimeBuilder, RuntimeEquality runtimeEquality, CelOptions celOptions) {
- for (Function function : functions) {
- for (CelOverloadDecl overload : function.functionDecl.overloads()) {
- switch (overload.overloadId()) {
- case MAP_INSERT_OVERLOAD_MAP_MAP:
- runtimeBuilder.addFunctionBindings(
- CelFunctionBinding.from(
- MAP_INSERT_OVERLOAD_MAP_MAP,
- Map.class,
- Map.class,
- (map1, map2) -> mapInsertMap(map1, map2, runtimeEquality)));
- break;
- case MAP_INSERT_OVERLOAD_KEY_VALUE:
- runtimeBuilder.addFunctionBindings(
- CelFunctionBinding.from(
- MAP_INSERT_OVERLOAD_KEY_VALUE,
- ImmutableList.of(Map.class, Object.class, Object.class),
- args -> mapInsertKeyValue(args, runtimeEquality)));
- break;
- default:
- // Nothing to add.
- }
- }
- }
+ runtimeBuilder.addFunctionBindings(
+ CelFunctionBinding.fromOverloads(
+ MAP_INSERT_FUNCTION,
+ CelFunctionBinding.from(
+ MAP_INSERT_OVERLOAD_MAP_MAP,
+ Map.class,
+ Map.class,
+ (map1, map2) -> mapInsertMap(map1, map2, runtimeEquality)),
+ CelFunctionBinding.from(
+ MAP_INSERT_OVERLOAD_KEY_VALUE,
+ ImmutableList.of(Map.class, Object.class, Object.class),
+ args -> mapInsertKeyValue(args, runtimeEquality))));
}
@Override
@@ -182,38 +172,46 @@ public void setParserOptions(CelParserBuilder parserBuilder) {
parserBuilder.addMacros(macros());
}
- // TODO: Implement a more efficient map insertion based on mutability once mutable
- // maps are supported in Java stack.
- private static ImmutableMap mapInsertMap(
+ private static Map mapInsertMap(
Map, ?> targetMap, Map, ?> mapToMerge, RuntimeEquality equality) {
- ImmutableMap.Builder resultBuilder =
- ImmutableMap.builderWithExpectedSize(targetMap.size() + mapToMerge.size());
-
- for (Map.Entry, ?> entry : mapToMerge.entrySet()) {
- if (equality.findInMap(targetMap, entry.getKey()).isPresent()) {
+ for (Object key : mapToMerge.keySet()) {
+ if (equality.findInMap(targetMap, key).isPresent()) {
throw new IllegalArgumentException(
- String.format("insert failed: key '%s' already exists", entry.getKey()));
- } else {
- resultBuilder.put(entry.getKey(), entry.getValue());
+ String.format("insert failed: key '%s' already exists", key));
}
}
- return resultBuilder.putAll(targetMap).buildOrThrow();
+
+ if (targetMap instanceof MutableMapValue) {
+ MutableMapValue wrapper = (MutableMapValue) targetMap;
+ wrapper.putAll(mapToMerge);
+ return wrapper;
+ }
+
+ return ImmutableMap.builderWithExpectedSize(targetMap.size() + mapToMerge.size())
+ .putAll(targetMap)
+ .putAll(mapToMerge)
+ .buildOrThrow();
}
- private static ImmutableMap mapInsertKeyValue(
- Object[] args, RuntimeEquality equality) {
- Map, ?> map = (Map, ?>) args[0];
+ private static Map mapInsertKeyValue(Object[] args, RuntimeEquality equality) {
+ Map, ?> mapArg = (Map, ?>) args[0];
Object key = args[1];
Object value = args[2];
- if (equality.findInMap(map, key).isPresent()) {
+ if (equality.findInMap(mapArg, key).isPresent()) {
throw new IllegalArgumentException(
String.format("insert failed: key '%s' already exists", key));
}
+ if (mapArg instanceof MutableMapValue) {
+ MutableMapValue mutableMap = (MutableMapValue) mapArg;
+ mutableMap.put(key, value);
+ return mutableMap;
+ }
+
ImmutableMap.Builder builder =
- ImmutableMap.builderWithExpectedSize(map.size() + 1);
- return builder.put(key, value).putAll(map).buildOrThrow();
+ ImmutableMap.builderWithExpectedSize(mapArg.size() + 1);
+ return builder.put(key, value).putAll(mapArg).buildOrThrow();
}
private static Optional expandAllMacro(
@@ -481,9 +479,8 @@ private static Optional transformMapEntryMacro(
private static CelExpr validatedIterationVariable(
CelMacroExprFactory exprFactory, CelExpr argument) {
-
CelExpr arg = checkNotNull(argument);
- if (arg.exprKind().getKind() != CelExpr.ExprKind.Kind.IDENT) {
+ if (!isSimpleIdentifier(arg)) {
return reportArgumentError(exprFactory, arg);
} else if (arg.exprKind().ident().name().equals("__result__")) {
return reportAccumulatorOverwriteError(exprFactory, arg);
@@ -492,6 +489,12 @@ private static CelExpr validatedIterationVariable(
}
}
+ private static boolean isSimpleIdentifier(CelExpr expr) {
+ return expr.getKind() == CelExpr.ExprKind.Kind.IDENT
+ && !expr.ident().name().isEmpty()
+ && !expr.ident().name().startsWith(".");
+ }
+
private static CelExpr reportArgumentError(CelMacroExprFactory exprFactory, CelExpr argument) {
return exprFactory.reportError(
CelIssue.formatError(
diff --git a/extensions/src/main/java/dev/cel/extensions/CelEncoderExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelEncoderExtensions.java
index a98f9db41..498b8555e 100644
--- a/extensions/src/main/java/dev/cel/extensions/CelEncoderExtensions.java
+++ b/extensions/src/main/java/dev/cel/extensions/CelEncoderExtensions.java
@@ -135,9 +135,13 @@ public void setRuntimeOptions(CelRuntimeBuilder runtimeBuilder) {
functions.forEach(
function -> {
if (celOptions.evaluateCanonicalTypesToNativeValues()) {
- runtimeBuilder.addFunctionBindings(function.nativeBytesFunctionBinding);
+ runtimeBuilder.addFunctionBindings(
+ CelFunctionBinding.fromOverloads(
+ function.getFunction(), function.nativeBytesFunctionBinding));
} else {
- runtimeBuilder.addFunctionBindings(function.protoBytesFunctionBinding);
+ runtimeBuilder.addFunctionBindings(
+ CelFunctionBinding.fromOverloads(
+ function.getFunction(), function.protoBytesFunctionBinding));
}
});
}
diff --git a/extensions/src/main/java/dev/cel/extensions/CelExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelExtensions.java
index 2d14ed118..8adc39384 100644
--- a/extensions/src/main/java/dev/cel/extensions/CelExtensions.java
+++ b/extensions/src/main/java/dev/cel/extensions/CelExtensions.java
@@ -15,11 +15,13 @@
package dev.cel.extensions;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
-import static java.util.Arrays.stream;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
+import com.google.errorprone.annotations.InlineMe;
import dev.cel.common.CelOptions;
+import dev.cel.extensions.CelMathExtensions.Function;
+import java.util.EnumSet;
import java.util.Set;
/**
@@ -121,12 +123,9 @@ public static CelProtoExtensions protos() {
* This will include all functions denoted in {@link CelMathExtensions.Function}, including any
* future additions. To expose only a subset of these, use {@link #math(CelOptions,
* CelMathExtensions.Function...)} or {@link #math(CelOptions,int)} instead.
- *
- * @param celOptions CelOptions to configure CelMathExtension with. This should be the same
- * options object used to configure the compilation/runtime environments.
*/
- public static CelMathExtensions math(CelOptions celOptions) {
- return CelMathExtensions.library(celOptions).latest();
+ public static CelMathExtensions math() {
+ return CelMathExtensions.library().latest();
}
/**
@@ -134,8 +133,8 @@ public static CelMathExtensions math(CelOptions celOptions) {
*
*
Refer to README.md for functions available in each version.
*/
- public static CelMathExtensions math(CelOptions celOptions, int version) {
- return CelMathExtensions.library(celOptions).version(version);
+ public static CelMathExtensions math(int version) {
+ return CelMathExtensions.library().version(version);
}
/**
@@ -150,13 +149,9 @@ public static CelMathExtensions math(CelOptions celOptions, int version) {
* collision.
*
*
This will include only the specific functions denoted by {@link CelMathExtensions.Function}.
- *
- * @param celOptions CelOptions to configure CelMathExtension with. This should be the same
- * options object used to configure the compilation/runtime environments.
*/
- public static CelMathExtensions math(
- CelOptions celOptions, CelMathExtensions.Function... functions) {
- return math(celOptions, ImmutableSet.copyOf(functions));
+ public static CelMathExtensions math(CelMathExtensions.Function... functions) {
+ return math(ImmutableSet.copyOf(functions));
}
/**
@@ -171,13 +166,49 @@ public static CelMathExtensions math(
* collision.
*
*
This will include only the specific functions denoted by {@link CelMathExtensions.Function}.
- *
- * @param celOptions CelOptions to configure CelMathExtension with. This should be the same
- * options object used to configure the compilation/runtime environments.
*/
+ public static CelMathExtensions math(Set functions) {
+ return new CelMathExtensions(functions);
+ }
+
+ /**
+ * @deprecated Use {@link #math()} instead.
+ */
+ @Deprecated
+ @InlineMe(replacement = "CelExtensions.math()", imports = "dev.cel.extensions.CelExtensions")
+ public static CelMathExtensions math(CelOptions unused) {
+ return math();
+ }
+
+ /**
+ * @deprecated Use {@link #math(int)} instead.
+ */
+ @Deprecated
+ @InlineMe(
+ replacement = "CelExtensions.math(version)",
+ imports = "dev.cel.extensions.CelExtensions")
+ public static CelMathExtensions math(CelOptions unused, int version) {
+ return math(version);
+ }
+
+ /**
+ * @deprecated Use {@link #math(Function...)} instead.
+ */
+ @Deprecated
+ public static CelMathExtensions math(CelOptions unused, CelMathExtensions.Function... functions) {
+ return math(ImmutableSet.copyOf(functions));
+ }
+
+ /**
+ * @deprecated Use {@link #math(Set)} instead.
+ */
+ @Deprecated
+ @InlineMe(
+ replacement = "CelExtensions.math(functions)",
+ imports = "dev.cel.extensions.CelExtensions")
public static CelMathExtensions math(
- CelOptions celOptions, Set functions) {
- return new CelMathExtensions(celOptions, functions);
+ CelOptions unused, Set functions) {
+ return math(functions);
}
/**
@@ -319,6 +350,18 @@ public static CelComprehensionsExtensions comprehensions() {
return COMPREHENSIONS_EXTENSIONS;
}
+ /**
+ * Extensions for supporting native Java types (POJOs) in CEL.
+ *
+ * Refer to README.md for details on property discovery, type mapping, and limitations.
+ *
+ *
Note: Passing classes with unsupported types or anonymous/local classes will result in an
+ * {@link IllegalArgumentException} when the runtime is built.
+ */
+ public static CelNativeTypesExtensions nativeTypes(Class>... classes) {
+ return CelNativeTypesExtensions.nativeTypes(classes);
+ }
+
/**
* Retrieves all function names used by every extension libraries.
*
@@ -328,18 +371,17 @@ public static CelComprehensionsExtensions comprehensions() {
*/
public static ImmutableSet getAllFunctionNames() {
return Streams.concat(
- stream(CelMathExtensions.Function.values())
- .map(CelMathExtensions.Function::getFunction),
- stream(CelStringExtensions.Function.values())
+ EnumSet.allOf(Function.class).stream().map(CelMathExtensions.Function::getFunction),
+ EnumSet.allOf(CelStringExtensions.Function.class).stream()
.map(CelStringExtensions.Function::getFunction),
- stream(SetsFunction.values()).map(SetsFunction::getFunction),
- stream(CelEncoderExtensions.Function.values())
+ EnumSet.allOf(SetsFunction.class).stream().map(SetsFunction::getFunction),
+ EnumSet.allOf(CelEncoderExtensions.Function.class).stream()
.map(CelEncoderExtensions.Function::getFunction),
- stream(CelListsExtensions.Function.values())
+ EnumSet.allOf(CelListsExtensions.Function.class).stream()
.map(CelListsExtensions.Function::getFunction),
- stream(CelRegexExtensions.Function.values())
+ EnumSet.allOf(CelRegexExtensions.Function.class).stream()
.map(CelRegexExtensions.Function::getFunction),
- stream(CelComprehensionsExtensions.Function.values())
+ EnumSet.allOf(CelComprehensionsExtensions.Function.class).stream()
.map(CelComprehensionsExtensions.Function::getFunction))
.collect(toImmutableSet());
}
@@ -354,7 +396,7 @@ public static CelExtensionLibrary extends CelExtensionLibrary.FeatureSet> getE
case "lists":
return CelListsExtensions.library();
case "math":
- return CelMathExtensions.library(options);
+ return CelMathExtensions.library();
case "optional":
return CelOptionalLibrary.library();
case "protos":
diff --git a/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java
index a91edd822..79539b008 100644
--- a/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java
+++ b/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java
@@ -128,7 +128,8 @@ public enum Function {
"list_sort",
"Sorts a list with comparable elements.",
ListType.create(TypeParamType.create("T")),
- ListType.create(TypeParamType.create("T"))))),
+ ListType.create(TypeParamType.create("T")))),
+ CelFunctionBinding.from("list_sort", Collection.class, CelListsExtensions::sort)),
SORT_BY(
CelFunctionDecl.newFunctionDeclaration(
"lists.@sortByAssociatedKeys",
@@ -136,7 +137,11 @@ public enum Function {
"list_sortByAssociatedKeys",
"Sorts a list by a key value. Used by the 'sortBy' macro",
ListType.create(TypeParamType.create("T")),
- ListType.create(TypeParamType.create("T")))));
+ ListType.create(TypeParamType.create("T")))),
+ CelFunctionBinding.from(
+ "list_sortByAssociatedKeys",
+ Collection.class,
+ CelListsExtensions::sortByAssociatedKeys));
private final CelFunctionDecl functionDecl;
private final ImmutableSet functionBindings;
@@ -147,7 +152,10 @@ String getFunction() {
Function(CelFunctionDecl functionDecl, CelFunctionBinding... functionBindings) {
this.functionDecl = functionDecl;
- this.functionBindings = ImmutableSet.copyOf(functionBindings);
+ this.functionBindings =
+ functionBindings.length > 0
+ ? CelFunctionBinding.fromOverloads(functionDecl.name(), functionBindings)
+ : ImmutableSet.of();
}
}
@@ -240,32 +248,13 @@ public void setRuntimeOptions(CelRuntimeBuilder runtimeBuilder) {
@Override
public void setRuntimeOptions(
CelRuntimeBuilder runtimeBuilder, RuntimeEquality runtimeEquality, CelOptions celOptions) {
- for (Function function : functions) {
- runtimeBuilder.addFunctionBindings(function.functionBindings);
- for (CelOverloadDecl overload : function.functionDecl.overloads()) {
- switch (overload.overloadId()) {
- case "list_distinct":
- runtimeBuilder.addFunctionBindings(
- CelFunctionBinding.from(
- "list_distinct", Collection.class, (list) -> distinct(list, runtimeEquality)));
- break;
- case "list_sort":
- runtimeBuilder.addFunctionBindings(
- CelFunctionBinding.from(
- "list_sort", Collection.class, (list) -> sort(list, celOptions)));
- break;
- case "list_sortByAssociatedKeys":
- runtimeBuilder.addFunctionBindings(
- CelFunctionBinding.from(
- "list_sortByAssociatedKeys",
- Collection.class,
- (list) -> sortByAssociatedKeys(list, celOptions)));
- break;
- default:
- // Nothing to add
- }
- }
- }
+ functions.forEach(function -> runtimeBuilder.addFunctionBindings(function.functionBindings));
+
+ runtimeBuilder.addFunctionBindings(
+ CelFunctionBinding.fromOverloads(
+ "distinct",
+ CelFunctionBinding.from(
+ "list_distinct", Collection.class, (list) -> distinct(list, runtimeEquality))));
}
private static ImmutableList slice(Collection list, long from, long to) {
@@ -369,22 +358,18 @@ private static List reverse(Collection list) {
}
}
- private static ImmutableList sort(Collection objects, CelOptions options) {
- return ImmutableList.sortedCopyOf(
- new CelObjectComparator(options.enableHeterogeneousNumericComparisons()), objects);
+ private static ImmutableList sort(Collection objects) {
+ return ImmutableList.sortedCopyOf(new CelObjectComparator(), objects);
}
private static class CelObjectComparator implements Comparator {
- private final boolean enableHeterogeneousNumericComparisons;
- CelObjectComparator(boolean enableHeterogeneousNumericComparisons) {
- this.enableHeterogeneousNumericComparisons = enableHeterogeneousNumericComparisons;
- }
+ CelObjectComparator() {}
@SuppressWarnings({"unchecked"})
@Override
public int compare(Object o1, Object o2) {
- if (o1 instanceof Number && o2 instanceof Number && enableHeterogeneousNumericComparisons) {
+ if (o1 instanceof Number && o2 instanceof Number) {
return ComparisonFunctions.numericCompare((Number) o1, (Number) o2);
}
@@ -444,12 +429,9 @@ private static Optional sortByMacro(
@SuppressWarnings({"unchecked", "rawtypes"})
private static ImmutableList sortByAssociatedKeys(
- Collection> keyValuePairs, CelOptions options) {
+ Collection> keyValuePairs) {
List[] array = keyValuePairs.toArray(new List[0]);
- Arrays.sort(
- array,
- new CelObjectByKeyComparator(
- new CelObjectComparator(options.enableHeterogeneousNumericComparisons())));
+ Arrays.sort(array, new CelObjectByKeyComparator(new CelObjectComparator()));
ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(array.length);
for (List pair : array) {
builder.add(pair.get(1));
diff --git a/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java
index 57c8c1378..63108aa0c 100644
--- a/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java
+++ b/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java
@@ -27,7 +27,6 @@
import dev.cel.checker.CelCheckerBuilder;
import dev.cel.common.CelFunctionDecl;
import dev.cel.common.CelIssue;
-import dev.cel.common.CelOptions;
import dev.cel.common.CelOverloadDecl;
import dev.cel.common.ast.CelConstant;
import dev.cel.common.ast.CelExpr;
@@ -136,7 +135,8 @@ public final class CelMathExtensions
return builder.buildOrThrow();
}
- enum Function {
+ /** Enumeration of functions for Math extension. */
+ public enum Function {
MAX(
CelFunctionDecl.newFunctionDeclaration(
MATH_MAX_FUNCTION,
@@ -206,51 +206,59 @@ enum Function {
MATH_MAX_OVERLOAD_DOC,
SimpleType.DYN,
ListType.create(SimpleType.DYN))),
- ImmutableSet.of(
- CelFunctionBinding.from("math_@max_double", Double.class, x -> x),
- CelFunctionBinding.from("math_@max_int", Long.class, x -> x),
- CelFunctionBinding.from(
- "math_@max_double_double", Double.class, Double.class, CelMathExtensions::maxPair),
- CelFunctionBinding.from(
- "math_@max_int_int", Long.class, Long.class, CelMathExtensions::maxPair),
- CelFunctionBinding.from(
- "math_@max_int_double", Long.class, Double.class, CelMathExtensions::maxPair),
- CelFunctionBinding.from(
- "math_@max_double_int", Double.class, Long.class, CelMathExtensions::maxPair),
- CelFunctionBinding.from("math_@max_list_dyn", List.class, CelMathExtensions::maxList)),
- ImmutableSet.of(
- CelFunctionBinding.from("math_@max_uint", Long.class, x -> x),
- CelFunctionBinding.from(
- "math_@max_uint_uint", Long.class, Long.class, CelMathExtensions::maxPair),
- CelFunctionBinding.from(
- "math_@max_double_uint", Double.class, Long.class, CelMathExtensions::maxPair),
- CelFunctionBinding.from(
- "math_@max_uint_int", Long.class, Long.class, CelMathExtensions::maxPair),
- CelFunctionBinding.from(
- "math_@max_uint_double", Long.class, Double.class, CelMathExtensions::maxPair),
- CelFunctionBinding.from(
- "math_@max_int_uint", Long.class, Long.class, CelMathExtensions::maxPair)),
- ImmutableSet.of(
- CelFunctionBinding.from("math_@max_uint", UnsignedLong.class, x -> x),
- CelFunctionBinding.from(
- "math_@max_uint_uint",
- UnsignedLong.class,
- UnsignedLong.class,
- CelMathExtensions::maxPair),
- CelFunctionBinding.from(
- "math_@max_double_uint",
- Double.class,
- UnsignedLong.class,
- CelMathExtensions::maxPair),
- CelFunctionBinding.from(
- "math_@max_uint_int", UnsignedLong.class, Long.class, CelMathExtensions::maxPair),
- CelFunctionBinding.from(
- "math_@max_uint_double",
- UnsignedLong.class,
- Double.class,
- CelMathExtensions::maxPair),
- CelFunctionBinding.from(
- "math_@max_int_uint", Long.class, UnsignedLong.class, CelMathExtensions::maxPair))),
+ ImmutableSet.builder()
+ .add(CelFunctionBinding.from("math_@max_double", Double.class, x -> x))
+ .add(CelFunctionBinding.from("math_@max_int", Long.class, x -> x))
+ .add(
+ CelFunctionBinding.from(
+ "math_@max_double_double",
+ Double.class,
+ Double.class,
+ CelMathExtensions::maxPair))
+ .add(
+ CelFunctionBinding.from(
+ "math_@max_int_int", Long.class, Long.class, CelMathExtensions::maxPair))
+ .add(
+ CelFunctionBinding.from(
+ "math_@max_int_double", Long.class, Double.class, CelMathExtensions::maxPair))
+ .add(
+ CelFunctionBinding.from(
+ "math_@max_double_int", Double.class, Long.class, CelMathExtensions::maxPair))
+ .add(
+ CelFunctionBinding.from(
+ "math_@max_list_dyn", List.class, CelMathExtensions::maxList))
+ .add(CelFunctionBinding.from("math_@max_uint", UnsignedLong.class, x -> x))
+ .add(
+ CelFunctionBinding.from(
+ "math_@max_uint_uint",
+ UnsignedLong.class,
+ UnsignedLong.class,
+ CelMathExtensions::maxPair))
+ .add(
+ CelFunctionBinding.from(
+ "math_@max_double_uint",
+ Double.class,
+ UnsignedLong.class,
+ CelMathExtensions::maxPair))
+ .add(
+ CelFunctionBinding.from(
+ "math_@max_uint_int",
+ UnsignedLong.class,
+ Long.class,
+ CelMathExtensions::maxPair))
+ .add(
+ CelFunctionBinding.from(
+ "math_@max_uint_double",
+ UnsignedLong.class,
+ Double.class,
+ CelMathExtensions::maxPair))
+ .add(
+ CelFunctionBinding.from(
+ "math_@max_int_uint",
+ Long.class,
+ UnsignedLong.class,
+ CelMathExtensions::maxPair))
+ .build()),
MIN(
CelFunctionDecl.newFunctionDeclaration(
MATH_MIN_FUNCTION,
@@ -320,51 +328,59 @@ enum Function {
MATH_MIN_OVERLOAD_DOC,
SimpleType.DYN,
ListType.create(SimpleType.DYN))),
- ImmutableSet.of(
- CelFunctionBinding.from("math_@min_double", Double.class, x -> x),
- CelFunctionBinding.from("math_@min_int", Long.class, x -> x),
- CelFunctionBinding.from(
- "math_@min_double_double", Double.class, Double.class, CelMathExtensions::minPair),
- CelFunctionBinding.from(
- "math_@min_int_int", Long.class, Long.class, CelMathExtensions::minPair),
- CelFunctionBinding.from(
- "math_@min_int_double", Long.class, Double.class, CelMathExtensions::minPair),
- CelFunctionBinding.from(
- "math_@min_double_int", Double.class, Long.class, CelMathExtensions::minPair),
- CelFunctionBinding.from("math_@min_list_dyn", List.class, CelMathExtensions::minList)),
- ImmutableSet.of(
- CelFunctionBinding.from("math_@min_uint", Long.class, x -> x),
- CelFunctionBinding.from(
- "math_@min_uint_uint", Long.class, Long.class, CelMathExtensions::minPair),
- CelFunctionBinding.from(
- "math_@min_double_uint", Double.class, Long.class, CelMathExtensions::minPair),
- CelFunctionBinding.from(
- "math_@min_uint_int", Long.class, Long.class, CelMathExtensions::minPair),
- CelFunctionBinding.from(
- "math_@min_uint_double", Long.class, Double.class, CelMathExtensions::minPair),
- CelFunctionBinding.from(
- "math_@min_int_uint", Long.class, Long.class, CelMathExtensions::minPair)),
- ImmutableSet.of(
- CelFunctionBinding.from("math_@min_uint", UnsignedLong.class, x -> x),
- CelFunctionBinding.from(
- "math_@min_uint_uint",
- UnsignedLong.class,
- UnsignedLong.class,
- CelMathExtensions::minPair),
- CelFunctionBinding.from(
- "math_@min_double_uint",
- Double.class,
- UnsignedLong.class,
- CelMathExtensions::minPair),
- CelFunctionBinding.from(
- "math_@min_uint_int", UnsignedLong.class, Long.class, CelMathExtensions::minPair),
- CelFunctionBinding.from(
- "math_@min_uint_double",
- UnsignedLong.class,
- Double.class,
- CelMathExtensions::minPair),
- CelFunctionBinding.from(
- "math_@min_int_uint", Long.class, UnsignedLong.class, CelMathExtensions::minPair))),
+ ImmutableSet.builder()
+ .add(CelFunctionBinding.from("math_@min_double", Double.class, x -> x))
+ .add(CelFunctionBinding.from("math_@min_int", Long.class, x -> x))
+ .add(
+ CelFunctionBinding.from(
+ "math_@min_double_double",
+ Double.class,
+ Double.class,
+ CelMathExtensions::minPair))
+ .add(
+ CelFunctionBinding.from(
+ "math_@min_int_int", Long.class, Long.class, CelMathExtensions::minPair))
+ .add(
+ CelFunctionBinding.from(
+ "math_@min_int_double", Long.class, Double.class, CelMathExtensions::minPair))
+ .add(
+ CelFunctionBinding.from(
+ "math_@min_double_int", Double.class, Long.class, CelMathExtensions::minPair))
+ .add(
+ CelFunctionBinding.from(
+ "math_@min_list_dyn", List.class, CelMathExtensions::minList))
+ .add(CelFunctionBinding.from("math_@min_uint", UnsignedLong.class, x -> x))
+ .add(
+ CelFunctionBinding.from(
+ "math_@min_uint_uint",
+ UnsignedLong.class,
+ UnsignedLong.class,
+ CelMathExtensions::minPair))
+ .add(
+ CelFunctionBinding.from(
+ "math_@min_double_uint",
+ Double.class,
+ UnsignedLong.class,
+ CelMathExtensions::minPair))
+ .add(
+ CelFunctionBinding.from(
+ "math_@min_uint_int",
+ UnsignedLong.class,
+ Long.class,
+ CelMathExtensions::minPair))
+ .add(
+ CelFunctionBinding.from(
+ "math_@min_uint_double",
+ UnsignedLong.class,
+ Double.class,
+ CelMathExtensions::minPair))
+ .add(
+ CelFunctionBinding.from(
+ "math_@min_int_uint",
+ Long.class,
+ UnsignedLong.class,
+ CelMathExtensions::minPair))
+ .build()),
CEIL(
CelFunctionDecl.newFunctionDeclaration(
MATH_CEIL_FUNCTION,
@@ -646,26 +662,14 @@ enum Function {
private final CelFunctionDecl functionDecl;
private final ImmutableSet functionBindings;
- private final ImmutableSet functionBindingsULongSigned;
- private final ImmutableSet functionBindingsULongUnsigned;
String getFunction() {
return functionDecl.name();
}
Function(CelFunctionDecl functionDecl, ImmutableSet bindings) {
- this(functionDecl, bindings, ImmutableSet.of(), ImmutableSet.of());
- }
-
- Function(
- CelFunctionDecl functionDecl,
- ImmutableSet functionBindings,
- ImmutableSet functionBindingsULongSigned,
- ImmutableSet functionBindingsULongUnsigned) {
this.functionDecl = functionDecl;
- this.functionBindings = functionBindings;
- this.functionBindingsULongSigned = functionBindingsULongSigned;
- this.functionBindingsULongUnsigned = functionBindingsULongUnsigned;
+ this.functionBindings = bindings;
}
}
@@ -674,10 +678,8 @@ private static final class Library implements CelExtensionLibrarybuilder()
.addAll(version1.functions)
.add(Function.SQRT)
- .build(),
- enableUnsignedLongs);
+ .build());
}
@Override
@@ -724,25 +724,20 @@ public ImmutableSet versions() {
}
}
- private static final Library LIBRARY_UNSIGNED_LONGS_ENABLED = new Library(true);
- private static final Library LIBRARY_UNSIGNED_LONGS_DISABLED = new Library(false);
+ private static final Library LIBRARY = new Library();
- static CelExtensionLibrary library(CelOptions celOptions) {
- return celOptions.enableUnsignedLongs()
- ? LIBRARY_UNSIGNED_LONGS_ENABLED
- : LIBRARY_UNSIGNED_LONGS_DISABLED;
+ static CelExtensionLibrary