From bff6518fea378fa13ed21cf07e360cdbc26366c5 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 19 Feb 2026 17:06:13 -0800 Subject: [PATCH] Fix error message for missing attribute PiperOrigin-RevId: 872631759 --- .../CelAttributeNotFoundException.java | 5 ++++ .../cel/runtime/planner/MissingAttribute.java | 30 +++++++++++++++---- .../planner/PresenceTestQualifier.java | 4 +-- .../runtime/planner/ProgramPlannerTest.java | 18 +++++++---- 4 files changed, 44 insertions(+), 13 deletions(-) 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/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java index 596d1bae4..02b04781c 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java @@ -22,10 +22,18 @@ final class MissingAttribute implements Attribute { private final ImmutableSet missingAttributes; + private final Kind kind; @Override public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { - throw CelAttributeNotFoundException.forFieldResolution(missingAttributes); + switch (kind) { + case ATTRIBUTE_NOT_FOUND: + throw CelAttributeNotFoundException.forMissingAttributes(missingAttributes); + case FIELD_NOT_FOUND: + throw CelAttributeNotFoundException.forFieldResolution(missingAttributes); + } + + throw new IllegalArgumentException("Unexpected kind: " + kind); } @Override @@ -33,15 +41,25 @@ 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, Kind.ATTRIBUTE_NOT_FOUND); } - static MissingAttribute newMissingAttribute(ImmutableSet attributeNames) { - return new MissingAttribute(attributeNames); + static MissingAttribute newMissingField(String... attributeNames) { + return newMissingField(ImmutableSet.copyOf(attributeNames)); } - private MissingAttribute(ImmutableSet missingAttributes) { + static MissingAttribute newMissingField(ImmutableSet attributeNames) { + return new MissingAttribute(attributeNames, Kind.FIELD_NOT_FOUND); + } + + private MissingAttribute(ImmutableSet missingAttributes, Kind kind) { this.missingAttributes = missingAttributes; + this.kind = kind; + } + + private enum Kind { + ATTRIBUTE_NOT_FOUND, + FIELD_NOT_FOUND } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PresenceTestQualifier.java b/runtime/src/main/java/dev/cel/runtime/planner/PresenceTestQualifier.java index 973182b9b..5c2cba1ed 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/PresenceTestQualifier.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/PresenceTestQualifier.java @@ -14,7 +14,7 @@ package dev.cel.runtime.planner; -import static dev.cel.runtime.planner.MissingAttribute.newMissingAttribute; +import static dev.cel.runtime.planner.MissingAttribute.newMissingField; import dev.cel.common.values.SelectableValue; import java.util.Map; @@ -40,7 +40,7 @@ public Object qualify(Object obj) { return map.containsKey(value); } - return newMissingAttribute(value.toString()); + return newMissingField(value.toString()); } static PresenceTestQualifier create(Object value) { 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 657fb5ab3..d1141777f 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -300,6 +300,15 @@ public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) t assertThat(result).isEqualTo(testCase.type); } + @Test + public void plan_ident_missingAttribute_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("int_var"); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); + assertThat(e).hasMessageThat().contains("evaluation error at :0: No such attribute(s)"); + } + @Test public void plan_ident_withContainer() throws Exception { CelAbstractSyntaxTree ast = compile("abbr.ident"); @@ -713,14 +722,13 @@ public void plan_select_onMapVariable() throws Exception { 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 "; + String errorMessage = "evaluation error at :7: No such attribute(s): "; 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'"; + "cel.expr.conformance.proto3.map_var, cel.expr.conformance.map_var, cel.expr.map_var," + + " cel.map_var, "; } + errorMessage += "map_var"; CelEvaluationException e = assertThrows(CelEvaluationException.class, () -> program.eval(ImmutableMap.of()));