diff --git a/common/src/main/java/dev/cel/common/values/BUILD.bazel b/common/src/main/java/dev/cel/common/values/BUILD.bazel
index 433dcd477..51fa12e98 100644
--- a/common/src/main/java/dev/cel/common/values/BUILD.bazel
+++ b/common/src/main/java/dev/cel/common/values/BUILD.bazel
@@ -317,14 +317,18 @@ java_library(
srcs = [
"ProtoLiteCelValueConverter.java",
"ProtoMessageLiteValue.java",
+ "RawProtoMessageLiteValue.java",
],
tags = [
],
deps = [
":base_proto_cel_value_converter",
+ ":optimized_selectable",
+ ":select_field",
":values",
"//:auto_value",
"//common/annotations",
+ "//common/exceptions:attribute_not_found",
"//common/internal:cel_lite_descriptor_pool",
"//common/internal:well_known_proto",
"//common/types",
@@ -333,6 +337,7 @@ java_library(
"//protobuf:cel_lite_descriptor",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
+ "@maven//:org_jspecify_jspecify",
"@maven_android//:com_google_protobuf_protobuf_javalite",
],
)
@@ -342,14 +347,18 @@ cel_android_library(
srcs = [
"ProtoLiteCelValueConverter.java",
"ProtoMessageLiteValue.java",
+ "RawProtoMessageLiteValue.java",
],
tags = [
],
deps = [
":base_proto_cel_value_converter_android",
+ ":optimized_selectable_android",
+ ":select_field_android",
":values_android",
"//:auto_value",
"//common/annotations",
+ "//common/exceptions:attribute_not_found",
"//common/internal:cel_lite_descriptor_pool_android",
"//common/internal:well_known_proto_android",
"//common/types:type_providers_android",
@@ -358,6 +367,7 @@ cel_android_library(
"//protobuf:cel_lite_descriptor",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
+ "@maven//:org_jspecify_jspecify",
"@maven_android//:com_google_guava_guava",
"@maven_android//:com_google_protobuf_protobuf_javalite",
],
@@ -428,3 +438,85 @@ cel_android_library(
"@maven//:com_google_errorprone_error_prone_annotations",
],
)
+
+java_library(
+ name = "select_field",
+ srcs = ["SelectField.java"],
+ tags = [
+ ],
+ deps = [
+ "//:auto_value",
+ "//common/annotations",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:com_google_guava_guava",
+ "@maven//:org_jspecify_jspecify",
+ ],
+)
+
+cel_android_library(
+ name = "select_field_android",
+ srcs = ["SelectField.java"],
+ tags = [
+ ],
+ deps = [
+ "//:auto_value",
+ "//common/annotations",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:org_jspecify_jspecify",
+ "@maven_android//:com_google_guava_guava",
+ ],
+)
+
+java_library(
+ name = "optimized_selectable",
+ srcs = ["OptimizedSelectable.java"],
+ tags = [
+ ],
+ deps = [
+ ":select_field",
+ "//common/annotations",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ ],
+)
+
+cel_android_library(
+ name = "optimized_selectable_android",
+ srcs = ["OptimizedSelectable.java"],
+ tags = [
+ ],
+ deps = [
+ ":select_field_android",
+ "//common/annotations",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ ],
+)
+
+java_library(
+ name = "optimized_select_traversal",
+ srcs = ["OptimizedSelectTraversal.java"],
+ tags = [
+ ],
+ deps = [
+ ":optimized_selectable",
+ ":select_field",
+ ":values",
+ "//common/annotations",
+ "//common/exceptions:attribute_not_found",
+ "@maven//:com_google_guava_guava",
+ ],
+)
+
+cel_android_library(
+ name = "optimized_select_traversal_android",
+ srcs = ["OptimizedSelectTraversal.java"],
+ tags = [
+ ],
+ deps = [
+ ":optimized_selectable_android",
+ ":select_field_android",
+ ":values_android",
+ "//common/annotations",
+ "//common/exceptions:attribute_not_found",
+ "@maven_android//:com_google_guava_guava",
+ ],
+)
diff --git a/common/src/main/java/dev/cel/common/values/OptimizedSelectTraversal.java b/common/src/main/java/dev/cel/common/values/OptimizedSelectTraversal.java
new file mode 100644
index 000000000..2bb4e2935
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/values/OptimizedSelectTraversal.java
@@ -0,0 +1,158 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dev.cel.common.values;
+
+import com.google.common.collect.ImmutableList;
+import dev.cel.common.annotations.Internal;
+import dev.cel.common.exceptions.CelAttributeNotFoundException;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Walks a sequence of {@link SelectField} selections, dispatching each field over {@link
+ * OptimizedSelectable}, {@link SelectableValue}, or {@link Map}.
+ *
+ *
CEL Library Internals. Do Not Use.
+ */
+@Internal
+public final class OptimizedSelectTraversal {
+
+ /**
+ * Qualifies {@code target} through every field in {@code fields} and returns the terminal value.
+ *
+ * @param celValueConverter Converter for unadapted entries encountered in a root {@link Map}.
+ */
+ public static Object qualify(
+ Object target, ImmutableList fields, CelValueConverter celValueConverter) {
+ Object current = target;
+ for (int i = 0; i < fields.size(); i++) {
+ current = qualifyField(current, fields.get(i), celValueConverter);
+ }
+ return current;
+ }
+
+ /**
+ * Presence tests the terminal field of {@code fields}, navigating through all preceding fields.
+ *
+ * Absence of any intermediate field short-circuits to {@code false}.
+ */
+ public static boolean hasField(
+ Object target, ImmutableList fields, CelValueConverter celValueConverter) {
+ if (fields.isEmpty()) {
+ return false;
+ }
+ Object current = target;
+ int terminalIndex = fields.size() - 1;
+ for (int i = 0; i < terminalIndex; i++) {
+ Optional next = navigateField(current, fields.get(i), celValueConverter);
+ if (!next.isPresent()) {
+ return false;
+ }
+ current = next.get();
+ }
+ return hasTerminalField(current, fields.get(terminalIndex));
+ }
+
+ // SelectableValue is only ever instantiated with String keys in the select path.
+ @SuppressWarnings("unchecked")
+ private static Object qualifyField(
+ Object target, SelectField field, CelValueConverter celValueConverter) {
+ if (target instanceof ErrorValue) {
+ return target;
+ }
+ if (target instanceof OptimizedSelectable) {
+ return ((OptimizedSelectable) target).selectByFieldNumber(field);
+ }
+ if (target instanceof SelectableValue) {
+ SelectableValue selectable = (SelectableValue) target;
+ if (field.defaultValue() != null) {
+ return selectable
+ .find(field.fieldName())
+ .map(Object.class::cast)
+ .orElse(field.defaultValue());
+ }
+ return selectable.select(field.fieldName());
+ }
+ if (target instanceof Map) {
+ return getMapEntry((Map, ?>) target, field.fieldName(), celValueConverter);
+ }
+ throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
+ }
+
+ // SelectableValue is only ever instantiated with String keys in the select path.
+ @SuppressWarnings("unchecked")
+ private static Optional navigateField(
+ Object target, SelectField field, CelValueConverter celValueConverter) {
+ if (target instanceof ErrorValue) {
+ return Optional.of(target);
+ }
+ if (target instanceof OptimizedSelectable) {
+ return ((OptimizedSelectable) target).findByFieldNumber(field);
+ }
+ if (target instanceof SelectableValue) {
+ return ((SelectableValue) target).find(field.fieldName()).map(Object.class::cast);
+ }
+ if (target instanceof Map) {
+ return findMapEntry((Map, ?>) target, field.fieldName(), celValueConverter);
+ }
+ throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
+ }
+
+ // SelectableValue is only ever instantiated with String keys in the select path.
+ @SuppressWarnings("unchecked")
+ private static boolean hasTerminalField(Object target, SelectField field) {
+ if (target instanceof ErrorValue) {
+ return false;
+ }
+ if (target instanceof OptimizedSelectable) {
+ return ((OptimizedSelectable) target).hasFieldByNumber(field);
+ }
+ if (target instanceof SelectableValue) {
+ return ((SelectableValue) target).find(field.fieldName()).isPresent();
+ }
+ if (target instanceof Map) {
+ return ((Map, ?>) target).containsKey(field.fieldName());
+ }
+ throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
+ }
+
+ private static Object getMapEntry(
+ Map, ?> map, String key, CelValueConverter celValueConverter) {
+ return findMapEntry(map, key, celValueConverter)
+ .orElseThrow(() -> CelAttributeNotFoundException.forMissingMapKey(key));
+ }
+
+ private static Optional findMapEntry(
+ Map, ?> map, String key, CelValueConverter celValueConverter) {
+ Object mapValue = map.get(key);
+ if (mapValue != null) {
+ return Optional.of(toStepTarget(mapValue, celValueConverter));
+ }
+ if (!map.containsKey(key)) {
+ return Optional.empty();
+ }
+ throw CelAttributeNotFoundException.of(
+ String.format("Map value cannot be null for key: %s", key));
+ }
+
+ static Object toStepTarget(Object value, CelValueConverter celValueConverter) {
+ if (value instanceof Map) {
+ return value;
+ }
+ return celValueConverter.toRuntimeValue(value);
+ }
+
+ private OptimizedSelectTraversal() {}
+}
diff --git a/common/src/main/java/dev/cel/common/values/OptimizedSelectable.java b/common/src/main/java/dev/cel/common/values/OptimizedSelectable.java
new file mode 100644
index 000000000..828d15227
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/values/OptimizedSelectable.java
@@ -0,0 +1,45 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dev.cel.common.values;
+
+import com.google.errorprone.annotations.Immutable;
+import dev.cel.common.annotations.Internal;
+import java.util.Optional;
+
+/**
+ * Resolves an optimized field selection within a selection chain rewritten by the select optimizer.
+ *
+ * Implementations resolve individual field selections against themselves by protobuf field
+ * number. Walking the chain across multiple fields and heterogeneous values belongs to {@link
+ * OptimizedSelectTraversal}.
+ *
+ *
CEL Library Internals. Do Not Use.
+ */
+@Internal
+@Immutable
+public interface OptimizedSelectable {
+
+ /** Selects {@code field}, falling back to its default value or an empty submessage if absent. */
+ Object selectByFieldNumber(SelectField field);
+
+ /** Returns whether {@code field} is present. */
+ boolean hasFieldByNumber(SelectField field);
+
+ /**
+ * Returns the value of the field at {@code field} (a scalar or submessage) for an intermediate
+ * step of a presence test, or empty if absent.
+ */
+ Optional findByFieldNumber(SelectField field);
+}
diff --git a/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java b/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java
index 64d6ec1d4..0d53903a5 100644
--- a/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java
+++ b/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java
@@ -17,14 +17,15 @@
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.auto.value.AutoValue;
-import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Defaults;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.primitives.UnsignedLong;
import com.google.errorprone.annotations.Immutable;
+import com.google.protobuf.ByteString;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.ExtensionRegistryLite;
import com.google.protobuf.MessageLite;
@@ -45,6 +46,7 @@
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.Optional;
import java.util.TreeMap;
/**
@@ -60,6 +62,9 @@
@Immutable
@Internal
public final class ProtoLiteCelValueConverter extends BaseProtoCelValueConverter {
+ static final String MAP_KEY_FIELD_NAME = "key";
+ static final String MAP_VALUE_FIELD_NAME = "value";
+
private final CelLiteDescriptorPool descriptorPool;
public static ProtoLiteCelValueConverter newInstance(
@@ -67,6 +72,10 @@ public static ProtoLiteCelValueConverter newInstance(
return new ProtoLiteCelValueConverter(celLiteDescriptorPool);
}
+ boolean hasDescriptor(String protoTypeName) {
+ return descriptorPool.findDescriptor(protoTypeName).isPresent();
+ }
+
private static Object readPrimitiveField(
CodedInputStream inputStream, FieldLiteDescriptor fieldDescriptor) throws IOException {
switch (fieldDescriptor.getProtoFieldType()) {
@@ -80,7 +89,7 @@ private static Object readPrimitiveField(
case INT64:
return inputStream.readInt64();
case UINT32:
- return UnsignedLong.fromLongBits(inputStream.readUInt32());
+ return UnsignedLong.fromLongBits(Integer.toUnsignedLong(inputStream.readUInt32()));
case UINT64:
return UnsignedLong.fromLongBits(inputStream.readUInt64());
case BOOL:
@@ -153,11 +162,45 @@ private MessageLite.Builder getDefaultMessageBuilder(String protoTypeName) {
Object getDefaultCelValue(String protoTypeName, String fieldName) {
MessageLiteDescriptor messageDescriptor = descriptorPool.getDescriptorOrThrow(protoTypeName);
- FieldLiteDescriptor fieldDescriptor = messageDescriptor.getByFieldNameOrThrow(fieldName);
+ return getDefaultCelValue(messageDescriptor.getByFieldNameOrThrow(fieldName));
+ }
- Object defaultValue = getDefaultValue(fieldDescriptor);
+ Object getDefaultCelValue(FieldLiteDescriptor fieldDescriptor) {
+ return toRuntimeValue(getDefaultValue(fieldDescriptor));
+ }
- return toRuntimeValue(defaultValue);
+ Optional findFieldDescriptor(String protoTypeName, int fieldNumber) {
+ return descriptorPool
+ .findDescriptor(protoTypeName)
+ .flatMap(desc -> desc.findByFieldNumber(fieldNumber));
+ }
+
+ Optional tryDecodeWellKnownProto(ByteString bytes, String protoTypeName) {
+ Optional wellKnownProto = WellKnownProto.getByTypeName(protoTypeName);
+ if (!wellKnownProto.isPresent()) {
+ return Optional.empty();
+ }
+
+ return descriptorPool
+ .findDescriptor(protoTypeName)
+ .map(
+ descriptor ->
+ decodeWellKnownProto(bytes, protoTypeName, descriptor, wellKnownProto.get()));
+ }
+
+ private Object decodeWellKnownProto(
+ ByteString bytes,
+ String protoTypeName,
+ MessageLiteDescriptor descriptor,
+ WellKnownProto wellKnownProto) {
+ try {
+ MessageLite.Builder builder = descriptor.newMessageBuilder();
+ builder.mergeFrom(bytes, ExtensionRegistryLite.getEmptyRegistry());
+ return fromWellKnownProto(builder.build(), wellKnownProto);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Failed to decode well-known proto of type: " + protoTypeName, e);
+ }
}
@Override
@@ -193,7 +236,10 @@ protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wel
descriptorPool
.findDescriptor(message)
.orElseThrow(
- () -> new NoSuchElementException("Could not find a descriptor for: " + message));
+ () ->
+ new NoSuchElementException(
+ "Could not find a descriptor for message of type: "
+ + message.getClass().getName()));
return ProtoMessageLiteValue.create(message, descriptor.getProtoTypeName(), this);
}
@@ -260,16 +306,21 @@ private ImmutableList readPackedRepeatedFields(
private Map.Entry readSingleMapEntry(
CodedInputStream inputStream, FieldLiteDescriptor fieldDescriptor) throws IOException {
+ String entryTypeName = fieldDescriptor.getFieldProtoTypeName();
ImmutableMap singleMapEntry =
- readAllFields(inputStream.readByteArray(), fieldDescriptor.getFieldProtoTypeName())
- .values();
- Object key = checkNotNull(singleMapEntry.get("key"));
- Object value = checkNotNull(singleMapEntry.get("value"));
+ readAllFields(inputStream.readByteArray(), entryTypeName).values();
+ Object key = singleMapEntry.get(MAP_KEY_FIELD_NAME);
+ if (key == null) {
+ key = getDefaultCelValue(entryTypeName, MAP_KEY_FIELD_NAME);
+ }
+ Object value = singleMapEntry.get(MAP_VALUE_FIELD_NAME);
+ if (value == null) {
+ value = getDefaultCelValue(entryTypeName, MAP_VALUE_FIELD_NAME);
+ }
return new AbstractMap.SimpleEntry<>(key, value);
}
- @VisibleForTesting
MessageFields readAllFields(byte[] bytes, String protoTypeName) throws IOException {
MessageLiteDescriptor messageDescriptor = descriptorPool.getDescriptorOrThrow(protoTypeName);
CodedInputStream inputStream = CodedInputStream.newInstance(bytes);
@@ -344,19 +395,16 @@ MessageFields readAllFields(byte[] bytes, String protoTypeName) throws IOExcepti
if (fieldDescriptor.getEncodingType().equals(EncodingType.LIST)) {
String fieldName = fieldDescriptor.getFieldName();
List repeatedValues =
- repeatedFieldValues.computeIfAbsent(
- fieldNumber,
- (unused) -> {
- List newList = new ArrayList<>();
- fieldValues.put(fieldName, newList);
- return newList;
- });
+ repeatedFieldValues.computeIfAbsent(fieldNumber, (unused) -> new ArrayList<>());
if (payload instanceof Collection) {
repeatedValues.addAll((Collection>) payload);
} else {
repeatedValues.add(payload);
}
+ if (!repeatedValues.isEmpty()) {
+ fieldValues.put(fieldName, repeatedValues);
+ }
} else {
fieldValues.put(fieldDescriptor.getFieldName(), payload);
}
@@ -367,13 +415,11 @@ MessageFields readAllFields(byte[] bytes, String protoTypeName) throws IOExcepti
return MessageFields.create(fieldValues.buildKeepingLast(), unknownFields);
}
- ImmutableMap readAllFields(MessageLite msg, String protoTypeName)
- throws IOException {
- return readAllFields(msg.toByteArray(), protoTypeName).values();
+ MessageFields readMessageFields(MessageLite msg, String protoTypeName) throws IOException {
+ return readAllFields(msg.toByteArray(), protoTypeName);
}
- private static Object readUnknownField(int tagWireType, CodedInputStream inputStream)
- throws IOException {
+ static Object readUnknownField(int tagWireType, CodedInputStream inputStream) throws IOException {
switch (tagWireType) {
case WireFormat.WIRETYPE_VARINT:
return inputStream.readInt64();
@@ -393,16 +439,19 @@ private static Object readUnknownField(int tagWireType, CodedInputStream inputSt
}
@AutoValue
- @SuppressWarnings("AutoValueImmutableFields") // Unknowns are inaccessible to users.
+ @AutoValue.CopyAnnotations
+ @Immutable
+ @SuppressWarnings("Immutable") // Safe immutable fields
abstract static class MessageFields {
abstract ImmutableMap values();
- abstract Multimap unknowns();
+ abstract ImmutableListMultimap unknowns();
static MessageFields create(
ImmutableMap fieldValues, Multimap unknownFields) {
- return new AutoValue_ProtoLiteCelValueConverter_MessageFields(fieldValues, unknownFields);
+ return new AutoValue_ProtoLiteCelValueConverter_MessageFields(
+ fieldValues, ImmutableListMultimap.copyOf(unknownFields));
}
}
diff --git a/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java b/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java
index 2e4d980c7..99e95ebd3 100644
--- a/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java
+++ b/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java
@@ -17,11 +17,14 @@
import com.google.auto.value.AutoValue;
import com.google.auto.value.extension.memoized.Memoized;
import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.Immutable;
import com.google.protobuf.MessageLite;
+import dev.cel.common.annotations.Internal;
import dev.cel.common.types.CelType;
import dev.cel.common.types.StructTypeReference;
+import dev.cel.common.values.ProtoLiteCelValueConverter.MessageFields;
import java.io.IOException;
import java.util.Optional;
@@ -46,14 +49,23 @@ public abstract class ProtoMessageLiteValue extends StructValue fieldValues() {
+ MessageFields messageFields() {
try {
- return protoLiteCelValueConverter().readAllFields(value(), celType().name());
+ return protoLiteCelValueConverter().readMessageFields(value(), celType().name());
} catch (IOException e) {
throw new IllegalStateException("Unable to read message fields for " + celType().name(), e);
}
}
+ @Internal
+ public ImmutableMap fieldValues() {
+ return messageFields().values();
+ }
+
+ public ImmutableListMultimap unknownFields() {
+ return messageFields().unknowns();
+ }
+
@Override
public boolean isZeroValue() {
return value().getDefaultInstanceForType().equals(value());
diff --git a/common/src/main/java/dev/cel/common/values/RawProtoMessageLiteValue.java b/common/src/main/java/dev/cel/common/values/RawProtoMessageLiteValue.java
new file mode 100644
index 000000000..2a3bdf940
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/values/RawProtoMessageLiteValue.java
@@ -0,0 +1,514 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dev.cel.common.values;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static dev.cel.common.values.ProtoLiteCelValueConverter.MAP_KEY_FIELD_NAME;
+import static dev.cel.common.values.ProtoLiteCelValueConverter.MAP_VALUE_FIELD_NAME;
+
+import com.google.auto.value.AutoValue;
+import com.google.auto.value.extension.memoized.Memoized;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.primitives.UnsignedLong;
+import com.google.errorprone.annotations.Immutable;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.WireFormat;
+import dev.cel.common.annotations.Internal;
+import dev.cel.common.exceptions.CelAttributeNotFoundException;
+import dev.cel.common.internal.WellKnownProto;
+import dev.cel.common.types.CelType;
+import dev.cel.common.types.StructTypeReference;
+import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor;
+import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor.EncodingType;
+import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor.JavaType;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.TreeMap;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * RawProtoMessageLiteValue enables descriptorless evaluation of protobuf messages to address
+ * client-server version skew issues where newer fields or submessages lack generated classes and
+ * descriptors in the evaluation environment.
+ *
+ * Rather than requiring compiled {@link MessageLite} classes or runtime schema descriptors, this
+ * value encapsulates the raw wire-format {@link ByteString} payload and performs classless,
+ * reflection-free field traversal directly over wire tags via {@link CodedInputStream}.
+ */
+@AutoValue
+@AutoValue.CopyAnnotations
+@Immutable
+@SuppressWarnings("Immutable") // Immutable wire fields
+@Internal
+public abstract class RawProtoMessageLiteValue extends StructValue
+ implements OptimizedSelectable {
+
+ private static final String UNKNOWN_MESSAGE_TYPE_NAME = "cel.@unknownMessage";
+
+ abstract ByteString rawWireBytes();
+
+ @Override
+ public abstract CelType celType();
+
+ abstract ProtoLiteCelValueConverter protoLiteCelValueConverter();
+
+ @Override
+ public RawProtoMessageLiteValue value() {
+ return this;
+ }
+
+ @Memoized
+ ImmutableListMultimap unknownFields() {
+ try {
+ CodedInputStream inputStream = rawWireBytes().newCodedInput();
+ Multimap fields = Multimaps.newMultimap(new TreeMap<>(), ArrayList::new);
+ for (int tag = inputStream.readTag(); tag != 0; tag = inputStream.readTag()) {
+ int tagWireType = WireFormat.getTagWireType(tag);
+ int fieldNumber = WireFormat.getTagFieldNumber(tag);
+ fields.put(
+ fieldNumber, ProtoLiteCelValueConverter.readUnknownField(tagWireType, inputStream));
+ }
+ return ImmutableListMultimap.copyOf(fields);
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to parse raw proto message wire bytes", e);
+ }
+ }
+
+ @Override
+ public boolean isZeroValue() {
+ return rawWireBytes().isEmpty();
+ }
+
+ /**
+ * Direct field selection by name is unsupported on {@link RawProtoMessageLiteValue} because raw
+ * wire bytes lack message descriptors, and field names are not preserved on the protobuf wire.
+ */
+ @Override
+ public Object select(String field) {
+ throw CelAttributeNotFoundException.forFieldResolution(field);
+ }
+
+ /**
+ * Direct field presence testing by name is unsupported on {@link RawProtoMessageLiteValue}
+ * because raw wire bytes lack message descriptors and field names.
+ */
+ @Override
+ public Optional find(String field) {
+ throw CelAttributeNotFoundException.forFieldResolution(field);
+ }
+
+ @Override
+ public Object selectByFieldNumber(SelectField field) {
+ int fieldNumber = field.fieldNumber();
+ FieldLiteDescriptor fieldDescriptor =
+ protoLiteCelValueConverter()
+ .findFieldDescriptor(celType().name(), fieldNumber)
+ .orElse(null);
+ return selectWireOrDefault(
+ field, fieldDescriptor, unknownFields().get(fieldNumber), protoLiteCelValueConverter());
+ }
+
+ @Override
+ public boolean hasFieldByNumber(SelectField field) {
+ int fieldNumber = field.fieldNumber();
+ FieldLiteDescriptor fieldDescriptor =
+ protoLiteCelValueConverter()
+ .findFieldDescriptor(celType().name(), fieldNumber)
+ .orElse(null);
+ return isPresentInWire(field, fieldDescriptor, unknownFields().get(fieldNumber));
+ }
+
+ @Override
+ public Optional findByFieldNumber(SelectField field) {
+ int fieldNumber = field.fieldNumber();
+ FieldLiteDescriptor fieldDescriptor =
+ protoLiteCelValueConverter()
+ .findFieldDescriptor(celType().name(), fieldNumber)
+ .orElse(null);
+ return navigateWire(
+ field, fieldDescriptor, unknownFields().get(fieldNumber), protoLiteCelValueConverter());
+ }
+
+ /**
+ * Decodes a field value from preserved wire bytes, falling back to schema or default values.
+ *
+ * Package-private: shared with {@code ProtoMessageLiteValue} for unknown field resolution.
+ */
+ static Object selectWireOrDefault(
+ SelectField field,
+ @Nullable FieldLiteDescriptor fieldDescriptor,
+ ImmutableList unknowns,
+ ProtoLiteCelValueConverter converter) {
+ if (unknowns.isEmpty()) {
+ return resolveDefault(field, fieldDescriptor, converter);
+ }
+ return decodeWireField(field, fieldDescriptor, unknowns, converter);
+ }
+
+ private static Object decodeWireField(
+ SelectField field,
+ @Nullable FieldLiteDescriptor fieldDescriptor,
+ ImmutableList unknowns,
+ ProtoLiteCelValueConverter converter) {
+ if (fieldDescriptor != null && fieldDescriptor.getEncodingType() == EncodingType.MAP) {
+ return decodeMapEntries(unknowns, fieldDescriptor, converter);
+ }
+
+ int typeCode =
+ fieldDescriptor != null
+ ? fieldDescriptor.getProtoFieldType().getNumber()
+ : field.typeCode();
+ if (typeCode == SelectField.CEL_MAP_TYPE_CODE) {
+ throw new UnsupportedOperationException(
+ "Decoding unknown map field from wire bytes is unsupported: " + field.fieldName());
+ }
+ if (typeCode == SelectField.NO_TYPE_CODE) {
+ throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
+ }
+
+ boolean isRepeated =
+ fieldDescriptor != null
+ ? fieldDescriptor.getEncodingType() == EncodingType.LIST
+ : field.defaultValue() instanceof List;
+ String protoTypeName =
+ fieldDescriptor != null
+ ? fieldDescriptor.getFieldProtoTypeName()
+ : UNKNOWN_MESSAGE_TYPE_NAME;
+
+ return decodeWireEntries(unknowns, typeCode, protoTypeName, isRepeated, converter);
+ }
+
+ private static Object resolveDefault(
+ SelectField field,
+ @Nullable FieldLiteDescriptor fieldDescriptor,
+ ProtoLiteCelValueConverter converter) {
+ if (field.defaultValue() != null) {
+ return field.defaultValue();
+ }
+
+ if (fieldDescriptor == null) {
+ if (field.typeCode() == FieldLiteDescriptor.Type.MESSAGE.getNumber()) {
+ return create(ByteString.EMPTY, UNKNOWN_MESSAGE_TYPE_NAME, converter);
+ }
+ throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
+ }
+
+ String protoTypeName = fieldDescriptor.getFieldProtoTypeName();
+ if (fieldDescriptor.getEncodingType() == EncodingType.SINGULAR
+ && fieldDescriptor.getJavaType() == JavaType.MESSAGE
+ && !WellKnownProto.isWrapperType(protoTypeName)
+ && !converter.hasDescriptor(protoTypeName)) {
+ return create(ByteString.EMPTY, protoTypeName, converter);
+ }
+
+ return converter.getDefaultCelValue(fieldDescriptor);
+ }
+
+ /**
+ * Returns whether a field has presence in preserved wire bytes.
+ *
+ * Package-private: shared with {@code ProtoMessageLiteValue} for unknown field resolution.
+ */
+ static boolean isPresentInWire(
+ SelectField field,
+ @Nullable FieldLiteDescriptor fieldDescriptor,
+ ImmutableList unknowns) {
+ if (unknowns.isEmpty()) {
+ return false;
+ }
+
+ boolean isRepeated =
+ fieldDescriptor != null
+ ? fieldDescriptor.getEncodingType() == EncodingType.LIST
+ : field.defaultValue() instanceof List;
+ int typeCode =
+ fieldDescriptor != null
+ ? fieldDescriptor.getProtoFieldType().getNumber()
+ : field.typeCode();
+
+ if (!isRepeated) {
+ return true;
+ }
+
+ boolean isPackable =
+ typeCode != FieldLiteDescriptor.Type.STRING.getNumber()
+ && typeCode != FieldLiteDescriptor.Type.BYTES.getNumber()
+ && typeCode != FieldLiteDescriptor.Type.MESSAGE.getNumber()
+ && typeCode != FieldLiteDescriptor.Type.GROUP.getNumber();
+ if (!isPackable) {
+ return true;
+ }
+
+ for (Object raw : unknowns) {
+ if (!(raw instanceof ByteString) || !((ByteString) raw).isEmpty()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Navigates a field on preserved wire bytes, returning empty if absent.
+ *
+ * Package-private: shared with {@code ProtoMessageLiteValue} for unknown field resolution.
+ */
+ static Optional navigateWire(
+ SelectField field,
+ @Nullable FieldLiteDescriptor fieldDescriptor,
+ ImmutableList unknowns,
+ ProtoLiteCelValueConverter converter) {
+ if (!isPresentInWire(field, fieldDescriptor, unknowns)) {
+ return Optional.empty();
+ }
+ if (fieldDescriptor != null || field.typeCode() != SelectField.NO_TYPE_CODE) {
+ return Optional.of(selectWireOrDefault(field, fieldDescriptor, unknowns, converter));
+ }
+ Object lastEntry = unknowns.get(unknowns.size() - 1);
+ if (lastEntry instanceof ByteString) {
+ return Optional.of(
+ decodeWireEntries(
+ unknowns,
+ FieldLiteDescriptor.Type.MESSAGE.getNumber(),
+ UNKNOWN_MESSAGE_TYPE_NAME,
+ /* isRepeated= */ false,
+ converter));
+ }
+ return Optional.of(lastEntry);
+ }
+
+ private static ImmutableMap decodeMapEntries(
+ ImmutableList unknowns,
+ FieldLiteDescriptor mapFieldDescriptor,
+ ProtoLiteCelValueConverter converter) {
+ String entryTypeName = mapFieldDescriptor.getFieldProtoTypeName();
+ Object defaultKey = converter.getDefaultCelValue(entryTypeName, MAP_KEY_FIELD_NAME);
+ Object defaultValue = converter.getDefaultCelValue(entryTypeName, MAP_VALUE_FIELD_NAME);
+ Map resultMap = new LinkedHashMap<>();
+ for (Object raw : unknowns) {
+ ByteString bytes = requireType(raw, ByteString.class, WireFormat.FieldType.MESSAGE);
+ try {
+ ImmutableMap entryFields =
+ converter.readAllFields(bytes.toByteArray(), entryTypeName).values();
+ Object key = entryFields.get(MAP_KEY_FIELD_NAME);
+ key = (key == null) ? defaultKey : converter.toRuntimeValue(key);
+ Object value = entryFields.get(MAP_VALUE_FIELD_NAME);
+ value = (value == null) ? defaultValue : converter.toRuntimeValue(value);
+ resultMap.put(key, value);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Failed to decode map entry for field: " + mapFieldDescriptor.getFieldName(), e);
+ }
+ }
+ return ImmutableMap.copyOf(resultMap);
+ }
+
+ static @Nullable Object decodeWireEntries(
+ ImmutableCollection entries,
+ int typeCode,
+ String protoTypeName,
+ boolean isRepeated,
+ ProtoLiteCelValueConverter converter) {
+ WireFormat.FieldType fieldType =
+ FieldLiteDescriptor.Type.forNumber(typeCode).toWireFormatFieldType();
+ if (fieldType == WireFormat.FieldType.GROUP) {
+ throw new UnsupportedOperationException("Groups are not supported");
+ }
+ if (entries.isEmpty()) {
+ return isRepeated ? ImmutableList.of() : null;
+ }
+ if (isRepeated) {
+ ImmutableList.Builder listBuilder = ImmutableList.builder();
+ for (Object raw : entries) {
+ if (fieldType.isPackable() && (raw instanceof ByteString)) {
+ listBuilder.addAll(decodePacked((ByteString) raw, fieldType));
+ } else {
+ listBuilder.add(decodeWireValue(raw, fieldType, protoTypeName, converter));
+ }
+ }
+ return listBuilder.build();
+ }
+ if (fieldType == WireFormat.FieldType.MESSAGE) {
+ ByteString mergedBytes = ByteString.EMPTY;
+ for (Object item : entries) {
+ mergedBytes = mergedBytes.concat(requireType(item, ByteString.class, fieldType));
+ }
+ return decodeWireValue(mergedBytes, fieldType, protoTypeName, converter);
+ }
+ // Protobuf "last one wins" semantics for non-repeated scalar fields
+ return decodeWireValue(Iterables.getLast(entries), fieldType, protoTypeName, converter);
+ }
+
+ static Object decodeWireValue(
+ Object raw, int typeCode, String protoTypeName, ProtoLiteCelValueConverter converter) {
+ return decodeWireValue(
+ raw,
+ FieldLiteDescriptor.Type.forNumber(typeCode).toWireFormatFieldType(),
+ protoTypeName,
+ converter);
+ }
+
+ static Object decodeWireValue(
+ Object raw,
+ WireFormat.FieldType fieldType,
+ String protoTypeName,
+ ProtoLiteCelValueConverter converter) {
+ switch (fieldType) {
+ case DOUBLE:
+ return Double.longBitsToDouble(requireType(raw, Long.class, fieldType));
+ case FLOAT:
+ return (double) Float.intBitsToFloat(requireType(raw, Integer.class, fieldType));
+ case INT64:
+ case SFIXED64:
+ return requireType(raw, Long.class, fieldType);
+ case INT32:
+ case ENUM:
+ return (long) requireType(raw, Long.class, fieldType).intValue();
+ case UINT64:
+ case FIXED64:
+ return UnsignedLong.fromLongBits(requireType(raw, Long.class, fieldType));
+ case FIXED32:
+ return UnsignedLong.fromLongBits(
+ Integer.toUnsignedLong(requireType(raw, Integer.class, fieldType)));
+ case BOOL:
+ return requireType(raw, Long.class, fieldType) != 0L;
+ case STRING:
+ ByteString stringBytes = requireType(raw, ByteString.class, fieldType);
+ if (!stringBytes.isValidUtf8()) {
+ throw new IllegalArgumentException("Invalid UTF-8 in string field");
+ }
+ return stringBytes.toStringUtf8();
+ case GROUP:
+ throw new UnsupportedOperationException("Groups are not supported");
+ case MESSAGE:
+ ByteString msgBytes = requireType(raw, ByteString.class, fieldType);
+ return converter
+ .tryDecodeWellKnownProto(msgBytes, protoTypeName)
+ .orElseGet(() -> create(msgBytes, protoTypeName, converter));
+ case BYTES:
+ return CelByteString.of(requireType(raw, ByteString.class, fieldType).toByteArray());
+ case UINT32:
+ return UnsignedLong.fromLongBits(requireType(raw, Long.class, fieldType) & 0xFFFFFFFFL);
+ case SFIXED32:
+ return (long) requireType(raw, Integer.class, fieldType);
+ case SINT32:
+ return (long)
+ CodedInputStream.decodeZigZag32(requireType(raw, Long.class, fieldType).intValue());
+ case SINT64:
+ return CodedInputStream.decodeZigZag64(requireType(raw, Long.class, fieldType));
+ }
+ throw new IllegalArgumentException("Unsupported proto field type: " + fieldType);
+ }
+
+ private static T requireType(
+ Object raw, Class expectedType, WireFormat.FieldType fieldType) {
+ if (!expectedType.isInstance(raw)) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Expected %s for wire type %s, but got: %s",
+ expectedType.getSimpleName(),
+ fieldType,
+ raw != null ? raw.getClass().getName() : "null"));
+ }
+ return expectedType.cast(raw);
+ }
+
+ private static ImmutableList decodePacked(
+ ByteString bytes, WireFormat.FieldType fieldType) {
+ try {
+ CodedInputStream in = bytes.newCodedInput();
+ ImmutableList.Builder builder = ImmutableList.builder();
+ while (!in.isAtEnd()) {
+ switch (fieldType) {
+ case DOUBLE:
+ builder.add(Double.longBitsToDouble(in.readFixed64()));
+ break;
+ case FLOAT:
+ builder.add((double) Float.intBitsToFloat(in.readFixed32()));
+ break;
+ case INT64:
+ builder.add(in.readInt64());
+ break;
+ case UINT64:
+ builder.add(UnsignedLong.fromLongBits(in.readUInt64()));
+ break;
+ case INT32:
+ builder.add((long) in.readInt32());
+ break;
+ case FIXED64:
+ builder.add(UnsignedLong.fromLongBits(in.readFixed64()));
+ break;
+ case FIXED32:
+ builder.add(UnsignedLong.fromLongBits(Integer.toUnsignedLong(in.readFixed32())));
+ break;
+ case BOOL:
+ builder.add(in.readBool());
+ break;
+ case UINT32:
+ builder.add(UnsignedLong.fromLongBits(Integer.toUnsignedLong(in.readUInt32())));
+ break;
+ case ENUM:
+ builder.add((long) in.readEnum());
+ break;
+ case SFIXED32:
+ builder.add((long) in.readSFixed32());
+ break;
+ case SFIXED64:
+ builder.add(in.readSFixed64());
+ break;
+ case SINT32:
+ builder.add((long) in.readSInt32());
+ break;
+ case SINT64:
+ builder.add(in.readSInt64());
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported packed proto field type: " + fieldType);
+ }
+ }
+ return builder.build();
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to parse packed repeated field", e);
+ }
+ }
+
+ public static RawProtoMessageLiteValue create(
+ ByteString rawWireBytes, ProtoLiteCelValueConverter protoLiteCelValueConverter) {
+ return create(rawWireBytes, "", protoLiteCelValueConverter);
+ }
+
+ public static RawProtoMessageLiteValue create(
+ ByteString rawWireBytes,
+ String protoTypeName,
+ ProtoLiteCelValueConverter protoLiteCelValueConverter) {
+ checkNotNull(rawWireBytes);
+ checkNotNull(protoTypeName);
+ checkNotNull(protoLiteCelValueConverter);
+ return new AutoValue_RawProtoMessageLiteValue(
+ rawWireBytes, StructTypeReference.create(protoTypeName), protoLiteCelValueConverter);
+ }
+
+ RawProtoMessageLiteValue() {}
+}
diff --git a/common/src/main/java/dev/cel/common/values/SelectField.java b/common/src/main/java/dev/cel/common/values/SelectField.java
new file mode 100644
index 000000000..f9f8b6906
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/values/SelectField.java
@@ -0,0 +1,124 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dev.cel.common.values;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.auto.value.AutoValue;
+import com.google.errorprone.annotations.Immutable;
+import dev.cel.common.annotations.Internal;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * Represents a single field selection in an optimized selection chain.
+ *
+ * CEL Library Internals. Do Not Use.
+ */
+@Internal
+@AutoValue
+@AutoValue.CopyAnnotations
+@Immutable
+@SuppressWarnings("Immutable") // Default value is an immutable CEL literal or null
+public abstract class SelectField {
+
+ private static final int MAX_FIELD_NUMBER = 536870911;
+
+ /** CEL-specific type code used to encode CEL maps on the wire. */
+ public static final int CEL_MAP_TYPE_CODE = -1;
+
+ /** Sentinel for a presence-test qualifier, whose 2-tuple carries no type code. */
+ public static final int NO_TYPE_CODE = 0;
+
+ /** Protobuf field type code for {@code TYPE_MESSAGE} ({@code FieldDescriptorProto.Type}). */
+ public static final int MESSAGE_TYPE_CODE = 11;
+
+ // Mirrors FieldDescriptorProto.Type. Not validated against a protobuf enum because the :values
+ // target is deliberately protobuf-free; keep in sync with CelLiteDescriptor.FieldLiteDescriptor.
+ private static final int MIN_PROTO_TYPE_CODE = 1; // TYPE_DOUBLE
+ private static final int MAX_PROTO_TYPE_CODE = 18; // TYPE_SINT64
+ private static final int GROUP_PROTO_TYPE_CODE = 10; // Unsupported by CEL.
+
+ /** Protobuf field number of this hop. */
+ public abstract int fieldNumber();
+
+ /** Protobuf field name or map key of this hop. */
+ public abstract String fieldName();
+
+ /**
+ * Protobuf wire type code (1..18, except 10), {@link #CEL_MAP_TYPE_CODE}, or {@link
+ * #NO_TYPE_CODE}.
+ */
+ public abstract int typeCode();
+
+ /**
+ * Default value for this hop, or null if unspecified. When non-null, this must be an immutable
+ * CEL literal value.
+ */
+ public abstract @Nullable Object defaultValue();
+
+ /**
+ * Creates a presence-test qualifier hop.
+ *
+ * @param fieldNumber Protobuf field number. Takes {@code long} for compatibility with CEL's
+ * int64 constant representations.
+ * @param fieldName Protobuf field name.
+ */
+ public static SelectField create(long fieldNumber, String fieldName) {
+ checkArgument(
+ fieldNumber >= 1 && fieldNumber <= MAX_FIELD_NUMBER,
+ "Field number out of protobuf range: %s",
+ fieldNumber);
+ checkNotNull(fieldName);
+ return new AutoValue_SelectField(
+ (int) fieldNumber, fieldName, NO_TYPE_CODE, /* defaultValue= */ null);
+ }
+
+ /**
+ * Creates a fully-specified field selection hop with type code and optional default value.
+ *
+ * @param fieldNumber Protobuf field number. Takes {@code long} for compatibility with CEL's
+ * int64 constant representations.
+ * @param fieldName Protobuf field name.
+ * @param typeCode Protobuf wire type code or {@link #CEL_MAP_TYPE_CODE}. Takes {@code long} for
+ * compatibility with CEL's int64 constant representations.
+ * @param defaultValue Default value for the field, or null if unspecified.
+ */
+ public static SelectField create(
+ long fieldNumber, String fieldName, long typeCode, @Nullable Object defaultValue) {
+ checkArgument(
+ fieldNumber >= 1 && fieldNumber <= MAX_FIELD_NUMBER,
+ "Field number out of protobuf range: %s",
+ fieldNumber);
+ checkNotNull(fieldName);
+ checkArgument(isSupportedTypeCode(typeCode), "Invalid protobuf type code: %s", typeCode);
+ return new AutoValue_SelectField((int) fieldNumber, fieldName, (int) typeCode, defaultValue);
+ }
+
+ /**
+ * Returns whether {@code typeCode} is a protobuf field type code CEL supports, or the {@link
+ * #CEL_MAP_TYPE_CODE} sentinel.
+ */
+ public static boolean isSupportedTypeCode(long typeCode) {
+ if (typeCode == CEL_MAP_TYPE_CODE) {
+ return true;
+ }
+ return typeCode >= MIN_PROTO_TYPE_CODE
+ && typeCode <= MAX_PROTO_TYPE_CODE
+ && typeCode != GROUP_PROTO_TYPE_CODE;
+ }
+
+ SelectField() {}
+}
diff --git a/common/src/test/java/dev/cel/common/values/BUILD.bazel b/common/src/test/java/dev/cel/common/values/BUILD.bazel
index 76c761567..1732c6667 100644
--- a/common/src/test/java/dev/cel/common/values/BUILD.bazel
+++ b/common/src/test/java/dev/cel/common/values/BUILD.bazel
@@ -15,12 +15,14 @@ java_library(
"//common:cel_ast",
"//common:cel_descriptor_util",
"//common:options",
+ "//common/exceptions:attribute_not_found",
"//common/internal:cel_descriptor_pools",
"//common/internal:cel_lite_descriptor_pool",
"//common/internal:default_lite_descriptor_pool",
"//common/internal:default_message_factory",
"//common/internal:dynamic_proto",
"//common/internal:proto_message_factory",
+ "//common/internal:proto_time_utils",
"//common/types",
"//common/types:type_providers",
"//common/values",
@@ -28,10 +30,14 @@ java_library(
"//common/values:cel_value_provider",
"//common/values:combined_cel_value_converter",
"//common/values:combined_cel_value_provider",
+ "//common/values:optimized_select_traversal",
+ "//common/values:optimized_selectable",
"//common/values:proto_message_lite_value",
"//common/values:proto_message_lite_value_provider",
"//common/values:proto_message_value",
"//common/values:proto_message_value_provider",
+ "//common/values:select_field",
+ "//protobuf:cel_lite_descriptor",
"//testing/protos:test_all_types_cel_java_proto3",
"@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto",
"@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto",
diff --git a/common/src/test/java/dev/cel/common/values/OptimizedSelectTraversalTest.java b/common/src/test/java/dev/cel/common/values/OptimizedSelectTraversalTest.java
new file mode 100644
index 000000000..239eae57f
--- /dev/null
+++ b/common/src/test/java/dev/cel/common/values/OptimizedSelectTraversalTest.java
@@ -0,0 +1,424 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dev.cel.common.values;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+import dev.cel.common.exceptions.CelAttributeNotFoundException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(TestParameterInjector.class)
+public final class OptimizedSelectTraversalTest {
+
+ private static final CelValueConverter DEFAULT_CONVERTER = CelValueConverter.getDefaultInstance();
+
+ private enum TargetType {
+ MAP {
+ @Override
+ Object createTarget(Map data) {
+ return ImmutableMap.copyOf(data);
+ }
+
+ @Override
+ Object createNestedTarget(Map innerData) {
+ return ImmutableMap.of("outer_key", ImmutableMap.copyOf(innerData));
+ }
+ },
+ OPTIMIZED_SELECTABLE {
+ @Override
+ Object createTarget(Map data) {
+ return new FakeOptimizedSelectable(data);
+ }
+
+ @Override
+ Object createNestedTarget(Map innerData) {
+ return new FakeOptimizedSelectable(
+ ImmutableMap.of("outer_key", new FakeOptimizedSelectable(innerData)));
+ }
+ },
+ SELECTABLE_VALUE {
+ @Override
+ Object createTarget(Map data) {
+ return new FakeSelectableValue(data);
+ }
+
+ @Override
+ Object createNestedTarget(Map innerData) {
+ return new FakeSelectableValue(
+ ImmutableMap.of("outer_key", new FakeSelectableValue(innerData)));
+ }
+ };
+
+ abstract Object createTarget(Map data);
+
+ abstract Object createNestedTarget(Map innerData);
+ }
+
+ @SuppressWarnings("Immutable")
+ private enum NestedPresenceTestCase {
+ ALL_PRESENT(
+ ImmutableMap.of("inner_key", "nested_val"), "outer_key", "inner_key", /* expected= */ true),
+ INTERMEDIATE_MISSING(
+ ImmutableMap.of("inner_key", "nested_val"),
+ "missing_outer",
+ "inner_key",
+ /* expected= */ false),
+ TERMINAL_MISSING(
+ ImmutableMap.of("other_key", "nested_val"),
+ "outer_key",
+ "missing_terminal",
+ /* expected= */ false);
+
+ final ImmutableMap innerData;
+ final String outerField;
+ final String innerField;
+ final boolean expected;
+
+ NestedPresenceTestCase(
+ ImmutableMap innerData,
+ String outerField,
+ String innerField,
+ boolean expected) {
+ this.innerData = innerData;
+ this.outerField = outerField;
+ this.innerField = innerField;
+ this.expected = expected;
+ }
+ }
+
+ @Test
+ public void qualify_emptyFields_returnsTargetInstance() {
+ Object target = new Object();
+
+ Object result = OptimizedSelectTraversal.qualify(target, ImmutableList.of(), DEFAULT_CONVERTER);
+
+ assertThat(result).isSameInstanceAs(target);
+ }
+
+ @Test
+ public void qualify_singleField_success(@TestParameter TargetType targetType) {
+ Object target = targetType.createTarget(ImmutableMap.of("key", "value"));
+ ImmutableList fields = ImmutableList.of(SelectField.create(1L, "key", 9, ""));
+
+ Object result = OptimizedSelectTraversal.qualify(target, fields, DEFAULT_CONVERTER);
+
+ assertThat(result).isEqualTo("value");
+ }
+
+ @Test
+ public void qualify_nested_success(@TestParameter TargetType targetType) {
+ Object target = targetType.createNestedTarget(ImmutableMap.of("inner_key", "nested_value"));
+ ImmutableList fields =
+ ImmutableList.of(
+ SelectField.create(1L, "outer_key", SelectField.CEL_MAP_TYPE_CODE, ImmutableMap.of()),
+ SelectField.create(2L, "inner_key", 9, ""));
+
+ Object result = OptimizedSelectTraversal.qualify(target, fields, DEFAULT_CONVERTER);
+
+ assertThat(result).isEqualTo("nested_value");
+ }
+
+ @Test
+ public void qualify_singleField_missingThrowsException(@TestParameter TargetType targetType) {
+ Object target = targetType.createTarget(ImmutableMap.of("present", "value"));
+ ImmutableList fields =
+ ImmutableList.of(SelectField.create(1L, "missing", 9, null));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> OptimizedSelectTraversal.qualify(target, fields, DEFAULT_CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("missing");
+ }
+
+ @Test
+ public void qualify_map_nullValue_throwsException() {
+ Map map = new HashMap<>();
+ map.put("null_key", null);
+ ImmutableList fields = ImmutableList.of(SelectField.create(1L, "null_key"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> OptimizedSelectTraversal.qualify(map, fields, DEFAULT_CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("Map value cannot be null for key: null_key");
+ }
+
+ @Test
+ public void qualify_optimizedSelectable_absentWithDefaultValue_returnsDefault() {
+ FakeOptimizedSelectable selectable = new FakeOptimizedSelectable(ImmutableMap.of());
+ ImmutableList fields =
+ ImmutableList.of(SelectField.create(1L, "absent", 9, "default_fallback"));
+
+ Object result = OptimizedSelectTraversal.qualify(selectable, fields, DEFAULT_CONVERTER);
+
+ assertThat(result).isEqualTo("default_fallback");
+ }
+
+ @Test
+ public void qualify_selectableValue_absentWithDefaultValue_returnsDefault() {
+ FakeSelectableValue selectable = new FakeSelectableValue(ImmutableMap.of());
+ ImmutableList fields =
+ ImmutableList.of(SelectField.create(1L, "absent", 9, "default_fallback"));
+
+ Object result = OptimizedSelectTraversal.qualify(selectable, fields, DEFAULT_CONVERTER);
+
+ assertThat(result).isEqualTo("default_fallback");
+ }
+
+ @Test
+ public void qualify_rootMap_convertsUnadaptedEntryWithConverter() {
+ TrackingConverter customConverter = new TrackingConverter();
+ ImmutableMap rootMap = ImmutableMap.of("step1", "adapt_to_selectable");
+ ImmutableList fields =
+ ImmutableList.of(SelectField.create(1L, "step1"), SelectField.create(2L, "leaf", 9, ""));
+
+ Object result = OptimizedSelectTraversal.qualify(rootMap, fields, customConverter);
+
+ assertThat(result).isEqualTo("custom_adapted");
+ assertThat(customConverter.callCount.get()).isEqualTo(1);
+ }
+
+ @Test
+ public void qualify_unsupportedTarget_throwsException() {
+ ImmutableList fields = ImmutableList.of(SelectField.create(1L, "invalid_field"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> OptimizedSelectTraversal.qualify(12345L, fields, DEFAULT_CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("invalid_field");
+ }
+
+ @Test
+ public void qualify_intermediateUnsupportedTarget_throwsException() {
+ ImmutableMap map = ImmutableMap.of("scalar", 999L);
+ ImmutableList fields =
+ ImmutableList.of(
+ SelectField.create(1L, "scalar", 3, 0L), SelectField.create(2L, "unreachable", 9, ""));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> OptimizedSelectTraversal.qualify(map, fields, DEFAULT_CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("unreachable");
+ }
+
+ @Test
+ public void hasField_emptyFields_returnsFalse() {
+ Object target = ImmutableMap.of("key", "value");
+
+ boolean hasField =
+ OptimizedSelectTraversal.hasField(target, ImmutableList.of(), DEFAULT_CONVERTER);
+
+ assertThat(hasField).isFalse();
+ }
+
+ @Test
+ public void hasField_singleField(
+ @TestParameter TargetType targetType,
+ @TestParameter({"present_key", "missing_key"}) String queryKey) {
+ Object target = targetType.createTarget(ImmutableMap.of("present_key", "val"));
+ ImmutableList fields = ImmutableList.of(SelectField.create(1L, queryKey));
+
+ boolean hasField = OptimizedSelectTraversal.hasField(target, fields, DEFAULT_CONVERTER);
+
+ assertThat(hasField).isEqualTo(queryKey.equals("present_key"));
+ }
+
+ @Test
+ public void hasField_nestedFields(
+ @TestParameter TargetType targetType, @TestParameter NestedPresenceTestCase testCase) {
+ Object target = targetType.createNestedTarget(testCase.innerData);
+ ImmutableList fields =
+ ImmutableList.of(
+ SelectField.create(1L, testCase.outerField),
+ SelectField.create(2L, testCase.innerField));
+
+ boolean hasField = OptimizedSelectTraversal.hasField(target, fields, DEFAULT_CONVERTER);
+
+ assertThat(hasField).isEqualTo(testCase.expected);
+ }
+
+ @Test
+ public void hasField_map_terminalNullValue_returnsTrue() {
+ Map map = new HashMap<>();
+ map.put("null_key", null);
+ ImmutableList fields = ImmutableList.of(SelectField.create(1L, "null_key"));
+
+ boolean hasField = OptimizedSelectTraversal.hasField(map, fields, DEFAULT_CONVERTER);
+
+ assertThat(hasField).isTrue();
+ }
+
+ @Test
+ public void hasField_map_intermediateNullValue_throwsException() {
+ Map map = new HashMap<>();
+ map.put("child", null);
+ ImmutableList fields =
+ ImmutableList.of(SelectField.create(1L, "child"), SelectField.create(2L, "leaf"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> OptimizedSelectTraversal.hasField(map, fields, DEFAULT_CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("Map value cannot be null for key: child");
+ }
+
+ @Test
+ public void hasField_intermediateUnsupportedTarget_throwsException() {
+ ImmutableMap map = ImmutableMap.of("scalar_key", 100L);
+ ImmutableList fields =
+ ImmutableList.of(SelectField.create(1L, "scalar_key"), SelectField.create(2L, "child_key"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> OptimizedSelectTraversal.hasField(map, fields, DEFAULT_CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("child_key");
+ }
+
+ @Test
+ public void hasField_rootMap_convertsUnadaptedEntryWithConverter() {
+ TrackingConverter customConverter = new TrackingConverter();
+ ImmutableMap rootMap = ImmutableMap.of("step1", "adapt_to_selectable");
+ ImmutableList fields =
+ ImmutableList.of(SelectField.create(1L, "step1"), SelectField.create(2L, "leaf"));
+
+ boolean hasField = OptimizedSelectTraversal.hasField(rootMap, fields, customConverter);
+
+ assertThat(hasField).isTrue();
+ assertThat(customConverter.callCount.get()).isEqualTo(1);
+ }
+
+ @Test
+ public void hasField_unsupportedTarget_throwsException() {
+ ImmutableList fields = ImmutableList.of(SelectField.create(1L, "invalid_field"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> OptimizedSelectTraversal.hasField(12345L, fields, DEFAULT_CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("invalid_field");
+ }
+
+ @Test
+ public void qualify_errorValue_propagatesError() {
+ ErrorValue error = ErrorValue.create(1L, new RuntimeException("test error"));
+ ImmutableList fields =
+ ImmutableList.of(SelectField.create(1L, "field1"), SelectField.create(2L, "field2"));
+
+ Object result = OptimizedSelectTraversal.qualify(error, fields, DEFAULT_CONVERTER);
+
+ assertThat(result).isSameInstanceAs(error);
+ }
+
+ @Test
+ public void hasField_errorValue_returnsFalse() {
+ ErrorValue error = ErrorValue.create(1L, new RuntimeException("test error"));
+ ImmutableList fields =
+ ImmutableList.of(SelectField.create(1L, "field1"), SelectField.create(2L, "field2"));
+
+ boolean result = OptimizedSelectTraversal.hasField(error, fields, DEFAULT_CONVERTER);
+
+ assertThat(result).isFalse();
+ }
+
+ @SuppressWarnings("Immutable")
+ private static final class FakeOptimizedSelectable implements OptimizedSelectable {
+ private final ImmutableMap values;
+
+ @Override
+ public Object selectByFieldNumber(SelectField field) {
+ Object value = values.get(field.fieldName());
+ if (value != null) {
+ return value;
+ }
+ if (field.defaultValue() != null) {
+ return field.defaultValue();
+ }
+ throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
+ }
+
+ @Override
+ public boolean hasFieldByNumber(SelectField field) {
+ return values.containsKey(field.fieldName());
+ }
+
+ @Override
+ public Optional findByFieldNumber(SelectField field) {
+ return Optional.ofNullable(values.get(field.fieldName()));
+ }
+
+ FakeOptimizedSelectable(Map values) {
+ this.values = ImmutableMap.copyOf(values);
+ }
+ }
+
+ @SuppressWarnings("Immutable")
+ private static final class FakeSelectableValue implements SelectableValue {
+ private final ImmutableMap values;
+
+ @Override
+ public Object select(String field) {
+ Object value = values.get(field);
+ if (value != null) {
+ return value;
+ }
+ throw CelAttributeNotFoundException.forFieldResolution(field);
+ }
+
+ @Override
+ public Optional find(String field) {
+ return Optional.ofNullable(values.get(field));
+ }
+
+ FakeSelectableValue(Map values) {
+ this.values = ImmutableMap.copyOf(values);
+ }
+ }
+
+ @SuppressWarnings("Immutable")
+ private static final class TrackingConverter extends CelValueConverter {
+ private final AtomicInteger callCount = new AtomicInteger();
+
+ @Override
+ public Object toRuntimeValue(Object value) {
+ callCount.incrementAndGet();
+ if (Objects.equals(value, "adapt_to_selectable")) {
+ return new FakeOptimizedSelectable(ImmutableMap.of("leaf", "custom_adapted"));
+ }
+ return super.toRuntimeValue(value);
+ }
+ }
+}
diff --git a/common/src/test/java/dev/cel/common/values/ProtoLiteCelValueConverterTest.java b/common/src/test/java/dev/cel/common/values/ProtoLiteCelValueConverterTest.java
index 3b66171e4..0a11c23e5 100644
--- a/common/src/test/java/dev/cel/common/values/ProtoLiteCelValueConverterTest.java
+++ b/common/src/test/java/dev/cel/common/values/ProtoLiteCelValueConverterTest.java
@@ -15,6 +15,7 @@
package dev.cel.common.values;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
@@ -44,13 +45,36 @@
import dev.cel.common.values.ProtoLiteCelValueConverter.MessageFields;
import dev.cel.expr.conformance.proto3.TestAllTypes;
import dev.cel.expr.conformance.proto3.TestAllTypesCelDescriptor;
+import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor;
+import dev.cel.protobuf.CelLiteDescriptor.MessageLiteDescriptor;
+import java.io.IOException;
import java.time.Instant;
import java.util.LinkedHashMap;
+import java.util.NoSuchElementException;
+import java.util.Optional;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(TestParameterInjector.class)
public class ProtoLiteCelValueConverterTest {
+ private static final CelLiteDescriptorPool EMPTY_DESCRIPTOR_POOL =
+ new CelLiteDescriptorPool() {
+ @Override
+ public Optional findDescriptor(String protoTypeName) {
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional findDescriptor(MessageLite messageLite) {
+ return Optional.empty();
+ }
+
+ @Override
+ public MessageLiteDescriptor getDescriptorOrThrow(String protoTypeName) {
+ throw new NoSuchElementException(protoTypeName);
+ }
+ };
+
private static final CelLiteDescriptorPool DESCRIPTOR_POOL =
DefaultLiteDescriptorPool.newInstance(
ImmutableSet.of(TestAllTypesCelDescriptor.getDescriptor()));
@@ -307,7 +331,7 @@ public void readAllFields_unknownFieldsWithValues() throws Exception {
LinkedHashMap mapBoolDoubleValues =
(LinkedHashMap) fields.values().get("map_bool_double");
assertThat(mapBoolDoubleValues).containsExactly(true, 1.5d, false, 2.5d).inOrder();
- Multimap unknownValues = fields.unknowns();
+ ImmutableListMultimap unknownValues = fields.unknowns();
assertThat(unknownValues)
.containsExactly(
2500,
@@ -326,4 +350,99 @@ public void readAllFields_unknownFieldsWithValues() throws Exception {
ByteString.copyFromUtf8("\n\003bar\020\005"))
.inOrder();
}
+
+ @Test
+ public void getDefaultCelValue_fieldDescriptor_returnsDefault() {
+ FieldLiteDescriptor fieldDescriptor =
+ DESCRIPTOR_POOL
+ .getDescriptorOrThrow("cel.expr.conformance.proto3.TestAllTypes")
+ .getByFieldNameOrThrow("single_string");
+
+ Object defaultValue = PROTO_LITE_CEL_VALUE_CONVERTER.getDefaultCelValue(fieldDescriptor);
+
+ assertThat(defaultValue).isEqualTo("");
+ }
+
+ @Test
+ public void getDefaultCelValue_nestedMessageWithoutDescriptor_throwsNoSuchElementException() {
+ FieldLiteDescriptor nestedMsgField =
+ DESCRIPTOR_POOL
+ .getDescriptorOrThrow("cel.expr.conformance.proto3.TestAllTypes")
+ .getByFieldNameOrThrow("single_nested_message");
+ ProtoLiteCelValueConverter converterWithoutNested =
+ ProtoLiteCelValueConverter.newInstance(EMPTY_DESCRIPTOR_POOL);
+
+ assertThrows(
+ NoSuchElementException.class,
+ () -> converterWithoutNested.getDefaultCelValue(nestedMsgField));
+ }
+
+ @Test
+ public void tryDecodeWellKnownProto_validBytes_returnsDecodedValue() {
+ Int32Value int32Value = Int32Value.of(42);
+
+ Optional decoded =
+ PROTO_LITE_CEL_VALUE_CONVERTER.tryDecodeWellKnownProto(
+ int32Value.toByteString(), "google.protobuf.Int32Value");
+
+ assertThat(decoded).hasValue(42L);
+ }
+
+ @Test
+ public void tryDecodeWellKnownProto_notWellKnownType_returnsEmpty() {
+ Optional decoded =
+ PROTO_LITE_CEL_VALUE_CONVERTER.tryDecodeWellKnownProto(
+ ByteString.EMPTY, "cel.expr.conformance.proto3.TestAllTypes");
+
+ assertThat(decoded).isEmpty();
+ }
+
+ @Test
+ public void tryDecodeWellKnownProto_missingDescriptor_returnsEmpty() {
+ ProtoLiteCelValueConverter converter =
+ ProtoLiteCelValueConverter.newInstance(EMPTY_DESCRIPTOR_POOL);
+
+ Optional decoded =
+ converter.tryDecodeWellKnownProto(ByteString.EMPTY, "google.protobuf.Int32Value");
+
+ assertThat(decoded).isEmpty();
+ }
+
+ @Test
+ public void tryDecodeWellKnownProto_invalidBytes_throwsIllegalArgumentException() {
+ ByteString corruptBytes = ByteString.copyFrom(new byte[] {(byte) 0xFF, (byte) 0xFF});
+
+ IllegalArgumentException exception =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ PROTO_LITE_CEL_VALUE_CONVERTER.tryDecodeWellKnownProto(
+ corruptBytes, "google.protobuf.Int32Value"));
+
+ assertThat(exception)
+ .hasMessageThat()
+ .contains("Failed to decode well-known proto of type: google.protobuf.Int32Value");
+ assertThat(exception).hasCauseThat().isInstanceOf(IOException.class);
+ }
+
+ @Test
+ public void tryDecodeWellKnownProto_anyType_throwsUnsupportedOperationException() {
+ UnsupportedOperationException exception =
+ assertThrows(
+ UnsupportedOperationException.class,
+ () ->
+ PROTO_LITE_CEL_VALUE_CONVERTER.tryDecodeWellKnownProto(
+ ByteString.EMPTY, "google.protobuf.Any"));
+
+ assertThat(exception).hasMessageThat().contains("ANY_VALUE");
+ }
+
+ @Test
+ public void hasDescriptor_returnsExpectedResult() {
+ assertThat(
+ PROTO_LITE_CEL_VALUE_CONVERTER.hasDescriptor(
+ "cel.expr.conformance.proto3.TestAllTypes"))
+ .isTrue();
+ assertThat(PROTO_LITE_CEL_VALUE_CONVERTER.hasDescriptor("unknown.Type")).isFalse();
+ }
}
diff --git a/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueTest.java b/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueTest.java
index dbfb55cf9..88799878e 100644
--- a/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueTest.java
+++ b/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueTest.java
@@ -21,11 +21,17 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.UnsignedLong;
import com.google.protobuf.Any;
+import com.google.protobuf.BoolValue;
import com.google.protobuf.ByteString;
+import com.google.protobuf.BytesValue;
+import com.google.protobuf.CodedOutputStream;
+import com.google.protobuf.DoubleValue;
import com.google.protobuf.DynamicMessage;
+import com.google.protobuf.ExtensionRegistryLite;
import com.google.protobuf.FloatValue;
import com.google.protobuf.Int32Value;
import com.google.protobuf.Int64Value;
+import com.google.protobuf.StringValue;
import com.google.protobuf.Timestamp;
import com.google.protobuf.UInt32Value;
import com.google.protobuf.UInt64Value;
@@ -37,6 +43,7 @@
import dev.cel.expr.conformance.proto3.TestAllTypes.NestedEnum;
import dev.cel.expr.conformance.proto3.TestAllTypes.NestedMessage;
import dev.cel.expr.conformance.proto3.TestAllTypesCelDescriptor;
+import java.io.ByteArrayOutputStream;
import java.time.Duration;
import java.time.Instant;
import org.junit.Test;
@@ -153,19 +160,17 @@ public void selectField_success(@TestParameter SelectFieldTestCase testCase) {
.setSingleDouble(2.5d)
.setSingleString("test")
.setSingleBytes(ByteString.copyFrom(new byte[] {0x01}))
- .setSingleAny(
- Any.pack(DynamicMessage.newBuilder(com.google.protobuf.BoolValue.of(true)).build()))
+ .setSingleAny(Any.pack(DynamicMessage.newBuilder(BoolValue.of(true)).build()))
.setSingleDuration(com.google.protobuf.Duration.newBuilder().setSeconds(100))
.setSingleTimestamp(Timestamp.newBuilder().setSeconds(100))
.setSingleInt32Wrapper(Int32Value.of(5))
.setSingleInt64Wrapper(Int64Value.of(10L))
.setSingleUint32Wrapper(UInt32Value.of(1))
.setSingleUint64Wrapper(UInt64Value.of(UnsignedLong.MAX_VALUE.longValue()))
- .setSingleStringWrapper(com.google.protobuf.StringValue.of("hello"))
+ .setSingleStringWrapper(StringValue.of("hello"))
.setSingleFloatWrapper(FloatValue.of(7.5f))
- .setSingleDoubleWrapper(com.google.protobuf.DoubleValue.of(8.5d))
- .setSingleBytesWrapper(
- com.google.protobuf.BytesValue.of(ByteString.copyFrom(new byte[] {0x02})))
+ .setSingleDoubleWrapper(DoubleValue.of(8.5d))
+ .setSingleBytesWrapper(BytesValue.of(ByteString.copyFrom(new byte[] {0x02})))
.addRepeatedInt64(5L)
.addRepeatedInt64(6L)
.addRepeatedUint64(7L)
@@ -253,4 +258,26 @@ public void selectField_defaultValue(@TestParameter DefaultValueTestCase testCas
assertThat(selectedValue).isEqualTo(testCase.value);
}
+
+ @Test
+ public void unknownFields_retainsUnknownWireFields() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeInt64(999, 12345L);
+ cos.writeString(1000, "hello unknown");
+ cos.flush();
+
+ TestAllTypes msgWithUnknown =
+ TestAllTypes.parseFrom(baos.toByteArray(), ExtensionRegistryLite.getEmptyRegistry());
+ ProtoMessageLiteValue messageLiteValue =
+ ProtoMessageLiteValue.create(
+ msgWithUnknown,
+ "cel.expr.conformance.proto3.TestAllTypes",
+ PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ assertThat(messageLiteValue.unknownFields()).valuesForKey(999).containsExactly(12345L);
+ assertThat(messageLiteValue.unknownFields())
+ .valuesForKey(1000)
+ .containsExactly(ByteString.copyFromUtf8("hello unknown"));
+ }
}
diff --git a/common/src/test/java/dev/cel/common/values/RawProtoMessageLiteValueTest.java b/common/src/test/java/dev/cel/common/values/RawProtoMessageLiteValueTest.java
new file mode 100644
index 000000000..180883f60
--- /dev/null
+++ b/common/src/test/java/dev/cel/common/values/RawProtoMessageLiteValueTest.java
@@ -0,0 +1,1202 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dev.cel.common.values;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertThrows;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.primitives.UnsignedLong;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.CodedOutputStream;
+import com.google.protobuf.Int64Value;
+import com.google.protobuf.MessageLite;
+import com.google.protobuf.WireFormat;
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+import dev.cel.common.exceptions.CelAttributeNotFoundException;
+import dev.cel.common.internal.CelLiteDescriptorPool;
+import dev.cel.common.internal.DefaultLiteDescriptorPool;
+import dev.cel.common.internal.ProtoTimeUtils;
+import dev.cel.expr.conformance.proto3.TestAllTypes;
+import dev.cel.expr.conformance.proto3.TestAllTypesCelDescriptor;
+import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor;
+import dev.cel.protobuf.CelLiteDescriptor.MessageLiteDescriptor;
+import java.io.ByteArrayOutputStream;
+import java.time.Duration;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(TestParameterInjector.class)
+public final class RawProtoMessageLiteValueTest {
+
+ private static final ProtoLiteCelValueConverter CONVERTER =
+ ProtoLiteCelValueConverter.newInstance(
+ DefaultLiteDescriptorPool.newInstance(
+ ImmutableSet.of(TestAllTypesCelDescriptor.getDescriptor())));
+
+ private static final ProtoLiteCelValueConverter EMPTY_CONVERTER =
+ ProtoLiteCelValueConverter.newInstance(DefaultLiteDescriptorPool.newInstance());
+
+ private static Object decodeWireEntries(
+ ImmutableCollection entries, int typeCode, String protoTypeName, boolean isRepeated) {
+ return RawProtoMessageLiteValue.decodeWireEntries(
+ entries, typeCode, protoTypeName, isRepeated, EMPTY_CONVERTER);
+ }
+
+ private static Object decodeWireValue(
+ Object raw, WireFormat.FieldType fieldType, String protoTypeName) {
+ return RawProtoMessageLiteValue.decodeWireValue(raw, fieldType, protoTypeName, EMPTY_CONVERTER);
+ }
+
+ private static Object decodeWireValue(Object raw, int typeCode, String protoTypeName) {
+ return RawProtoMessageLiteValue.decodeWireValue(raw, typeCode, protoTypeName, EMPTY_CONVERTER);
+ }
+
+ @Test
+ public void create_accessorsAndType() {
+ ByteString bytes = ByteString.copyFromUtf8("test");
+ RawProtoMessageLiteValue value =
+ RawProtoMessageLiteValue.create(bytes, "custom.Message", EMPTY_CONVERTER);
+
+ assertThat(value.rawWireBytes()).isEqualTo(bytes);
+ assertThat(value.value()).isSameInstanceAs(value);
+ assertThat(value.celType().name()).isEqualTo("custom.Message");
+ }
+
+ @Test
+ public void create_defaultsEmptyTypeName() {
+ ByteString bytes = ByteString.copyFromUtf8("test");
+ RawProtoMessageLiteValue value = RawProtoMessageLiteValue.create(bytes, EMPTY_CONVERTER);
+
+ assertThat(value.rawWireBytes()).isEqualTo(bytes);
+ assertThat(value.celType().name()).isEmpty();
+ }
+
+ @Test
+ public void select_throwsCelAttributeNotFoundException() {
+ RawProtoMessageLiteValue value =
+ RawProtoMessageLiteValue.create(ByteString.EMPTY, "custom.Message", EMPTY_CONVERTER);
+
+ assertThrows(CelAttributeNotFoundException.class, () -> value.select("field"));
+ }
+
+ @Test
+ public void find_throwsCelAttributeNotFoundException() {
+ RawProtoMessageLiteValue value =
+ RawProtoMessageLiteValue.create(ByteString.EMPTY, "custom.Message", EMPTY_CONVERTER);
+
+ assertThrows(CelAttributeNotFoundException.class, () -> value.find("field"));
+ }
+
+ @Test
+ public void isZeroValue_emptyBytes_returnsTrue() {
+ RawProtoMessageLiteValue value =
+ RawProtoMessageLiteValue.create(ByteString.EMPTY, EMPTY_CONVERTER);
+
+ assertThat(value.isZeroValue()).isTrue();
+ }
+
+ @Test
+ public void isZeroValue_nonEmptyBytes_returnsFalse() {
+ RawProtoMessageLiteValue value =
+ RawProtoMessageLiteValue.create(ByteString.copyFromUtf8("data"), EMPTY_CONVERTER);
+
+ assertThat(value.isZeroValue()).isFalse();
+ }
+
+ @Test
+ public void hasFieldByNumber_scalarField_returnsExpectedPresence() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeInt64(1, 42L);
+ cos.flush();
+ RawProtoMessageLiteValue value =
+ RawProtoMessageLiteValue.create(ByteString.copyFrom(baos.toByteArray()), EMPTY_CONVERTER);
+
+ SelectField field1 =
+ SelectField.create(1L, "single_int64", FieldLiteDescriptor.Type.INT64.getNumber(), 0L);
+ SelectField field2 =
+ SelectField.create(2L, "single_int64", FieldLiteDescriptor.Type.INT64.getNumber(), 0L);
+
+ assertThat(value.hasFieldByNumber(field1)).isTrue();
+ assertThat(value.hasFieldByNumber(field2)).isFalse();
+ }
+
+ @Test
+ public void unknownFields_parsesWireTags() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeInt64(1, 42L);
+ cos.writeFixed32(2, 100);
+ cos.writeFixed64(3, 200L);
+ cos.writeString(4, "hello");
+ cos.flush();
+
+ RawProtoMessageLiteValue value =
+ RawProtoMessageLiteValue.create(ByteString.copyFrom(baos.toByteArray()), EMPTY_CONVERTER);
+
+ assertThat(value.unknownFields()).valuesForKey(1).containsExactly(42L);
+ assertThat(value.unknownFields()).valuesForKey(2).containsExactly(100);
+ assertThat(value.unknownFields()).valuesForKey(3).containsExactly(200L);
+ assertThat(value.unknownFields())
+ .valuesForKey(4)
+ .containsExactly(ByteString.copyFromUtf8("hello"));
+ }
+
+ @Test
+ public void decodeWireEntries_emptySingularEntries_returnsNull() {
+ Object intResult =
+ decodeWireEntries(
+ ImmutableList.of(),
+ FieldLiteDescriptor.Type.INT64.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ false);
+ Object messageResult =
+ decodeWireEntries(
+ ImmutableList.of(),
+ FieldLiteDescriptor.Type.MESSAGE.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ false);
+
+ assertThat(intResult).isNull();
+ assertThat(messageResult).isNull();
+ }
+
+ @Test
+ public void decodeWireEntries_emptyRepeatedEntries_returnsEmptyList() {
+ Object result =
+ decodeWireEntries(
+ ImmutableList.of(),
+ FieldLiteDescriptor.Type.INT64.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true);
+
+ assertThat((Iterable>) result).isEmpty();
+ }
+
+ @Test
+ public void decodeWireEntries_nonRepeated_lastOneWins() {
+ Object decoded =
+ decodeWireEntries(
+ ImmutableList.of(10L, 20L, 30L),
+ FieldLiteDescriptor.Type.INT64.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ false);
+
+ assertThat(decoded).isEqualTo(30L);
+ }
+
+ @Test
+ public void decodeWireEntries_repeatedUnpacked() {
+ Object decoded =
+ decodeWireEntries(
+ ImmutableList.of(10L, 20L, 30L),
+ FieldLiteDescriptor.Type.INT64.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true);
+
+ assertThat(decoded).isEqualTo(ImmutableList.of(10L, 20L, 30L));
+ }
+
+ @Test
+ public void decodeWireEntries_packedInt32() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeInt32NoTag(1);
+ cos.writeInt32NoTag(2);
+ cos.writeInt32NoTag(3);
+ cos.flush();
+
+ Object decoded =
+ decodeWireEntries(
+ ImmutableList.of(ByteString.copyFrom(baos.toByteArray())),
+ FieldLiteDescriptor.Type.INT32.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true);
+
+ assertThat(decoded).isEqualTo(ImmutableList.of(1L, 2L, 3L));
+ }
+
+ @Test
+ public void decodeWireEntries_packedInt64() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeInt64NoTag(100L);
+ cos.writeInt64NoTag(200L);
+ cos.flush();
+
+ Object decoded =
+ decodeWireEntries(
+ ImmutableList.of(ByteString.copyFrom(baos.toByteArray())),
+ FieldLiteDescriptor.Type.INT64.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true);
+
+ assertThat(decoded).isEqualTo(ImmutableList.of(100L, 200L));
+ }
+
+ @Test
+ public void decodeWireEntries_packedUint32() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeUInt32NoTag(50);
+ cos.flush();
+
+ Object decoded =
+ decodeWireEntries(
+ ImmutableList.of(ByteString.copyFrom(baos.toByteArray())),
+ FieldLiteDescriptor.Type.UINT32.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true);
+
+ assertThat(decoded).isEqualTo(ImmutableList.of(UnsignedLong.fromLongBits(50L)));
+ }
+
+ @Test
+ public void decodeWireEntries_packedUint64() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeUInt64NoTag(999L);
+ cos.flush();
+
+ Object decoded =
+ decodeWireEntries(
+ ImmutableList.of(ByteString.copyFrom(baos.toByteArray())),
+ FieldLiteDescriptor.Type.UINT64.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true);
+
+ assertThat(decoded).isEqualTo(ImmutableList.of(UnsignedLong.fromLongBits(999L)));
+ }
+
+ @Test
+ public void decodeWireEntries_packedSint32AndSint64() throws Exception {
+ ByteArrayOutputStream baos32 = new ByteArrayOutputStream();
+ CodedOutputStream cos32 = CodedOutputStream.newInstance(baos32);
+ cos32.writeSInt32NoTag(-10);
+ cos32.writeSInt32NoTag(20);
+ cos32.flush();
+
+ Object decoded32 =
+ decodeWireEntries(
+ ImmutableList.of(ByteString.copyFrom(baos32.toByteArray())),
+ FieldLiteDescriptor.Type.SINT32.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true);
+
+ assertThat(decoded32).isEqualTo(ImmutableList.of(-10L, 20L));
+
+ ByteArrayOutputStream baos64 = new ByteArrayOutputStream();
+ CodedOutputStream cos64 = CodedOutputStream.newInstance(baos64);
+ cos64.writeSInt64NoTag(-100L);
+ cos64.writeSInt64NoTag(200L);
+ cos64.flush();
+
+ Object decoded64 =
+ decodeWireEntries(
+ ImmutableList.of(ByteString.copyFrom(baos64.toByteArray())),
+ FieldLiteDescriptor.Type.SINT64.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true);
+
+ assertThat(decoded64).isEqualTo(ImmutableList.of(-100L, 200L));
+ }
+
+ @Test
+ public void decodeWireEntries_packedFixedAndSFixed() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeFixed32NoTag(10);
+ cos.writeFixed64NoTag(20L);
+ cos.writeSFixed32NoTag(-30);
+ cos.writeSFixed64NoTag(-40L);
+ cos.flush();
+
+ assertThat(
+ decodeWireEntries(
+ ImmutableList.of(ByteString.copyFrom(baos.toByteArray()).substring(0, 4)),
+ FieldLiteDescriptor.Type.FIXED32.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true))
+ .isEqualTo(ImmutableList.of(UnsignedLong.fromLongBits(10L)));
+
+ assertThat(
+ decodeWireEntries(
+ ImmutableList.of(ByteString.copyFrom(baos.toByteArray()).substring(4, 12)),
+ FieldLiteDescriptor.Type.FIXED64.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true))
+ .isEqualTo(ImmutableList.of(UnsignedLong.fromLongBits(20L)));
+
+ assertThat(
+ decodeWireEntries(
+ ImmutableList.of(ByteString.copyFrom(baos.toByteArray()).substring(12, 16)),
+ FieldLiteDescriptor.Type.SFIXED32.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true))
+ .isEqualTo(ImmutableList.of(-30L));
+
+ assertThat(
+ decodeWireEntries(
+ ImmutableList.of(ByteString.copyFrom(baos.toByteArray()).substring(16, 24)),
+ FieldLiteDescriptor.Type.SFIXED64.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true))
+ .isEqualTo(ImmutableList.of(-40L));
+ }
+
+ @Test
+ public void decodeWireEntries_packedBoolFloatDoubleEnum() throws Exception {
+ ByteArrayOutputStream baosBool = new ByteArrayOutputStream();
+ CodedOutputStream cosBool = CodedOutputStream.newInstance(baosBool);
+ cosBool.writeBoolNoTag(true);
+ cosBool.writeBoolNoTag(false);
+ cosBool.flush();
+
+ assertThat(
+ decodeWireEntries(
+ ImmutableList.of(ByteString.copyFrom(baosBool.toByteArray())),
+ FieldLiteDescriptor.Type.BOOL.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true))
+ .isEqualTo(ImmutableList.of(true, false));
+
+ ByteArrayOutputStream baosFloat = new ByteArrayOutputStream();
+ CodedOutputStream cosFloat = CodedOutputStream.newInstance(baosFloat);
+ cosFloat.writeFloatNoTag(1.5f);
+ cosFloat.flush();
+
+ assertThat(
+ decodeWireEntries(
+ ImmutableList.of(ByteString.copyFrom(baosFloat.toByteArray())),
+ FieldLiteDescriptor.Type.FLOAT.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true))
+ .isEqualTo(ImmutableList.of(1.5d));
+
+ ByteArrayOutputStream baosDouble = new ByteArrayOutputStream();
+ CodedOutputStream cosDouble = CodedOutputStream.newInstance(baosDouble);
+ cosDouble.writeDoubleNoTag(3.14d);
+ cosDouble.flush();
+
+ assertThat(
+ decodeWireEntries(
+ ImmutableList.of(ByteString.copyFrom(baosDouble.toByteArray())),
+ FieldLiteDescriptor.Type.DOUBLE.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true))
+ .isEqualTo(ImmutableList.of(3.14d));
+
+ ByteArrayOutputStream baosEnum = new ByteArrayOutputStream();
+ CodedOutputStream cosEnum = CodedOutputStream.newInstance(baosEnum);
+ cosEnum.writeEnumNoTag(2);
+ cosEnum.flush();
+
+ assertThat(
+ decodeWireEntries(
+ ImmutableList.of(ByteString.copyFrom(baosEnum.toByteArray())),
+ FieldLiteDescriptor.Type.ENUM.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true))
+ .isEqualTo(ImmutableList.of(2L));
+ }
+
+ @Test
+ public void decodeWireValue_allScalarWireTypes() {
+ assertThat(
+ decodeWireValue(
+ Double.doubleToRawLongBits(2.5d), WireFormat.FieldType.DOUBLE, "custom.Message"))
+ .isEqualTo(2.5d);
+
+ assertThat(
+ decodeWireValue(
+ Float.floatToRawIntBits(1.5f), WireFormat.FieldType.FLOAT, "custom.Message"))
+ .isEqualTo(1.5d);
+
+ assertThat(decodeWireValue(42L, WireFormat.FieldType.INT64, "custom.Message")).isEqualTo(42L);
+
+ assertThat(decodeWireValue(42L, WireFormat.FieldType.INT32, "custom.Message")).isEqualTo(42L);
+
+ assertThat(decodeWireValue(42L, WireFormat.FieldType.UINT64, "custom.Message"))
+ .isEqualTo(UnsignedLong.fromLongBits(42L));
+
+ assertThat(decodeWireValue(42L, WireFormat.FieldType.UINT32, "custom.Message"))
+ .isEqualTo(UnsignedLong.fromLongBits(42L));
+
+ assertThat(decodeWireValue(100, WireFormat.FieldType.FIXED32, "custom.Message"))
+ .isEqualTo(UnsignedLong.fromLongBits(100L));
+
+ assertThat(decodeWireValue(100L, WireFormat.FieldType.FIXED64, "custom.Message"))
+ .isEqualTo(UnsignedLong.fromLongBits(100L));
+
+ assertThat(decodeWireValue(-50, WireFormat.FieldType.SFIXED32, "custom.Message"))
+ .isEqualTo(-50L);
+
+ assertThat(decodeWireValue(-50L, WireFormat.FieldType.SFIXED64, "custom.Message"))
+ .isEqualTo(-50L);
+
+ assertThat(decodeWireValue(1L, WireFormat.FieldType.BOOL, "custom.Message")).isEqualTo(true);
+
+ assertThat(decodeWireValue(0L, WireFormat.FieldType.BOOL, "custom.Message")).isEqualTo(false);
+
+ assertThat(
+ decodeWireValue(
+ ByteString.copyFromUtf8("hello"), WireFormat.FieldType.STRING, "custom.Message"))
+ .isEqualTo("hello");
+
+ assertThat(
+ decodeWireValue(
+ ByteString.copyFromUtf8("bytes"), WireFormat.FieldType.BYTES, "custom.Message"))
+ .isEqualTo(CelByteString.of("bytes".getBytes(UTF_8)));
+
+ assertThat(
+ decodeWireValue(
+ 1L, // zigzag 1 -> -1
+ WireFormat.FieldType.SINT32,
+ "custom.Message"))
+ .isEqualTo(-1L);
+
+ assertThat(
+ decodeWireValue(
+ 1L, // zigzag 1 -> -1
+ WireFormat.FieldType.SINT64,
+ "custom.Message"))
+ .isEqualTo(-1L);
+
+ assertThat(decodeWireValue(3L, WireFormat.FieldType.ENUM, "custom.Message")).isEqualTo(3L);
+ }
+
+ @Test
+ public void decodeWireValue_messageType_returnsRawProtoMessageLiteValue() {
+ Object submessage =
+ decodeWireValue(
+ ByteString.copyFromUtf8("raw"), WireFormat.FieldType.MESSAGE, "sub.Message");
+
+ assertThat(submessage).isInstanceOf(RawProtoMessageLiteValue.class);
+ assertThat(((RawProtoMessageLiteValue) submessage).celType().name()).isEqualTo("sub.Message");
+ }
+
+ @Test
+ public void decodeWireValue_groupType_throwsUnsupportedOperationException() {
+ ByteString rawBytes = ByteString.copyFromUtf8("raw");
+
+ UnsupportedOperationException thrown =
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> decodeWireValue(rawBytes, WireFormat.FieldType.GROUP, "group.Message"));
+
+ assertThat(thrown).hasMessageThat().contains("Groups are not supported");
+ }
+
+ @Test
+ public void decodeWireEntries_groupType_throwsUnsupportedOperationException() {
+ ImmutableList rawEntries = ImmutableList.of();
+ int groupTypeCode = FieldLiteDescriptor.Type.GROUP.getNumber();
+
+ UnsupportedOperationException thrown =
+ assertThrows(
+ UnsupportedOperationException.class,
+ () ->
+ decodeWireEntries(
+ rawEntries, groupTypeCode, "group.Message", /* isRepeated= */ false));
+
+ assertThat(thrown).hasMessageThat().contains("Groups are not supported");
+ }
+
+ @Test
+ public void decodeWireEntries_invalidTypeCode_throwsIllegalArgumentException() {
+ ImmutableList rawEntries = ImmutableList.of();
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> decodeWireEntries(rawEntries, 999, "custom.Message", /* isRepeated= */ false));
+ }
+
+ @Test
+ public void decodeWireValue_invalidTypeCode_throws() {
+ assertThrows(IllegalArgumentException.class, () -> decodeWireValue(42L, 0, "custom.Message"));
+
+ assertThrows(IllegalArgumentException.class, () -> decodeWireValue(42L, 999, "custom.Message"));
+ }
+
+ @Test
+ public void decodeWireValue_int32HighBits_truncatedToSigned32Bit() {
+ Object decodedHigh =
+ decodeWireValue(0x100000005L, WireFormat.FieldType.INT32, "custom.Message");
+ Object decodedNegative =
+ decodeWireValue(0xFFFFFFFF80000000L, WireFormat.FieldType.INT32, "custom.Message");
+
+ assertThat(decodedHigh).isEqualTo(5L);
+ assertThat(decodedNegative).isEqualTo(-2147483648L);
+ }
+
+ @Test
+ public void decodeWireValue_enumHighBits_truncatedToSigned32Bit() {
+ Object decodedHigh = decodeWireValue(0x100000005L, WireFormat.FieldType.ENUM, "custom.Message");
+
+ assertThat(decodedHigh).isEqualTo(5L);
+ }
+
+ @Test
+ public void decodeWireValue_typeMismatch_throwsIllegalArgumentException() {
+ IllegalArgumentException thrownInt64 =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> decodeWireValue("not a long", WireFormat.FieldType.INT64, "custom.Message"));
+ assertThat(thrownInt64).hasMessageThat().contains("Expected Long for wire type INT64");
+
+ IllegalArgumentException thrownString =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> decodeWireValue(100L, WireFormat.FieldType.STRING, "custom.Message"));
+ assertThat(thrownString).hasMessageThat().contains("Expected ByteString for wire type STRING");
+
+ IllegalArgumentException thrownBytes =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> decodeWireValue(100L, WireFormat.FieldType.BYTES, "custom.Message"));
+ assertThat(thrownBytes).hasMessageThat().contains("Expected ByteString for wire type BYTES");
+
+ IllegalArgumentException thrownMessage =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> decodeWireValue(100L, WireFormat.FieldType.MESSAGE, "custom.Message"));
+ assertThat(thrownMessage)
+ .hasMessageThat()
+ .contains("Expected ByteString for wire type MESSAGE");
+
+ IllegalArgumentException thrownFloat =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> decodeWireValue(100L, WireFormat.FieldType.FLOAT, "custom.Message"));
+ assertThat(thrownFloat).hasMessageThat().contains("Expected Integer for wire type FLOAT");
+
+ IllegalArgumentException thrownDouble =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> decodeWireValue(100, WireFormat.FieldType.DOUBLE, "custom.Message"));
+ assertThat(thrownDouble).hasMessageThat().contains("Expected Long for wire type DOUBLE");
+ }
+
+ @Test
+ public void decodeWireValue_invalidUtf8String_throwsIllegalArgumentException() {
+ ByteString invalidUtf8 = ByteString.copyFrom(new byte[] {(byte) 0xC0, (byte) 0xAF});
+
+ IllegalArgumentException thrown =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> decodeWireValue(invalidUtf8, WireFormat.FieldType.STRING, "custom.Message"));
+ assertThat(thrown).hasMessageThat().contains("Invalid UTF-8 in string field");
+ }
+
+ @Test
+ public void decodeWireEntries_multiChunkPackedRepeated() throws Exception {
+ ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
+ CodedOutputStream cos1 = CodedOutputStream.newInstance(baos1);
+ cos1.writeInt32NoTag(1);
+ cos1.writeInt32NoTag(2);
+ cos1.flush();
+
+ ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
+ CodedOutputStream cos2 = CodedOutputStream.newInstance(baos2);
+ cos2.writeInt32NoTag(3);
+ cos2.writeInt32NoTag(4);
+ cos2.flush();
+
+ Object decoded =
+ decodeWireEntries(
+ ImmutableList.of(
+ ByteString.copyFrom(baos1.toByteArray()), ByteString.copyFrom(baos2.toByteArray())),
+ FieldLiteDescriptor.Type.INT32.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true);
+
+ assertThat((Iterable>) decoded).containsExactly(1L, 2L, 3L, 4L).inOrder();
+ }
+
+ @Test
+ public void decodeWireEntries_mixedPackedAndUnpackedRepeated() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeInt32NoTag(2);
+ cos.writeInt32NoTag(3);
+ cos.flush();
+
+ Object decoded =
+ decodeWireEntries(
+ ImmutableList.of(1L, ByteString.copyFrom(baos.toByteArray()), 4L),
+ FieldLiteDescriptor.Type.INT32.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true);
+
+ assertThat((Iterable>) decoded).containsExactly(1L, 2L, 3L, 4L).inOrder();
+ }
+
+ @Test
+ public void decodeWireEntries_singularMessage_mergesChunks() throws Exception {
+ ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
+ CodedOutputStream cos1 = CodedOutputStream.newInstance(baos1);
+ cos1.writeInt64(1, 100L);
+ cos1.flush();
+
+ ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
+ CodedOutputStream cos2 = CodedOutputStream.newInstance(baos2);
+ cos2.writeInt64(2, 200L);
+ cos2.flush();
+
+ Object decoded =
+ decodeWireEntries(
+ ImmutableList.of(
+ ByteString.copyFrom(baos1.toByteArray()), ByteString.copyFrom(baos2.toByteArray())),
+ FieldLiteDescriptor.Type.MESSAGE.getNumber(),
+ "sub.Message",
+ /* isRepeated= */ false);
+
+ assertThat(decoded).isInstanceOf(RawProtoMessageLiteValue.class);
+ RawProtoMessageLiteValue rawMessage = (RawProtoMessageLiteValue) decoded;
+ assertThat(rawMessage.unknownFields()).valuesForKey(1).containsExactly(100L);
+ assertThat(rawMessage.unknownFields()).valuesForKey(2).containsExactly(200L);
+ }
+
+ @Test
+ public void decodeWireValue_uint32HighBit_correctUnsignedLong() {
+ Object decoded = decodeWireValue(0xFFFFFFFFL, WireFormat.FieldType.UINT32, "custom.Message");
+
+ assertThat(decoded).isEqualTo(UnsignedLong.valueOf(4294967295L));
+ }
+
+ @Test
+ public void decodeWireValue_fixed32HighBit_correctUnsignedLong() {
+ Object decoded = decodeWireValue(-1, WireFormat.FieldType.FIXED32, "custom.Message");
+
+ assertThat(decoded).isEqualTo(UnsignedLong.valueOf(4294967295L));
+ }
+
+ @Test
+ public void decodeWireEntries_repeatedString() {
+ Object decoded =
+ decodeWireEntries(
+ ImmutableList.of(ByteString.copyFromUtf8("foo"), ByteString.copyFromUtf8("bar")),
+ FieldLiteDescriptor.Type.STRING.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true);
+
+ assertThat((Iterable>) decoded).containsExactly("foo", "bar").inOrder();
+ }
+
+ @Test
+ public void decodeWireEntries_repeatedBytes() {
+ Object decoded =
+ decodeWireEntries(
+ ImmutableList.of(ByteString.copyFromUtf8("foo"), ByteString.copyFromUtf8("bar")),
+ FieldLiteDescriptor.Type.BYTES.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true);
+
+ assertThat((Iterable>) decoded)
+ .containsExactly(
+ CelByteString.of("foo".getBytes(UTF_8)), CelByteString.of("bar".getBytes(UTF_8)))
+ .inOrder();
+ }
+
+ @Test
+ public void decodeWireEntries_repeatedMessage() {
+ Object decoded =
+ decodeWireEntries(
+ ImmutableList.of(ByteString.copyFromUtf8("msg1"), ByteString.copyFromUtf8("msg2")),
+ FieldLiteDescriptor.Type.MESSAGE.getNumber(),
+ "sub.Message",
+ /* isRepeated= */ true);
+
+ assertThat((Iterable>) decoded)
+ .containsExactly(
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFromUtf8("msg1"), "sub.Message", EMPTY_CONVERTER),
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFromUtf8("msg2"), "sub.Message", EMPTY_CONVERTER))
+ .inOrder();
+ }
+
+ @Test
+ public void decodeWireEntries_packedTruncated_throwsIllegalStateException() {
+ // Varint with MSB set (0x80) indicates continuation, but stream ends prematurely.
+ ByteString truncated = ByteString.copyFrom(new byte[] {(byte) 0x80});
+
+ IllegalStateException thrown =
+ assertThrows(
+ IllegalStateException.class,
+ () ->
+ decodeWireEntries(
+ ImmutableList.of(truncated),
+ FieldLiteDescriptor.Type.INT32.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true));
+
+ assertThat(thrown).hasMessageThat().contains("Failed to parse packed repeated field");
+ }
+
+ @Test
+ public void selectByFieldNumber_presentOnWire_decoded() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeString(14, "hello");
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ EMPTY_CONVERTER);
+
+ Object val = raw.selectByFieldNumber(SelectField.create(14L, "single_string", 9, ""));
+
+ assertThat(val).isEqualTo("hello");
+ }
+
+ @Test
+ public void selectByFieldNumber_absentWithDefaultValue_returnsDefault() {
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.EMPTY, "cel.expr.conformance.proto3.TestAllTypes", EMPTY_CONVERTER);
+
+ Object val = raw.selectByFieldNumber(SelectField.create(14L, "single_string", 9, "default"));
+
+ assertThat(val).isEqualTo("default");
+ }
+
+ @Test
+ public void selectByFieldNumber_withConverter_resolvesDescriptor() {
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.EMPTY, "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+
+ Object val = raw.selectByFieldNumber(SelectField.create(14L, "single_string"));
+
+ assertThat(val).isEqualTo("");
+ }
+
+ @Test
+ public void
+ selectByFieldNumber_absentSubmessageWithMissingChildDescriptor_returnsEmptyRawProtoMessageLiteValue() {
+ CelLiteDescriptorPool poolWithoutNested =
+ new CelLiteDescriptorPool() {
+ @Override
+ public Optional findDescriptor(String protoTypeName) {
+ if (protoTypeName.equals("cel.expr.conformance.proto3.TestAllTypes")) {
+ return DefaultLiteDescriptorPool.newInstance(
+ ImmutableSet.of(TestAllTypesCelDescriptor.getDescriptor()))
+ .findDescriptor(protoTypeName);
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional findDescriptor(MessageLite messageLite) {
+ return Optional.empty();
+ }
+
+ @Override
+ public MessageLiteDescriptor getDescriptorOrThrow(String protoTypeName) {
+ return findDescriptor(protoTypeName)
+ .orElseThrow(() -> new NoSuchElementException(protoTypeName));
+ }
+ };
+ ProtoLiteCelValueConverter converter =
+ ProtoLiteCelValueConverter.newInstance(poolWithoutNested);
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.EMPTY, "cel.expr.conformance.proto3.TestAllTypes", converter);
+
+ Object val = raw.selectByFieldNumber(SelectField.create(21L, "single_nested_message"));
+
+ assertThat(val).isInstanceOf(RawProtoMessageLiteValue.class);
+ RawProtoMessageLiteValue rawChild = (RawProtoMessageLiteValue) val;
+ assertThat(rawChild.rawWireBytes()).isEqualTo(ByteString.EMPTY);
+ assertThat(rawChild.celType().name())
+ .isEqualTo("cel.expr.conformance.proto3.TestAllTypes.NestedMessage");
+ }
+
+ @Test
+ public void
+ selectByFieldNumber_absentRepeatedMessageWithMissingChildDescriptor_returnsEmptyList() {
+ CelLiteDescriptorPool poolWithoutNested =
+ new CelLiteDescriptorPool() {
+ @Override
+ public Optional findDescriptor(String protoTypeName) {
+ if (protoTypeName.equals("cel.expr.conformance.proto3.TestAllTypes")) {
+ return DefaultLiteDescriptorPool.newInstance(
+ ImmutableSet.of(TestAllTypesCelDescriptor.getDescriptor()))
+ .findDescriptor(protoTypeName);
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional findDescriptor(MessageLite messageLite) {
+ return Optional.empty();
+ }
+
+ @Override
+ public MessageLiteDescriptor getDescriptorOrThrow(String protoTypeName) {
+ return findDescriptor(protoTypeName)
+ .orElseThrow(() -> new NoSuchElementException(protoTypeName));
+ }
+ };
+ ProtoLiteCelValueConverter converter =
+ ProtoLiteCelValueConverter.newInstance(poolWithoutNested);
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.EMPTY, "cel.expr.conformance.proto3.TestAllTypes", converter);
+
+ Object val =
+ raw.selectByFieldNumber(
+ SelectField.create(
+ TestAllTypes.REPEATED_NESTED_MESSAGE_FIELD_NUMBER, "repeated_nested_message"));
+
+ assertThat(val).isEqualTo(ImmutableList.of());
+ }
+
+ @Test
+ public void selectByFieldNumber_unknownFieldWithoutTypeCode_throwsCelAttributeNotFoundException()
+ throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeString(999, "unknown");
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ CONVERTER);
+ SelectField selectField = SelectField.create(999L, "unknown_field");
+
+ assertThrows(CelAttributeNotFoundException.class, () -> raw.selectByFieldNumber(selectField));
+ }
+
+ @Test
+ public void hasFieldByNumber_wirePresent_returnsTrue() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeString(TestAllTypes.SINGLE_STRING_FIELD_NUMBER, "present");
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ EMPTY_CONVERTER);
+
+ assertThat(
+ raw.hasFieldByNumber(
+ SelectField.create(TestAllTypes.SINGLE_STRING_FIELD_NUMBER, "single_string")))
+ .isTrue();
+ }
+
+ @Test
+ public void hasFieldByNumber_wireAbsent_returnsFalse() {
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.EMPTY, "cel.expr.conformance.proto3.TestAllTypes", EMPTY_CONVERTER);
+
+ assertThat(
+ raw.hasFieldByNumber(
+ SelectField.create(TestAllTypes.SINGLE_STRING_FIELD_NUMBER, "single_string")))
+ .isFalse();
+ }
+
+ @Test
+ public void hasFieldByNumber_emptyPackedRepeated_returnsFalse(
+ @TestParameter boolean withDescriptor) throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeBytes(TestAllTypes.REPEATED_INT32_FIELD_NUMBER, ByteString.EMPTY);
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ withDescriptor ? CONVERTER : EMPTY_CONVERTER);
+ SelectField selectField =
+ withDescriptor
+ ? SelectField.create(TestAllTypes.REPEATED_INT32_FIELD_NUMBER, "repeated_int32")
+ : SelectField.create(
+ TestAllTypes.REPEATED_INT32_FIELD_NUMBER,
+ "repeated_int32",
+ FieldLiteDescriptor.Type.INT32.getNumber(),
+ ImmutableList.of());
+
+ assertThat(raw.hasFieldByNumber(selectField)).isFalse();
+ }
+
+ @Test
+ public void hasFieldByNumber_nonEmptyPackedRepeated_returnsTrue() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ ByteArrayOutputStream packed = new ByteArrayOutputStream();
+ CodedOutputStream packedCos = CodedOutputStream.newInstance(packed);
+ packedCos.writeInt32NoTag(42);
+ packedCos.flush();
+ cos.writeBytes(
+ TestAllTypes.REPEATED_INT32_FIELD_NUMBER, ByteString.copyFrom(packed.toByteArray()));
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ CONVERTER);
+
+ assertThat(
+ raw.hasFieldByNumber(
+ SelectField.create(TestAllTypes.REPEATED_INT32_FIELD_NUMBER, "repeated_int32")))
+ .isTrue();
+ }
+
+ @Test
+ public void hasFieldByNumber_emptyByteStringOnScalarPackableField_returnsTrue(
+ @TestParameter boolean withDescriptor) throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeByteArray(TestAllTypes.SINGLE_INT32_FIELD_NUMBER, new byte[0]);
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ withDescriptor ? CONVERTER : EMPTY_CONVERTER);
+ SelectField selectField =
+ withDescriptor
+ ? SelectField.create(TestAllTypes.SINGLE_INT32_FIELD_NUMBER, "single_int32")
+ : SelectField.create(
+ TestAllTypes.SINGLE_INT32_FIELD_NUMBER,
+ "single_int32",
+ FieldLiteDescriptor.Type.INT32.getNumber(),
+ 0);
+
+ assertThat(raw.hasFieldByNumber(selectField)).isTrue();
+ }
+
+ @Test
+ public void findByFieldNumber_intermediatePresent_returnsSubmessage() throws Exception {
+ ByteArrayOutputStream subBaos1 = new ByteArrayOutputStream();
+ CodedOutputStream subCos1 = CodedOutputStream.newInstance(subBaos1);
+ subCos1.writeInt32(1, 42);
+ subCos1.flush();
+
+ ByteArrayOutputStream subBaos2 = new ByteArrayOutputStream();
+ CodedOutputStream subCos2 = CodedOutputStream.newInstance(subBaos2);
+ subCos2.writeInt32(2, 84);
+ subCos2.flush();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeBytes(21, ByteString.copyFrom(subBaos1.toByteArray()));
+ cos.writeBytes(21, ByteString.copyFrom(subBaos2.toByteArray()));
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ EMPTY_CONVERTER);
+
+ Optional nav = raw.findByFieldNumber(SelectField.create(21L, "single_nested_message"));
+
+ RawProtoMessageLiteValue expected =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(subBaos1.toByteArray())
+ .concat(ByteString.copyFrom(subBaos2.toByteArray())),
+ "cel.@unknownMessage",
+ EMPTY_CONVERTER);
+ assertThat(nav).hasValue(expected);
+ }
+
+ @Test
+ public void findByFieldNumber_intermediateAbsent_returnsEmpty() {
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.EMPTY, "cel.expr.conformance.proto3.TestAllTypes", EMPTY_CONVERTER);
+
+ Optional nav = raw.findByFieldNumber(SelectField.create(999L, "absent"));
+
+ assertThat(nav).isEmpty();
+ }
+
+ @SuppressWarnings("ImmutableEnumChecker") // Test only
+ private enum SelectByFieldNumberTestCase {
+ INT64(SelectField.create(TestAllTypes.SINGLE_INT64_FIELD_NUMBER, "single_int64"), 99L),
+ STRING(SelectField.create(TestAllTypes.SINGLE_STRING_FIELD_NUMBER, "single_string"), "hello"),
+ MAP_STRING_STRING(
+ SelectField.create(TestAllTypes.MAP_STRING_STRING_FIELD_NUMBER, "map_string_string"),
+ ImmutableMap.of("k1", "v1", "k2", "v2")),
+ MAP_INT32_BYTES(
+ SelectField.create(TestAllTypes.MAP_INT32_BYTES_FIELD_NUMBER, "map_int32_bytes"),
+ ImmutableMap.of(
+ 0L, CelByteString.copyFromUtf8("val_for_default_key"), 42L, CelByteString.EMPTY)),
+ DURATION(
+ SelectField.create(TestAllTypes.SINGLE_DURATION_FIELD_NUMBER, "single_duration"),
+ Duration.ofSeconds(10L, 500L)),
+ INT64_WRAPPER(
+ SelectField.create(TestAllTypes.SINGLE_INT64_WRAPPER_FIELD_NUMBER, "single_int64_wrapper"),
+ 12345L);
+
+ private final SelectField selectField;
+ private final Object expectedValue;
+
+ private SelectByFieldNumberTestCase(SelectField selectField, Object expectedValue) {
+ this.selectField = selectField;
+ this.expectedValue = expectedValue;
+ }
+ }
+
+ @Test
+ public void selectByFieldNumber_withDescriptor_decodesExpectedValue(
+ @TestParameter SelectByFieldNumberTestCase testCase) {
+ TestAllTypes proto =
+ TestAllTypes.newBuilder()
+ .setSingleInt64(99L)
+ .setSingleString("hello")
+ .putMapStringString("k1", "v1")
+ .putMapStringString("k2", "v2")
+ .putMapInt32Bytes(0, ByteString.copyFromUtf8("val_for_default_key"))
+ .putMapInt32Bytes(42, ByteString.EMPTY)
+ .setSingleDuration(ProtoTimeUtils.toProtoDuration(Duration.ofSeconds(10L, 500L)))
+ .setSingleInt64Wrapper(Int64Value.of(12345L))
+ .build();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ proto.toByteString(), "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+
+ Object selected = raw.selectByFieldNumber(testCase.selectField);
+
+ assertThat(selected).isEqualTo(testCase.expectedValue);
+ }
+
+ @Test
+ public void findByFieldNumber_scalarField_returnsScalar(@TestParameter boolean withDescriptor) {
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleInt64(99L).build();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ proto.toByteString(),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ withDescriptor ? CONVERTER : EMPTY_CONVERTER);
+
+ Optional nav =
+ raw.findByFieldNumber(
+ SelectField.create(TestAllTypes.SINGLE_INT64_FIELD_NUMBER, "single_int64"));
+
+ assertThat(nav).hasValue(99L);
+ }
+
+ @Test
+ public void selectByFieldNumber_unsetWrapperFieldWithoutWrapperDescriptor_returnsNullValue() {
+ MessageLiteDescriptor testAllTypesDesc =
+ TestAllTypesCelDescriptor.getDescriptor()
+ .getProtoTypeNamesToDescriptors()
+ .get("cel.expr.conformance.proto3.TestAllTypes");
+ CelLiteDescriptorPool poolWithoutWrappers =
+ new CelLiteDescriptorPool() {
+ @Override
+ public Optional findDescriptor(String protoTypeName) {
+ if (protoTypeName.equals(testAllTypesDesc.getProtoTypeName())) {
+ return Optional.of(testAllTypesDesc);
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional findDescriptor(MessageLite messageLite) {
+ return findDescriptor(messageLite.getClass().getName());
+ }
+
+ @Override
+ public MessageLiteDescriptor getDescriptorOrThrow(String protoTypeName) {
+ return findDescriptor(protoTypeName)
+ .orElseThrow(() -> new NoSuchElementException(protoTypeName));
+ }
+ };
+ ProtoLiteCelValueConverter converter =
+ ProtoLiteCelValueConverter.newInstance(poolWithoutWrappers);
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.EMPTY, "cel.expr.conformance.proto3.TestAllTypes", converter);
+
+ Object result =
+ raw.selectByFieldNumber(
+ SelectField.create(
+ TestAllTypes.SINGLE_INT64_WRAPPER_FIELD_NUMBER, "single_int64_wrapper"));
+
+ assertThat(result).isEqualTo(NullValue.NULL_VALUE);
+ }
+
+ @Test
+ public void findByFieldNumber_typedFieldWithoutDescriptor_returnsSelectedValue() {
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleUint32(123).build();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ proto.toByteString(), "cel.expr.conformance.proto3.TestAllTypes", EMPTY_CONVERTER);
+
+ Optional nav =
+ raw.findByFieldNumber(
+ SelectField.create(
+ TestAllTypes.SINGLE_UINT32_FIELD_NUMBER,
+ "single_uint32",
+ FieldLiteDescriptor.Type.UINT32.getNumber(),
+ 0L));
+
+ assertThat(nav).hasValue(UnsignedLong.fromLongBits(123L));
+ }
+
+ @Test
+ public void selectByFieldNumber_absentMessageFieldWithoutDescriptor_returnsUnknownMessage() {
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(ByteString.EMPTY, EMPTY_CONVERTER);
+
+ Object selected =
+ raw.selectByFieldNumber(
+ SelectField.create(
+ 21L, "single_nested_message", FieldLiteDescriptor.Type.MESSAGE.getNumber(), null));
+
+ assertThat(selected).isInstanceOf(RawProtoMessageLiteValue.class);
+ RawProtoMessageLiteValue message = (RawProtoMessageLiteValue) selected;
+ assertThat(message.rawWireBytes()).isEqualTo(ByteString.EMPTY);
+ assertThat(message.celType().name()).isEqualTo("cel.@unknownMessage");
+ }
+
+ @Test
+ public void
+ selectByFieldNumber_unknownMapFieldWithWireEntries_throwsUnsupportedOperationException() {
+ TestAllTypes proto = TestAllTypes.newBuilder().putMapStringString("key", "val").build();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ proto.toByteString(), "cel.expr.conformance.proto3.TestAllTypes", EMPTY_CONVERTER);
+ SelectField field =
+ SelectField.create(
+ TestAllTypes.MAP_STRING_STRING_FIELD_NUMBER,
+ "map_string_string",
+ SelectField.CEL_MAP_TYPE_CODE,
+ null);
+
+ UnsupportedOperationException e =
+ assertThrows(UnsupportedOperationException.class, () -> raw.selectByFieldNumber(field));
+
+ assertThat(e)
+ .hasMessageThat()
+ .contains("Decoding unknown map field from wire bytes is unsupported");
+ }
+}
diff --git a/common/src/test/java/dev/cel/common/values/SelectFieldTest.java b/common/src/test/java/dev/cel/common/values/SelectFieldTest.java
new file mode 100644
index 000000000..ba9dc7008
--- /dev/null
+++ b/common/src/test/java/dev/cel/common/values/SelectFieldTest.java
@@ -0,0 +1,133 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dev.cel.common.values;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class SelectFieldTest {
+
+ @Test
+ public void create_twoArguments_success() {
+ SelectField field = SelectField.create(1L, "foo");
+
+ assertThat(field.fieldNumber()).isEqualTo(1);
+ assertThat(field.fieldName()).isEqualTo("foo");
+ assertThat(field.typeCode()).isEqualTo(SelectField.NO_TYPE_CODE);
+ assertThat(field.defaultValue()).isNull();
+ }
+
+ @Test
+ public void create_fourArguments_success() {
+ SelectField field = SelectField.create(2L, "bar", 9, "default_str");
+
+ assertThat(field.fieldNumber()).isEqualTo(2);
+ assertThat(field.fieldName()).isEqualTo("bar");
+ assertThat(field.typeCode()).isEqualTo(9);
+ assertThat(field.defaultValue()).isEqualTo("default_str");
+ }
+
+ @Test
+ public void create_mapTypeCode_success() {
+ SelectField field = SelectField.create(3L, "map_field", -1, null);
+
+ assertThat(field.typeCode()).isEqualTo(-1);
+ }
+
+ @Test
+ public void create_twoArgNullFieldName_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> SelectField.create(1L, null));
+ }
+
+ @Test
+ public void create_fourArgNullFieldName_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> SelectField.create(1L, null, 9, null));
+ }
+
+ @Test
+ public void create_fieldNumberBelowMinimum_throwsIllegalArgumentException() {
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> SelectField.create(0L, "foo"));
+
+ assertThat(thrown).hasMessageThat().contains("Field number out of protobuf range: 0");
+ }
+
+ @Test
+ public void create_fieldNumberNegative_throwsIllegalArgumentException() {
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> SelectField.create(-1L, "foo"));
+
+ assertThat(thrown).hasMessageThat().contains("Field number out of protobuf range: -1");
+ }
+
+ @Test
+ public void create_fieldNumberAboveMaximum_throwsIllegalArgumentException() {
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> SelectField.create(536870912L, "foo"));
+
+ assertThat(thrown).hasMessageThat().contains("Field number out of protobuf range: 536870912");
+ }
+
+ @Test
+ public void create_typeCodeZero_throwsIllegalArgumentException() {
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> SelectField.create(1L, "foo", 0, null));
+
+ assertThat(thrown).hasMessageThat().contains("Invalid protobuf type code: 0");
+ }
+
+ @Test
+ public void create_typeCodeAboveMaximum_throwsIllegalArgumentException() {
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> SelectField.create(1L, "foo", 19, null));
+
+ assertThat(thrown).hasMessageThat().contains("Invalid protobuf type code: 19");
+ }
+
+ @Test
+ public void create_typeCodeBelowSentinel_throwsIllegalArgumentException() {
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> SelectField.create(1L, "foo", -2, null));
+
+ assertThat(thrown).hasMessageThat().contains("Invalid protobuf type code: -2");
+ }
+
+ @Test
+ public void create_typeCodeGroupProto_throwsIllegalArgumentException() {
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> SelectField.create(1L, "foo", 10, null));
+
+ assertThat(thrown).hasMessageThat().contains("Invalid protobuf type code: 10");
+ }
+
+ @Test
+ public void equalsAndHashCode_testedProperly() {
+ new EqualsTester()
+ .addEqualityGroup(SelectField.create(1L, "foo"), SelectField.create(1L, "foo"))
+ .addEqualityGroup(SelectField.create(2L, "foo"), SelectField.create(2L, "foo"))
+ .addEqualityGroup(SelectField.create(1L, "bar"), SelectField.create(1L, "bar"))
+ .addEqualityGroup(
+ SelectField.create(1L, "foo", 9, "default"),
+ SelectField.create(1L, "foo", 9, "default"))
+ .addEqualityGroup(SelectField.create(1L, "foo", 9, "other_default"))
+ .testEquals();
+ }
+}
diff --git a/common/values/BUILD.bazel b/common/values/BUILD.bazel
index 9853289a9..192f01de8 100644
--- a/common/values/BUILD.bazel
+++ b/common/values/BUILD.bazel
@@ -126,3 +126,39 @@ cel_android_library(
name = "base_proto_message_value_provider_android",
exports = ["//common/src/main/java/dev/cel/common/values:base_proto_message_value_provider_android"],
)
+
+java_library(
+ name = "select_field",
+ visibility = ["//:internal"],
+ exports = ["//common/src/main/java/dev/cel/common/values:select_field"],
+)
+
+cel_android_library(
+ name = "select_field_android",
+ visibility = ["//:internal"],
+ exports = ["//common/src/main/java/dev/cel/common/values:select_field_android"],
+)
+
+java_library(
+ name = "optimized_selectable",
+ visibility = ["//:internal"],
+ exports = ["//common/src/main/java/dev/cel/common/values:optimized_selectable"],
+)
+
+cel_android_library(
+ name = "optimized_selectable_android",
+ visibility = ["//:internal"],
+ exports = ["//common/src/main/java/dev/cel/common/values:optimized_selectable_android"],
+)
+
+java_library(
+ name = "optimized_select_traversal",
+ visibility = ["//:internal"],
+ exports = ["//common/src/main/java/dev/cel/common/values:optimized_select_traversal"],
+)
+
+cel_android_library(
+ name = "optimized_select_traversal_android",
+ visibility = ["//:internal"],
+ exports = ["//common/src/main/java/dev/cel/common/values:optimized_select_traversal_android"],
+)