From 81448a4d0c19709f465d933103e82e0064e19b63 Mon Sep 17 00:00:00 2001 From: Andrew Parmet Date: Sat, 9 May 2026 09:51:53 -0400 Subject: [PATCH 1/3] Handle FIXED32/FIXED64 as unsigned in ProtoCelValueConverter The CEL spec maps proto fixed32/fixed64 to CEL's uint. cel-java's checker (DescriptorMappings) and its legacy DescriptorMessageProvider path (ProtoAdapter) both correctly map them. The CelValue path via ProtoCelValueConverter.fromProtoMessageFieldToCelValue missed FIXED32/FIXED64 and fell through to normalizePrimitive, producing Integer/Long. Overload resolution at runtime then failed with "No matching overload" when the checker said uint but the runtime served a signed integer. Add the missing case labels alongside UINT32/UINT64 and extend the ProtoMessageValue test suite to cover fixed32/fixed64 selection. --- .../common/values/ProtoCelValueConverter.java | 2 ++ .../common/values/ProtoMessageValueTest.java | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java index 948df759c..89e2e029c 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java @@ -154,11 +154,13 @@ public Object fromProtoMessageFieldToCelValue(Message message, FieldDescriptor f return toRuntimeValue(result); case UINT32: + case FIXED32: if (!fieldDescriptor.isRepeated()) { return UnsignedLong.valueOf((int) result); } break; case UINT64: + case FIXED64: if (!fieldDescriptor.isRepeated()) { return UnsignedLong.fromLongBits((long) result); } diff --git a/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java b/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java index 365dd32b4..eca2cf8e9 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java @@ -303,6 +303,35 @@ public void selectField_durationOutOfRange_success(int seconds, int nanos) { .isEqualTo(Duration.ofSeconds(seconds, nanos)); } + // The CEL spec maps proto's fixed32/fixed64 to CEL's uint. cel-java's checker (via + // DescriptorMappings) and its legacy DescriptorMessageProvider (via ProtoAdapter) both honor + // this. The CelValue path via ProtoCelValueConverter.fromProtoMessageFieldToCelValue used to + // miss FIXED32/FIXED64, falling through to normalizePrimitive and producing Integer/Long. That + // caused overload resolution to fail at runtime with "No matching overload" when the checker + // said uint but the runtime served a signed integer. + @Test + public void selectField_fixed32_returnsUnsignedLong() { + TestAllTypes testAllTypes = TestAllTypes.newBuilder().setSingleFixed32(1).build(); + + ProtoMessageValue protoMessageValue = + ProtoMessageValue.create( + testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false); + + assertThat(protoMessageValue.select("single_fixed32")).isEqualTo(UnsignedLong.valueOf(1L)); + } + + @Test + public void selectField_fixed64_returnsUnsignedLong() { + TestAllTypes testAllTypes = + TestAllTypes.newBuilder().setSingleFixed64(UnsignedLong.MAX_VALUE.longValue()).build(); + + ProtoMessageValue protoMessageValue = + ProtoMessageValue.create( + testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false); + + assertThat(protoMessageValue.select("single_fixed64")).isEqualTo(UnsignedLong.MAX_VALUE); + } + @SuppressWarnings("ImmutableEnumChecker") // Test only private enum SelectFieldJsonValueTestCase { NULL(Value.newBuilder().build(), NullValue.NULL_VALUE), From c73558cc24248f61b885fd718333811b37944e50 Mon Sep 17 00:00:00 2001 From: Andrew Parmet Date: Sat, 9 May 2026 09:53:24 -0400 Subject: [PATCH 2/3] Preserve FieldMask as a message in the CelValue runtime path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BaseProtoCelValueConverter.fromWellKnownProto converts FieldMask to a comma-separated string of its paths. That's correct in JSON-assignment contexts (handled separately by CelProtoJsonAdapter), but it breaks CEL expressions that access fields on the FieldMask itself — e.g. `fieldMask.paths` — because the string has no field `paths`. The CEL spec's WKT conversion table does not list FieldMask. cel-go treats it as a regular message, as does cel-java's legacy ProtoLiteAdapter.adaptValueToWellKnownProto for non-JSON contexts. Override fromWellKnownProto in ProtoCelValueConverter to preserve FieldMask as a ProtoMessageValue. JSON-assignment paths (e.g. TestAllTypes{single_value: FieldMask{...}}) are unaffected because EvalCreateStruct and CelValueRuntimeTypeProvider.createMessage both unwrap StructValue results before the outer assignment runs, and the outer assignment goes through CelProtoJsonAdapter.adaptValueToJsonValue which handles FieldMask → string conversion at that layer. --- .../common/values/ProtoCelValueConverter.java | 12 ++++++++++ .../common/values/ProtoMessageValueTest.java | 24 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java index 89e2e029c..18003a242 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java @@ -71,6 +71,18 @@ protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wel "Unpacking failed for message: " + message.getDescriptorForType().getFullName(), e); } return toRuntimeValue(unpackedMessage); + case FIELD_MASK: + // The base-class default converts FieldMask to a comma-separated string (its JSON wire + // form). That's correct when a FieldMask is being assigned to a google.protobuf.Value + // field (see ProtoLiteAdapter/CelProtoJsonAdapter, which handle JSON assignment at + // their own level), but it breaks CEL expressions that access fields on the FieldMask + // itself (e.g. `fieldMask.paths`) — the string has no field `paths`. + // + // The CEL spec's WKT conversion table does not list FieldMask. cel-go treats it as a + // regular message, as does cel-java's legacy ProtoLiteAdapter.adaptValueToWellKnownProto + // for non-JSON contexts. Preserve that behavior here by wrapping as a ProtoMessageValue. + return ProtoMessageValue.create( + (Message) message, celDescriptorPool, this, celOptions.enableJsonFieldNames()); default: return super.fromWellKnownProto(message, wellKnownProto); } diff --git a/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java b/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java index eca2cf8e9..37ba956e6 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java @@ -332,6 +332,30 @@ public void selectField_fixed64_returnsUnsignedLong() { assertThat(protoMessageValue.select("single_fixed64")).isEqualTo(UnsignedLong.MAX_VALUE); } + // FieldMask is not in the CEL spec's WKT conversion table and should be treated as a regular + // message so expressions like `fieldMask.paths` work. The base-class default + // (BaseProtoCelValueConverter) converts FieldMask to a comma-separated string in + // fromWellKnownProto; that's only correct in JSON-assignment contexts (handled separately by + // CelProtoJsonAdapter). ProtoCelValueConverter overrides to preserve FieldMask as a + // ProtoMessageValue so field selection continues to work. + @Test + public void selectField_fieldMask_returnsProtoMessageValue() { + TestAllTypes testAllTypes = + TestAllTypes.newBuilder() + .setFieldMask( + com.google.protobuf.FieldMask.newBuilder().addPaths("foo").addPaths("bar")) + .build(); + + ProtoMessageValue protoMessageValue = + ProtoMessageValue.create( + testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false); + + Object selected = protoMessageValue.select("field_mask"); + assertThat(selected).isInstanceOf(ProtoMessageValue.class); + assertThat(((ProtoMessageValue) selected).select("paths")) + .isEqualTo(ImmutableList.of("foo", "bar")); + } + @SuppressWarnings("ImmutableEnumChecker") // Test only private enum SelectFieldJsonValueTestCase { NULL(Value.newBuilder().build(), NullValue.NULL_VALUE), From f641f76c80c8bb34ebd473d2e5f7ec965b5b8024 Mon Sep 17 00:00:00 2001 From: Andrew Parmet Date: Sat, 30 May 2026 14:21:47 -0400 Subject: [PATCH 3/3] Bump version to 0.13.0-planner-SNAPSHOT for local publishing --- publish/cel_version.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/publish/cel_version.bzl b/publish/cel_version.bzl index 70fa1a010..74b95dbdb 100644 --- a/publish/cel_version.bzl +++ b/publish/cel_version.bzl @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. """Maven artifact version for CEL.""" -CEL_VERSION = "0.13.0" +CEL_VERSION = "0.13.0-planner-SNAPSHOT"