Skip to content

Commit 9bab28b

Browse files
l46kokcopybara-github
authored andcommitted
Specify better error message and code for invalid field selections
PiperOrigin-RevId: 625085969
1 parent 8b68baf commit 9bab28b

File tree

6 files changed

+77
-15
lines changed

6 files changed

+77
-15
lines changed

runtime/src/main/java/dev/cel/runtime/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,9 @@ java_library(
220220
srcs = ["RuntimeTypeProviderLegacyImpl.java"],
221221
deps = [
222222
":unknown_attributes",
223+
"//common:error_codes",
223224
"//common:options",
225+
"//common:runtime_exception",
224226
"//common/annotations",
225227
"//common/internal:cel_descriptor_pools",
226228
"//common/internal:dynamic_proto",

runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ public Object selectField(Object message, String fieldName) {
168168
}
169169
}
170170

171-
MessageOrBuilder typedMessage = assertFullProtoMessage(message);
171+
MessageOrBuilder typedMessage = assertFullProtoMessage(message, fieldName);
172172
FieldDescriptor fieldDescriptor = findField(typedMessage.getDescriptorForType(), fieldName);
173173
// check whether the field is a wrapper type, then test has and return null
174174
if (isWrapperType(fieldDescriptor) && !typedMessage.hasField(fieldDescriptor)) {
@@ -202,7 +202,7 @@ public Object hasField(Object message, String fieldName) {
202202
return map.containsKey(fieldName);
203203
}
204204

205-
MessageOrBuilder typedMessage = assertFullProtoMessage(message);
205+
MessageOrBuilder typedMessage = assertFullProtoMessage(message, fieldName);
206206
FieldDescriptor fieldDescriptor = findField(typedMessage.getDescriptorForType(), fieldName);
207207
if (fieldDescriptor.isRepeated()) {
208208
return typedMessage.getRepeatedFieldCount(fieldDescriptor) > 0;
@@ -228,16 +228,16 @@ private FieldDescriptor findField(Descriptor descriptor, String fieldName) {
228228
return fieldDescriptor;
229229
}
230230

231-
private static MessageOrBuilder assertFullProtoMessage(Object candidate) {
231+
private static MessageOrBuilder assertFullProtoMessage(Object candidate, String fieldName) {
232232
if (!(candidate instanceof MessageOrBuilder)) {
233-
// This is an internal error. It should not happen for type checked expressions.
233+
// This can happen when the field selection is done on dyn, and it is not a message.
234234
throw new CelRuntimeException(
235-
new IllegalStateException(
235+
new IllegalArgumentException(
236236
String.format(
237-
"[internal] expected an instance of 'com.google.protobuf.MessageOrBuilder' "
238-
+ "but found '%s'",
239-
candidate.getClass().getName())),
240-
CelErrorCode.INTERNAL_ERROR);
237+
"Error resolving field '%s'. Field selections must be performed on messages or"
238+
+ " maps.",
239+
fieldName)),
240+
CelErrorCode.ATTRIBUTE_NOT_FOUND);
241241
}
242242
return (MessageOrBuilder) candidate;
243243
}

runtime/src/main/java/dev/cel/runtime/RuntimeTypeProviderLegacyImpl.java

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import com.google.common.annotations.VisibleForTesting;
2020
import com.google.common.base.Preconditions;
2121
import com.google.errorprone.annotations.Immutable;
22+
import dev.cel.common.CelErrorCode;
2223
import dev.cel.common.CelOptions;
24+
import dev.cel.common.CelRuntimeException;
2325
import dev.cel.common.annotations.Internal;
2426
import dev.cel.common.internal.CelDescriptorPool;
2527
import dev.cel.common.internal.DynamicProto;
@@ -69,17 +71,37 @@ public Object createMessage(String messageName, Map<String, Object> values) {
6971
@Override
7072
@SuppressWarnings("unchecked")
7173
public Object selectField(Object message, String fieldName) {
72-
SelectableValue<CelValue> selectableValue =
73-
(SelectableValue<CelValue>) protoCelValueConverter.fromJavaObjectToCelValue(message);
74+
CelValue convertedCelValue = protoCelValueConverter.fromJavaObjectToCelValue(message);
75+
if (!(convertedCelValue instanceof SelectableValue)) {
76+
throw new CelRuntimeException(
77+
new IllegalArgumentException(
78+
String.format(
79+
"Error resolving field '%s'. Field selections must be performed on messages or"
80+
+ " maps.",
81+
fieldName)),
82+
CelErrorCode.ATTRIBUTE_NOT_FOUND);
83+
}
84+
85+
SelectableValue<CelValue> selectableValue = (SelectableValue<CelValue>) convertedCelValue;
7486

7587
return unwrapCelValue(selectableValue.select(StringValue.create(fieldName)));
7688
}
7789

7890
@Override
7991
@SuppressWarnings("unchecked")
8092
public Object hasField(Object message, String fieldName) {
81-
SelectableValue<CelValue> selectableValue =
82-
(SelectableValue<CelValue>) protoCelValueConverter.fromJavaObjectToCelValue(message);
93+
CelValue convertedCelValue = protoCelValueConverter.fromJavaObjectToCelValue(message);
94+
if (!(convertedCelValue instanceof SelectableValue)) {
95+
throw new CelRuntimeException(
96+
new IllegalArgumentException(
97+
String.format(
98+
"Error resolving field '%s'. Field selections must be performed on messages or"
99+
+ " maps.",
100+
fieldName)),
101+
CelErrorCode.ATTRIBUTE_NOT_FOUND);
102+
}
103+
104+
SelectableValue<CelValue> selectableValue = (SelectableValue<CelValue>) convertedCelValue;
83105

84106
return selectableValue.find(StringValue.create(fieldName)).isPresent();
85107
}

runtime/src/test/java/dev/cel/runtime/DescriptorMessageProviderTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,8 @@ public void selectField_nonProtoObjectError() {
165165
CelRuntimeException e =
166166
Assert.assertThrows(
167167
CelRuntimeException.class, () -> provider.selectField("hello", "not_a_field"));
168-
assertThat(e).hasCauseThat().isInstanceOf(IllegalStateException.class);
169-
assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.INTERNAL_ERROR);
168+
assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
169+
assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.ATTRIBUTE_NOT_FOUND);
170170
}
171171

172172
@Test
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Source: dyn('hello').invalid
2+
=====>
3+
bindings: {}
4+
error: evaluation error at test_location:12: Error resolving field 'invalid'. Field selections must be performed on messages or maps.
5+
error_code: ATTRIBUTE_NOT_FOUND
6+
7+
Source: has(dyn('hello').invalid)
8+
=====>
9+
bindings: {}
10+
error: evaluation error at test_location:3: Error resolving field 'invalid'. Field selections must be performed on messages or maps.
11+
error_code: ATTRIBUTE_NOT_FOUND
12+
13+
Source: dyn([]).invalid
14+
=====>
15+
bindings: {}
16+
error: evaluation error at test_location:7: Error resolving field 'invalid'. Field selections must be performed on messages or maps.
17+
error_code: ATTRIBUTE_NOT_FOUND
18+
19+
Source: has(dyn([]).invalid)
20+
=====>
21+
bindings: {}
22+
error: evaluation error at test_location:3: Error resolving field 'invalid'. Field selections must be performed on messages or maps.
23+
error_code: ATTRIBUTE_NOT_FOUND

testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1600,6 +1600,21 @@ public void dynConversions() throws Exception {
16001600
runTest(Activation.EMPTY);
16011601
}
16021602

1603+
@Test
1604+
public void dyn_error() throws Exception {
1605+
source = "dyn('hello').invalid";
1606+
runTest(Activation.EMPTY);
1607+
1608+
source = "has(dyn('hello').invalid)";
1609+
runTest(Activation.EMPTY);
1610+
1611+
source = "dyn([]).invalid";
1612+
runTest(Activation.EMPTY);
1613+
1614+
source = "has(dyn([]).invalid)";
1615+
runTest(Activation.EMPTY);
1616+
}
1617+
16031618
// This lambda implements @Immutable interface 'Function', but 'InterpreterTest' has field 'eval'
16041619
// of type 'com.google.api.expr.cel.testing.Eval', the declaration of
16051620
// type

0 commit comments

Comments
 (0)