Skip to content

Commit 70fd5b7

Browse files
l46kokcopybara-github
authored andcommitted
Add WireMessageLite for evaluating raw messages, decode registered unknown submessage fields into MessageLite
PiperOrigin-RevId: 992414157
1 parent d217c9c commit 70fd5b7

12 files changed

Lines changed: 418 additions & 215 deletions

‎common/src/main/java/dev/cel/common/values/BUILD.bazel‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ java_library(
320320
"ProtoLiteCelValueConverter.java",
321321
"ProtoMessageLiteValue.java",
322322
"RawProtoMessageLiteValue.java",
323+
"WireMessageLite.java",
323324
],
324325
tags = [
325326
],
@@ -350,6 +351,7 @@ cel_android_library(
350351
"ProtoLiteCelValueConverter.java",
351352
"ProtoMessageLiteValue.java",
352353
"RawProtoMessageLiteValue.java",
354+
"WireMessageLite.java",
353355
],
354356
tags = [
355357
],
@@ -380,6 +382,7 @@ java_library(
380382
srcs = ["ProtoMessageLiteValueProvider.java"],
381383
tags = [
382384
],
385+
exports = [":proto_message_lite_value"],
383386
deps = [
384387
":base_proto_message_value_provider",
385388
":proto_message_lite_value",
@@ -399,6 +402,7 @@ cel_android_library(
399402
srcs = ["ProtoMessageLiteValueProvider.java"],
400403
tags = [
401404
],
405+
exports = [":proto_message_lite_value_android"],
402406
deps = [
403407
":base_proto_message_value_provider_android",
404408
":proto_message_lite_value_android",

‎common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java‎

Lines changed: 23 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import com.google.protobuf.CodedInputStream;
3030
import com.google.protobuf.ExtensionRegistryLite;
3131
import com.google.protobuf.MessageLite;
32-
import com.google.protobuf.MessageLiteOrBuilder;
3332
import com.google.protobuf.WireFormat;
3433
import dev.cel.common.annotations.Internal;
3534
import dev.cel.common.internal.CelLiteDescriptorPool;
@@ -45,7 +44,6 @@
4544
import java.util.LinkedHashMap;
4645
import java.util.List;
4746
import java.util.Map;
48-
import java.util.NoSuchElementException;
4947
import java.util.Optional;
5048
import java.util.TreeMap;
5149

@@ -110,6 +108,8 @@ private static Object readFixed32BitField(
110108
case FLOAT:
111109
return inputStream.readFloat();
112110
case FIXED32:
111+
return UnsignedLong.fromLongBits(
112+
Integer.toUnsignedLong(inputStream.readRawLittleEndian32()));
113113
case SFIXED32:
114114
return inputStream.readRawLittleEndian32();
115115
default:
@@ -124,6 +124,7 @@ private static Object readFixed64BitField(
124124
case DOUBLE:
125125
return inputStream.readDouble();
126126
case FIXED64:
127+
return UnsignedLong.fromLongBits(inputStream.readRawLittleEndian64());
127128
case SFIXED64:
128129
return inputStream.readRawLittleEndian64();
129130
default:
@@ -171,31 +172,21 @@ Optional<FieldLiteDescriptor> findFieldDescriptor(String protoTypeName, int fiel
171172
.flatMap(desc -> desc.findByFieldNumber(fieldNumber));
172173
}
173174

174-
Optional<Object> tryDecodeWellKnownProto(ByteString bytes, String protoTypeName) {
175-
Optional<WellKnownProto> wellKnownProto = WellKnownProto.getByTypeName(protoTypeName);
176-
if (!wellKnownProto.isPresent()) {
177-
return Optional.empty();
178-
}
179-
175+
Optional<Object> tryDecodeProtoMessage(ByteString bytes, String protoTypeName) {
180176
return descriptorPool
181177
.findDescriptor(protoTypeName)
182-
.map(
183-
descriptor ->
184-
decodeWellKnownProto(bytes, protoTypeName, descriptor, wellKnownProto.get()));
178+
.map(descriptor -> decodeProtoMessage(bytes, protoTypeName, descriptor));
185179
}
186180

187-
private Object decodeWellKnownProto(
188-
ByteString bytes,
189-
String protoTypeName,
190-
MessageLiteDescriptor descriptor,
191-
WellKnownProto wellKnownProto) {
181+
private Object decodeProtoMessage(
182+
ByteString bytes, String protoTypeName, MessageLiteDescriptor descriptor) {
192183
try {
193-
MessageLite.Builder builder = descriptor.newMessageBuilder();
194-
builder.mergeFrom(bytes, ExtensionRegistryLite.getEmptyRegistry());
195-
return fromWellKnownProto(builder.build(), wellKnownProto);
184+
MessageLite.Builder builder =
185+
descriptor.newMessageBuilder().mergeFrom(bytes, ExtensionRegistryLite.getEmptyRegistry());
186+
return toRuntimeValue(builder.build(), descriptor);
196187
} catch (IOException e) {
197188
throw new IllegalArgumentException(
198-
"Failed to decode well-known proto of type: " + protoTypeName, e);
189+
"Failed to decode proto message of type: " + protoTypeName, e);
199190
}
200191
}
201192

@@ -209,35 +200,21 @@ public Object toRuntimeValue(Object value) {
209200
if (descriptor == null) {
210201
return RawProtoMessageLiteValue.create(msg.toByteString(), this);
211202
}
212-
WellKnownProto wellKnownProto =
213-
WellKnownProto.getByTypeName(descriptor.getProtoTypeName()).orElse(null);
214-
215-
if (wellKnownProto == null) {
216-
return ProtoMessageLiteValue.create(msg, descriptor.getProtoTypeName(), this);
217-
}
218-
219-
return fromWellKnownProto(msg, wellKnownProto);
203+
return toRuntimeValue(msg, descriptor);
220204
}
221205

222206
return super.toRuntimeValue(value);
223207
}
224208

225-
@Override
226-
protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wellKnownProto) {
227-
if (wellKnownProto == WellKnownProto.FIELD_MASK) {
228-
MessageLite message = (MessageLite) msg;
229-
MessageLiteDescriptor descriptor =
230-
descriptorPool
231-
.findDescriptor(message)
232-
.orElseThrow(
233-
() ->
234-
new NoSuchElementException(
235-
"Could not find a descriptor for message of type: "
236-
+ message.getClass().getName()));
237-
return ProtoMessageLiteValue.create(message, descriptor.getProtoTypeName(), this);
209+
private Object toRuntimeValue(MessageLite msg, MessageLiteDescriptor descriptor) {
210+
WellKnownProto wellKnownProto =
211+
WellKnownProto.getByTypeName(descriptor.getProtoTypeName()).orElse(null);
212+
213+
if (wellKnownProto == null || wellKnownProto == WellKnownProto.FIELD_MASK) {
214+
return ProtoMessageLiteValue.create(msg, descriptor.getProtoTypeName(), this);
238215
}
239216

240-
return super.fromWellKnownProto(msg, wellKnownProto);
217+
return fromWellKnownProto(msg, wellKnownProto);
241218
}
242219

243220
private Object getDefaultValue(FieldLiteDescriptor fieldDescriptor) {
@@ -257,11 +234,13 @@ private Object getScalarDefaultValue(FieldLiteDescriptor fieldDescriptor) {
257234
JavaType type = fieldDescriptor.getJavaType();
258235
switch (type) {
259236
case INT:
260-
return fieldDescriptor.getProtoFieldType().equals(FieldLiteDescriptor.Type.UINT32)
237+
return (fieldDescriptor.getProtoFieldType().equals(FieldLiteDescriptor.Type.UINT32)
238+
|| fieldDescriptor.getProtoFieldType().equals(FieldLiteDescriptor.Type.FIXED32))
261239
? UnsignedLong.ZERO
262240
: Defaults.defaultValue(long.class);
263241
case LONG:
264-
return fieldDescriptor.getProtoFieldType().equals(FieldLiteDescriptor.Type.UINT64)
242+
return (fieldDescriptor.getProtoFieldType().equals(FieldLiteDescriptor.Type.UINT64)
243+
|| fieldDescriptor.getProtoFieldType().equals(FieldLiteDescriptor.Type.FIXED64))
265244
? UnsignedLong.ZERO
266245
: Defaults.defaultValue(long.class);
267246
case ENUM:

‎common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
*/
5353
@AutoValue
5454
@Immutable
55-
public abstract class ProtoMessageLiteValue extends StructValue<String, MessageLite>
55+
abstract class ProtoMessageLiteValue extends StructValue<String, MessageLite>
5656
implements OptimizedSelectable {
5757

5858
@Override
@@ -142,7 +142,7 @@ public Optional<Object> findByFieldNumber(SelectField field) {
142142
.orElse(null);
143143
}
144144

145-
public static ProtoMessageLiteValue create(
145+
static ProtoMessageLiteValue create(
146146
MessageLite value, String typeName, ProtoLiteCelValueConverter protoLiteCelValueConverter) {
147147
checkNotNull(value);
148148
checkNotNull(typeName);

‎common/src/main/java/dev/cel/common/values/RawProtoMessageLiteValue.java‎

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import com.google.protobuf.ByteString;
3131
import com.google.protobuf.CodedInputStream;
3232
import com.google.protobuf.WireFormat;
33-
import dev.cel.common.annotations.Internal;
3433
import dev.cel.common.exceptions.CelAttributeNotFoundException;
3534
import dev.cel.common.types.CelType;
3635
import dev.cel.common.types.StructTypeReference;
@@ -49,38 +48,54 @@
4948
* client-server version skew issues where newer fields or submessages lack generated classes and
5049
* descriptors in the evaluation environment.
5150
*
52-
* <p>Rather than requiring compiled {@link MessageLite} classes or runtime schema descriptors, this
51+
* <p>Rather than requiring compiled {@code MessageLite} classes or runtime schema descriptors, this
5352
* value encapsulates the raw wire-format {@link ByteString} payload and performs classless,
5453
* reflection-free field traversal directly over wire tags via {@link CodedInputStream}.
5554
*/
5655
@AutoValue
5756
@AutoValue.CopyAnnotations
5857
@Immutable
5958
@SuppressWarnings("Immutable") // Immutable wire fields
60-
@Internal
61-
public abstract class RawProtoMessageLiteValue extends StructValue<String, RawProtoMessageLiteValue>
62-
implements OptimizedSelectable {
59+
abstract class RawProtoMessageLiteValue extends StructValue<String, WireMessageLite>
60+
implements OptimizedSelectable, WireMessageLite {
6361

6462
private static final String UNKNOWN_MESSAGE_TYPE_NAME = "cel.@unknownMessage";
6563
private static final int MAP_KEY_FIELD_NUMBER = 1;
6664
private static final int MAP_VALUE_FIELD_NUMBER = 2;
6765

68-
abstract ByteString rawWireBytes();
66+
@Override
67+
public abstract ByteString toByteString();
6968

7069
@Override
7170
public abstract CelType celType();
7271

7372
abstract ProtoLiteCelValueConverter protoLiteCelValueConverter();
7473

7574
@Override
76-
public RawProtoMessageLiteValue value() {
75+
public String protoTypeName() {
76+
return celType().name();
77+
}
78+
79+
@Override
80+
public WireMessageLite value() {
7781
return this;
7882
}
7983

84+
@Override
85+
public final boolean equals(Object other) {
86+
// TODO: Support message equality
87+
throw new UnsupportedOperationException("Message equality is not supported");
88+
}
89+
90+
@Override
91+
public final int hashCode() {
92+
throw new UnsupportedOperationException("Message equality is not supported");
93+
}
94+
8095
@Memoized
8196
ImmutableListMultimap<Integer, Object> unknownFields() {
8297
try {
83-
CodedInputStream inputStream = rawWireBytes().newCodedInput();
98+
CodedInputStream inputStream = toByteString().newCodedInput();
8499
Multimap<Integer, Object> fields = Multimaps.newMultimap(new TreeMap<>(), ArrayList::new);
85100
for (int tag = inputStream.readTag(); tag != 0; tag = inputStream.readTag()) {
86101
int tagWireType = WireFormat.getTagWireType(tag);
@@ -96,7 +111,7 @@ ImmutableListMultimap<Integer, Object> unknownFields() {
96111

97112
@Override
98113
public boolean isZeroValue() {
99-
return rawWireBytes().isEmpty();
114+
return toByteString().isEmpty();
100115
}
101116

102117
/**
@@ -169,27 +184,15 @@ private static Object decodeWireField(
169184
}
170185

171186
boolean isRepeated = field.defaultValue() instanceof List;
172-
String protoTypeName = resolveProtoTypeName(field);
173-
174-
return decodeWireEntries(unknowns, typeCode, protoTypeName, isRepeated, converter);
175-
}
176187

177-
/**
178-
* Resolves the protobuf message type name for a field from the optimizer metadata in {@link
179-
* SelectField}, or {@link #UNKNOWN_MESSAGE_TYPE_NAME} if unspecified.
180-
*/
181-
private static String resolveProtoTypeName(SelectField field) {
182-
if (!field.protoTypeName().isEmpty()) {
183-
return field.protoTypeName();
184-
}
185-
return UNKNOWN_MESSAGE_TYPE_NAME;
188+
return decodeWireEntries(unknowns, typeCode, field.protoTypeName(), isRepeated, converter);
186189
}
187190

188191
private static Object resolveDefault(SelectField field, ProtoLiteCelValueConverter converter) {
189192
if (field.defaultValue() != null) {
190193
return field.defaultValue();
191194
}
192-
return create(ByteString.EMPTY, resolveProtoTypeName(field), converter);
195+
return decodeMessageValue(ByteString.EMPTY, field.protoTypeName(), converter);
193196
}
194197

195198
/**
@@ -419,9 +422,7 @@ static Object decodeWireValue(
419422
throw new UnsupportedOperationException("Groups are not supported");
420423
case MESSAGE:
421424
ByteString msgBytes = requireType(raw, ByteString.class, fieldType);
422-
return converter
423-
.tryDecodeWellKnownProto(msgBytes, protoTypeName)
424-
.orElseGet(() -> create(msgBytes, protoTypeName, converter));
425+
return decodeMessageValue(msgBytes, protoTypeName, converter);
425426
case BYTES:
426427
return CelByteString.of(requireType(raw, ByteString.class, fieldType).toByteArray());
427428
case UINT32:
@@ -437,6 +438,13 @@ static Object decodeWireValue(
437438
throw new IllegalArgumentException("Unsupported proto field type: " + fieldType);
438439
}
439440

441+
private static Object decodeMessageValue(
442+
ByteString msgBytes, String protoTypeName, ProtoLiteCelValueConverter converter) {
443+
return converter
444+
.tryDecodeProtoMessage(msgBytes, protoTypeName)
445+
.orElseGet(() -> create(msgBytes, protoTypeName, converter));
446+
}
447+
440448
private static <T> T requireType(
441449
Object raw, Class<T> expectedType, WireFormat.FieldType fieldType) {
442450
if (!expectedType.isInstance(raw)) {
@@ -509,12 +517,12 @@ private static ImmutableList<Object> decodePacked(
509517
}
510518
}
511519

512-
public static RawProtoMessageLiteValue create(
520+
static RawProtoMessageLiteValue create(
513521
ByteString rawWireBytes, ProtoLiteCelValueConverter protoLiteCelValueConverter) {
514522
return create(rawWireBytes, "", protoLiteCelValueConverter);
515523
}
516524

517-
public static RawProtoMessageLiteValue create(
525+
static RawProtoMessageLiteValue create(
518526
ByteString rawWireBytes,
519527
String protoTypeName,
520528
ProtoLiteCelValueConverter protoLiteCelValueConverter) {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.common.values;
16+
17+
import com.google.errorprone.annotations.Immutable;
18+
import com.google.protobuf.ByteString;
19+
import dev.cel.common.annotations.Beta;
20+
21+
/**
22+
* Represents a protobuf message evaluation result in {@code CelLiteRuntime} when no {@code
23+
* CelLiteDescriptor} is registered for the message type.
24+
*
25+
* <p>When a message-typed expression is evaluated in {@code CelLiteRuntime}:
26+
*
27+
* <ul>
28+
* <li>If a {@code CelLiteDescriptor} is registered for the message type, evaluation produces a
29+
* {@code MessageLite} instance.
30+
* <li>Otherwise, evaluation produces a {@code WireMessageLite} carrying the message's protobuf
31+
* type name and wire-encoded payload.
32+
* </ul>
33+
*/
34+
@Immutable
35+
@Beta
36+
public interface WireMessageLite {
37+
38+
/**
39+
* Returns the fully-qualified protobuf message type name (e.g. {@code
40+
* "cel.expr.conformance.proto3.TestAllTypes.NestedMessage"}), or {@code "cel.@unknownMessage"} if
41+
* the message type name is not known at runtime.
42+
*/
43+
String protoTypeName();
44+
45+
/** Serializes the message to a {@link ByteString} in protobuf wire format. */
46+
ByteString toByteString();
47+
}

0 commit comments

Comments
 (0)