Note: This is not to be confused with the notion of CEL Runtime. Use {@code
- * CelEvaluationException} instead to signify an evaluation error.
- *
- *
TODO: Make this class abstract and define specific exception classes that
- * corresponds to the CelErrorCode.
+ * CelEvaluationException} instead to signify an evaluation error. corresponds to the CelErrorCode.
*/
@Internal
public class CelRuntimeException extends RuntimeException {
private final CelErrorCode errorCode;
+ public CelRuntimeException(String errorMessage, CelErrorCode errorCode) {
+ super(errorMessage);
+ this.errorCode = errorCode;
+ }
+
public CelRuntimeException(Throwable cause, CelErrorCode errorCode) {
super(cause);
this.errorCode = errorCode;
diff --git a/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel b/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel
new file mode 100644
index 000000000..6bd1ad9ca
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel
@@ -0,0 +1,87 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
+package(
+ default_applicable_licenses = ["//:license"],
+ default_visibility = [
+ "//common/exceptions:__pkg__",
+ "//publish:__pkg__",
+ ],
+)
+
+java_library(
+ name = "attribute_not_found",
+ srcs = ["CelAttributeNotFoundException.java"],
+ # used_by_android
+ tags = [
+ ],
+ deps = [
+ "//common:error_codes",
+ "//common:runtime_exception",
+ "//common/annotations",
+ ],
+)
+
+java_library(
+ name = "divide_by_zero",
+ srcs = ["CelDivideByZeroException.java"],
+ # used_by_android
+ tags = [
+ ],
+ deps = [
+ "//common:error_codes",
+ "//common:runtime_exception",
+ "//common/annotations",
+ ],
+)
+
+java_library(
+ name = "index_out_of_bounds",
+ srcs = ["CelIndexOutOfBoundsException.java"],
+ # used_by_android
+ tags = [
+ ],
+ deps = [
+ "//common:error_codes",
+ "//common:runtime_exception",
+ "//common/annotations",
+ ],
+)
+
+java_library(
+ name = "bad_format",
+ srcs = ["CelBadFormatException.java"],
+ # used_by_android
+ tags = [
+ ],
+ deps = [
+ "//common:error_codes",
+ "//common:runtime_exception",
+ "//common/annotations",
+ ],
+)
+
+java_library(
+ name = "numeric_overflow",
+ srcs = ["CelNumericOverflowException.java"],
+ # used_by_android
+ tags = [
+ ],
+ deps = [
+ "//common:error_codes",
+ "//common:runtime_exception",
+ "//common/annotations",
+ ],
+)
+
+java_library(
+ name = "invalid_argument",
+ srcs = ["CelInvalidArgumentException.java"],
+ # used_by_android
+ tags = [
+ ],
+ deps = [
+ "//common:error_codes",
+ "//common:runtime_exception",
+ "//common/annotations",
+ ],
+)
diff --git a/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java b/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java
new file mode 100644
index 000000000..4aa43693d
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java
@@ -0,0 +1,57 @@
+// 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.exceptions;
+
+import dev.cel.common.CelErrorCode;
+import dev.cel.common.CelRuntimeException;
+import dev.cel.common.annotations.Internal;
+import java.util.Arrays;
+import java.util.Collection;
+
+/** Indicates an attempt to access a map or object using an invalid attribute or key. */
+@Internal
+public final class CelAttributeNotFoundException extends CelRuntimeException {
+
+ public static CelAttributeNotFoundException of(String message) {
+ return new CelAttributeNotFoundException(message);
+ }
+
+ public static CelAttributeNotFoundException forMissingMapKey(String key) {
+ return new CelAttributeNotFoundException(String.format("key '%s' is not present in map.", key));
+ }
+
+ public static CelAttributeNotFoundException forFieldResolution(String... fields) {
+ return forFieldResolution(Arrays.asList(fields));
+ }
+
+ public static CelAttributeNotFoundException forFieldResolution(Collection fields) {
+ return new CelAttributeNotFoundException(formatErrorMessage(fields));
+ }
+
+ private static String formatErrorMessage(Collection fields) {
+ String maybePlural = "";
+ if (fields.size() > 1) {
+ maybePlural = "s";
+ }
+
+ return String.format(
+ "Error resolving field%s '%s'. Field selections must be performed on messages or maps.",
+ maybePlural, String.join(", ", fields));
+ }
+
+ private CelAttributeNotFoundException(String message) {
+ super(message, CelErrorCode.ATTRIBUTE_NOT_FOUND);
+ }
+}
diff --git a/common/src/main/java/dev/cel/common/exceptions/CelBadFormatException.java b/common/src/main/java/dev/cel/common/exceptions/CelBadFormatException.java
new file mode 100644
index 000000000..92dde0101
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/exceptions/CelBadFormatException.java
@@ -0,0 +1,32 @@
+// 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.exceptions;
+
+import dev.cel.common.CelErrorCode;
+import dev.cel.common.CelRuntimeException;
+import dev.cel.common.annotations.Internal;
+
+/** Indicates that a data conversion failed due to a mismatch in the format specification. */
+@Internal
+public final class CelBadFormatException extends CelRuntimeException {
+
+ public CelBadFormatException(Throwable cause) {
+ super(cause, CelErrorCode.BAD_FORMAT);
+ }
+
+ public CelBadFormatException(String errorMessage) {
+ super(errorMessage, CelErrorCode.BAD_FORMAT);
+ }
+}
diff --git a/common/src/main/java/dev/cel/common/exceptions/CelDivideByZeroException.java b/common/src/main/java/dev/cel/common/exceptions/CelDivideByZeroException.java
new file mode 100644
index 000000000..d433d804c
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/exceptions/CelDivideByZeroException.java
@@ -0,0 +1,32 @@
+// 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.exceptions;
+
+import dev.cel.common.CelErrorCode;
+import dev.cel.common.CelRuntimeException;
+import dev.cel.common.annotations.Internal;
+
+/** Indicates that a division by zero occurred. */
+@Internal
+public final class CelDivideByZeroException extends CelRuntimeException {
+
+ public CelDivideByZeroException() {
+ super("/ by zero", CelErrorCode.DIVIDE_BY_ZERO);
+ }
+
+ public CelDivideByZeroException(Throwable cause) {
+ super(cause, CelErrorCode.DIVIDE_BY_ZERO);
+ }
+}
diff --git a/common/src/main/java/dev/cel/common/exceptions/CelIndexOutOfBoundsException.java b/common/src/main/java/dev/cel/common/exceptions/CelIndexOutOfBoundsException.java
new file mode 100644
index 000000000..3c2d0d03a
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/exceptions/CelIndexOutOfBoundsException.java
@@ -0,0 +1,28 @@
+// 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.exceptions;
+
+import dev.cel.common.CelErrorCode;
+import dev.cel.common.CelRuntimeException;
+import dev.cel.common.annotations.Internal;
+
+/** Indicates that a list index access was attempted using an index that is out of bounds. */
+@Internal
+public final class CelIndexOutOfBoundsException extends CelRuntimeException {
+
+ public CelIndexOutOfBoundsException(Object index) {
+ super("Index out of bounds: " + index, CelErrorCode.INDEX_OUT_OF_BOUNDS);
+ }
+}
diff --git a/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java b/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java
new file mode 100644
index 000000000..5bbaf6cab
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java
@@ -0,0 +1,28 @@
+// 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.exceptions;
+
+import dev.cel.common.CelErrorCode;
+import dev.cel.common.CelRuntimeException;
+import dev.cel.common.annotations.Internal;
+
+/** Indicates that an invalid argument was supplied to a function. */
+@Internal
+public final class CelInvalidArgumentException extends CelRuntimeException {
+
+ public CelInvalidArgumentException(Throwable cause) {
+ super(cause, CelErrorCode.INVALID_ARGUMENT);
+ }
+}
diff --git a/common/src/main/java/dev/cel/common/exceptions/CelNumericOverflowException.java b/common/src/main/java/dev/cel/common/exceptions/CelNumericOverflowException.java
new file mode 100644
index 000000000..439d1348f
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/exceptions/CelNumericOverflowException.java
@@ -0,0 +1,35 @@
+// 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.exceptions;
+
+import dev.cel.common.CelErrorCode;
+import dev.cel.common.CelRuntimeException;
+import dev.cel.common.annotations.Internal;
+
+/**
+ * Indicates that a numeric overflow occurred due to arithmetic operations or conversions resulting
+ * in a value outside the representable range.
+ */
+@Internal
+public final class CelNumericOverflowException extends CelRuntimeException {
+
+ public CelNumericOverflowException(String message) {
+ super(message, CelErrorCode.NUMERIC_OVERFLOW);
+ }
+
+ public CelNumericOverflowException(Throwable cause) {
+ super(cause, CelErrorCode.NUMERIC_OVERFLOW);
+ }
+}
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 3ab68c80e..c3f3727a1 100644
--- a/common/src/main/java/dev/cel/common/values/CelValueConverter.java
+++ b/common/src/main/java/dev/cel/common/values/CelValueConverter.java
@@ -33,7 +33,7 @@
@SuppressWarnings("unchecked") // Unchecked cast of generics due to type-erasure (ex: MapValue).
@Internal
@Immutable
-abstract class CelValueConverter {
+public abstract class CelValueConverter {
/** Adapts a {@link CelValue} to a plain old Java Object. */
public Object unwrap(CelValue celValue) {
@@ -72,8 +72,6 @@ public Object toRuntimeValue(Object value) {
.map(this::toRuntimeValue)
.map(OptionalValue::create)
.orElse(OptionalValue.EMPTY);
- } else if (value instanceof Exception) {
- return ErrorValue.create((Exception) value);
}
return normalizePrimitive(value);
diff --git a/common/src/main/java/dev/cel/common/values/ErrorValue.java b/common/src/main/java/dev/cel/common/values/ErrorValue.java
index 818f86850..6bc04cda4 100644
--- a/common/src/main/java/dev/cel/common/values/ErrorValue.java
+++ b/common/src/main/java/dev/cel/common/values/ErrorValue.java
@@ -33,6 +33,8 @@
"Immutable") // Exception is technically not immutable as the stacktrace is malleable.
public abstract class ErrorValue extends CelValue {
+ public abstract long exprId();
+
@Override
public abstract Exception value();
@@ -46,7 +48,7 @@ public CelType celType() {
return SimpleType.ERROR;
}
- public static ErrorValue create(Exception value) {
- return new AutoValue_ErrorValue(value);
+ public static ErrorValue create(long exprId, Exception value) {
+ return new AutoValue_ErrorValue(exprId, value);
}
}
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 e4d767ef4..308d7b510 100644
--- a/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java
+++ b/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java
@@ -34,15 +34,6 @@ public void toRuntimeValue_optionalValue() {
assertThat(optionalValue).isEqualTo(OptionalValue.create("test"));
}
- @Test
- public void toRuntimeValue_errorValue() {
- IllegalArgumentException e = new IllegalArgumentException("error");
-
- ErrorValue errorValue = (ErrorValue) CEL_VALUE_CONVERTER.toRuntimeValue(e);
-
- assertThat(errorValue.value()).isEqualTo(e);
- }
-
@Test
@SuppressWarnings("unchecked") // Test only
public void unwrap_optionalValue() {
diff --git a/common/src/test/java/dev/cel/common/values/ErrorValueTest.java b/common/src/test/java/dev/cel/common/values/ErrorValueTest.java
index 0db711f72..a6a4edb66 100644
--- a/common/src/test/java/dev/cel/common/values/ErrorValueTest.java
+++ b/common/src/test/java/dev/cel/common/values/ErrorValueTest.java
@@ -27,7 +27,7 @@ public class ErrorValueTest {
@Test
public void errorValue_construct() {
IllegalArgumentException exception = new IllegalArgumentException("test");
- ErrorValue opaqueValue = ErrorValue.create(exception);
+ ErrorValue opaqueValue = ErrorValue.create(0L, exception);
assertThat(opaqueValue.value()).isEqualTo(exception);
assertThat(opaqueValue.isZeroValue()).isFalse();
@@ -35,12 +35,12 @@ public void errorValue_construct() {
@Test
public void create_nullValue_throws() {
- assertThrows(NullPointerException.class, () -> ErrorValue.create(null));
+ assertThrows(NullPointerException.class, () -> ErrorValue.create(0L, null));
}
@Test
public void celTypeTest() {
- ErrorValue value = ErrorValue.create(new IllegalArgumentException("test"));
+ ErrorValue value = ErrorValue.create(0L, new IllegalArgumentException("test"));
assertThat(value.celType()).isEqualTo(SimpleType.ERROR);
}
diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel
index f3b60d4d7..7760d96b8 100644
--- a/runtime/BUILD.bazel
+++ b/runtime/BUILD.bazel
@@ -11,10 +11,10 @@ java_library(
exports = [
":evaluation_exception",
":late_function_binding",
+ ":metadata",
"//runtime/src/main/java/dev/cel/runtime",
"//runtime/src/main/java/dev/cel/runtime:descriptor_message_provider",
"//runtime/src/main/java/dev/cel/runtime:function_overload",
- "//runtime/src/main/java/dev/cel/runtime:metadata",
"//runtime/src/main/java/dev/cel/runtime:runtime_type_provider",
],
)
@@ -249,3 +249,9 @@ cel_android_library(
name = "program_android",
exports = ["//runtime/src/main/java/dev/cel/runtime:program_android"],
)
+
+java_library(
+ name = "metadata",
+ visibility = ["//:internal"],
+ exports = ["//runtime/src/main/java/dev/cel/runtime:metadata"],
+)
diff --git a/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java b/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java
index 6aaed4da7..6adbcb672 100644
--- a/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java
+++ b/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java
@@ -83,10 +83,16 @@ public static CelEvaluationExceptionBuilder newBuilder(String message, Object...
*/
@Internal
public static CelEvaluationExceptionBuilder newBuilder(CelRuntimeException celRuntimeException) {
+ // TODO: Temporary until migration is complete.
Throwable cause = celRuntimeException.getCause();
- return new CelEvaluationExceptionBuilder(cause.getMessage())
- .setCause(cause)
- .setErrorCode(celRuntimeException.getErrorCode());
+ String message =
+ cause == null
+ ? celRuntimeException.getMessage()
+ : celRuntimeException.getCause().getMessage();
+
+ return new CelEvaluationExceptionBuilder(message)
+ .setErrorCode(celRuntimeException.getErrorCode())
+ .setCause(cause);
}
private CelEvaluationExceptionBuilder(String message) {
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java
index 0ce487ceb..f20c9aadd 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java
@@ -14,64 +14,13 @@
package dev.cel.runtime.planner;
-
-import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.Immutable;
-import dev.cel.common.types.CelTypeProvider;
-import dev.cel.common.types.TypeType;
import dev.cel.runtime.GlobalResolver;
+/** Represents a resolvable symbol or path (such as a variable or a field selection). */
@Immutable
interface Attribute {
Object resolve(GlobalResolver ctx);
- final class MaybeAttribute implements Attribute {
- private final ImmutableList attributes;
-
- @Override
- public Object resolve(GlobalResolver ctx) {
- for (Attribute attr : attributes) {
- Object value = attr.resolve(ctx);
- if (value != null) {
- return value;
- }
- }
-
- // TODO: Handle unknowns
- throw new UnsupportedOperationException("Unknown attributes is not supported yet");
- }
-
- MaybeAttribute(ImmutableList attributes) {
- this.attributes = attributes;
- }
- }
-
- final class NamespacedAttribute implements Attribute {
- private final ImmutableList namespacedNames;
- private final CelTypeProvider typeProvider;
-
- @Override
- public Object resolve(GlobalResolver ctx) {
- for (String name : namespacedNames) {
- Object value = ctx.resolve(name);
- if (value != null) {
- // TODO: apply qualifiers
- return value;
- }
-
- TypeType type = typeProvider.findType(name).map(TypeType::create).orElse(null);
- if (type != null) {
- return type;
- }
- }
-
- // TODO: Handle unknowns
- throw new UnsupportedOperationException("Unknown attributes is not supported yet");
- }
-
- NamespacedAttribute(CelTypeProvider typeProvider, ImmutableList namespacedNames) {
- this.typeProvider = typeProvider;
- this.namespacedNames = namespacedNames;
- }
- }
+ Attribute addQualifier(Qualifier qualifier);
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java b/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java
index bda7c46a6..632c6cd91 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java
@@ -15,35 +15,46 @@
package dev.cel.runtime.planner;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.Immutable;
import dev.cel.common.CelContainer;
import dev.cel.common.types.CelTypeProvider;
-import dev.cel.runtime.planner.Attribute.MaybeAttribute;
-import dev.cel.runtime.planner.Attribute.NamespacedAttribute;
+import dev.cel.common.values.CelValueConverter;
@Immutable
final class AttributeFactory {
- private final CelContainer unusedContainer;
+ private final CelContainer container;
private final CelTypeProvider typeProvider;
+ private final CelValueConverter celValueConverter;
NamespacedAttribute newAbsoluteAttribute(String... names) {
- return new NamespacedAttribute(typeProvider, ImmutableList.copyOf(names));
+ return new NamespacedAttribute(typeProvider, celValueConverter, ImmutableSet.copyOf(names));
}
- MaybeAttribute newMaybeAttribute(String... names) {
- // TODO: Resolve container names
+ RelativeAttribute newRelativeAttribute(PlannedInterpretable operand) {
+ return new RelativeAttribute(operand, celValueConverter);
+ }
+
+ MaybeAttribute newMaybeAttribute(String name) {
return new MaybeAttribute(
- ImmutableList.of(new NamespacedAttribute(typeProvider, ImmutableList.copyOf(names))));
+ this,
+ ImmutableList.of(
+ new NamespacedAttribute(
+ typeProvider, celValueConverter, container.resolveCandidateNames(name))));
}
static AttributeFactory newAttributeFactory(
- CelContainer celContainer, CelTypeProvider typeProvider) {
- return new AttributeFactory(celContainer, typeProvider);
+ CelContainer celContainer,
+ CelTypeProvider typeProvider,
+ CelValueConverter celValueConverter) {
+ return new AttributeFactory(celContainer, typeProvider, celValueConverter);
}
- private AttributeFactory(CelContainer container, CelTypeProvider typeProvider) {
- this.unusedContainer = container;
+ private AttributeFactory(
+ CelContainer container, CelTypeProvider typeProvider, CelValueConverter celValueConverter) {
+ this.container = container;
this.typeProvider = typeProvider;
+ this.celValueConverter = celValueConverter;
}
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel
index 3ad0c0173..45936203a 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel
+++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel
@@ -14,6 +14,7 @@ java_library(
],
deps = [
":attribute",
+ ":error_metadata",
":eval_and",
":eval_attribute",
":eval_conditional",
@@ -25,7 +26,11 @@ java_library(
":eval_unary",
":eval_var_args_call",
":eval_zero_arity",
+ ":interpretable_attribute",
+ ":planned_interpretable",
":planned_program",
+ ":qualifier",
+ ":string_qualifier",
"//:auto_value",
"//common:cel_ast",
"//common:container",
@@ -34,11 +39,11 @@ java_library(
"//common/ast",
"//common/types",
"//common/types:type_providers",
+ "//common/values",
"//common/values:cel_value_provider",
"//runtime:dispatcher",
"//runtime:evaluation_exception",
"//runtime:evaluation_exception_builder",
- "//runtime:interpretable",
"//runtime:program",
"//runtime:resolved_overload",
"@maven//:com_google_code_findbugs_annotations",
@@ -51,6 +56,9 @@ java_library(
name = "planned_program",
srcs = ["PlannedProgram.java"],
deps = [
+ ":error_metadata",
+ ":planned_interpretable",
+ ":strict_error_exception",
"//:auto_value",
"//common:runtime_exception",
"//common/values",
@@ -68,6 +76,7 @@ java_library(
name = "eval_const",
srcs = ["EvalConstant.java"],
deps = [
+ ":planned_interpretable",
"//common/values",
"//common/values:cel_byte_string",
"//runtime:evaluation_listener",
@@ -78,27 +87,67 @@ java_library(
],
)
+java_library(
+ name = "interpretable_attribute",
+ srcs = ["InterpretableAttribute.java"],
+ deps = [
+ ":planned_interpretable",
+ ":qualifier",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ ],
+)
+
java_library(
name = "attribute",
srcs = [
"Attribute.java",
"AttributeFactory.java",
+ "MaybeAttribute.java",
+ "MissingAttribute.java",
+ "NamespacedAttribute.java",
+ "RelativeAttribute.java",
],
deps = [
+ ":eval_helpers",
+ ":planned_interpretable",
+ ":qualifier",
"//common:container",
+ "//common/exceptions:attribute_not_found",
"//common/types",
"//common/types:type_providers",
+ "//common/values",
+ "//common/values:cel_value",
"//runtime:interpretable",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
],
)
+java_library(
+ name = "qualifier",
+ srcs = ["Qualifier.java"],
+ deps = [
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ ],
+)
+
+java_library(
+ name = "string_qualifier",
+ srcs = ["StringQualifier.java"],
+ deps = [
+ ":qualifier",
+ "//common/exceptions:attribute_not_found",
+ "//common/values",
+ ],
+)
+
java_library(
name = "eval_attribute",
srcs = ["EvalAttribute.java"],
deps = [
":attribute",
+ ":interpretable_attribute",
+ ":qualifier",
"//runtime:evaluation_listener",
"//runtime:function_resolver",
"//runtime:interpretable",
@@ -111,6 +160,7 @@ java_library(
name = "eval_zero_arity",
srcs = ["EvalZeroArity.java"],
deps = [
+ ":planned_interpretable",
"//runtime:evaluation_exception",
"//runtime:evaluation_listener",
"//runtime:function_resolver",
@@ -123,6 +173,8 @@ java_library(
name = "eval_unary",
srcs = ["EvalUnary.java"],
deps = [
+ ":eval_helpers",
+ ":planned_interpretable",
"//runtime:evaluation_exception",
"//runtime:evaluation_listener",
"//runtime:function_resolver",
@@ -135,6 +187,8 @@ java_library(
name = "eval_var_args_call",
srcs = ["EvalVarArgsCall.java"],
deps = [
+ ":eval_helpers",
+ ":planned_interpretable",
"//runtime:evaluation_exception",
"//runtime:evaluation_listener",
"//runtime:function_resolver",
@@ -148,6 +202,7 @@ java_library(
srcs = ["EvalOr.java"],
deps = [
":eval_helpers",
+ ":planned_interpretable",
"//common/values",
"//runtime:evaluation_listener",
"//runtime:function_resolver",
@@ -161,6 +216,7 @@ java_library(
srcs = ["EvalAnd.java"],
deps = [
":eval_helpers",
+ ":planned_interpretable",
"//common/values",
"//runtime:evaluation_listener",
"//runtime:function_resolver",
@@ -173,6 +229,7 @@ java_library(
name = "eval_conditional",
srcs = ["EvalConditional.java"],
deps = [
+ ":planned_interpretable",
"//runtime:evaluation_exception",
"//runtime:evaluation_listener",
"//runtime:function_resolver",
@@ -185,6 +242,7 @@ java_library(
name = "eval_create_struct",
srcs = ["EvalCreateStruct.java"],
deps = [
+ ":planned_interpretable",
"//common/types",
"//common/values",
"//common/values:cel_value_provider",
@@ -201,6 +259,7 @@ java_library(
name = "eval_create_list",
srcs = ["EvalCreateList.java"],
deps = [
+ ":planned_interpretable",
"//runtime:evaluation_exception",
"//runtime:evaluation_listener",
"//runtime:function_resolver",
@@ -214,6 +273,7 @@ java_library(
name = "eval_create_map",
srcs = ["EvalCreateMap.java"],
deps = [
+ ":planned_interpretable",
"//runtime:evaluation_exception",
"//runtime:evaluation_listener",
"//runtime:function_resolver",
@@ -227,7 +287,39 @@ java_library(
name = "eval_helpers",
srcs = ["EvalHelpers.java"],
deps = [
+ ":planned_interpretable",
+ ":strict_error_exception",
+ "//common:error_codes",
+ "//common:runtime_exception",
"//common/values",
"//runtime:interpretable",
],
)
+
+java_library(
+ name = "strict_error_exception",
+ srcs = ["StrictErrorException.java"],
+ deps = [
+ "//common:error_codes",
+ "//common:runtime_exception",
+ ],
+)
+
+java_library(
+ name = "error_metadata",
+ srcs = ["ErrorMetadata.java"],
+ deps = [
+ "//runtime:metadata",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:com_google_guava_guava",
+ ],
+)
+
+java_library(
+ name = "planned_interpretable",
+ srcs = ["PlannedInterpretable.java"],
+ deps = [
+ "//runtime:interpretable",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ ],
+)
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ErrorMetadata.java b/runtime/src/main/java/dev/cel/runtime/planner/ErrorMetadata.java
new file mode 100644
index 000000000..2358bd0f9
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/planner/ErrorMetadata.java
@@ -0,0 +1,52 @@
+// 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.runtime.planner;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.errorprone.annotations.Immutable;
+import dev.cel.runtime.Metadata;
+
+@Immutable
+final class ErrorMetadata implements Metadata {
+
+ private final ImmutableMap exprIdToPositionMap;
+ private final String location;
+
+ @Override
+ public String getLocation() {
+ return location;
+ }
+
+ @Override
+ public int getPosition(long exprId) {
+ return exprIdToPositionMap.getOrDefault(exprId, 0);
+ }
+
+ @Override
+ public boolean hasPosition(long exprId) {
+ return exprIdToPositionMap.containsKey(exprId);
+ }
+
+ static ErrorMetadata create(ImmutableMap exprIdToPositionMap, String location) {
+ return new ErrorMetadata(exprIdToPositionMap, location);
+ }
+
+ private ErrorMetadata(ImmutableMap exprIdToPositionMap, String location) {
+ this.exprIdToPositionMap = checkNotNull(exprIdToPositionMap);
+ this.location = checkNotNull(location);
+ }
+}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java
index 4bf9af517..a3a39ce8a 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java
@@ -21,17 +21,16 @@
import dev.cel.runtime.CelEvaluationListener;
import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
-final class EvalAnd implements Interpretable {
+final class EvalAnd extends PlannedInterpretable {
@SuppressWarnings("Immutable")
- private final Interpretable[] args;
+ private final PlannedInterpretable[] args;
@Override
public Object eval(GlobalResolver resolver) {
ErrorValue errorValue = null;
- for (Interpretable arg : args) {
+ for (PlannedInterpretable arg : args) {
Object argVal = evalNonstrictly(arg, resolver);
if (argVal instanceof Boolean) {
// Short-circuit on false
@@ -75,11 +74,12 @@ public Object eval(
throw new UnsupportedOperationException("Not yet supported");
}
- static EvalAnd create(Interpretable[] args) {
- return new EvalAnd(args);
+ static EvalAnd create(long exprId, PlannedInterpretable[] args) {
+ return new EvalAnd(exprId, args);
}
- private EvalAnd(Interpretable[] args) {
+ private EvalAnd(long exprId, PlannedInterpretable[] args) {
+ super(exprId);
Preconditions.checkArgument(args.length == 2);
this.args = args;
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java
index 23b7fa63c..826f7e1fa 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java
@@ -18,16 +18,20 @@
import dev.cel.runtime.CelEvaluationListener;
import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
@Immutable
-final class EvalAttribute implements Interpretable {
+final class EvalAttribute extends InterpretableAttribute {
private final Attribute attr;
@Override
public Object eval(GlobalResolver resolver) {
- return attr.resolve(resolver);
+ Object resolved = attr.resolve(resolver);
+ if (resolved instanceof MissingAttribute) {
+ ((MissingAttribute) resolved).resolve(resolver);
+ }
+
+ return resolved;
}
@Override
@@ -51,11 +55,18 @@ public Object eval(
throw new UnsupportedOperationException("Not yet supported");
}
- static EvalAttribute create(Attribute attr) {
- return new EvalAttribute(attr);
+ @Override
+ public EvalAttribute addQualifier(long exprId, Qualifier qualifier) {
+ Attribute newAttribute = attr.addQualifier(qualifier);
+ return create(exprId, newAttribute);
+ }
+
+ static EvalAttribute create(long exprId, Attribute attr) {
+ return new EvalAttribute(exprId, attr);
}
- private EvalAttribute(Attribute attr) {
+ private EvalAttribute(long exprId, Attribute attr) {
+ super(exprId);
this.attr = attr;
}
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java
index ca32b405c..4445d3e71 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java
@@ -21,7 +21,7 @@
import dev.cel.runtime.GlobalResolver;
import dev.cel.runtime.Interpretable;
-final class EvalConditional implements Interpretable {
+final class EvalConditional extends PlannedInterpretable {
@SuppressWarnings("Immutable")
private final Interpretable[] args;
@@ -67,11 +67,12 @@ public Object eval(
throw new UnsupportedOperationException("Not yet supported");
}
- static EvalConditional create(Interpretable[] args) {
- return new EvalConditional(args);
+ static EvalConditional create(long exprId, Interpretable[] args) {
+ return new EvalConditional(exprId, args);
}
- private EvalConditional(Interpretable[] args) {
+ private EvalConditional(long exprId, Interpretable[] args) {
+ super(exprId);
Preconditions.checkArgument(args.length == 3);
this.args = args;
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java
index a1a2dc998..408d04046 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java
@@ -21,10 +21,9 @@
import dev.cel.runtime.CelEvaluationListener;
import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
@Immutable
-final class EvalConstant implements Interpretable {
+final class EvalConstant extends PlannedInterpretable {
// Pre-allocation of common constants
private static final EvalConstant NULL_VALUE = new EvalConstant(NullValue.NULL_VALUE);
@@ -115,6 +114,7 @@ static EvalConstant create(Object value) {
}
private EvalConstant(Object constant) {
+ super(/* exprId= */ -1); // It's not possible to throw while evaluating a constant
this.constant = constant;
}
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java
index 6a1917475..4ec275eef 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java
@@ -23,7 +23,7 @@
import dev.cel.runtime.Interpretable;
@Immutable
-final class EvalCreateList implements Interpretable {
+final class EvalCreateList extends PlannedInterpretable {
// Array contents are not mutated
@SuppressWarnings("Immutable")
@@ -51,17 +51,20 @@ public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctio
}
@Override
- public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver,
+ public Object eval(
+ GlobalResolver resolver,
+ CelFunctionResolver lateBoundFunctionResolver,
CelEvaluationListener listener) {
// TODO: Implement support
throw new UnsupportedOperationException("Not yet supported");
}
- static EvalCreateList create(Interpretable[] values) {
- return new EvalCreateList(values);
+ static EvalCreateList create(long exprId, Interpretable[] values) {
+ return new EvalCreateList(exprId, values);
}
- private EvalCreateList(Interpretable[] values) {
+ private EvalCreateList(long exprId, Interpretable[] values) {
+ super(exprId);
this.values = values;
}
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java
index a3fb2cb85..38d690303 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java
@@ -24,7 +24,7 @@
import dev.cel.runtime.Interpretable;
@Immutable
-final class EvalCreateMap implements Interpretable {
+final class EvalCreateMap extends PlannedInterpretable {
// Array contents are not mutated
@SuppressWarnings("Immutable")
@@ -58,18 +58,20 @@ public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctio
}
@Override
- public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver,
+ public Object eval(
+ GlobalResolver resolver,
+ CelFunctionResolver lateBoundFunctionResolver,
CelEvaluationListener listener) {
// TODO: Implement support
throw new UnsupportedOperationException("Not yet supported");
}
-
- static EvalCreateMap create(Interpretable[] keys, Interpretable[] values) {
- return new EvalCreateMap(keys, values);
+ static EvalCreateMap create(long exprId, Interpretable[] keys, Interpretable[] values) {
+ return new EvalCreateMap(exprId, keys, values);
}
- private EvalCreateMap(Interpretable[] keys, Interpretable[] values) {
+ private EvalCreateMap(long exprId, Interpretable[] keys, Interpretable[] values) {
+ super(exprId);
Preconditions.checkArgument(keys.length == values.length);
this.keys = keys;
this.values = values;
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java
index 5c1f1d77b..7553add80 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java
@@ -28,7 +28,7 @@
import java.util.Map;
@Immutable
-final class EvalCreateStruct implements Interpretable {
+final class EvalCreateStruct extends PlannedInterpretable {
private final CelValueProvider valueProvider;
private final StructType structType;
@@ -84,18 +84,21 @@ public Object eval(
}
static EvalCreateStruct create(
+ long exprId,
CelValueProvider valueProvider,
StructType structType,
String[] keys,
Interpretable[] values) {
- return new EvalCreateStruct(valueProvider, structType, keys, values);
+ return new EvalCreateStruct(exprId, valueProvider, structType, keys, values);
}
private EvalCreateStruct(
+ long exprId,
CelValueProvider valueProvider,
StructType structType,
String[] keys,
Interpretable[] values) {
+ super(exprId);
this.valueProvider = valueProvider;
this.structType = structType;
this.keys = keys;
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java
index 82bd6124a..3b5bda1bc 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java
@@ -14,17 +14,33 @@
package dev.cel.runtime.planner;
+import dev.cel.common.CelErrorCode;
+import dev.cel.common.CelRuntimeException;
import dev.cel.common.values.ErrorValue;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
final class EvalHelpers {
- static Object evalNonstrictly(Interpretable interpretable, GlobalResolver resolver) {
+ static Object evalNonstrictly(PlannedInterpretable interpretable, GlobalResolver resolver) {
try {
return interpretable.eval(resolver);
+ } catch (StrictErrorException e) {
+ // Intercept the strict exception to get a more localized expr ID for error reporting purposes
+ // Example: foo [1] && strict_err [2] -> ID 2 is propagated.
+ return ErrorValue.create(e.exprId(), e);
} catch (Exception e) {
- return ErrorValue.create(e);
+ return ErrorValue.create(interpretable.exprId(), e);
+ }
+ }
+
+ static Object evalStrictly(PlannedInterpretable interpretable, GlobalResolver resolver) {
+ try {
+ return interpretable.eval(resolver);
+ } catch (CelRuntimeException e) {
+ throw new StrictErrorException(e, interpretable.exprId());
+ } catch (Exception e) {
+ throw new StrictErrorException(
+ e.getCause(), CelErrorCode.INTERNAL_ERROR, interpretable.exprId());
}
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java
index afa02dfb8..f287bdd59 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java
@@ -21,17 +21,16 @@
import dev.cel.runtime.CelEvaluationListener;
import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
-final class EvalOr implements Interpretable {
+final class EvalOr extends PlannedInterpretable {
@SuppressWarnings("Immutable")
- private final Interpretable[] args;
+ private final PlannedInterpretable[] args;
@Override
public Object eval(GlobalResolver resolver) {
ErrorValue errorValue = null;
- for (Interpretable arg : args) {
+ for (PlannedInterpretable arg : args) {
Object argVal = evalNonstrictly(arg, resolver);
if (argVal instanceof Boolean) {
// Short-circuit on true
@@ -75,11 +74,12 @@ public Object eval(
throw new UnsupportedOperationException("Not yet supported");
}
- static EvalOr create(Interpretable[] args) {
- return new EvalOr(args);
+ static EvalOr create(long exprId, PlannedInterpretable[] args) {
+ return new EvalOr(exprId, args);
}
- private EvalOr(Interpretable[] args) {
+ private EvalOr(long exprId, PlannedInterpretable[] args) {
+ super(exprId);
Preconditions.checkArgument(args.length == 2);
this.args = args;
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java
index c6daff4b2..13b59d11e 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java
@@ -14,21 +14,24 @@
package dev.cel.runtime.planner;
+import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly;
+import static dev.cel.runtime.planner.EvalHelpers.evalStrictly;
+
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.CelEvaluationListener;
import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.CelResolvedOverload;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
-final class EvalUnary implements Interpretable {
+final class EvalUnary extends PlannedInterpretable {
private final CelResolvedOverload resolvedOverload;
- private final Interpretable arg;
+ private final PlannedInterpretable arg;
@Override
public Object eval(GlobalResolver resolver) throws CelEvaluationException {
- Object argVal = arg.eval(resolver);
+ Object argVal =
+ resolvedOverload.isStrict() ? evalStrictly(arg, resolver) : evalNonstrictly(arg, resolver);
Object[] arguments = new Object[] {argVal};
return resolvedOverload.getDefinition().apply(arguments);
@@ -55,11 +58,13 @@ public Object eval(
throw new UnsupportedOperationException("Not yet supported");
}
- static EvalUnary create(CelResolvedOverload resolvedOverload, Interpretable arg) {
- return new EvalUnary(resolvedOverload, arg);
+ static EvalUnary create(
+ long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable arg) {
+ return new EvalUnary(exprId, resolvedOverload, arg);
}
- private EvalUnary(CelResolvedOverload resolvedOverload, Interpretable arg) {
+ private EvalUnary(long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable arg) {
+ super(exprId);
this.resolvedOverload = resolvedOverload;
this.arg = arg;
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java
index 48fc7ba04..a2a4c0acc 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java
@@ -14,25 +14,30 @@
package dev.cel.runtime.planner;
+import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly;
+import static dev.cel.runtime.planner.EvalHelpers.evalStrictly;
+
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.CelEvaluationListener;
import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.CelResolvedOverload;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
@SuppressWarnings("Immutable")
-final class EvalVarArgsCall implements Interpretable {
+final class EvalVarArgsCall extends PlannedInterpretable {
private final CelResolvedOverload resolvedOverload;
- private final Interpretable[] args;
+ private final PlannedInterpretable[] args;
@Override
public Object eval(GlobalResolver resolver) throws CelEvaluationException {
Object[] argVals = new Object[args.length];
for (int i = 0; i < args.length; i++) {
- Interpretable arg = args[i];
- argVals[i] = arg.eval(resolver);
+ PlannedInterpretable arg = args[i];
+ argVals[i] =
+ resolvedOverload.isStrict()
+ ? evalStrictly(arg, resolver)
+ : evalNonstrictly(arg, resolver);
}
return resolvedOverload.getDefinition().apply(argVals);
@@ -59,11 +64,14 @@ public Object eval(
throw new UnsupportedOperationException("Not yet supported");
}
- static EvalVarArgsCall create(CelResolvedOverload resolvedOverload, Interpretable[] args) {
- return new EvalVarArgsCall(resolvedOverload, args);
+ static EvalVarArgsCall create(
+ long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable[] args) {
+ return new EvalVarArgsCall(exprId, resolvedOverload, args);
}
- private EvalVarArgsCall(CelResolvedOverload resolvedOverload, Interpretable[] args) {
+ private EvalVarArgsCall(
+ long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable[] args) {
+ super(exprId);
this.resolvedOverload = resolvedOverload;
this.args = args;
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java
index 813b84629..628e4a70f 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java
@@ -19,10 +19,8 @@
import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.CelResolvedOverload;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
-
-final class EvalZeroArity implements Interpretable {
+final class EvalZeroArity extends PlannedInterpretable {
private static final Object[] EMPTY_ARRAY = new Object[0];
private final CelResolvedOverload resolvedOverload;
@@ -53,11 +51,12 @@ public Object eval(
throw new UnsupportedOperationException("Not yet supported");
}
- static EvalZeroArity create(CelResolvedOverload resolvedOverload) {
- return new EvalZeroArity(resolvedOverload);
+ static EvalZeroArity create(long exprId, CelResolvedOverload resolvedOverload) {
+ return new EvalZeroArity(exprId, resolvedOverload);
}
- private EvalZeroArity(CelResolvedOverload resolvedOverload) {
+ private EvalZeroArity(long exprId, CelResolvedOverload resolvedOverload) {
+ super(exprId);
this.resolvedOverload = resolvedOverload;
}
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/InterpretableAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/InterpretableAttribute.java
new file mode 100644
index 000000000..547380c11
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/planner/InterpretableAttribute.java
@@ -0,0 +1,27 @@
+// 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.runtime.planner;
+
+import com.google.errorprone.annotations.Immutable;
+
+@Immutable
+abstract class InterpretableAttribute extends PlannedInterpretable {
+
+ abstract InterpretableAttribute addQualifier(long exprId, Qualifier qualifier);
+
+ InterpretableAttribute(long exprId) {
+ super(exprId);
+ }
+}
\ No newline at end of file
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java
new file mode 100644
index 000000000..542067349
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java
@@ -0,0 +1,81 @@
+// 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.runtime.planner;
+
+import com.google.common.collect.ImmutableList;
+import com.google.errorprone.annotations.Immutable;
+import dev.cel.runtime.GlobalResolver;
+
+/**
+ * An attribute that attempts to resolve a variable against a list of potential namespaced
+ * attributes. This is used during parsed-only evaluation.
+ */
+@Immutable
+final class MaybeAttribute implements Attribute {
+ private final AttributeFactory attrFactory;
+ private final ImmutableList attributes;
+
+ @Override
+ public Object resolve(GlobalResolver ctx) {
+ MissingAttribute maybeError = null;
+ for (NamespacedAttribute attr : attributes) {
+ Object value = attr.resolve(ctx);
+ if (value == null) {
+ continue;
+ }
+
+ if (value instanceof MissingAttribute) {
+ maybeError = (MissingAttribute) value;
+ // When the variable is missing in a maybe attribute, defer erroring.
+ // The variable may exist in other namespaced attributes.
+ continue;
+ }
+
+ return value;
+ }
+
+ return maybeError;
+ }
+
+ @Override
+ public Attribute addQualifier(Qualifier qualifier) {
+ Object strQualifier = qualifier.value();
+ ImmutableList.Builder augmentedNamesBuilder = ImmutableList.builder();
+ ImmutableList.Builder attributesBuilder = ImmutableList.builder();
+ for (NamespacedAttribute attr : attributes) {
+ if (strQualifier instanceof String && attr.qualifiers().isEmpty()) {
+ for (String varName : attr.candidateVariableNames()) {
+ augmentedNamesBuilder.add(varName + "." + strQualifier);
+ }
+ }
+
+ attributesBuilder.add(attr.addQualifier(qualifier));
+ }
+ ImmutableList augmentedNames = augmentedNamesBuilder.build();
+ ImmutableList.Builder namespacedAttributeBuilder = ImmutableList.builder();
+ if (!augmentedNames.isEmpty()) {
+ namespacedAttributeBuilder.add(
+ attrFactory.newAbsoluteAttribute(augmentedNames.toArray(new String[0])));
+ }
+
+ namespacedAttributeBuilder.addAll(attributesBuilder.build());
+ return new MaybeAttribute(attrFactory, namespacedAttributeBuilder.build());
+ }
+
+ MaybeAttribute(AttributeFactory attrFactory, ImmutableList attributes) {
+ this.attrFactory = attrFactory;
+ this.attributes = attributes;
+ }
+}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java
new file mode 100644
index 000000000..bbb4e0422
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java
@@ -0,0 +1,47 @@
+// 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.runtime.planner;
+
+import com.google.common.collect.ImmutableSet;
+import dev.cel.common.exceptions.CelAttributeNotFoundException;
+import dev.cel.runtime.GlobalResolver;
+
+/** Represents a missing attribute that is surfaced while resolving a struct field or a map key. */
+final class MissingAttribute implements Attribute {
+
+ private final ImmutableSet missingAttributes;
+
+ @Override
+ public Object resolve(GlobalResolver ctx) {
+ throw CelAttributeNotFoundException.forFieldResolution(missingAttributes);
+ }
+
+ @Override
+ public Attribute addQualifier(Qualifier qualifier) {
+ throw new UnsupportedOperationException("Unsupported operation");
+ }
+
+ static MissingAttribute newMissingAttribute(String... attributeNames) {
+ return newMissingAttribute(ImmutableSet.copyOf(attributeNames));
+ }
+
+ static MissingAttribute newMissingAttribute(ImmutableSet attributeNames) {
+ return new MissingAttribute(attributeNames);
+ }
+
+ private MissingAttribute(ImmutableSet missingAttributes) {
+ this.missingAttributes = missingAttributes;
+ }
+}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java
new file mode 100644
index 000000000..b90ac0824
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java
@@ -0,0 +1,126 @@
+// 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.runtime.planner;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.errorprone.annotations.Immutable;
+import dev.cel.common.types.CelType;
+import dev.cel.common.types.CelTypeProvider;
+import dev.cel.common.types.EnumType;
+import dev.cel.common.types.TypeType;
+import dev.cel.common.values.CelValue;
+import dev.cel.common.values.CelValueConverter;
+import dev.cel.runtime.GlobalResolver;
+import java.util.NoSuchElementException;
+
+@Immutable
+final class NamespacedAttribute implements Attribute {
+ private final ImmutableSet namespacedNames;
+ private final ImmutableList qualifiers;
+ private final CelValueConverter celValueConverter;
+ private final CelTypeProvider typeProvider;
+
+ @Override
+ public Object resolve(GlobalResolver ctx) {
+ for (String name : namespacedNames) {
+ Object value = ctx.resolve(name);
+ if (value != null) {
+ if (!qualifiers.isEmpty()) {
+ return applyQualifiers(value, celValueConverter, qualifiers);
+ } else {
+ return value;
+ }
+ }
+
+ CelType type = typeProvider.findType(name).orElse(null);
+ if (type != null) {
+ if (qualifiers.isEmpty()) {
+ // Resolution of a fully qualified type name: foo.bar.baz
+ return TypeType.create(type);
+ } else {
+ // This is potentially a fully qualified reference to an enum value
+ if (type instanceof EnumType && qualifiers.size() == 1) {
+ EnumType enumType = (EnumType) type;
+ String strQualifier = (String) qualifiers.get(0).value();
+ return enumType
+ .findNumberByName(strQualifier)
+ .orElseThrow(
+ () ->
+ new NoSuchElementException(
+ String.format(
+ "Field %s was not found on enum %s",
+ enumType.name(), strQualifier)));
+ }
+ }
+
+ throw new IllegalStateException(
+ "Unexpected type resolution when there were remaining qualifiers: " + type.name());
+ }
+ }
+
+ return MissingAttribute.newMissingAttribute(namespacedNames);
+ }
+
+ ImmutableList qualifiers() {
+ return qualifiers;
+ }
+
+ ImmutableSet candidateVariableNames() {
+ return namespacedNames;
+ }
+
+ @Override
+ public NamespacedAttribute addQualifier(Qualifier qualifier) {
+ return new NamespacedAttribute(
+ typeProvider,
+ celValueConverter,
+ namespacedNames,
+ ImmutableList.builder().addAll(qualifiers).add(qualifier).build());
+ }
+
+ private static Object applyQualifiers(
+ Object value, CelValueConverter celValueConverter, ImmutableList qualifiers) {
+ Object obj = celValueConverter.toRuntimeValue(value);
+
+ for (Qualifier qualifier : qualifiers) {
+ obj = qualifier.qualify(obj);
+ }
+
+ if (obj instanceof CelValue) {
+ obj = celValueConverter.unwrap((CelValue) obj);
+ }
+
+ return obj;
+ }
+
+ NamespacedAttribute(
+ CelTypeProvider typeProvider,
+ CelValueConverter celValueConverter,
+ ImmutableSet namespacedNames) {
+ this(typeProvider, celValueConverter, namespacedNames, ImmutableList.of());
+ }
+
+ private NamespacedAttribute(
+ CelTypeProvider typeProvider,
+ CelValueConverter celValueConverter,
+ ImmutableSet namespacedNames,
+ ImmutableList qualifiers) {
+ this.typeProvider = typeProvider;
+ this.celValueConverter = celValueConverter;
+ this.namespacedNames = namespacedNames;
+ this.qualifiers = qualifiers;
+ }
+}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java
new file mode 100644
index 000000000..87a1a7dc4
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java
@@ -0,0 +1,31 @@
+// 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.runtime.planner;
+
+import com.google.errorprone.annotations.Immutable;
+import dev.cel.runtime.Interpretable;
+
+@Immutable
+abstract class PlannedInterpretable implements Interpretable {
+ private final long exprId;
+
+ long exprId() {
+ return exprId;
+ }
+
+ PlannedInterpretable(long exprId) {
+ this.exprId = exprId;
+ }
+}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java
index 2c0d402c2..d1214fab0 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java
@@ -23,14 +23,15 @@
import dev.cel.runtime.CelEvaluationExceptionBuilder;
import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
import dev.cel.runtime.Program;
import java.util.Map;
@Immutable
@AutoValue
abstract class PlannedProgram implements Program {
- abstract Interpretable interpretable();
+ abstract PlannedInterpretable interpretable();
+
+ abstract ErrorMetadata metadata();
@Override
public Object eval() throws CelEvaluationException {
@@ -48,34 +49,36 @@ public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctio
throw new UnsupportedOperationException("Late bound functions not supported yet");
}
- private Object evalOrThrow(Interpretable interpretable, GlobalResolver resolver)
+ private Object evalOrThrow(PlannedInterpretable interpretable, GlobalResolver resolver)
throws CelEvaluationException {
try {
Object evalResult = interpretable.eval(resolver);
if (evalResult instanceof ErrorValue) {
ErrorValue errorValue = (ErrorValue) evalResult;
- throw newCelEvaluationException(errorValue.value());
+ throw newCelEvaluationException(errorValue.exprId(), errorValue.value());
}
return evalResult;
} catch (RuntimeException e) {
- throw newCelEvaluationException(e);
+ throw newCelEvaluationException(interpretable.exprId(), e);
}
}
- private static CelEvaluationException newCelEvaluationException(Exception e) {
+ private CelEvaluationException newCelEvaluationException(long exprId, Exception e) {
CelEvaluationExceptionBuilder builder;
- if (e instanceof CelRuntimeException) {
+ if (e instanceof StrictErrorException) {
// Preserve detailed error, including error codes if one exists.
+ builder = CelEvaluationExceptionBuilder.newBuilder((CelRuntimeException) e.getCause());
+ } else if (e instanceof CelRuntimeException) {
builder = CelEvaluationExceptionBuilder.newBuilder((CelRuntimeException) e);
} else {
builder = CelEvaluationExceptionBuilder.newBuilder(e.getMessage()).setCause(e);
}
- return builder.build();
+ return builder.setMetadata(metadata(), exprId).build();
}
- static Program create(Interpretable interpretable) {
- return new AutoValue_PlannedProgram(interpretable);
+ static Program create(PlannedInterpretable interpretable, ErrorMetadata metadata) {
+ return new AutoValue_PlannedProgram(interpretable, metadata);
}
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java
index bf7729c0f..be197649f 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java
@@ -37,12 +37,12 @@
import dev.cel.common.types.CelTypeProvider;
import dev.cel.common.types.StructType;
import dev.cel.common.types.TypeType;
+import dev.cel.common.values.CelValueConverter;
import dev.cel.common.values.CelValueProvider;
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.CelEvaluationExceptionBuilder;
import dev.cel.runtime.CelResolvedOverload;
import dev.cel.runtime.DefaultDispatcher;
-import dev.cel.runtime.Interpretable;
import dev.cel.runtime.Program;
import java.util.NoSuchElementException;
import java.util.Optional;
@@ -66,22 +66,26 @@ public final class ProgramPlanner {
* CelAbstractSyntaxTree}.
*/
public Program plan(CelAbstractSyntaxTree ast) throws CelEvaluationException {
- Interpretable plannedInterpretable;
+ PlannedInterpretable plannedInterpretable;
try {
plannedInterpretable = plan(ast.getExpr(), PlannerContext.create(ast));
} catch (RuntimeException e) {
throw CelEvaluationExceptionBuilder.newBuilder(e.getMessage()).setCause(e).build();
}
- return PlannedProgram.create(plannedInterpretable);
+ ErrorMetadata errorMetadata =
+ ErrorMetadata.create(ast.getSource().getPositionsMap(), ast.getSource().getDescription());
+ return PlannedProgram.create(plannedInterpretable, errorMetadata);
}
- private Interpretable plan(CelExpr celExpr, PlannerContext ctx) {
+ private PlannedInterpretable plan(CelExpr celExpr, PlannerContext ctx) {
switch (celExpr.getKind()) {
case CONSTANT:
return planConstant(celExpr.constant());
case IDENT:
return planIdent(celExpr, ctx);
+ case SELECT:
+ return planSelect(celExpr, ctx);
case CALL:
return planCall(celExpr, ctx);
case LIST:
@@ -97,7 +101,28 @@ private Interpretable plan(CelExpr celExpr, PlannerContext ctx) {
}
}
- private Interpretable planConstant(CelConstant celConstant) {
+ private PlannedInterpretable planSelect(CelExpr celExpr, PlannerContext ctx) {
+ CelSelect select = celExpr.select();
+ PlannedInterpretable operand = plan(select.operand(), ctx);
+
+ InterpretableAttribute attribute;
+ if (operand instanceof EvalAttribute) {
+ attribute = (EvalAttribute) operand;
+ } else {
+ attribute =
+ EvalAttribute.create(celExpr.id(), attributeFactory.newRelativeAttribute(operand));
+ }
+
+ if (select.testOnly()) {
+ throw new UnsupportedOperationException("Presence tests not supported yet");
+ }
+
+ Qualifier qualifier = StringQualifier.create(select.field());
+
+ return attribute.addQualifier(celExpr.id(), qualifier);
+ }
+
+ private PlannedInterpretable planConstant(CelConstant celConstant) {
switch (celConstant.getKind()) {
case NULL_VALUE:
return EvalConstant.create(celConstant.nullValue());
@@ -118,16 +143,17 @@ private Interpretable planConstant(CelConstant celConstant) {
}
}
- private Interpretable planIdent(CelExpr celExpr, PlannerContext ctx) {
+ private PlannedInterpretable planIdent(CelExpr celExpr, PlannerContext ctx) {
CelReference ref = ctx.referenceMap().get(celExpr.id());
if (ref != null) {
return planCheckedIdent(celExpr.id(), ref, ctx.typeMap());
}
- return EvalAttribute.create(attributeFactory.newMaybeAttribute(celExpr.ident().name()));
+ return EvalAttribute.create(
+ celExpr.id(), attributeFactory.newMaybeAttribute(celExpr.ident().name()));
}
- private Interpretable planCheckedIdent(
+ private PlannedInterpretable planCheckedIdent(
long id, CelReference identRef, ImmutableMap typeMap) {
if (identRef.value().isPresent()) {
return planConstant(identRef.value().get());
@@ -146,10 +172,10 @@ private Interpretable planCheckedIdent(
return EvalConstant.create(identType);
}
- return EvalAttribute.create(attributeFactory.newAbsoluteAttribute(identRef.name()));
+ return EvalAttribute.create(id, attributeFactory.newAbsoluteAttribute(identRef.name()));
}
- private Interpretable planCall(CelExpr expr, PlannerContext ctx) {
+ private PlannedInterpretable planCall(CelExpr expr, PlannerContext ctx) {
ResolvedFunction resolvedFunction = resolveFunction(expr, ctx.referenceMap());
CelExpr target = resolvedFunction.target().orElse(null);
int argCount = expr.call().args().size();
@@ -157,7 +183,7 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) {
argCount++;
}
- Interpretable[] evaluatedArgs = new Interpretable[argCount];
+ PlannedInterpretable[] evaluatedArgs = new PlannedInterpretable[argCount];
int offset = 0;
if (target != null) {
@@ -175,11 +201,11 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) {
if (operator != null) {
switch (operator) {
case LOGICAL_OR:
- return EvalOr.create(evaluatedArgs);
+ return EvalOr.create(expr.id(), evaluatedArgs);
case LOGICAL_AND:
- return EvalAnd.create(evaluatedArgs);
+ return EvalAnd.create(expr.id(), evaluatedArgs);
case CONDITIONAL:
- return EvalConditional.create(evaluatedArgs);
+ return EvalConditional.create(expr.id(), evaluatedArgs);
default:
// fall-through
}
@@ -200,21 +226,21 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) {
switch (argCount) {
case 0:
- return EvalZeroArity.create(resolvedOverload);
+ return EvalZeroArity.create(expr.id(), resolvedOverload);
case 1:
- return EvalUnary.create(resolvedOverload, evaluatedArgs[0]);
+ return EvalUnary.create(expr.id(), resolvedOverload, evaluatedArgs[0]);
default:
- return EvalVarArgsCall.create(resolvedOverload, evaluatedArgs);
+ return EvalVarArgsCall.create(expr.id(), resolvedOverload, evaluatedArgs);
}
}
- private Interpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) {
+ private PlannedInterpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) {
CelStruct struct = celExpr.struct();
StructType structType = resolveStructType(struct);
ImmutableList entries = struct.entries();
String[] keys = new String[entries.size()];
- Interpretable[] values = new Interpretable[entries.size()];
+ PlannedInterpretable[] values = new PlannedInterpretable[entries.size()];
for (int i = 0; i < entries.size(); i++) {
Entry entry = entries.get(i);
@@ -222,28 +248,28 @@ private Interpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) {
values[i] = plan(entry.value(), ctx);
}
- return EvalCreateStruct.create(valueProvider, structType, keys, values);
+ return EvalCreateStruct.create(celExpr.id(), valueProvider, structType, keys, values);
}
- private Interpretable planCreateList(CelExpr celExpr, PlannerContext ctx) {
+ private PlannedInterpretable planCreateList(CelExpr celExpr, PlannerContext ctx) {
CelList list = celExpr.list();
ImmutableList elements = list.elements();
- Interpretable[] values = new Interpretable[elements.size()];
+ PlannedInterpretable[] values = new PlannedInterpretable[elements.size()];
for (int i = 0; i < elements.size(); i++) {
values[i] = plan(elements.get(i), ctx);
}
- return EvalCreateList.create(values);
+ return EvalCreateList.create(celExpr.id(), values);
}
- private Interpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) {
+ private PlannedInterpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) {
CelMap map = celExpr.map();
ImmutableList entries = map.entries();
- Interpretable[] keys = new Interpretable[entries.size()];
- Interpretable[] values = new Interpretable[entries.size()];
+ PlannedInterpretable[] keys = new PlannedInterpretable[entries.size()];
+ PlannedInterpretable[] values = new PlannedInterpretable[entries.size()];
for (int i = 0; i < entries.size(); i++) {
CelMap.Entry entry = entries.get(i);
@@ -251,7 +277,7 @@ private Interpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) {
values[i] = plan(entry.value(), ctx);
}
- return EvalCreateMap.create(keys, values);
+ return EvalCreateMap.create(celExpr.id(), keys, values);
}
/**
@@ -400,19 +426,23 @@ public static ProgramPlanner newPlanner(
CelTypeProvider typeProvider,
CelValueProvider valueProvider,
DefaultDispatcher dispatcher,
+ CelValueConverter celValueConverter,
CelContainer container) {
- return new ProgramPlanner(typeProvider, valueProvider, dispatcher, container);
+ return new ProgramPlanner(
+ typeProvider, valueProvider, dispatcher, celValueConverter, container);
}
private ProgramPlanner(
CelTypeProvider typeProvider,
CelValueProvider valueProvider,
DefaultDispatcher dispatcher,
+ CelValueConverter celValueConverter,
CelContainer container) {
this.typeProvider = typeProvider;
this.valueProvider = valueProvider;
this.dispatcher = dispatcher;
this.container = container;
- this.attributeFactory = AttributeFactory.newAttributeFactory(container, typeProvider);
+ this.attributeFactory =
+ AttributeFactory.newAttributeFactory(container, typeProvider, celValueConverter);
}
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/Qualifier.java b/runtime/src/main/java/dev/cel/runtime/planner/Qualifier.java
new file mode 100644
index 000000000..82e48e95a
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/planner/Qualifier.java
@@ -0,0 +1,28 @@
+// 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.runtime.planner;
+
+import com.google.errorprone.annotations.Immutable;
+
+/**
+ * Represents a qualification step (such as a field selection or map key lookup) applied to an
+ * intermediate value during attribute resolution.
+ */
+@Immutable
+interface Qualifier {
+ Object value();
+
+ Object qualify(Object value);
+}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java
new file mode 100644
index 000000000..7357d8147
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java
@@ -0,0 +1,70 @@
+// 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.runtime.planner;
+
+import com.google.common.collect.ImmutableList;
+import com.google.errorprone.annotations.Immutable;
+import dev.cel.common.values.CelValueConverter;
+import dev.cel.runtime.GlobalResolver;
+
+/**
+ * An attribute resolved relative to a base expression (operand) by applying a sequence of
+ * qualifiers.
+ */
+@Immutable
+final class RelativeAttribute implements Attribute {
+
+ private final PlannedInterpretable operand;
+ private final CelValueConverter celValueConverter;
+ private final ImmutableList qualifiers;
+
+ @Override
+ public Object resolve(GlobalResolver ctx) {
+ Object obj = EvalHelpers.evalStrictly(operand, ctx);
+ obj = celValueConverter.toRuntimeValue(obj);
+
+ for (Qualifier qualifier : qualifiers) {
+ obj = qualifier.qualify(obj);
+ }
+
+ // TODO: Handle unknowns
+
+ return obj;
+ }
+
+ @Override
+ public Attribute addQualifier(Qualifier qualifier) {
+ return new RelativeAttribute(
+ this.operand,
+ celValueConverter,
+ ImmutableList.builderWithExpectedSize(qualifiers.size() + 1)
+ .addAll(this.qualifiers)
+ .add(qualifier)
+ .build());
+ }
+
+ RelativeAttribute(PlannedInterpretable operand, CelValueConverter celValueConverter) {
+ this(operand, celValueConverter, ImmutableList.of());
+ }
+
+ private RelativeAttribute(
+ PlannedInterpretable operand,
+ CelValueConverter celValueConverter,
+ ImmutableList qualifiers) {
+ this.operand = operand;
+ this.celValueConverter = celValueConverter;
+ this.qualifiers = qualifiers;
+ }
+}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/StrictErrorException.java b/runtime/src/main/java/dev/cel/runtime/planner/StrictErrorException.java
new file mode 100644
index 000000000..3acd6ff27
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/planner/StrictErrorException.java
@@ -0,0 +1,42 @@
+// 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.runtime.planner;
+
+import dev.cel.common.CelErrorCode;
+import dev.cel.common.CelRuntimeException;
+
+/**
+ * An exception that's raised when a strict call failed to invoke, which includes the source of
+ * expression ID, along with canonical CelErrorCode.
+ *
+ *
Note that StrictErrorException should not be surfaced directly back to the user.
+ */
+final class StrictErrorException extends CelRuntimeException {
+
+ private final long exprId;
+
+ long exprId() {
+ return exprId;
+ }
+
+ StrictErrorException(CelRuntimeException cause, long exprId) {
+ this(cause, cause.getErrorCode(), exprId);
+ }
+
+ StrictErrorException(Throwable cause, CelErrorCode errorCode, long exprId) {
+ super(cause, errorCode);
+ this.exprId = exprId;
+ }
+}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/StringQualifier.java b/runtime/src/main/java/dev/cel/runtime/planner/StringQualifier.java
new file mode 100644
index 000000000..4ceaa0e51
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/planner/StringQualifier.java
@@ -0,0 +1,61 @@
+// 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.runtime.planner;
+
+import dev.cel.common.exceptions.CelAttributeNotFoundException;
+import dev.cel.common.values.SelectableValue;
+import java.util.Map;
+
+/** A qualifier that accesses fields or map keys using a string identifier. */
+final class StringQualifier implements Qualifier {
+
+ private final String value;
+
+ @Override
+ public String value() {
+ return value;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked") // Qualifications on maps/structs must be a string
+ public Object qualify(Object obj) {
+ if (obj instanceof SelectableValue) {
+ return ((SelectableValue) obj).select(value);
+ } else if (obj instanceof Map) {
+ Map map = (Map) obj;
+ if (!map.containsKey(value)) {
+ throw CelAttributeNotFoundException.forMissingMapKey(value);
+ }
+
+ Object mapVal = map.get(value);
+
+ if (mapVal == null) {
+ throw CelAttributeNotFoundException.of(
+ String.format("Map value cannot be null for key: %s", value));
+ }
+ return map.get(value);
+ }
+
+ throw CelAttributeNotFoundException.forFieldResolution(value);
+ }
+
+ static StringQualifier create(String value) {
+ return new StringQualifier(value);
+ }
+
+ private StringQualifier(String value) {
+ this.value = value;
+ }
+}
diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel
index 9e3a79ed9..9e5855f54 100644
--- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel
+++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel
@@ -33,6 +33,7 @@ java_library(
"//common/values",
"//common/values:cel_byte_string",
"//common/values:cel_value_provider",
+ "//common/values:proto_message_value",
"//common/values:proto_message_value_provider",
"//compiler",
"//compiler:compiler_builder",
diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java
index 205e2ef8b..60b629f7b 100644
--- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java
+++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java
@@ -51,15 +51,19 @@
import dev.cel.common.types.OptionalType;
import dev.cel.common.types.ProtoMessageTypeProvider;
import dev.cel.common.types.SimpleType;
+import dev.cel.common.types.StructTypeReference;
import dev.cel.common.types.TypeType;
import dev.cel.common.values.CelByteString;
+import dev.cel.common.values.CelValueConverter;
import dev.cel.common.values.CelValueProvider;
import dev.cel.common.values.NullValue;
+import dev.cel.common.values.ProtoCelValueConverter;
import dev.cel.common.values.ProtoMessageValueProvider;
import dev.cel.compiler.CelCompiler;
import dev.cel.compiler.CelCompilerFactory;
import dev.cel.expr.conformance.proto3.GlobalEnum;
import dev.cel.expr.conformance.proto3.TestAllTypes;
+import dev.cel.expr.conformance.proto3.TestAllTypes.NestedMessage;
import dev.cel.extensions.CelExtensions;
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.CelFunctionBinding;
@@ -99,17 +103,26 @@ public final class ProgramPlannerTest {
private static final DynamicProto DYNAMIC_PROTO =
DynamicProto.create(DefaultMessageFactory.create(DESCRIPTOR_POOL));
private static final CelValueProvider VALUE_PROVIDER =
- ProtoMessageValueProvider.newInstance(CelOptions.DEFAULT, DYNAMIC_PROTO);
+ ProtoMessageValueProvider.newInstance(CEL_OPTIONS, DYNAMIC_PROTO);
+ private static final CelValueConverter CEL_VALUE_CONVERTER =
+ ProtoCelValueConverter.newInstance(DESCRIPTOR_POOL, DYNAMIC_PROTO);
private static final CelContainer CEL_CONTAINER =
- CelContainer.newBuilder().setName("cel.expr.conformance.proto3").build();
+ CelContainer.newBuilder()
+ .setName("cel.expr.conformance.proto3")
+ .addAbbreviations("really.long.abbr")
+ .build();
private static final ProgramPlanner PLANNER =
- ProgramPlanner.newPlanner(TYPE_PROVIDER, VALUE_PROVIDER, newDispatcher(), CEL_CONTAINER);
+ ProgramPlanner.newPlanner(
+ TYPE_PROVIDER, VALUE_PROVIDER, newDispatcher(), CEL_VALUE_CONVERTER, CEL_CONTAINER);
+
private static final CelCompiler CEL_COMPILER =
CelCompilerFactory.standardCelCompilerBuilder()
+ .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName()))
.addVar("map_var", MapType.create(SimpleType.STRING, SimpleType.DYN))
.addVar("int_var", SimpleType.INT)
.addVar("dyn_var", SimpleType.DYN)
+ .addVar("really.long.abbr.ident", SimpleType.DYN)
.addFunctionDeclarations(
newFunctionDeclaration("zero", newGlobalOverload("zero_overload", SimpleType.INT)),
newFunctionDeclaration("error", newGlobalOverload("error_overload", SimpleType.INT)),
@@ -296,10 +309,6 @@ public void plan_constant(@TestParameter ConstantTestCase testCase) throws Excep
@Test
public void plan_ident_enum() throws Exception {
- if (isParseOnly) {
- // TODO Skip for now, requires attribute qualification
- return;
- }
CelAbstractSyntaxTree ast =
compile(GlobalEnum.getDescriptor().getFullName() + "." + GlobalEnum.GAR);
Program program = PLANNER.plan(ast);
@@ -321,14 +330,6 @@ public void plan_ident_variable() throws Exception {
@Test
public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) throws Exception {
- if (isParseOnly) {
- if (testCase.equals(TypeLiteralTestCase.DURATION)
- || testCase.equals(TypeLiteralTestCase.TIMESTAMP)
- || testCase.equals(TypeLiteralTestCase.PROTO_MESSAGE_TYPE)) {
- // TODO Skip for now, requires attribute qualification
- return;
- }
- }
CelAbstractSyntaxTree ast = compile(testCase.expression);
Program program = PLANNER.plan(ast);
@@ -337,6 +338,16 @@ public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) t
assertThat(result).isEqualTo(testCase.type);
}
+ @Test
+ public void plan_ident_withContainer() throws Exception {
+ CelAbstractSyntaxTree ast = compile("abbr.ident");
+ Program program = PLANNER.plan(ast);
+
+ Object result = program.eval(ImmutableMap.of("really.long.abbr.ident", 1L));
+
+ assertThat(result).isEqualTo(1);
+ }
+
@Test
@SuppressWarnings("unchecked") // test only
public void plan_createList() throws Exception {
@@ -421,7 +432,7 @@ public void plan_call_throws() throws Exception {
Program program = PLANNER.plan(ast);
CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval);
- assertThat(e).hasMessageThat().contains("evaluation error: Intentional error");
+ assertThat(e).hasMessageThat().contains("evaluation error at :5: Intentional error");
assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
}
@@ -514,8 +525,8 @@ public void plan_call_logicalOr_throws(String expression) throws Exception {
Program program = PLANNER.plan(ast);
CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval);
- // TODO: Tag metadata (source loc)
- assertThat(e).hasMessageThat().isEqualTo("evaluation error: / by zero");
+ assertThat(e).hasMessageThat().startsWith("evaluation error at :");
+ assertThat(e).hasMessageThat().endsWith("/ by zero");
assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class);
assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO);
}
@@ -546,8 +557,8 @@ public void plan_call_logicalAnd_throws(String expression) throws Exception {
Program program = PLANNER.plan(ast);
CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval);
- // TODO: Tag metadata (source loc)
- assertThat(e).hasMessageThat().isEqualTo("evaluation error: / by zero");
+ assertThat(e).hasMessageThat().startsWith("evaluation error at :");
+ assertThat(e).hasMessageThat().endsWith("/ by zero");
assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class);
assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO);
}
@@ -576,7 +587,8 @@ public void plan_call_conditional_throws(String expression) throws Exception {
Program program = PLANNER.plan(ast);
CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval);
- assertThat(e).hasMessageThat().isEqualTo("evaluation error: / by zero");
+ assertThat(e).hasMessageThat().startsWith("evaluation error at :");
+ assertThat(e).hasMessageThat().endsWith("/ by zero");
assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class);
assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO);
}
@@ -596,6 +608,140 @@ public void plan_call_withContainer(String expression) throws Exception {
assertThat(result).isEqualTo(8);
}
+ @Test
+ public void plan_select_protoMessageField() throws Exception {
+ CelAbstractSyntaxTree ast = compile("msg.single_string");
+ Program program = PLANNER.plan(ast);
+
+ String result =
+ (String)
+ program.eval(
+ ImmutableMap.of("msg", TestAllTypes.newBuilder().setSingleString("foo").build()));
+
+ assertThat(result).isEqualTo("foo");
+ }
+
+ @Test
+ public void plan_select_nestedProtoMessage() throws Exception {
+ CelAbstractSyntaxTree ast = compile("msg.single_nested_message");
+ NestedMessage nestedMessage = NestedMessage.newBuilder().setBb(42).build();
+ Program program = PLANNER.plan(ast);
+
+ Object result =
+ program.eval(
+ ImmutableMap.of(
+ "msg", TestAllTypes.newBuilder().setSingleNestedMessage(nestedMessage).build()));
+
+ assertThat(result).isEqualTo(nestedMessage);
+ }
+
+ @Test
+ public void plan_select_nestedProtoMessageField() throws Exception {
+ CelAbstractSyntaxTree ast = compile("msg.single_nested_message.bb");
+ Program program = PLANNER.plan(ast);
+
+ Object result =
+ program.eval(
+ ImmutableMap.of(
+ "msg",
+ TestAllTypes.newBuilder()
+ .setSingleNestedMessage(NestedMessage.newBuilder().setBb(42))
+ .build()));
+
+ assertThat(result).isEqualTo(42);
+ }
+
+ @Test
+ public void plan_select_safeTraversal() throws Exception {
+ CelAbstractSyntaxTree ast = compile("msg.single_nested_message.bb");
+ Program program = PLANNER.plan(ast);
+
+ Object result = program.eval(ImmutableMap.of("msg", TestAllTypes.newBuilder().build()));
+
+ assertThat(result).isEqualTo(0L);
+ }
+
+ @Test
+ public void plan_select_onCreateStruct() throws Exception {
+ CelAbstractSyntaxTree ast =
+ compile("cel.expr.conformance.proto3.TestAllTypes{ single_string: 'foo'}.single_string");
+ Program program = PLANNER.plan(ast);
+
+ Object result = program.eval();
+
+ assertThat(result).isEqualTo("foo");
+ }
+
+ @Test
+ public void plan_select_onCreateMap() throws Exception {
+ CelAbstractSyntaxTree ast = compile("{'foo':'bar'}.foo");
+ Program program = PLANNER.plan(ast);
+
+ Object result = program.eval();
+
+ assertThat(result).isEqualTo("bar");
+ }
+
+ @Test
+ public void plan_select_onMapVariable() throws Exception {
+ CelAbstractSyntaxTree ast = compile("map_var.foo");
+ Program program = PLANNER.plan(ast);
+
+ Object result = program.eval(ImmutableMap.of("map_var", ImmutableMap.of("foo", 42L)));
+
+ assertThat(result).isEqualTo(42L);
+ }
+
+ @Test
+ public void plan_select_mapVarInputMissing_throws() throws Exception {
+ CelAbstractSyntaxTree ast = compile("map_var.foo");
+ Program program = PLANNER.plan(ast);
+ String errorMessage = "evaluation error at :7: Error resolving ";
+ if (isParseOnly) {
+ errorMessage +=
+ "fields 'cel.expr.conformance.proto3.map_var, cel.expr.conformance.map_var,"
+ + " cel.expr.map_var, cel.map_var, map_var'";
+ } else {
+ errorMessage += "field 'map_var'";
+ }
+
+ CelEvaluationException e =
+ assertThrows(CelEvaluationException.class, () -> program.eval(ImmutableMap.of()));
+
+ assertThat(e).hasMessageThat().contains(errorMessage);
+ }
+
+ @Test
+ public void plan_select_mapVarKeyMissing_throws() throws Exception {
+ CelAbstractSyntaxTree ast = compile("map_var.foo");
+ Program program = PLANNER.plan(ast);
+
+ CelEvaluationException e =
+ assertThrows(
+ CelEvaluationException.class,
+ () -> program.eval(ImmutableMap.of("map_var", ImmutableMap.of())));
+ assertThat(e)
+ .hasMessageThat()
+ .contains("evaluation error at :7: key 'foo' is not present in map");
+ }
+
+ @Test
+ public void plan_select_stringQualificationFail_throws() throws Exception {
+ CelAbstractSyntaxTree ast = compile("map_var.foo");
+ Program program = PLANNER.plan(ast);
+
+ CelEvaluationException e =
+ assertThrows(
+ CelEvaluationException.class,
+ () -> program.eval(ImmutableMap.of("map_var", "bogus string")));
+
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ "evaluation error at :7: Error resolving field 'foo'. Field selections must be"
+ + " performed on messages or maps.");
+ }
+
private CelAbstractSyntaxTree compile(String expression) throws Exception {
CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst();
if (isParseOnly) {