Skip to content

Commit bd149d9

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

12 files changed

Lines changed: 603 additions & 215 deletions

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

Lines changed: 2 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
],

‎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: 46 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;
@@ -39,6 +38,7 @@
3938
import java.util.AbstractMap;
4039
import java.util.ArrayList;
4140
import java.util.List;
41+
import java.util.Locale;
4242
import java.util.Map;
4343
import java.util.Optional;
4444
import java.util.TreeMap;
@@ -49,38 +49,63 @@
4949
* client-server version skew issues where newer fields or submessages lack generated classes and
5050
* descriptors in the evaluation environment.
5151
*
52-
* <p>Rather than requiring compiled {@link MessageLite} classes or runtime schema descriptors, this
52+
* <p>Rather than requiring compiled {@code MessageLite} classes or runtime schema descriptors, this
5353
* value encapsulates the raw wire-format {@link ByteString} payload and performs classless,
5454
* reflection-free field traversal directly over wire tags via {@link CodedInputStream}.
5555
*/
5656
@AutoValue
5757
@AutoValue.CopyAnnotations
5858
@Immutable
5959
@SuppressWarnings("Immutable") // Immutable wire fields
60-
@Internal
61-
public abstract class RawProtoMessageLiteValue extends StructValue<String, RawProtoMessageLiteValue>
62-
implements OptimizedSelectable {
60+
abstract class RawProtoMessageLiteValue extends StructValue<String, WireMessageLite>
61+
implements OptimizedSelectable, WireMessageLite {
6362

6463
private static final String UNKNOWN_MESSAGE_TYPE_NAME = "cel.@unknownMessage";
6564
private static final int MAP_KEY_FIELD_NUMBER = 1;
6665
private static final int MAP_VALUE_FIELD_NUMBER = 2;
6766

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

7070
@Override
7171
public abstract CelType celType();
7272

7373
abstract ProtoLiteCelValueConverter protoLiteCelValueConverter();
7474

7575
@Override
76-
public RawProtoMessageLiteValue value() {
76+
public String protoTypeName() {
77+
return celType().name();
78+
}
79+
80+
@Override
81+
public WireMessageLite value() {
7782
return this;
7883
}
7984

85+
@Override
86+
public final boolean equals(Object other) {
87+
// TODO: Support message equality
88+
throw new UnsupportedOperationException("Message equality is not supported");
89+
}
90+
91+
@Override
92+
public final int hashCode() {
93+
throw new UnsupportedOperationException("Message equality is not supported");
94+
}
95+
96+
@Override
97+
public final String toString() {
98+
return String.format(
99+
Locale.US,
100+
"WireMessageLite{protoTypeName=%s, size=%d}",
101+
protoTypeName(),
102+
toByteString().size());
103+
}
104+
80105
@Memoized
81106
ImmutableListMultimap<Integer, Object> unknownFields() {
82107
try {
83-
CodedInputStream inputStream = rawWireBytes().newCodedInput();
108+
CodedInputStream inputStream = toByteString().newCodedInput();
84109
Multimap<Integer, Object> fields = Multimaps.newMultimap(new TreeMap<>(), ArrayList::new);
85110
for (int tag = inputStream.readTag(); tag != 0; tag = inputStream.readTag()) {
86111
int tagWireType = WireFormat.getTagWireType(tag);
@@ -96,7 +121,7 @@ ImmutableListMultimap<Integer, Object> unknownFields() {
96121

97122
@Override
98123
public boolean isZeroValue() {
99-
return rawWireBytes().isEmpty();
124+
return toByteString().isEmpty();
100125
}
101126

102127
/**
@@ -169,27 +194,15 @@ private static Object decodeWireField(
169194
}
170195

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

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;
198+
return decodeWireEntries(unknowns, typeCode, field.protoTypeName(), isRepeated, converter);
186199
}
187200

188201
private static Object resolveDefault(SelectField field, ProtoLiteCelValueConverter converter) {
189202
if (field.defaultValue() != null) {
190203
return field.defaultValue();
191204
}
192-
return create(ByteString.EMPTY, resolveProtoTypeName(field), converter);
205+
return decodeMessageValue(ByteString.EMPTY, field.protoTypeName(), converter);
193206
}
194207

195208
/**
@@ -419,9 +432,7 @@ static Object decodeWireValue(
419432
throw new UnsupportedOperationException("Groups are not supported");
420433
case MESSAGE:
421434
ByteString msgBytes = requireType(raw, ByteString.class, fieldType);
422-
return converter
423-
.tryDecodeWellKnownProto(msgBytes, protoTypeName)
424-
.orElseGet(() -> create(msgBytes, protoTypeName, converter));
435+
return decodeMessageValue(msgBytes, protoTypeName, converter);
425436
case BYTES:
426437
return CelByteString.of(requireType(raw, ByteString.class, fieldType).toByteArray());
427438
case UINT32:
@@ -437,6 +448,13 @@ static Object decodeWireValue(
437448
throw new IllegalArgumentException("Unsupported proto field type: " + fieldType);
438449
}
439450

451+
private static Object decodeMessageValue(
452+
ByteString msgBytes, String protoTypeName, ProtoLiteCelValueConverter converter) {
453+
return converter
454+
.tryDecodeProtoMessage(msgBytes, protoTypeName)
455+
.orElseGet(() -> create(msgBytes, protoTypeName, converter));
456+
}
457+
440458
private static <T> T requireType(
441459
Object raw, Class<T> expectedType, WireFormat.FieldType fieldType) {
442460
if (!expectedType.isInstance(raw)) {
@@ -509,12 +527,12 @@ private static ImmutableList<Object> decodePacked(
509527
}
510528
}
511529

512-
public static RawProtoMessageLiteValue create(
530+
static RawProtoMessageLiteValue create(
513531
ByteString rawWireBytes, ProtoLiteCelValueConverter protoLiteCelValueConverter) {
514532
return create(rawWireBytes, "", protoLiteCelValueConverter);
515533
}
516534

517-
public static RawProtoMessageLiteValue create(
535+
static RawProtoMessageLiteValue create(
518536
ByteString rawWireBytes,
519537
String protoTypeName,
520538
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)