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..792f88486 100644
--- a/common/src/main/java/dev/cel/common/values/BUILD.bazel
+++ b/common/src/main/java/dev/cel/common/values/BUILD.bazel
@@ -17,7 +17,9 @@ CEL_VALUES_SOURCES = [
"ErrorValue.java",
"NullValue.java",
"OpaqueValue.java",
+ "OptimizedSelectable.java",
"OptionalValue.java",
+ "SelectField.java",
"SelectableValue.java",
"StructValue.java",
]
@@ -167,6 +169,7 @@ java_library(
":preadapted_list",
"//:auto_value",
"//common/annotations",
+ "//common/exceptions:attribute_not_found",
"//common/types",
"//common/types:type_providers",
"@maven//:com_google_errorprone_error_prone_annotations",
@@ -218,6 +221,7 @@ cel_android_library(
":preadapted_list_android",
"//:auto_value",
"//common/annotations",
+ "//common/exceptions:attribute_not_found",
"//common/types:type_providers_android",
"//common/types:types_android",
"@maven//:com_google_errorprone_error_prone_annotations",
@@ -317,6 +321,7 @@ java_library(
srcs = [
"ProtoLiteCelValueConverter.java",
"ProtoMessageLiteValue.java",
+ "RawProtoMessageLiteValue.java",
],
tags = [
],
@@ -325,6 +330,7 @@ java_library(
":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 +339,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,6 +349,7 @@ cel_android_library(
srcs = [
"ProtoLiteCelValueConverter.java",
"ProtoMessageLiteValue.java",
+ "RawProtoMessageLiteValue.java",
],
tags = [
],
@@ -350,6 +358,7 @@ cel_android_library(
":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",
],
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..3885e38b5
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/values/OptimizedSelectable.java
@@ -0,0 +1,152 @@
+// 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 com.google.errorprone.annotations.Immutable;
+import dev.cel.common.annotations.Internal;
+import dev.cel.common.exceptions.CelAttributeNotFoundException;
+import java.util.Map;
+import java.util.Optional;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * Represents a value capable of evaluating optimized field selection and presence testing for field
+ * selection optimization.
+ *
+ *
CEL Library Internals. Do Not Use.
+ */
+@Internal
+@Immutable
+public interface OptimizedSelectable {
+
+ /**
+ * Evaluates a multi-hop qualification chain across the provided fields.
+ *
+ *
Implementations may override this method to provide an allocation-free traversal over
+ * underlying schema or message descriptors. The default implementation iterates sequentially over
+ * {@link #selectField} for intermediate submessages and the leaf.
+ */
+ default Object qualify(ImmutableList fields) {
+ Object current = this;
+ for (SelectField field : fields) {
+ if (current instanceof OptimizedSelectable) {
+ current = ((OptimizedSelectable) current).selectField(field);
+ } else if (current instanceof SelectableValue) {
+ Optional found =
+ SelectField.findField((SelectableValue>) current, field.fieldName());
+ if (found.isPresent()) {
+ current = found.get();
+ } else if (field.defaultValue() != null) {
+ current = field.defaultValue();
+ } else {
+ throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
+ }
+ } else if (current instanceof Map) {
+ Map, ?> map = (Map, ?>) current;
+ Object mapValue = map.get(field.fieldName());
+ if (mapValue != null) {
+ current = mapValue;
+ } else if (map.containsKey(field.fieldName())) {
+ current = NullValue.NULL_VALUE;
+ } else {
+ throw CelAttributeNotFoundException.forMissingMapKey(field.fieldName());
+ }
+ } else {
+ throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
+ }
+ }
+ return current;
+ }
+
+ /**
+ * Performs field selection for a single hop in an optimized selection chain.
+ *
+ * @param field The field hop descriptor containing field number, name, type code, and default
+ * value.
+ * @return The selected field value, or default value / empty submessage if absent.
+ */
+ Object selectField(SelectField field);
+
+ /**
+ * Evaluates presence testing across the provided fields.
+ *
+ * Implementations may override this method to provide an allocation-free traversal over
+ * underlying schema or message descriptors. The default implementation iterates sequentially over
+ * {@link #navigateField} for intermediate submessages and {@link #hasField} for the leaf.
+ */
+ default boolean hasField(ImmutableList fields) {
+ if (fields.isEmpty()) {
+ return false;
+ }
+ int lastIndex = fields.size() - 1;
+ Object current = this;
+ for (int i = 0; i < lastIndex; i++) {
+ SelectField field = fields.get(i);
+ if (current instanceof OptimizedSelectable) {
+ current = ((OptimizedSelectable) current).navigateField(field);
+ if (current == null) {
+ return false;
+ }
+ } else if (current instanceof SelectableValue) {
+ Optional found =
+ SelectField.findField((SelectableValue>) current, field.fieldName());
+ if (!found.isPresent()) {
+ return false;
+ }
+ current = found.get();
+ } else if (current instanceof Map) {
+ Map, ?> map = (Map, ?>) current;
+ if (!map.containsKey(field.fieldName())) {
+ return false;
+ }
+ Object mapValue = map.get(field.fieldName());
+ current = (mapValue != null) ? mapValue : NullValue.NULL_VALUE;
+ } else {
+ throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
+ }
+ }
+ SelectField terminalField = fields.get(lastIndex);
+ if (current instanceof OptimizedSelectable) {
+ return ((OptimizedSelectable) current).hasField(terminalField);
+ } else if (current instanceof SelectableValue) {
+ return SelectField.findField((SelectableValue>) current, terminalField.fieldName())
+ .isPresent();
+ } else if (current instanceof Map) {
+ return ((Map, ?>) current).containsKey(terminalField.fieldName());
+ } else {
+ throw CelAttributeNotFoundException.forFieldResolution(terminalField.fieldName());
+ }
+ }
+
+ /**
+ * Evaluates presence testing for a single hop in an optimized selection chain.
+ *
+ * @param field The field hop descriptor.
+ * @return True if the field is present, false otherwise.
+ */
+ boolean hasField(SelectField field);
+
+ /**
+ * Navigates into an intermediate submessage for presence testing.
+ *
+ * Returns {@code null} instead of an {@link Optional} to avoid object allocation overhead on
+ * hot-path intermediate navigation hops.
+ *
+ * @param field The field hop descriptor.
+ * @return The submessage if present, or {@code null} if absent.
+ */
+ @Nullable Object navigateField(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..0b582785d 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;
/**
@@ -62,8 +64,36 @@
public final class ProtoLiteCelValueConverter extends BaseProtoCelValueConverter {
private final CelLiteDescriptorPool descriptorPool;
+ 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("Descriptor not found: " + protoTypeName);
+ }
+ };
+
+ private static final ProtoLiteCelValueConverter DEFAULT_INSTANCE =
+ new ProtoLiteCelValueConverter(EMPTY_DESCRIPTOR_POOL);
+
+ public static ProtoLiteCelValueConverter newInstance() {
+ return DEFAULT_INSTANCE;
+ }
+
public static ProtoLiteCelValueConverter newInstance(
CelLiteDescriptorPool celLiteDescriptorPool) {
+ if (celLiteDescriptorPool == EMPTY_DESCRIPTOR_POOL) {
+ return DEFAULT_INSTANCE;
+ }
return new ProtoLiteCelValueConverter(celLiteDescriptorPool);
}
@@ -80,7 +110,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:
@@ -160,6 +190,39 @@ Object getDefaultCelValue(String protoTypeName, String fieldName) {
return toRuntimeValue(defaultValue);
}
+ Optional findFieldDescriptor(String protoTypeName, int fieldNumber) {
+ return descriptorPool
+ .findDescriptor(protoTypeName)
+ .flatMap(desc -> desc.findByFieldNumber(fieldNumber));
+ }
+
+ Optional findDefaultCelValue(FieldLiteDescriptor fieldDescriptor) {
+ try {
+ return Optional.of(toRuntimeValue(getDefaultValue(fieldDescriptor)));
+ } catch (NoSuchElementException e) {
+ return Optional.empty();
+ }
+ }
+
+ Optional tryDecodeWellKnownProto(ByteString bytes, String protoTypeName) {
+ Optional wellKnownProto = WellKnownProto.getByTypeName(protoTypeName);
+ if (!wellKnownProto.isPresent()) {
+ return Optional.empty();
+ }
+ Optional descriptor = descriptorPool.findDescriptor(protoTypeName);
+ if (!descriptor.isPresent()) {
+ return Optional.empty();
+ }
+ try {
+ MessageLite.Builder builder = descriptor.get().newMessageBuilder();
+ builder.mergeFrom(bytes, ExtensionRegistryLite.getEmptyRegistry());
+ return Optional.of(fromWellKnownProto(builder.build(), wellKnownProto.get()));
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Failed to decode well-known proto of type: " + protoTypeName, e);
+ }
+ }
+
@Override
@SuppressWarnings("LiteProtoToString") // No alternative identifier to use. Debug only info is OK.
public Object toRuntimeValue(Object value) {
@@ -193,7 +256,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);
}
@@ -269,7 +335,6 @@ private Map.Entry readSingleMapEntry(
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 +409,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 +429,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 +453,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..7e5dcd425 100644
--- a/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java
+++ b/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java
@@ -17,13 +17,17 @@
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.types.CelType;
import dev.cel.common.types.StructTypeReference;
+import dev.cel.common.values.ProtoLiteCelValueConverter.MessageFields;
+import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor;
import java.io.IOException;
import java.util.Optional;
+import org.jspecify.annotations.Nullable;
/**
* ProtoMessageLiteValue is a struct value with protobuf support for {@link MessageLite}.
@@ -35,7 +39,8 @@
*/
@AutoValue
@Immutable
-public abstract class ProtoMessageLiteValue extends StructValue {
+public abstract class ProtoMessageLiteValue extends StructValue
+ implements OptimizedSelectable {
@Override
public abstract MessageLite value();
@@ -46,14 +51,22 @@ 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);
}
}
+ private ImmutableMap fieldValues() {
+ return messageFields().values();
+ }
+
+ ImmutableListMultimap unknownFields() {
+ return messageFields().unknowns();
+ }
+
@Override
public boolean isZeroValue() {
return value().getDefaultInstanceForType().equals(value());
@@ -72,6 +85,51 @@ public Optional find(String field) {
.map(value -> protoLiteCelValueConverter().toRuntimeValue(fieldValue));
}
+ @Override
+ public Object selectField(SelectField field) {
+ int fieldNumber = field.fieldNumber();
+ Optional fieldDescriptor =
+ protoLiteCelValueConverter().findFieldDescriptor(celType().name(), fieldNumber);
+ String currentFieldName =
+ fieldDescriptor.map(FieldLiteDescriptor::getFieldName).orElse(field.fieldName());
+ Object fieldValue = fieldValues().get(currentFieldName);
+ if (fieldValue != null) {
+ return protoLiteCelValueConverter().toRuntimeValue(fieldValue);
+ }
+ return RawProtoMessageLiteValue.selectUnknownOrDefault(
+ field, fieldDescriptor, unknownFields().get(fieldNumber), protoLiteCelValueConverter());
+ }
+
+ @Override
+ public boolean hasField(SelectField field) {
+ int fieldNumber = field.fieldNumber();
+ Optional fieldDescriptor =
+ protoLiteCelValueConverter().findFieldDescriptor(celType().name(), fieldNumber);
+ String currentFieldName =
+ fieldDescriptor.map(FieldLiteDescriptor::getFieldName).orElse(field.fieldName());
+ Object fieldValue = fieldValues().get(currentFieldName);
+ if (fieldValue != null) {
+ return true;
+ }
+ return RawProtoMessageLiteValue.isPresentInUnknowns(
+ field, fieldDescriptor, unknownFields().get(fieldNumber), protoLiteCelValueConverter());
+ }
+
+ @Override
+ public @Nullable Object navigateField(SelectField field) {
+ int fieldNumber = field.fieldNumber();
+ Optional fieldDescriptor =
+ protoLiteCelValueConverter().findFieldDescriptor(celType().name(), fieldNumber);
+ String currentFieldName =
+ fieldDescriptor.map(FieldLiteDescriptor::getFieldName).orElse(field.fieldName());
+ Object fieldValue = fieldValues().get(currentFieldName);
+ if (fieldValue != null) {
+ return protoLiteCelValueConverter().toRuntimeValue(fieldValue);
+ }
+ return RawProtoMessageLiteValue.navigateUnknown(
+ field, fieldDescriptor, unknownFields().get(fieldNumber), protoLiteCelValueConverter());
+ }
+
public static ProtoMessageLiteValue create(
MessageLite value, String typeName, ProtoLiteCelValueConverter protoLiteCelValueConverter) {
Preconditions.checkNotNull(value);
@@ -79,4 +137,7 @@ public static ProtoMessageLiteValue create(
return new AutoValue_ProtoMessageLiteValue(
value, StructTypeReference.create(typeName), protoLiteCelValueConverter);
}
+
+ // Package-private constructor to prevent subclassing outside the package.
+ ProtoMessageLiteValue() {}
}
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..c9e9716a5
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/values/RawProtoMessageLiteValue.java
@@ -0,0 +1,505 @@
+// 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 com.google.auto.value.AutoValue;
+import com.google.auto.value.extension.memoized.Memoized;
+import com.google.common.annotations.VisibleForTesting;
+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.MessageLite;
+import com.google.protobuf.WireFormat;
+import dev.cel.common.annotations.Internal;
+import dev.cel.common.exceptions.CelAttributeNotFoundException;
+import dev.cel.common.types.CelType;
+import dev.cel.common.types.StructTypeReference;
+import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor;
+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 {
+
+ static final String UNKNOWN_MESSAGE_TYPE_NAME = "cel.@unknownMessage";
+ static final int CEL_MAP_TYPE_CODE = -1;
+
+ 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);
+ }
+ }
+
+ boolean hasField(int fieldNumber) {
+ return unknownFields().containsKey(fieldNumber);
+ }
+
+ @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.
+ *
+ * Field traversal on classless messages must be performed via optimized attribute steps
+ * ({@code cel.@attribute} and {@code cel.@hasField}), where the AST optimizer supplies the
+ * pre-resolved protobuf field numbers.
+ *
+ * @throws CelAttributeNotFoundException always, indicating the field cannot be resolved by name.
+ */
+ @Override
+ public Object select(String field) {
+ throw CelAttributeNotFoundException.forFieldResolution(field);
+ }
+
+ @Override
+ public Optional find(String field) {
+ return Optional.empty();
+ }
+
+ @Override
+ public Object selectField(SelectField field) {
+ int fieldNumber = field.fieldNumber();
+ Optional fieldDescriptor =
+ protoLiteCelValueConverter().findFieldDescriptor(celType().name(), fieldNumber);
+ return selectUnknownOrDefault(
+ field, fieldDescriptor, unknownFields().get(fieldNumber), protoLiteCelValueConverter());
+ }
+
+ @Override
+ public boolean hasField(SelectField field) {
+ int fieldNumber = field.fieldNumber();
+ Optional fieldDescriptor =
+ protoLiteCelValueConverter().findFieldDescriptor(celType().name(), fieldNumber);
+ return isPresentInUnknowns(
+ field, fieldDescriptor, unknownFields().get(fieldNumber), protoLiteCelValueConverter());
+ }
+
+ @Override
+ public @Nullable Object navigateField(SelectField field) {
+ int fieldNumber = field.fieldNumber();
+ Optional fieldDescriptor =
+ protoLiteCelValueConverter().findFieldDescriptor(celType().name(), fieldNumber);
+ return navigateUnknown(
+ field, fieldDescriptor, unknownFields().get(fieldNumber), protoLiteCelValueConverter());
+ }
+
+ static Object selectUnknownOrDefault(
+ SelectField field,
+ Optional fieldDescriptor,
+ ImmutableList unknowns,
+ ProtoLiteCelValueConverter converter) {
+ if (!unknowns.isEmpty()) {
+ if (fieldDescriptor.isPresent()
+ && fieldDescriptor.get().getEncodingType() == FieldLiteDescriptor.EncodingType.MAP) {
+ return decodeMapEntries(unknowns, fieldDescriptor.get(), converter);
+ }
+ int typeCode;
+ boolean isRepeated;
+ String protoTypeName;
+ if (fieldDescriptor.isPresent()) {
+ FieldLiteDescriptor fd = fieldDescriptor.get();
+ typeCode = fd.getProtoFieldType().getNumber();
+ isRepeated = fd.getEncodingType() == FieldLiteDescriptor.EncodingType.LIST;
+ protoTypeName = fd.getFieldProtoTypeName();
+ } else {
+ typeCode = field.typeCode();
+ isRepeated = field.defaultValue() instanceof List;
+ protoTypeName = UNKNOWN_MESSAGE_TYPE_NAME;
+ }
+ if (typeCode == SelectField.CEL_MAP_TYPE_CODE) {
+ throw new UnsupportedOperationException(
+ "Decoding unknown map field from wire bytes is unsupported: " + field.fieldName());
+ }
+ return decodeWireEntries(unknowns, typeCode, protoTypeName, isRepeated, converter);
+ }
+
+ if (field.defaultValue() != null) {
+ return field.defaultValue();
+ }
+
+ Optional typeDefault = fieldDescriptor.flatMap(converter::findDefaultCelValue);
+ if (typeDefault.isPresent()) {
+ return typeDefault.get();
+ }
+
+ int typeCode =
+ fieldDescriptor.map(d -> d.getProtoFieldType().getNumber()).orElse(field.typeCode());
+ String protoTypeName =
+ fieldDescriptor
+ .map(FieldLiteDescriptor::getFieldProtoTypeName)
+ .orElse(UNKNOWN_MESSAGE_TYPE_NAME);
+
+ if (typeCode == FieldLiteDescriptor.Type.MESSAGE.getNumber()) {
+ Optional wellKnown =
+ converter.tryDecodeWellKnownProto(ByteString.EMPTY, protoTypeName);
+ if (wellKnown.isPresent()) {
+ return wellKnown.get();
+ }
+ return create(ByteString.EMPTY, protoTypeName, converter);
+ }
+
+ throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
+ }
+
+ static boolean isPresentInUnknowns(
+ SelectField field,
+ Optional fieldDescriptor,
+ ImmutableList unknowns,
+ ProtoLiteCelValueConverter converter) {
+ if (unknowns.isEmpty()) {
+ return false;
+ }
+ boolean isRepeated;
+ int typeCode;
+ if (fieldDescriptor.isPresent()) {
+ FieldLiteDescriptor fd = fieldDescriptor.get();
+ isRepeated = fd.getEncodingType() == FieldLiteDescriptor.EncodingType.LIST;
+ typeCode = fd.getProtoFieldType().getNumber();
+ } else {
+ isRepeated = field.defaultValue() instanceof List;
+ typeCode = field.typeCode();
+ }
+
+ if (isRepeated) {
+ 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;
+ }
+ return true;
+ }
+
+ static @Nullable Object navigateUnknown(
+ SelectField field,
+ Optional fieldDescriptor,
+ ImmutableList unknowns,
+ ProtoLiteCelValueConverter converter) {
+ if (!isPresentInUnknowns(field, fieldDescriptor, unknowns, converter)) {
+ return null;
+ }
+ if (fieldDescriptor.isPresent() || field.typeCode() != SelectField.NO_TYPE_CODE) {
+ return selectUnknownOrDefault(field, fieldDescriptor, unknowns, converter);
+ }
+ Object lastEntry = unknowns.get(unknowns.size() - 1);
+ if (lastEntry instanceof ByteString) {
+ return decodeWireEntries(
+ unknowns,
+ FieldLiteDescriptor.Type.MESSAGE.getNumber(),
+ UNKNOWN_MESSAGE_TYPE_NAME,
+ /* isRepeated= */ false,
+ converter);
+ }
+ return lastEntry;
+ }
+
+ private static ImmutableMap decodeMapEntries(
+ ImmutableList unknowns,
+ FieldLiteDescriptor mapFieldDescriptor,
+ ProtoLiteCelValueConverter converter) {
+ String entryTypeName = mapFieldDescriptor.getFieldProtoTypeName();
+ 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("key");
+ if (key == null) {
+ key = converter.getDefaultCelValue(entryTypeName, "key");
+ } else {
+ key = converter.toRuntimeValue(key);
+ }
+ Object value = entryFields.get("value");
+ if (value == null) {
+ value = converter.getDefaultCelValue(entryTypeName, "value");
+ } else {
+ value = 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);
+ }
+
+ @VisibleForTesting
+ static @Nullable Object decodeWireEntries(
+ ImmutableCollection entries, int typeCode, String protoTypeName, boolean isRepeated) {
+ return decodeWireEntries(
+ entries, typeCode, protoTypeName, isRepeated, ProtoLiteCelValueConverter.newInstance());
+ }
+
+ 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);
+ }
+
+ @VisibleForTesting
+ static Object decodeWireValue(Object raw, int typeCode, String protoTypeName) {
+ return decodeWireValue(
+ raw, FieldLiteDescriptor.Type.forNumber(typeCode).toWireFormatFieldType(), protoTypeName);
+ }
+
+ @VisibleForTesting
+ static Object decodeWireValue(Object raw, WireFormat.FieldType fieldType, String protoTypeName) {
+ return decodeWireValue(raw, fieldType, protoTypeName, ProtoLiteCelValueConverter.newInstance());
+ }
+
+ private 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);
+ Optional wellKnown = converter.tryDecodeWellKnownProto(msgBytes, protoTypeName);
+ if (wellKnown.isPresent()) {
+ return wellKnown.get();
+ }
+ return RawProtoMessageLiteValue.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) {
+ return create(rawWireBytes, "", ProtoLiteCelValueConverter.newInstance());
+ }
+
+ public static RawProtoMessageLiteValue create(ByteString rawWireBytes, String protoTypeName) {
+ return create(rawWireBytes, protoTypeName, ProtoLiteCelValueConverter.newInstance());
+ }
+
+ 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..ef9e085b1
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/values/SelectField.java
@@ -0,0 +1,82 @@
+// 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 java.util.Optional;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * Represents a single field selection hop 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 {
+
+ public static final long MAX_FIELD_NUMBER = 536870911L;
+ public static final int CEL_MAP_TYPE_CODE = -1;
+
+ static final int NO_TYPE_CODE = 0;
+
+ public abstract int fieldNumber();
+
+ public abstract String fieldName();
+
+ public abstract int typeCode();
+
+ public abstract @Nullable Object defaultValue();
+
+ 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);
+ }
+
+ public static SelectField create(
+ long fieldNumber, String fieldName, int typeCode, @Nullable Object defaultValue) {
+ checkArgument(
+ fieldNumber >= 1 && fieldNumber <= MAX_FIELD_NUMBER,
+ "Field number out of protobuf range: %s",
+ fieldNumber);
+ checkNotNull(fieldName);
+ checkArgument(
+ typeCode == CEL_MAP_TYPE_CODE || (typeCode >= 1 && typeCode <= 18),
+ "Invalid protobuf type code: %s",
+ typeCode);
+ return new AutoValue_SelectField((int) fieldNumber, fieldName, typeCode, defaultValue);
+ }
+
+ @SuppressWarnings("unchecked") // Structs and maps qualified by a select chain are String keyed.
+ static Optional findField(SelectableValue> selectable, String fieldName) {
+ return (Optional) ((SelectableValue) selectable).find(fieldName);
+ }
+
+ // Package-private constructor to prevent subclassing outside the package.
+ 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..f1ee2ad3e 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",
@@ -32,6 +34,7 @@ java_library(
"//common/values:proto_message_lite_value_provider",
"//common/values:proto_message_value",
"//common/values:proto_message_value_provider",
+ "//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/ProtoMessageLiteValueTest.java b/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueTest.java
index dbfb55cf9..db1ca87c0 100644
--- a/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueTest.java
+++ b/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueTest.java
@@ -15,28 +15,37 @@
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.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;
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.expr.conformance.proto3.TestAllTypes;
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 +162,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 +260,296 @@ 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"));
+ }
+
+ @Test
+ public void selectField_knownField_returnsValue() {
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleString("foo").build();
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ Object result = val.selectField(SelectField.create(14L, "single_string", 9, "default"));
+
+ assertThat(result).isEqualTo("foo");
+ }
+
+ @Test
+ public void selectField_unknownWireField_decoded() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeInt64(999, 42L);
+ cos.flush();
+ TestAllTypes proto =
+ TestAllTypes.parseFrom(baos.toByteArray(), ExtensionRegistryLite.getEmptyRegistry());
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ Object result = val.selectField(SelectField.create(999L, "unknown_field", 3, 0L));
+
+ assertThat(result).isEqualTo(42L);
+ }
+
+ @Test
+ public void selectField_unknownRepeatedWireField_decoded() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeInt64(999, 10L);
+ cos.writeInt64(999, 20L);
+ cos.flush();
+ TestAllTypes proto =
+ TestAllTypes.parseFrom(baos.toByteArray(), ExtensionRegistryLite.getEmptyRegistry());
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ Object result =
+ val.selectField(SelectField.create(999L, "unknown_repeated", 3, ImmutableList.of()));
+
+ assertThat((Iterable>) result).containsExactly(10L, 20L).inOrder();
+ }
+
+ @Test
+ public void selectField_renamedField_resolvesByFieldNumber() {
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleString("foo").build();
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ Object result = val.selectField(SelectField.create(14L, "renamed_string", 9, "default"));
+
+ assertThat(result).isEqualTo("foo");
+ }
+
+ @Test
+ public void selectField_renamedMapField_resolvesByFieldNumber() {
+ TestAllTypes proto = TestAllTypes.newBuilder().putMapStringString("k", "v").build();
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ Object result = val.selectField(SelectField.create(61L, "renamed_map", -1, ImmutableMap.of()));
+
+ assertThat(result).isEqualTo(ImmutableMap.of("k", "v"));
+ }
+
+ @Test
+ public void selectField_renamedRepeatedField_resolvesByFieldNumber() {
+ TestAllTypes proto =
+ TestAllTypes.newBuilder().addRepeatedInt64(10L).addRepeatedInt64(20L).build();
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ Object result =
+ val.selectField(SelectField.create(32L, "renamed_repeated", 3, ImmutableList.of()));
+
+ assertThat(result).isEqualTo(ImmutableList.of(10L, 20L));
+ }
+
+ @Test
+ public void navigateField_intermediateUnknownSubmessage_returnsRawProtoMessage()
+ throws Exception {
+ ByteArrayOutputStream subBaos = new ByteArrayOutputStream();
+ CodedOutputStream subCos = CodedOutputStream.newInstance(subBaos);
+ subCos.writeString(1, "inner");
+ subCos.flush();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeBytes(998, ByteString.copyFrom(subBaos.toByteArray()));
+ cos.flush();
+ TestAllTypes proto =
+ TestAllTypes.parseFrom(baos.toByteArray(), ExtensionRegistryLite.getEmptyRegistry());
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ Object nav = val.navigateField(SelectField.create(998L, "unknown_submessage"));
+
+ assertThat(nav).isInstanceOf(RawProtoMessageLiteValue.class);
+ }
+
+ @Test
+ public void hasField_knownField_returnsTrue() {
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleString("present").build();
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ assertThat(val.hasField(SelectField.create(14L, "single_string"))).isTrue();
+ }
+
+ @Test
+ public void hasField_unknownFieldPresent_returnsTrue() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeInt64(999, 42L);
+ cos.flush();
+ TestAllTypes proto =
+ TestAllTypes.parseFrom(baos.toByteArray(), ExtensionRegistryLite.getEmptyRegistry());
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ assertThat(val.hasField(SelectField.create(999L, "unknown_present"))).isTrue();
+ }
+
+ @Test
+ public void hasField_unknownFieldAbsent_returnsFalse() {
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ TestAllTypes.getDefaultInstance(),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ assertThat(val.hasField(SelectField.create(888L, "unknown_absent"))).isFalse();
+ }
+
+ @Test
+ public void hasField_renamedField_resolvesByFieldNumber() {
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleString("present").build();
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ assertThat(val.hasField(SelectField.create(14L, "renamed_string"))).isTrue();
+ }
+
+ @Test
+ public void hasField_renamedMapField_resolvesByFieldNumber() {
+ TestAllTypes proto = TestAllTypes.newBuilder().putMapStringString("k", "v").build();
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ assertThat(val.hasField(SelectField.create(61L, "renamed_map"))).isTrue();
+ }
+
+ @Test
+ public void hasField_renamedRepeatedField_resolvesByFieldNumber() {
+ TestAllTypes proto = TestAllTypes.newBuilder().addRepeatedInt64(10L).build();
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ assertThat(val.hasField(SelectField.create(32L, "renamed_repeated"))).isTrue();
+ }
+
+ @Test
+ public void qualify_emptyList_returnsThis() {
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ TestAllTypes.getDefaultInstance(),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ assertThat(val.qualify(ImmutableList.of())).isSameInstanceAs(val);
+ }
+
+ @Test
+ public void hasField_emptyList_returnsFalse() {
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ TestAllTypes.getDefaultInstance(),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ assertThat(val.hasField(ImmutableList.of())).isFalse();
+ }
+
+ @Test
+ public void qualify_crossTypeToMap_resolvesMapKey() {
+ TestAllTypes proto = TestAllTypes.newBuilder().putMapStringString("k", "v").build();
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ Object result =
+ val.qualify(
+ ImmutableList.of(
+ SelectField.create(61L, "map_string_string", -1, ImmutableMap.of()),
+ SelectField.create(1L, "k", 9, "")));
+
+ assertThat(result).isEqualTo("v");
+ }
+
+ @Test
+ public void hasField_crossTypeToMap_resolvesPresence() {
+ TestAllTypes proto = TestAllTypes.newBuilder().putMapStringString("k", "v").build();
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ assertThat(
+ val.hasField(
+ ImmutableList.of(
+ SelectField.create(61L, "map_string_string"), SelectField.create(1L, "k"))))
+ .isTrue();
+ assertThat(
+ val.hasField(
+ ImmutableList.of(
+ SelectField.create(61L, "map_string_string"),
+ SelectField.create(1L, "missing"))))
+ .isFalse();
+ }
+
+ @Test
+ public void qualify_intermediateScalar_throwsCelAttributeNotFoundWithChildFieldName() {
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleInt64(42L).build();
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () ->
+ val.qualify(
+ ImmutableList.of(
+ SelectField.create(2L, "single_int64", 3, 0L),
+ SelectField.create(3L, "leaf_field", 9, ""))));
+
+ assertThat(thrown).hasMessageThat().contains("leaf_field");
+ }
+
+ @Test
+ public void hasField_intermediateScalar_throwsCelAttributeNotFoundWithChildFieldName() {
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleInt64(42L).build();
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () ->
+ val.hasField(
+ ImmutableList.of(
+ SelectField.create(2L, "single_int64"),
+ SelectField.create(3L, "leaf_field"))));
+
+ assertThat(thrown).hasMessageThat().contains("leaf_field");
+ }
}
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..8358d8050
--- /dev/null
+++ b/common/src/test/java/dev/cel/common/values/RawProtoMessageLiteValueTest.java
@@ -0,0 +1,985 @@
+// 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.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.WireFormat;
+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 java.io.ByteArrayOutputStream;
+import java.time.Duration;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class RawProtoMessageLiteValueTest {
+
+ private static final CelLiteDescriptorPool DESCRIPTOR_POOL =
+ DefaultLiteDescriptorPool.newInstance(
+ ImmutableSet.of(TestAllTypesCelDescriptor.getDescriptor()));
+
+ private static final ProtoLiteCelValueConverter CONVERTER =
+ ProtoLiteCelValueConverter.newInstance(DESCRIPTOR_POOL);
+
+ @Test
+ public void create_accessorsAndType() {
+ ByteString bytes = ByteString.copyFromUtf8("test");
+ RawProtoMessageLiteValue value = RawProtoMessageLiteValue.create(bytes, "custom.Message");
+
+ assertThat(value.rawWireBytes()).isEqualTo(bytes);
+ assertThat(value.value()).isSameInstanceAs(value);
+ assertThat(value.celType().name()).isEqualTo("custom.Message");
+ }
+
+ @Test
+ public void create_singleArgDefaultsEmptyTypeName() {
+ ByteString bytes = ByteString.copyFromUtf8("test");
+ RawProtoMessageLiteValue value = RawProtoMessageLiteValue.create(bytes);
+
+ assertThat(value.rawWireBytes()).isEqualTo(bytes);
+ assertThat(value.celType().name()).isEmpty();
+ }
+
+ @Test
+ public void select_throwsCelAttributeNotFoundException() {
+ RawProtoMessageLiteValue value =
+ RawProtoMessageLiteValue.create(ByteString.EMPTY, "custom.Message");
+
+ assertThrows(CelAttributeNotFoundException.class, () -> value.select("field"));
+ }
+
+ @Test
+ public void find_returnsEmptyOptional() {
+ RawProtoMessageLiteValue value =
+ RawProtoMessageLiteValue.create(ByteString.EMPTY, "custom.Message");
+
+ assertThat(value.find("field")).isEmpty();
+ }
+
+ @Test
+ public void isZeroValue_emptyBytes_returnsTrue() {
+ RawProtoMessageLiteValue value = RawProtoMessageLiteValue.create(ByteString.EMPTY);
+
+ assertThat(value.isZeroValue()).isTrue();
+ }
+
+ @Test
+ public void isZeroValue_nonEmptyBytes_returnsFalse() {
+ RawProtoMessageLiteValue value =
+ RawProtoMessageLiteValue.create(ByteString.copyFromUtf8("data"));
+
+ assertThat(value.isZeroValue()).isFalse();
+ }
+
+ @Test
+ public void hasField_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()));
+
+ assertThat(value.hasField(1)).isTrue();
+ assertThat(value.hasField(2)).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()));
+
+ 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 =
+ RawProtoMessageLiteValue.decodeWireEntries(
+ ImmutableList.of(),
+ FieldLiteDescriptor.Type.INT64.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ false);
+ Object messageResult =
+ RawProtoMessageLiteValue.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 =
+ RawProtoMessageLiteValue.decodeWireEntries(
+ ImmutableList.of(),
+ FieldLiteDescriptor.Type.INT64.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true);
+
+ assertThat((Iterable>) result).isEmpty();
+ }
+
+ @Test
+ public void decodeWireEntries_nonRepeated_lastOneWins() {
+ Object decoded =
+ RawProtoMessageLiteValue.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 =
+ RawProtoMessageLiteValue.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 =
+ RawProtoMessageLiteValue.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 =
+ RawProtoMessageLiteValue.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 =
+ RawProtoMessageLiteValue.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 =
+ RawProtoMessageLiteValue.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 =
+ RawProtoMessageLiteValue.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 =
+ RawProtoMessageLiteValue.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(
+ RawProtoMessageLiteValue.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(
+ RawProtoMessageLiteValue.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(
+ RawProtoMessageLiteValue.decodeWireEntries(
+ ImmutableList.of(ByteString.copyFrom(baos.toByteArray()).substring(12, 16)),
+ FieldLiteDescriptor.Type.SFIXED32.getNumber(),
+ "custom.Message",
+ /* isRepeated= */ true))
+ .isEqualTo(ImmutableList.of(-30L));
+
+ assertThat(
+ RawProtoMessageLiteValue.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(
+ RawProtoMessageLiteValue.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(
+ RawProtoMessageLiteValue.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(
+ RawProtoMessageLiteValue.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(
+ RawProtoMessageLiteValue.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(
+ RawProtoMessageLiteValue.decodeWireValue(
+ Double.doubleToRawLongBits(2.5d), WireFormat.FieldType.DOUBLE, "custom.Message"))
+ .isEqualTo(2.5d);
+
+ assertThat(
+ RawProtoMessageLiteValue.decodeWireValue(
+ Float.floatToRawIntBits(1.5f), WireFormat.FieldType.FLOAT, "custom.Message"))
+ .isEqualTo(1.5d);
+
+ assertThat(
+ RawProtoMessageLiteValue.decodeWireValue(
+ 42L, WireFormat.FieldType.INT64, "custom.Message"))
+ .isEqualTo(42L);
+
+ assertThat(
+ RawProtoMessageLiteValue.decodeWireValue(
+ 42L, WireFormat.FieldType.INT32, "custom.Message"))
+ .isEqualTo(42L);
+
+ assertThat(
+ RawProtoMessageLiteValue.decodeWireValue(
+ 42L, WireFormat.FieldType.UINT64, "custom.Message"))
+ .isEqualTo(UnsignedLong.fromLongBits(42L));
+
+ assertThat(
+ RawProtoMessageLiteValue.decodeWireValue(
+ 42L, WireFormat.FieldType.UINT32, "custom.Message"))
+ .isEqualTo(UnsignedLong.fromLongBits(42L));
+
+ assertThat(
+ RawProtoMessageLiteValue.decodeWireValue(
+ 100, WireFormat.FieldType.FIXED32, "custom.Message"))
+ .isEqualTo(UnsignedLong.fromLongBits(100L));
+
+ assertThat(
+ RawProtoMessageLiteValue.decodeWireValue(
+ 100L, WireFormat.FieldType.FIXED64, "custom.Message"))
+ .isEqualTo(UnsignedLong.fromLongBits(100L));
+
+ assertThat(
+ RawProtoMessageLiteValue.decodeWireValue(
+ -50, WireFormat.FieldType.SFIXED32, "custom.Message"))
+ .isEqualTo(-50L);
+
+ assertThat(
+ RawProtoMessageLiteValue.decodeWireValue(
+ -50L, WireFormat.FieldType.SFIXED64, "custom.Message"))
+ .isEqualTo(-50L);
+
+ assertThat(
+ RawProtoMessageLiteValue.decodeWireValue(
+ 1L, WireFormat.FieldType.BOOL, "custom.Message"))
+ .isEqualTo(true);
+
+ assertThat(
+ RawProtoMessageLiteValue.decodeWireValue(
+ 0L, WireFormat.FieldType.BOOL, "custom.Message"))
+ .isEqualTo(false);
+
+ assertThat(
+ RawProtoMessageLiteValue.decodeWireValue(
+ ByteString.copyFromUtf8("hello"), WireFormat.FieldType.STRING, "custom.Message"))
+ .isEqualTo("hello");
+
+ assertThat(
+ RawProtoMessageLiteValue.decodeWireValue(
+ ByteString.copyFromUtf8("bytes"), WireFormat.FieldType.BYTES, "custom.Message"))
+ .isEqualTo(CelByteString.of("bytes".getBytes(UTF_8)));
+
+ assertThat(
+ RawProtoMessageLiteValue.decodeWireValue(
+ 1L, // zigzag 1 -> -1
+ WireFormat.FieldType.SINT32,
+ "custom.Message"))
+ .isEqualTo(-1L);
+
+ assertThat(
+ RawProtoMessageLiteValue.decodeWireValue(
+ 1L, // zigzag 1 -> -1
+ WireFormat.FieldType.SINT64,
+ "custom.Message"))
+ .isEqualTo(-1L);
+
+ assertThat(
+ RawProtoMessageLiteValue.decodeWireValue(
+ 3L, WireFormat.FieldType.ENUM, "custom.Message"))
+ .isEqualTo(3L);
+ }
+
+ @Test
+ public void decodeWireValue_messageType_returnsRawProtoMessageLiteValue() {
+ Object submessage =
+ RawProtoMessageLiteValue.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,
+ () ->
+ RawProtoMessageLiteValue.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,
+ () ->
+ RawProtoMessageLiteValue.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,
+ () ->
+ RawProtoMessageLiteValue.decodeWireEntries(
+ rawEntries, 999, "custom.Message", /* isRepeated= */ false));
+ }
+
+ @Test
+ public void decodeWireValue_invalidTypeCode_throws() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> RawProtoMessageLiteValue.decodeWireValue(42L, 0, "custom.Message"));
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> RawProtoMessageLiteValue.decodeWireValue(42L, 999, "custom.Message"));
+ }
+
+ @Test
+ public void decodeWireValue_int32HighBits_truncatedToSigned32Bit() {
+ Object decodedHigh =
+ RawProtoMessageLiteValue.decodeWireValue(
+ 0x100000005L, WireFormat.FieldType.INT32, "custom.Message");
+ Object decodedNegative =
+ RawProtoMessageLiteValue.decodeWireValue(
+ 0xFFFFFFFF80000000L, WireFormat.FieldType.INT32, "custom.Message");
+
+ assertThat(decodedHigh).isEqualTo(5L);
+ assertThat(decodedNegative).isEqualTo(-2147483648L);
+ }
+
+ @Test
+ public void decodeWireValue_enumHighBits_truncatedToSigned32Bit() {
+ Object decodedHigh =
+ RawProtoMessageLiteValue.decodeWireValue(
+ 0x100000005L, WireFormat.FieldType.ENUM, "custom.Message");
+
+ assertThat(decodedHigh).isEqualTo(5L);
+ }
+
+ @Test
+ public void decodeWireValue_typeMismatch_throwsIllegalArgumentException() {
+ IllegalArgumentException thrownInt64 =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ RawProtoMessageLiteValue.decodeWireValue(
+ "not a long", WireFormat.FieldType.INT64, "custom.Message"));
+ assertThat(thrownInt64).hasMessageThat().contains("Expected Long for wire type INT64");
+
+ IllegalArgumentException thrownString =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ RawProtoMessageLiteValue.decodeWireValue(
+ 100L, WireFormat.FieldType.STRING, "custom.Message"));
+ assertThat(thrownString).hasMessageThat().contains("Expected ByteString for wire type STRING");
+
+ IllegalArgumentException thrownBytes =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ RawProtoMessageLiteValue.decodeWireValue(
+ 100L, WireFormat.FieldType.BYTES, "custom.Message"));
+ assertThat(thrownBytes).hasMessageThat().contains("Expected ByteString for wire type BYTES");
+
+ IllegalArgumentException thrownMessage =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ RawProtoMessageLiteValue.decodeWireValue(
+ 100L, WireFormat.FieldType.MESSAGE, "custom.Message"));
+ assertThat(thrownMessage)
+ .hasMessageThat()
+ .contains("Expected ByteString for wire type MESSAGE");
+
+ IllegalArgumentException thrownFloat =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ RawProtoMessageLiteValue.decodeWireValue(
+ 100L, WireFormat.FieldType.FLOAT, "custom.Message"));
+ assertThat(thrownFloat).hasMessageThat().contains("Expected Integer for wire type FLOAT");
+
+ IllegalArgumentException thrownDouble =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ RawProtoMessageLiteValue.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,
+ () ->
+ RawProtoMessageLiteValue.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 =
+ RawProtoMessageLiteValue.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 =
+ RawProtoMessageLiteValue.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 =
+ RawProtoMessageLiteValue.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 =
+ RawProtoMessageLiteValue.decodeWireValue(
+ 0xFFFFFFFFL, WireFormat.FieldType.UINT32, "custom.Message");
+
+ assertThat(decoded).isEqualTo(UnsignedLong.valueOf(4294967295L));
+ }
+
+ @Test
+ public void decodeWireValue_fixed32HighBit_correctUnsignedLong() {
+ Object decoded =
+ RawProtoMessageLiteValue.decodeWireValue(
+ -1, WireFormat.FieldType.FIXED32, "custom.Message");
+
+ assertThat(decoded).isEqualTo(UnsignedLong.valueOf(4294967295L));
+ }
+
+ @Test
+ public void decodeWireEntries_repeatedString() {
+ Object decoded =
+ RawProtoMessageLiteValue.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 =
+ RawProtoMessageLiteValue.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 =
+ RawProtoMessageLiteValue.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"),
+ RawProtoMessageLiteValue.create(ByteString.copyFromUtf8("msg2"), "sub.Message"))
+ .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,
+ () ->
+ RawProtoMessageLiteValue.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 selectField_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");
+
+ Object val = raw.selectField(SelectField.create(14L, "single_string", 9, ""));
+
+ assertThat(val).isEqualTo("hello");
+ }
+
+ @Test
+ public void selectField_absentWithDefaultValue_returnsDefault() {
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.EMPTY, "cel.expr.conformance.proto3.TestAllTypes");
+
+ Object val = raw.selectField(SelectField.create(14L, "single_string", 9, "default"));
+
+ assertThat(val).isEqualTo("default");
+ }
+
+ @Test
+ public void selectField_withConverter_resolvesDescriptor() {
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.EMPTY, "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+
+ Object val = raw.selectField(SelectField.create(14L, "single_string"));
+
+ assertThat(val).isEqualTo("");
+ }
+
+ @Test
+ public void hasField_wirePresent_returnsTrue() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeString(14, "present");
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()), "cel.expr.conformance.proto3.TestAllTypes");
+
+ assertThat(raw.hasField(SelectField.create(14L, "single_string"))).isTrue();
+ }
+
+ @Test
+ public void hasField_wireAbsent_returnsFalse() {
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.EMPTY, "cel.expr.conformance.proto3.TestAllTypes");
+
+ assertThat(raw.hasField(SelectField.create(15L, "other_field"))).isFalse();
+ }
+
+ @Test
+ public void hasField_emptyPackedRepeated_withDescriptor_returnsFalse() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeBytes(31, ByteString.EMPTY);
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ CONVERTER);
+
+ assertThat(raw.hasField(SelectField.create(31L, "repeated_int32"))).isFalse();
+ }
+
+ @Test
+ public void hasField_emptyPackedRepeated_withoutDescriptor_returnsFalse() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeBytes(31, ByteString.EMPTY);
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()), "cel.expr.conformance.proto3.TestAllTypes");
+
+ assertThat(raw.hasField(SelectField.create(31L, "repeated_int32", 5, ImmutableList.of())))
+ .isFalse();
+ }
+
+ @Test
+ public void hasField_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(31, ByteString.copyFrom(packed.toByteArray()));
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ CONVERTER);
+
+ assertThat(raw.hasField(SelectField.create(31L, "repeated_int32"))).isTrue();
+ }
+
+ @Test
+ public void navigateField_intermediatePresentAndAbsent() throws Exception {
+ ByteArrayOutputStream subBaos = new ByteArrayOutputStream();
+ CodedOutputStream subCos = CodedOutputStream.newInstance(subBaos);
+ subCos.writeInt32(1, 42);
+ subCos.flush();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeBytes(21, ByteString.copyFrom(subBaos.toByteArray()));
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()), "cel.expr.conformance.proto3.TestAllTypes");
+
+ Object nav = raw.navigateField(SelectField.create(21L, "single_nested_message"));
+
+ assertThat(nav).isInstanceOf(RawProtoMessageLiteValue.class);
+ assertThat(raw.navigateField(SelectField.create(999L, "absent"))).isNull();
+ }
+
+ @Test
+ public void selectField_mapFieldWithDescriptor_decodesEntries() {
+ TestAllTypes proto =
+ TestAllTypes.newBuilder()
+ .putMapStringString("k1", "v1")
+ .putMapStringString("k2", "v2")
+ .build();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ proto.toByteString(), "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+
+ Object selected =
+ raw.selectField(SelectField.create(61L, "map_string_string", -1, ImmutableMap.of()));
+
+ assertThat(selected).isEqualTo(ImmutableMap.of("k1", "v1", "k2", "v2"));
+ }
+
+ @Test
+ public void selectField_wellKnownDuration_convertsToJavaDuration() {
+ TestAllTypes proto =
+ TestAllTypes.newBuilder()
+ .setSingleDuration(ProtoTimeUtils.toProtoDuration(Duration.ofSeconds(10L, 500L)))
+ .build();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ proto.toByteString(), "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+
+ Object selected =
+ raw.selectField(SelectField.create(101L, "single_duration", 11, Duration.ZERO));
+
+ assertThat(selected).isEqualTo(Duration.ofSeconds(10L, 500L));
+ }
+
+ @Test
+ public void selectField_wellKnownInt64Wrapper_convertsToLong() {
+ TestAllTypes proto =
+ TestAllTypes.newBuilder().setSingleInt64Wrapper(Int64Value.of(12345L)).build();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ proto.toByteString(), "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+
+ Object selected =
+ raw.selectField(
+ SelectField.create(
+ TestAllTypes.SINGLE_INT64_WRAPPER_FIELD_NUMBER,
+ "single_int64_wrapper",
+ 11,
+ NullValue.NULL_VALUE));
+
+ assertThat(selected).isEqualTo(12345L);
+ }
+
+ @Test
+ public void navigateField_scalarFieldWithDescriptor_returnsScalar() {
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleInt64(99L).build();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ proto.toByteString(), "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+
+ Object nav = raw.navigateField(SelectField.create(2L, "single_int64"));
+
+ assertThat(nav).isEqualTo(99L);
+ }
+
+ @Test
+ public void navigateField_scalarFieldWithoutDescriptor_returnsScalar() {
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleInt64(99L).build();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ proto.toByteString(), "cel.expr.conformance.proto3.TestAllTypes");
+
+ Object nav = raw.navigateField(SelectField.create(2L, "single_int64"));
+
+ assertThat(nav).isEqualTo(99L);
+ }
+}
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..1cecdbfb6
--- /dev/null
+++ b/common/src/test/java/dev/cel/common/values/SelectFieldTest.java
@@ -0,0 +1,105 @@
+// 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_nullFieldName_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> SelectField.create(1L, null));
+ 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_invalidTypeCode_throwsIllegalArgumentException() {
+ assertThrows(IllegalArgumentException.class, () -> SelectField.create(1L, "foo", 0, null));
+ assertThrows(IllegalArgumentException.class, () -> SelectField.create(1L, "foo", 19, null));
+ assertThrows(IllegalArgumentException.class, () -> SelectField.create(1L, "foo", -2, null));
+ assertThrows(IllegalArgumentException.class, () -> SelectField.create(1L, "foo", 999, null));
+ }
+
+ @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/protobuf/src/main/java/dev/cel/protobuf/CelLiteDescriptor.java b/protobuf/src/main/java/dev/cel/protobuf/CelLiteDescriptor.java
index c066bb18e..fcee6215a 100644
--- a/protobuf/src/main/java/dev/cel/protobuf/CelLiteDescriptor.java
+++ b/protobuf/src/main/java/dev/cel/protobuf/CelLiteDescriptor.java
@@ -18,6 +18,7 @@
import com.google.errorprone.annotations.Immutable;
import com.google.protobuf.MessageLite;
+import com.google.protobuf.WireFormat;
import dev.cel.common.annotations.Internal;
import java.util.Collections;
import java.util.HashMap;
@@ -184,24 +185,95 @@ public enum JavaType {
* This is exactly the same as com.google.protobuf.Descriptors#Type
*/
public enum Type {
- DOUBLE,
- FLOAT,
- INT64,
- UINT64,
- INT32,
- FIXED64,
- FIXED32,
- BOOL,
- STRING,
- GROUP,
- MESSAGE,
- BYTES,
- UINT32,
- ENUM,
- SFIXED32,
- SFIXED64,
- SINT32,
- SINT64
+ DOUBLE(1, WireFormat.FieldType.DOUBLE),
+ FLOAT(2, WireFormat.FieldType.FLOAT),
+ INT64(3, WireFormat.FieldType.INT64),
+ UINT64(4, WireFormat.FieldType.UINT64),
+ INT32(5, WireFormat.FieldType.INT32),
+ FIXED64(6, WireFormat.FieldType.FIXED64),
+ FIXED32(7, WireFormat.FieldType.FIXED32),
+ BOOL(8, WireFormat.FieldType.BOOL),
+ STRING(9, WireFormat.FieldType.STRING),
+ GROUP(10, WireFormat.FieldType.GROUP),
+ MESSAGE(11, WireFormat.FieldType.MESSAGE),
+ BYTES(12, WireFormat.FieldType.BYTES),
+ UINT32(13, WireFormat.FieldType.UINT32),
+ ENUM(14, WireFormat.FieldType.ENUM),
+ SFIXED32(15, WireFormat.FieldType.SFIXED32),
+ SFIXED64(16, WireFormat.FieldType.SFIXED64),
+ SINT32(17, WireFormat.FieldType.SINT32),
+ SINT64(18, WireFormat.FieldType.SINT64);
+
+ private final int number;
+ private final WireFormat.FieldType wireFormatFieldType;
+
+ /** Gets the type number corresponding to {@code FieldDescriptorProto.Type#getNumber()}. */
+ public int getNumber() {
+ return number;
+ }
+
+ /** Converts this type to the corresponding {@link WireFormat.FieldType}. */
+ public WireFormat.FieldType toWireFormatFieldType() {
+ return wireFormatFieldType;
+ }
+
+ /**
+ * Returns the {@link Type} for the specified protobuf type number.
+ *
+ * @throws IllegalArgumentException if the number does not correspond to a valid protobuf
+ * type.
+ */
+ public static Type forNumber(int number) {
+ switch (number) {
+ case 1:
+ return DOUBLE;
+ case 2:
+ return FLOAT;
+ case 3:
+ return INT64;
+ case 4:
+ return UINT64;
+ case 5:
+ return INT32;
+ case 6:
+ return FIXED64;
+ case 7:
+ return FIXED32;
+ case 8:
+ return BOOL;
+ case 9:
+ return STRING;
+ case 10:
+ return GROUP;
+ case 11:
+ return MESSAGE;
+ case 12:
+ return BYTES;
+ case 13:
+ return UINT32;
+ case 14:
+ return ENUM;
+ case 15:
+ return SFIXED32;
+ case 16:
+ return SFIXED64;
+ case 17:
+ return SINT32;
+ case 18:
+ return SINT64;
+ default:
+ throw new IllegalArgumentException("Unsupported proto type code: " + number);
+ }
+ }
+
+ private Type(int number, WireFormat.FieldType wireFormatFieldType) {
+ this.number = number;
+ this.wireFormatFieldType = Objects.requireNonNull(wireFormatFieldType);
+ }
+ }
+
+ public int getFieldNumber() {
+ return fieldNumber;
}
public String getFieldName() {
@@ -269,9 +341,9 @@ public FieldLiteDescriptor(
String fieldProtoTypeName) {
this.fieldNumber = fieldNumber;
this.fieldName = Objects.requireNonNull(fieldName);
- this.javaType = javaType;
- this.encodingType = encodingType;
- this.protoFieldType = protoFieldType;
+ this.javaType = Objects.requireNonNull(javaType);
+ this.encodingType = Objects.requireNonNull(encodingType);
+ this.protoFieldType = Objects.requireNonNull(protoFieldType);
this.isPacked = isPacked;
this.fieldProtoTypeName = Objects.requireNonNull(fieldProtoTypeName);
}
diff --git a/protobuf/src/test/java/dev/cel/protobuf/BUILD.bazel b/protobuf/src/test/java/dev/cel/protobuf/BUILD.bazel
index 58e298b29..635379aab 100644
--- a/protobuf/src/test/java/dev/cel/protobuf/BUILD.bazel
+++ b/protobuf/src/test/java/dev/cel/protobuf/BUILD.bazel
@@ -16,6 +16,7 @@ java_test(
"@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto_lite",
"@maven//:com_google_testparameterinjector_test_parameter_injector",
"@maven//:junit_junit",
+ "@maven_android//:com_google_protobuf_protobuf_javalite",
],
)
diff --git a/protobuf/src/test/java/dev/cel/protobuf/CelLiteDescriptorTest.java b/protobuf/src/test/java/dev/cel/protobuf/CelLiteDescriptorTest.java
index 1ceed29bb..95dacd6ef 100644
--- a/protobuf/src/test/java/dev/cel/protobuf/CelLiteDescriptorTest.java
+++ b/protobuf/src/test/java/dev/cel/protobuf/CelLiteDescriptorTest.java
@@ -15,7 +15,10 @@
package dev.cel.protobuf;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+import com.google.protobuf.WireFormat;
+import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import dev.cel.expr.conformance.proto3.TestAllTypesCelLiteDescriptor;
import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor;
@@ -146,4 +149,96 @@ public void fieldDescriptor_nestedMessage_fullyQualifiedNames() {
assertThat(fieldLiteDescriptor.getFieldProtoTypeName())
.isEqualTo("cel.expr.conformance.proto3.TestAllTypes.NestedMessage");
}
+
+ private enum ProtoFieldTypeTestCase {
+ DOUBLE(FieldLiteDescriptor.Type.DOUBLE, 1, WireFormat.FieldType.DOUBLE),
+ FLOAT(FieldLiteDescriptor.Type.FLOAT, 2, WireFormat.FieldType.FLOAT),
+ INT64(FieldLiteDescriptor.Type.INT64, 3, WireFormat.FieldType.INT64),
+ UINT64(FieldLiteDescriptor.Type.UINT64, 4, WireFormat.FieldType.UINT64),
+ INT32(FieldLiteDescriptor.Type.INT32, 5, WireFormat.FieldType.INT32),
+ FIXED64(FieldLiteDescriptor.Type.FIXED64, 6, WireFormat.FieldType.FIXED64),
+ FIXED32(FieldLiteDescriptor.Type.FIXED32, 7, WireFormat.FieldType.FIXED32),
+ BOOL(FieldLiteDescriptor.Type.BOOL, 8, WireFormat.FieldType.BOOL),
+ STRING(FieldLiteDescriptor.Type.STRING, 9, WireFormat.FieldType.STRING),
+ GROUP(FieldLiteDescriptor.Type.GROUP, 10, WireFormat.FieldType.GROUP),
+ MESSAGE(FieldLiteDescriptor.Type.MESSAGE, 11, WireFormat.FieldType.MESSAGE),
+ BYTES(FieldLiteDescriptor.Type.BYTES, 12, WireFormat.FieldType.BYTES),
+ UINT32(FieldLiteDescriptor.Type.UINT32, 13, WireFormat.FieldType.UINT32),
+ ENUM(FieldLiteDescriptor.Type.ENUM, 14, WireFormat.FieldType.ENUM),
+ SFIXED32(FieldLiteDescriptor.Type.SFIXED32, 15, WireFormat.FieldType.SFIXED32),
+ SFIXED64(FieldLiteDescriptor.Type.SFIXED64, 16, WireFormat.FieldType.SFIXED64),
+ SINT32(FieldLiteDescriptor.Type.SINT32, 17, WireFormat.FieldType.SINT32),
+ SINT64(FieldLiteDescriptor.Type.SINT64, 18, WireFormat.FieldType.SINT64);
+
+ private final FieldLiteDescriptor.Type type;
+ private final int expectedNumber;
+ private final WireFormat.FieldType expectedWireType;
+
+ ProtoFieldTypeTestCase(
+ FieldLiteDescriptor.Type type, int expectedNumber, WireFormat.FieldType expectedWireType) {
+ this.type = type;
+ this.expectedNumber = expectedNumber;
+ this.expectedWireType = expectedWireType;
+ }
+ }
+
+ @Test
+ public void protoFieldType_numbersAndWireTypes(@TestParameter ProtoFieldTypeTestCase testCase) {
+ assertThat(testCase.type.getNumber()).isEqualTo(testCase.expectedNumber);
+ assertThat(testCase.type.toWireFormatFieldType()).isEqualTo(testCase.expectedWireType);
+ }
+
+ @Test
+ public void protoFieldType_forNumber_roundTripAllTypes(
+ @TestParameter FieldLiteDescriptor.Type type) {
+ assertThat(FieldLiteDescriptor.Type.forNumber(type.getNumber())).isEqualTo(type);
+ }
+
+ @Test
+ public void protoFieldType_forNumber_outOfRange_throws(
+ @TestParameter({"-2147483648", "-1", "0", "19", "100", "2147483647"}) int invalidNumber) {
+ IllegalArgumentException e =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> FieldLiteDescriptor.Type.forNumber(invalidNumber));
+
+ assertThat(e).hasMessageThat().isEqualTo("Unsupported proto type code: " + invalidNumber);
+ }
+
+ @Test
+ public void fieldLiteDescriptor_nullParameters_throws() {
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ new FieldLiteDescriptor(
+ 1,
+ "field",
+ null,
+ EncodingType.SINGULAR,
+ FieldLiteDescriptor.Type.INT32,
+ false,
+ ""));
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ new FieldLiteDescriptor(
+ 1,
+ "field",
+ FieldLiteDescriptor.JavaType.INT,
+ null,
+ FieldLiteDescriptor.Type.INT32,
+ false,
+ ""));
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ new FieldLiteDescriptor(
+ 1,
+ "field",
+ FieldLiteDescriptor.JavaType.INT,
+ EncodingType.SINGULAR,
+ null,
+ false,
+ ""));
+ }
}
diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel
index 9518e1601..f21e99d88 100644
--- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel
+++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel
@@ -41,6 +41,7 @@ LITE_RUNTIME_SOURCES = [
# keep sorted
LITE_RUNTIME_IMPL_SOURCES = [
+ "LiteAttributeStep.java",
"LiteRuntimeImpl.java",
]
@@ -990,9 +991,11 @@ java_library(
":program",
":runtime_equality",
":runtime_helpers",
+ ":unknown_attributes",
"//common:cel_ast",
"//common:container",
"//common:options",
+ "//common/exceptions:attribute_not_found",
"//common/types:default_type_provider",
"//common/types:type_providers",
"//common/values",
@@ -1003,6 +1006,7 @@ java_library(
"@maven//:com_google_code_findbugs_annotations",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
+ "@maven//:org_jspecify_jspecify",
],
)
@@ -1018,9 +1022,11 @@ cel_android_library(
":program_android",
":runtime_equality_android",
":runtime_helpers_android",
+ ":unknown_attributes_android",
"//common:cel_ast_android",
"//common:container_android",
"//common:options",
+ "//common/exceptions:attribute_not_found",
"//common/types:default_type_provider_android",
"//common/types:type_providers_android",
"//common/values:cel_value_provider_android",
@@ -1031,6 +1037,7 @@ cel_android_library(
"@maven//:com_google_code_findbugs_annotations",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
+ "@maven//:org_jspecify_jspecify",
"@maven_android//:com_google_guava_guava",
],
)
diff --git a/runtime/src/main/java/dev/cel/runtime/LiteAttributeStep.java b/runtime/src/main/java/dev/cel/runtime/LiteAttributeStep.java
new file mode 100644
index 000000000..cc3cd048e
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/LiteAttributeStep.java
@@ -0,0 +1,317 @@
+// 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.runtime;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.collect.ImmutableList;
+import dev.cel.common.exceptions.CelAttributeNotFoundException;
+import dev.cel.common.values.CelValueConverter;
+import dev.cel.common.values.ErrorValue;
+import dev.cel.common.values.NullValue;
+import dev.cel.common.values.OptimizedSelectable;
+import dev.cel.common.values.OptionalValue;
+import dev.cel.common.values.SelectField;
+import dev.cel.common.values.SelectableValue;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * LiteAttributeStep evaluates the optimized {@code cel.@attribute} and {@code cel.@hasField}
+ * expressions emitted by {@code SelectOptimizer}.
+ *
+ *
Qualification and presence testing dispatch polymorphically over {@link OptimizedSelectable}
+ * (for proto messages with descriptor-based or classless wire traversal), {@link SelectableValue},
+ * and {@link Map}.
+ *
+ *
CEL Library Internals. Do Not Use.
+ */
+final class LiteAttributeStep {
+
+ /**
+ * Qualifies an attribute by applying each qualifier in {@code qualifierLists} in order.
+ *
+ *
Each qualifier is a {@code [field_number, field_name, type_code]} 3-tuple (for submessages),
+ * or a {@code [field_number, field_name, type_code, default_value]} 4-tuple (for leaf fields with
+ * pre-resolved default values).
+ */
+ static @Nullable Object qualifyAttribute(
+ @Nullable Object target, List> qualifierLists, CelValueConverter celValueConverter) {
+ checkNotNull(qualifierLists);
+ checkNotNull(celValueConverter);
+
+ ImmutableList fields = toSelectFields(qualifierLists);
+
+ if (target instanceof CelUnknownSet
+ || target instanceof ErrorValue
+ || target instanceof Exception) {
+ return target;
+ }
+
+ if (target == null || target instanceof NullValue) {
+ if (fields.isEmpty()) {
+ return target;
+ }
+ throw CelAttributeNotFoundException.forFieldResolution(fields.get(0).fieldName());
+ }
+
+ if (fields.isEmpty()) {
+ return celValueConverter.maybeUnwrap(target);
+ }
+
+ Object runtimeTarget = toStepTarget(target, celValueConverter);
+ if (runtimeTarget instanceof OptionalValue) {
+ throw new UnsupportedOperationException(
+ "Optional operands are not yet supported by the lite select-optimized runtime");
+ }
+
+ if (runtimeTarget instanceof OptimizedSelectable) {
+ Object result = ((OptimizedSelectable) runtimeTarget).qualify(fields);
+ return celValueConverter.maybeUnwrap(result);
+ }
+
+ Object obj = runtimeTarget;
+ for (SelectField field : fields) {
+ if (obj instanceof OptimizedSelectable) {
+ obj = ((OptimizedSelectable) obj).selectField(field);
+ } else if (obj instanceof SelectableValue) {
+ Optional found = find((SelectableValue>) obj, field.fieldName());
+ if (found.isPresent()) {
+ obj = toStepTarget(found.get(), celValueConverter);
+ } else if (field.defaultValue() != null) {
+ obj = field.defaultValue();
+ } else {
+ throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
+ }
+ } else if (obj instanceof Map) {
+ Map, ?> map = (Map, ?>) obj;
+ Object mapValue = map.get(field.fieldName());
+ if (mapValue != null) {
+ obj = toStepTarget(mapValue, celValueConverter);
+ } else if (map.containsKey(field.fieldName())) {
+ obj = NullValue.NULL_VALUE;
+ } else {
+ throw CelAttributeNotFoundException.forMissingMapKey(field.fieldName());
+ }
+ } else {
+ throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
+ }
+ }
+
+ if (obj instanceof Map) {
+ obj = celValueConverter.toRuntimeValue(obj);
+ }
+ return celValueConverter.maybeUnwrap(obj);
+ }
+
+ /**
+ * Tests presence of an attribute by navigating the leading qualifiers in {@code qualifierLists}
+ * and presence testing the last one.
+ *
+ * Each qualifier is a {@code [field_number, field_name]} pair.
+ */
+ static Object hasField(
+ @Nullable Object target, List> qualifierLists, CelValueConverter celValueConverter) {
+ checkNotNull(qualifierLists);
+ checkNotNull(celValueConverter);
+
+ ImmutableList fields = toPresenceFields(qualifierLists);
+
+ if (target instanceof CelUnknownSet
+ || target instanceof ErrorValue
+ || target instanceof Exception) {
+ return target;
+ }
+
+ if (fields.isEmpty()) {
+ return false;
+ }
+
+ if (target == null || target instanceof NullValue) {
+ throw CelAttributeNotFoundException.forFieldResolution(fields.get(0).fieldName());
+ }
+
+ Object runtimeTarget = toStepTarget(target, celValueConverter);
+ if (runtimeTarget instanceof OptionalValue) {
+ throw new UnsupportedOperationException(
+ "Optional operands are not yet supported by the lite select-optimized runtime");
+ }
+
+ if (runtimeTarget instanceof OptimizedSelectable) {
+ return ((OptimizedSelectable) runtimeTarget).hasField(fields);
+ }
+
+ int lastIndex = fields.size() - 1;
+ Object obj = runtimeTarget;
+ for (int i = 0; i < lastIndex; i++) {
+ SelectField field = fields.get(i);
+ if (obj instanceof OptimizedSelectable) {
+ obj = ((OptimizedSelectable) obj).navigateField(field);
+ if (obj == null) {
+ return false;
+ }
+ } else if (obj instanceof SelectableValue) {
+ Optional found = find((SelectableValue>) obj, field.fieldName());
+ if (!found.isPresent()) {
+ return false;
+ }
+ obj = toStepTarget(found.get(), celValueConverter);
+ } else if (obj instanceof Map) {
+ Map, ?> map = (Map, ?>) obj;
+ if (!map.containsKey(field.fieldName())) {
+ return false;
+ }
+ Object mapValue = map.get(field.fieldName());
+ obj = (mapValue != null) ? toStepTarget(mapValue, celValueConverter) : NullValue.NULL_VALUE;
+ } else {
+ throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
+ }
+ }
+
+ SelectField terminalField = fields.get(lastIndex);
+ if (obj instanceof OptimizedSelectable) {
+ return ((OptimizedSelectable) obj).hasField(terminalField);
+ } else if (obj instanceof SelectableValue) {
+ return find((SelectableValue>) obj, terminalField.fieldName()).isPresent();
+ } else if (obj instanceof Map) {
+ return ((Map, ?>) obj).containsKey(terminalField.fieldName());
+ } else {
+ throw CelAttributeNotFoundException.forFieldResolution(terminalField.fieldName());
+ }
+ }
+
+ private static Object toStepTarget(Object value, CelValueConverter celValueConverter) {
+ if (value instanceof Map) {
+ return value;
+ }
+ return celValueConverter.toRuntimeValue(value);
+ }
+
+ @SuppressWarnings("unchecked") // Structs and maps qualified by a select chain are String keyed.
+ private static Optional find(SelectableValue> selectable, String fieldName) {
+ return (Optional) ((SelectableValue) selectable).find(fieldName);
+ }
+
+ private static SelectField toSelectField(Object item) {
+ List> qualifier = asQualifier(item, /* minSize= */ 3);
+ long fieldNumber = fieldNumberOf(qualifier);
+ String fieldName = fieldNameOf(qualifier);
+ int typeCode = typeCodeOf(qualifier);
+ Object defaultValue = defaultValueOf(qualifier);
+ return SelectField.create(fieldNumber, fieldName, typeCode, defaultValue);
+ }
+
+ private static SelectField toPresenceField(Object item) {
+ List> qualifier = asQualifier(item, /* minSize= */ 2);
+ long fieldNumber = fieldNumberOf(qualifier);
+ String fieldName = fieldNameOf(qualifier);
+ if (qualifier.size() >= 3) {
+ int typeCode = typeCodeOf(qualifier);
+ Object defaultValue = defaultValueOf(qualifier);
+ return SelectField.create(fieldNumber, fieldName, typeCode, defaultValue);
+ }
+ return SelectField.create(fieldNumber, fieldName);
+ }
+
+ private static ImmutableList toSelectFields(List> qualifierLists) {
+ ImmutableList.Builder builder =
+ ImmutableList.builderWithExpectedSize(qualifierLists.size());
+ for (Object item : qualifierLists) {
+ builder.add(toSelectField(item));
+ }
+ return builder.build();
+ }
+
+ private static ImmutableList toPresenceFields(List> qualifierLists) {
+ ImmutableList.Builder builder =
+ ImmutableList.builderWithExpectedSize(qualifierLists.size());
+ for (Object item : qualifierLists) {
+ builder.add(toPresenceField(item));
+ }
+ return builder.build();
+ }
+
+ /** Validates and returns a single {@code [field_number, field_name, ...]} qualifier tuple. */
+ private static List> asQualifier(Object item, int minSize) {
+ if (!(item instanceof List)) {
+ throw new IllegalArgumentException("Expected qualifier list, got: " + item);
+ }
+ List> qualifier = (List>) item;
+ if (qualifier.size() < minSize
+ || !isInteger(qualifier.get(0))
+ || !(qualifier.get(1) instanceof String)
+ || (minSize > 2 && !isInteger(qualifier.get(2)))) {
+ throw new IllegalArgumentException("Invalid qualifier format: " + qualifier);
+ }
+ return qualifier;
+ }
+
+ private static boolean isInteger(Object raw) {
+ return raw instanceof Long
+ || raw instanceof Integer
+ || raw instanceof Short
+ || raw instanceof Byte;
+ }
+
+ private static long fieldNumberOf(List> qualifier) {
+ Object raw = qualifier.get(0);
+ if (isInteger(raw)) {
+ long fieldNumber = ((Number) raw).longValue();
+ if (fieldNumber <= 0 || fieldNumber > SelectField.MAX_FIELD_NUMBER) {
+ throw new IllegalArgumentException(
+ "Invalid protobuf field number: "
+ + fieldNumber
+ + " (must be between 1 and "
+ + SelectField.MAX_FIELD_NUMBER
+ + ")");
+ }
+ return fieldNumber;
+ }
+ throw new IllegalArgumentException(
+ "Expected integer field number in qualifier[0], got: " + raw);
+ }
+
+ private static String fieldNameOf(List> qualifier) {
+ return (String) qualifier.get(1);
+ }
+
+ private static int typeCodeOf(List> qualifier) {
+ Object raw = qualifier.get(2);
+ if (isInteger(raw)) {
+ long typeCode = ((Number) raw).longValue();
+ if (typeCode != SelectField.CEL_MAP_TYPE_CODE && (typeCode < 1 || typeCode > 18)) {
+ throw new IllegalArgumentException("Invalid protobuf type code: " + typeCode);
+ }
+ return (int) typeCode;
+ }
+ throw new IllegalArgumentException("Expected integer type code in qualifier[2], got: " + raw);
+ }
+
+ /**
+ * Returns the pre-resolved default value from a 4-tuple qualifier {@code [field_number,
+ * field_name, type_code, default_value]}, or {@code null} for 3-tuple qualifiers (submessages
+ * without an inlined default).
+ */
+ private static @Nullable Object defaultValueOf(List> qualifier) {
+ if (qualifier.size() > 3) {
+ return qualifier.get(3);
+ }
+ return null;
+ }
+
+ private LiteAttributeStep() {}
+}
diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel
index f898b66fe..336db4f64 100644
--- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel
+++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel
@@ -40,6 +40,7 @@ java_library(
"//common:options",
"//common:proto_v1alpha1_ast",
"//common/ast",
+ "//common/exceptions:attribute_not_found",
"//common/exceptions:bad_format",
"//common/exceptions:divide_by_zero",
"//common/exceptions:numeric_overflow",
@@ -57,6 +58,7 @@ java_library(
"//common/types:message_type_provider",
"//common/values",
"//common/values:cel_byte_string",
+ "//common/values:proto_message_lite_value",
"//common/values:proto_message_lite_value_provider",
"//compiler",
"//compiler:compiler_builder",
@@ -76,6 +78,7 @@ java_library(
"//runtime:late_function_binding",
"//runtime:lite_runtime",
"//runtime:lite_runtime_factory",
+ "//runtime:lite_runtime_impl",
"//runtime:partial_vars",
"//runtime:proto_message_activation_factory",
"//runtime:proto_message_runtime_equality",
diff --git a/runtime/src/test/java/dev/cel/runtime/LiteAttributeStepTest.java b/runtime/src/test/java/dev/cel/runtime/LiteAttributeStepTest.java
new file mode 100644
index 000000000..29478b54b
--- /dev/null
+++ b/runtime/src/test/java/dev/cel/runtime/LiteAttributeStepTest.java
@@ -0,0 +1,1346 @@
+// 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.runtime;
+
+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.protobuf.ByteString;
+import com.google.protobuf.CodedOutputStream;
+import com.google.protobuf.ExtensionRegistryLite;
+import dev.cel.common.exceptions.CelAttributeNotFoundException;
+import dev.cel.common.values.ErrorValue;
+import dev.cel.common.values.NullValue;
+import dev.cel.common.values.OptionalValue;
+import dev.cel.common.values.ProtoLiteCelValueConverter;
+import dev.cel.common.values.ProtoMessageLiteValue;
+import dev.cel.common.values.ProtoMessageLiteValueProvider;
+import dev.cel.common.values.RawProtoMessageLiteValue;
+import dev.cel.common.values.SelectableValue;
+import dev.cel.expr.conformance.proto3.TestAllTypes;
+import dev.cel.expr.conformance.proto3.TestAllTypesCelDescriptor;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class LiteAttributeStepTest {
+
+ private static final ProtoLiteCelValueConverter CONVERTER =
+ (ProtoLiteCelValueConverter)
+ ProtoMessageLiteValueProvider.newInstance(TestAllTypesCelDescriptor.getDescriptor())
+ .protoCelValueConverter();
+
+ private static final class TestSelectableValue implements SelectableValue {
+ private final ImmutableMap values;
+
+ @Override
+ public Object select(String field) {
+ if (values.containsKey(field)) {
+ return values.get(field);
+ }
+ throw new NoSuchElementException("Field not found: " + field);
+ }
+
+ @Override
+ public Optional find(String field) {
+ return Optional.ofNullable(values.get(field));
+ }
+
+ private TestSelectableValue(ImmutableMap values) {
+ this.values = values;
+ }
+ }
+
+ private static ProtoMessageLiteValue createProtoMessageWithUnknowns(
+ TestAllTypes knownMessage, byte[] unknownBytes) throws IOException {
+ ByteArrayOutputStream combined = new ByteArrayOutputStream();
+ knownMessage.writeTo(combined);
+ combined.write(unknownBytes);
+ TestAllTypes parsed =
+ TestAllTypes.parseFrom(combined.toByteArray(), ExtensionRegistryLite.getEmptyRegistry());
+ return ProtoMessageLiteValue.create(
+ parsed, "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+ }
+
+ @Test
+ public void qualifyAttribute_nullTarget_emptyQualifiers_returnsNull() {
+ Object result = LiteAttributeStep.qualifyAttribute(null, ImmutableList.of(), CONVERTER);
+
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void qualifyAttribute_nonNullTarget_emptyQualifiers_returnsTarget() {
+ Object result = LiteAttributeStep.qualifyAttribute("target_val", ImmutableList.of(), CONVERTER);
+
+ assertThat(result).isEqualTo("target_val");
+ }
+
+ @Test
+ public void qualifyAttribute_nullTarget_throwsCelAttributeNotFoundException() {
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(1, "field", 9, "default_val"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> LiteAttributeStep.qualifyAttribute(null, qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("field");
+ }
+
+ @Test
+ public void qualifyAttribute_nullValueTarget_throwsCelAttributeNotFoundException() {
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(1, "field", 9, "default_val"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> LiteAttributeStep.qualifyAttribute(NullValue.NULL_VALUE, qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("field");
+ }
+
+ @Test
+ public void qualifyAttribute_selectable_missingFieldWithNullDefault_throwsException() {
+ TestSelectableValue selectable = new TestSelectableValue(ImmutableMap.of());
+ ImmutableList> qualifiers = ImmutableList.of(Arrays.asList(1, "missing", 9, null));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> LiteAttributeStep.qualifyAttribute(selectable, qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("missing");
+ }
+
+ @Test
+ public void qualifyAttribute_selectableValue_missingFieldNoDefault_throwsException() {
+ TestSelectableValue selectable = new TestSelectableValue(ImmutableMap.of());
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(1, "missing", 9));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> LiteAttributeStep.qualifyAttribute(selectable, qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("missing");
+ }
+
+ @Test
+ public void qualifyAttribute_emptyOptional_throwsUnsupportedOperationException() {
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(1, "field", 9, "default_val"));
+
+ UnsupportedOperationException thrown =
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> LiteAttributeStep.qualifyAttribute(OptionalValue.EMPTY, qualifiers, CONVERTER));
+
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Optional operands are not yet supported by the lite select-optimized runtime");
+ }
+
+ @Test
+ public void qualifyAttribute_optionalContainingNullValue_throwsUnsupportedOperationException() {
+ OptionalValue, ?> target = OptionalValue.create(NullValue.NULL_VALUE);
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(1, "field", 9, "default_val"));
+
+ UnsupportedOperationException thrown =
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> LiteAttributeStep.qualifyAttribute(target, qualifiers, CONVERTER));
+
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Optional operands are not yet supported by the lite select-optimized runtime");
+ }
+
+ @Test
+ public void qualifyAttribute_optionalPresent_throwsUnsupportedOperationException() {
+ OptionalValue, ?> target = OptionalValue.create(ImmutableMap.of("field", "present_val"));
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(1, "field", 9, "default_val"));
+
+ UnsupportedOperationException thrown =
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> LiteAttributeStep.qualifyAttribute(target, qualifiers, CONVERTER));
+
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Optional operands are not yet supported by the lite select-optimized runtime");
+ }
+
+ @Test
+ public void qualifyAttribute_protoMessageLite_renamedField_resolvesPopulatedValue() {
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleString("known_val").build();
+ ProtoMessageLiteValue message =
+ ProtoMessageLiteValue.create(proto, "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+ // Field 14 is "single_string" in descriptor, but qualifier carries renamed AST name.
+ ImmutableList> qualifiers =
+ ImmutableList.of(ImmutableList.of(14, "renamed_single_string", 9, "default_val"));
+
+ Object result = LiteAttributeStep.qualifyAttribute(message, qualifiers, CONVERTER);
+
+ assertThat(result).isEqualTo("known_val");
+ }
+
+ @Test
+ public void qualifyAttribute_protoMessageLite_renamedIntermediateSubmessage_resolvesLeafValue() {
+ TestAllTypes.NestedMessage nested = TestAllTypes.NestedMessage.newBuilder().setBb(42).build();
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleNestedMessage(nested).build();
+ ProtoMessageLiteValue message =
+ ProtoMessageLiteValue.create(proto, "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+ // Field 21 is "single_nested_message" in descriptor, but qualifier carries renamed AST name.
+ ImmutableList> qualifiers =
+ ImmutableList.of(
+ ImmutableList.of(21, "renamed_single_nested_message", 11),
+ ImmutableList.of(1, "bb", 5, 0L));
+
+ Object result = LiteAttributeStep.qualifyAttribute(message, qualifiers, CONVERTER);
+
+ assertThat(result).isEqualTo(42L);
+ }
+
+ @Test
+ public void qualifyAttribute_protoMessageLite_knownFieldValue() {
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleString("known_val").build();
+ ProtoMessageLiteValue message =
+ ProtoMessageLiteValue.create(proto, "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+
+ Object result =
+ LiteAttributeStep.qualifyAttribute(
+ message,
+ ImmutableList.of(ImmutableList.of(14, "single_string", 9, "default_val")),
+ CONVERTER);
+
+ assertThat(result).isEqualTo("known_val");
+ }
+
+ @Test
+ public void qualifyAttribute_protoMessageLite_unknownFieldValue() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeString(999, "unknown_val");
+ cos.flush();
+ ProtoMessageLiteValue message =
+ createProtoMessageWithUnknowns(TestAllTypes.getDefaultInstance(), baos.toByteArray());
+
+ Object result =
+ LiteAttributeStep.qualifyAttribute(
+ message,
+ ImmutableList.of(ImmutableList.of(999, "unknown_field", 9, "default_val")),
+ CONVERTER);
+
+ assertThat(result).isEqualTo("unknown_val");
+ }
+
+ @Test
+ public void qualifyAttribute_protoMessageLite_missingFieldReturnsDefault() {
+ ProtoMessageLiteValue message =
+ ProtoMessageLiteValue.create(
+ TestAllTypes.getDefaultInstance(),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ CONVERTER);
+
+ Object result =
+ LiteAttributeStep.qualifyAttribute(
+ message,
+ ImmutableList.of(ImmutableList.of(9999, "missing_field", 9, "default_val")),
+ CONVERTER);
+
+ assertThat(result).isEqualTo("default_val");
+ }
+
+ @Test
+ public void qualifyAttribute_protoMessageLite_knownMapField() {
+ TestAllTypes proto = TestAllTypes.newBuilder().putMapStringString("key_1", "val_1").build();
+ ProtoMessageLiteValue message =
+ ProtoMessageLiteValue.create(proto, "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+
+ Object result =
+ LiteAttributeStep.qualifyAttribute(
+ message,
+ ImmutableList.of(ImmutableList.of(61, "map_string_string", -1, ImmutableMap.of())),
+ CONVERTER);
+
+ assertThat(result).isEqualTo(ImmutableMap.of("key_1", "val_1"));
+ }
+
+ @Test
+ public void qualifyAttribute_protoMessageLite_unsetMapFieldReturnsDefault() {
+ ProtoMessageLiteValue message =
+ ProtoMessageLiteValue.create(
+ TestAllTypes.getDefaultInstance(),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ CONVERTER);
+
+ Object result =
+ LiteAttributeStep.qualifyAttribute(
+ message,
+ ImmutableList.of(ImmutableList.of(61, "map_string_string", -1, ImmutableMap.of())),
+ CONVERTER);
+
+ assertThat((Map, ?>) result).isEmpty();
+ }
+
+ @Test
+ public void qualifyAttribute_proto2CustomDefault_takesPrecedenceOverTypeDefault() {
+ ProtoMessageLiteValue message =
+ ProtoMessageLiteValue.create(
+ TestAllTypes.getDefaultInstance(),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ CONVERTER);
+
+ Object result =
+ LiteAttributeStep.qualifyAttribute(
+ message, ImmutableList.of(ImmutableList.of(1, "single_int32", 5, -32L)), CONVERTER);
+
+ assertThat(result).isEqualTo(-32L);
+ }
+
+ @Test
+ public void qualifyAttribute_protoMessageLite_knownUnsetSubmessage_returnsDefaultInstance() {
+ ProtoMessageLiteValue message =
+ ProtoMessageLiteValue.create(
+ TestAllTypes.getDefaultInstance(),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ CONVERTER);
+
+ Object result =
+ LiteAttributeStep.qualifyAttribute(
+ message,
+ ImmutableList.of(ImmutableList.of(21, "single_nested_message", 11)),
+ CONVERTER);
+
+ assertThat(result).isEqualTo(TestAllTypes.NestedMessage.getDefaultInstance());
+ }
+
+ @Test
+ public void qualifyAttribute_protoMessageLite_unknownSubmessage_hasSentinelProtoTypeName()
+ throws Exception {
+ ByteArrayOutputStream subBaos = new ByteArrayOutputStream();
+ CodedOutputStream subCos = CodedOutputStream.newInstance(subBaos);
+ subCos.writeInt32(1, 55);
+ subCos.flush();
+ ByteArrayOutputStream rootBaos = new ByteArrayOutputStream();
+ CodedOutputStream rootCos = CodedOutputStream.newInstance(rootBaos);
+ rootCos.writeBytes(999, ByteString.copyFrom(subBaos.toByteArray()));
+ rootCos.flush();
+ ProtoMessageLiteValue message =
+ createProtoMessageWithUnknowns(TestAllTypes.getDefaultInstance(), rootBaos.toByteArray());
+
+ Object result =
+ LiteAttributeStep.qualifyAttribute(
+ message, ImmutableList.of(ImmutableList.of(999, "unknown_submessage", 11)), CONVERTER);
+
+ assertThat(result).isInstanceOf(RawProtoMessageLiteValue.class);
+ RawProtoMessageLiteValue nested = (RawProtoMessageLiteValue) result;
+ assertThat(nested.celType().name()).isEqualTo("cel.@unknownMessage");
+ }
+
+ @Test
+ public void qualifyAttribute_rawProtoMessageLite_unknownFieldValue() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeString(10, "raw_val");
+ cos.flush();
+ RawProtoMessageLiteValue rawMessage =
+ RawProtoMessageLiteValue.create(ByteString.copyFrom(baos.toByteArray()));
+
+ Object result =
+ LiteAttributeStep.qualifyAttribute(
+ rawMessage,
+ ImmutableList.of(ImmutableList.of(10, "raw_field", 9, "default_val")),
+ CONVERTER);
+
+ assertThat(result).isEqualTo("raw_val");
+ }
+
+ @Test
+ public void qualifyAttribute_rawProtoMessageLite_repeatedField() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeInt32(31, 10);
+ cos.writeInt32(31, 20);
+ cos.flush();
+ RawProtoMessageLiteValue rawMessage =
+ RawProtoMessageLiteValue.create(ByteString.copyFrom(baos.toByteArray()));
+
+ Object result =
+ LiteAttributeStep.qualifyAttribute(
+ rawMessage,
+ ImmutableList.of(ImmutableList.of(31, "repeated_int32", 5, ImmutableList.of())),
+ CONVERTER);
+
+ assertThat((Iterable>) result).containsExactly(10L, 20L).inOrder();
+ }
+
+ @Test
+ public void qualifyAttribute_rawProtoMessageLite_missingFieldReturnsDefault() {
+ RawProtoMessageLiteValue rawMessage = RawProtoMessageLiteValue.create(ByteString.EMPTY);
+
+ Object result =
+ LiteAttributeStep.qualifyAttribute(
+ rawMessage,
+ ImmutableList.of(ImmutableList.of(99, "missing_field", 9, "default_val")),
+ CONVERTER);
+
+ assertThat(result).isEqualTo("default_val");
+ }
+
+ @Test
+ public void qualifyAttribute_rawProto_missingFieldNoDefault_throwsException() {
+ RawProtoMessageLiteValue rawMessage = RawProtoMessageLiteValue.create(ByteString.EMPTY);
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(99, "missing_field", 9));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> LiteAttributeStep.qualifyAttribute(rawMessage, qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("missing_field");
+ }
+
+ @Test
+ public void qualifyAttribute_protoMessageLite_unknownMapField_throwsUnsupportedOperation()
+ throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeString(999, "unknown_map_entry");
+ cos.flush();
+ ProtoMessageLiteValue message =
+ createProtoMessageWithUnknowns(TestAllTypes.getDefaultInstance(), baos.toByteArray());
+ ImmutableList> qualifiers =
+ ImmutableList.of(ImmutableList.of(999, "unknown_map", -1, ImmutableMap.of()));
+
+ UnsupportedOperationException thrown =
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> LiteAttributeStep.qualifyAttribute(message, qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("unknown_map");
+ }
+
+ @Test
+ public void qualifyAttribute_rawProtoMessageLite_unknownMapField_throwsUnsupportedOperation()
+ throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeString(10, "unknown_map_entry");
+ cos.flush();
+ RawProtoMessageLiteValue rawMessage =
+ RawProtoMessageLiteValue.create(ByteString.copyFrom(baos.toByteArray()));
+ ImmutableList> qualifiers =
+ ImmutableList.of(ImmutableList.of(10, "unknown_map", -1, ImmutableMap.of()));
+
+ UnsupportedOperationException thrown =
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> LiteAttributeStep.qualifyAttribute(rawMessage, qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("unknown_map");
+ }
+
+ @Test
+ public void qualifyAttribute_selectableValue_present() {
+ TestSelectableValue selectable =
+ new TestSelectableValue(ImmutableMap.of("field", "selectable_val"));
+
+ Object result =
+ LiteAttributeStep.qualifyAttribute(
+ selectable,
+ ImmutableList.of(ImmutableList.of(1, "field", 9, "default_val")),
+ CONVERTER);
+
+ assertThat(result).isEqualTo("selectable_val");
+ }
+
+ @Test
+ public void qualifyAttribute_selectableValue_absentReturnsDefault() {
+ TestSelectableValue selectable = new TestSelectableValue(ImmutableMap.of());
+
+ Object result =
+ LiteAttributeStep.qualifyAttribute(
+ selectable,
+ ImmutableList.of(ImmutableList.of(1, "missing", 9, "default_val")),
+ CONVERTER);
+
+ assertThat(result).isEqualTo("default_val");
+ }
+
+ @Test
+ public void qualifyAttribute_map_present() {
+ ImmutableMap map = ImmutableMap.of("key", "map_val");
+
+ Object result =
+ LiteAttributeStep.qualifyAttribute(
+ map, ImmutableList.of(ImmutableList.of(1, "key", 9, "default_val")), CONVERTER);
+
+ assertThat(result).isEqualTo("map_val");
+ }
+
+ @Test
+ public void qualifyAttribute_map_nullValueReturnsNullValue() {
+ ImmutableMap map = ImmutableMap.of("key", NullValue.NULL_VALUE);
+
+ Object result =
+ LiteAttributeStep.qualifyAttribute(
+ map, ImmutableList.of(ImmutableList.of(1, "key", 9, "default_val")), CONVERTER);
+
+ assertThat(result).isEqualTo(NullValue.NULL_VALUE);
+ }
+
+ @Test
+ public void qualifyAttribute_map_missingKeyThrowsException() {
+ ImmutableMap map = ImmutableMap.of();
+ ImmutableList> qualifiers =
+ ImmutableList.of(ImmutableList.of(1, "missing", 9, "default_val"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> LiteAttributeStep.qualifyAttribute(map, qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("missing");
+ }
+
+ @Test
+ public void qualifyAttribute_unsupportedTargetThrowsException() {
+ int unsupportedTarget = 12345;
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(1, "field", 9, "default_val"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> LiteAttributeStep.qualifyAttribute(unsupportedTarget, qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("field");
+ }
+
+ @Test
+ public void qualifyAttribute_invalidQualifierElementThrowsException() {
+ ImmutableMap target = ImmutableMap.of("field", "val");
+ ImmutableList> invalidQualifiers = ImmutableList.of("invalid_non_list_qualifier");
+
+ IllegalArgumentException thrown =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LiteAttributeStep.qualifyAttribute(target, invalidQualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("Expected qualifier list");
+ }
+
+ @Test
+ public void qualifyAttribute_malformedQualifierFormatThrowsException() {
+ ImmutableMap target = ImmutableMap.of("field", "val");
+ ImmutableList> malformedQualifiers = ImmutableList.of(ImmutableList.of(1, "field"));
+
+ IllegalArgumentException thrown =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LiteAttributeStep.qualifyAttribute(target, malformedQualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("Invalid qualifier format");
+ }
+
+ @Test
+ public void qualifyAttribute_unsupportedTargetType_throwsCelAttributeNotFoundException() {
+ Object target = new Object();
+ ImmutableList> qualifiers =
+ ImmutableList.of(ImmutableList.of(1, "field", 9, NullValue.NULL_VALUE));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> LiteAttributeStep.qualifyAttribute(target, qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("field");
+ }
+
+ @Test
+ public void hasField_unsupportedTargetType_throwsCelAttributeNotFoundException() {
+ Object target = new Object();
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(1, "field"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> LiteAttributeStep.hasField(target, qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("field");
+ }
+
+ @Test
+ public void qualifyAttribute_multiStepChaining() throws Exception {
+ ByteArrayOutputStream subBaos = new ByteArrayOutputStream();
+ CodedOutputStream subCos = CodedOutputStream.newInstance(subBaos);
+ subCos.writeString(20, "nested_val");
+ subCos.flush();
+ ByteString subBytes = ByteString.copyFrom(subBaos.toByteArray());
+ ByteArrayOutputStream rootBaos = new ByteArrayOutputStream();
+ CodedOutputStream rootCos = CodedOutputStream.newInstance(rootBaos);
+ rootCos.writeBytes(999, subBytes);
+ rootCos.flush();
+ ProtoMessageLiteValue rootMessage =
+ createProtoMessageWithUnknowns(TestAllTypes.getDefaultInstance(), rootBaos.toByteArray());
+
+ Object result =
+ LiteAttributeStep.qualifyAttribute(
+ rootMessage,
+ ImmutableList.of(
+ ImmutableList.of(999, "unknown_submessage", 11, NullValue.NULL_VALUE),
+ ImmutableList.of(20, "nested_field", 9, "default")),
+ CONVERTER);
+
+ assertThat(result).isEqualTo("nested_val");
+ }
+
+ @Test
+ public void hasField_nullTarget_throwsCelAttributeNotFoundException() {
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(1, "field"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> LiteAttributeStep.hasField(null, qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("field");
+ }
+
+ @Test
+ public void hasField_nullValueTarget_throwsCelAttributeNotFoundException() {
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(1, "field"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> LiteAttributeStep.hasField(NullValue.NULL_VALUE, qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("field");
+ }
+
+ @Test
+ public void hasField_emptyOptional_throwsUnsupportedOperationException() {
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(1, "field"));
+
+ UnsupportedOperationException thrown =
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> LiteAttributeStep.hasField(OptionalValue.EMPTY, qualifiers, CONVERTER));
+
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Optional operands are not yet supported by the lite select-optimized runtime");
+ }
+
+ @Test
+ public void hasField_optionalContainingNullValue_throwsUnsupportedOperationException() {
+ OptionalValue, ?> target = OptionalValue.create(NullValue.NULL_VALUE);
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(1, "field"));
+
+ UnsupportedOperationException thrown =
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> LiteAttributeStep.hasField(target, qualifiers, CONVERTER));
+
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Optional operands are not yet supported by the lite select-optimized runtime");
+ }
+
+ @Test
+ public void hasField_optionalPresent_throwsUnsupportedOperationException() {
+ OptionalValue, ?> target = OptionalValue.create(ImmutableMap.of("field", "val"));
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(1, "field"));
+
+ UnsupportedOperationException thrown =
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> LiteAttributeStep.hasField(target, qualifiers, CONVERTER));
+
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Optional operands are not yet supported by the lite select-optimized runtime");
+ }
+
+ @Test
+ public void hasField_protoMessageLite_renamedField_returnsTrue() {
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleString("val").build();
+ ProtoMessageLiteValue message =
+ ProtoMessageLiteValue.create(proto, "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+ // Field 14 is "single_string" in descriptor, but qualifier carries renamed AST name.
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(14, "renamed_single_string"));
+
+ Object result = LiteAttributeStep.hasField(message, qualifiers, CONVERTER);
+
+ assertThat(result).isEqualTo(true);
+ }
+
+ @Test
+ public void hasField_protoMessageLite_renamedIntermediateSubmessage_returnsTrue() {
+ TestAllTypes.NestedMessage nested = TestAllTypes.NestedMessage.newBuilder().setBb(42).build();
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleNestedMessage(nested).build();
+ ProtoMessageLiteValue message =
+ ProtoMessageLiteValue.create(proto, "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+ // Field 21 is "single_nested_message" in descriptor, but qualifier carries renamed AST name.
+ ImmutableList> qualifiers =
+ ImmutableList.of(
+ ImmutableList.of(21, "renamed_single_nested_message"), ImmutableList.of(1, "bb"));
+
+ Object result = LiteAttributeStep.hasField(message, qualifiers, CONVERTER);
+
+ assertThat(result).isEqualTo(true);
+ }
+
+ @Test
+ public void hasField_protoMessageLite_knownFieldReturnsTrue() {
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleString("val").build();
+ ProtoMessageLiteValue message =
+ ProtoMessageLiteValue.create(proto, "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+
+ Object result =
+ LiteAttributeStep.hasField(
+ message, ImmutableList.of(ImmutableList.of(14, "single_string")), CONVERTER);
+
+ assertThat(result).isEqualTo(true);
+ }
+
+ @Test
+ public void hasField_protoMessageLite_unknownFieldReturnsTrue() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeInt64(999, 12345L);
+ cos.flush();
+ ProtoMessageLiteValue message =
+ createProtoMessageWithUnknowns(TestAllTypes.getDefaultInstance(), baos.toByteArray());
+
+ Object result =
+ LiteAttributeStep.hasField(
+ message, ImmutableList.of(ImmutableList.of(999, "unknown_field")), CONVERTER);
+
+ assertThat(result).isEqualTo(true);
+ }
+
+ @Test
+ public void hasField_protoMessageLite_absentReturnsFalse() {
+ ProtoMessageLiteValue message =
+ ProtoMessageLiteValue.create(
+ TestAllTypes.getDefaultInstance(),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ CONVERTER);
+
+ Object result =
+ LiteAttributeStep.hasField(
+ message, ImmutableList.of(ImmutableList.of(9999, "missing_field")), CONVERTER);
+
+ assertThat(result).isEqualTo(false);
+ }
+
+ @Test
+ public void hasField_protoMessageLite_emptyRepeatedFieldReturnsFalse() {
+ ProtoMessageLiteValue message =
+ ProtoMessageLiteValue.create(
+ TestAllTypes.getDefaultInstance(),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ CONVERTER);
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(44, "repeated_string"));
+
+ Object result = LiteAttributeStep.hasField(message, qualifiers, CONVERTER);
+
+ assertThat(result).isEqualTo(false);
+ }
+
+ @Test
+ public void hasField_protoMessageLite_populatedRepeatedFieldReturnsTrue() {
+ TestAllTypes proto = TestAllTypes.newBuilder().addRepeatedString("val").build();
+ ProtoMessageLiteValue message =
+ ProtoMessageLiteValue.create(proto, "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(44, "repeated_string"));
+
+ Object result = LiteAttributeStep.hasField(message, qualifiers, CONVERTER);
+
+ assertThat(result).isEqualTo(true);
+ }
+
+ @Test
+ public void hasField_protoMessageLite_emptyMapFieldReturnsFalse() {
+ ProtoMessageLiteValue message =
+ ProtoMessageLiteValue.create(
+ TestAllTypes.getDefaultInstance(),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ CONVERTER);
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(64, "map_bool_string"));
+
+ Object result = LiteAttributeStep.hasField(message, qualifiers, CONVERTER);
+
+ assertThat(result).isEqualTo(false);
+ }
+
+ @Test
+ public void hasField_protoMessageLite_populatedMapFieldReturnsTrue() {
+ TestAllTypes proto = TestAllTypes.newBuilder().putMapBoolString(true, "val").build();
+ ProtoMessageLiteValue message =
+ ProtoMessageLiteValue.create(proto, "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(64, "map_bool_string"));
+
+ Object result = LiteAttributeStep.hasField(message, qualifiers, CONVERTER);
+
+ assertThat(result).isEqualTo(true);
+ }
+
+ @Test
+ public void hasField_rawProtoMessageLite_unknownFieldReturnsTrue() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeInt64(10, 42L);
+ cos.flush();
+ RawProtoMessageLiteValue rawMessage =
+ RawProtoMessageLiteValue.create(ByteString.copyFrom(baos.toByteArray()));
+
+ Object result =
+ LiteAttributeStep.hasField(
+ rawMessage, ImmutableList.of(ImmutableList.of(10, "field")), CONVERTER);
+
+ assertThat(result).isEqualTo(true);
+ }
+
+ @Test
+ public void hasField_rawProtoMessageLite_absentReturnsFalse() {
+ RawProtoMessageLiteValue rawMessage = RawProtoMessageLiteValue.create(ByteString.EMPTY);
+
+ Object result =
+ LiteAttributeStep.hasField(
+ rawMessage, ImmutableList.of(ImmutableList.of(99, "missing_field")), CONVERTER);
+
+ assertThat(result).isEqualTo(false);
+ }
+
+ @Test
+ public void hasField_selectableValue_presentReturnsTrue() {
+ TestSelectableValue selectable = new TestSelectableValue(ImmutableMap.of("field", "val"));
+
+ Object result =
+ LiteAttributeStep.hasField(
+ selectable, ImmutableList.of(ImmutableList.of(1, "field")), CONVERTER);
+
+ assertThat(result).isEqualTo(true);
+ }
+
+ @Test
+ public void hasField_selectableValue_absentReturnsFalse() {
+ TestSelectableValue selectable = new TestSelectableValue(ImmutableMap.of());
+
+ Object result =
+ LiteAttributeStep.hasField(
+ selectable, ImmutableList.of(ImmutableList.of(1, "field")), CONVERTER);
+
+ assertThat(result).isEqualTo(false);
+ }
+
+ @Test
+ public void hasField_map_presentReturnsTrue() {
+ ImmutableMap map = ImmutableMap.of("key", "val");
+
+ Object result =
+ LiteAttributeStep.hasField(map, ImmutableList.of(ImmutableList.of(1, "key")), CONVERTER);
+
+ assertThat(result).isEqualTo(true);
+ }
+
+ @Test
+ public void hasField_map_absentReturnsFalse() {
+ ImmutableMap map = ImmutableMap.of();
+
+ Object result =
+ LiteAttributeStep.hasField(
+ map, ImmutableList.of(ImmutableList.of(1, "missing")), CONVERTER);
+
+ assertThat(result).isEqualTo(false);
+ }
+
+ @Test
+ public void hasField_protoMessageLite_knownMapFieldPresent_returnsTrue() {
+ TestAllTypes proto = TestAllTypes.newBuilder().putMapStringString("key_1", "val_1").build();
+ ProtoMessageLiteValue message =
+ ProtoMessageLiteValue.create(proto, "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+
+ Object result =
+ LiteAttributeStep.hasField(
+ message, ImmutableList.of(ImmutableList.of(61, "map_string_string")), CONVERTER);
+
+ assertThat(result).isEqualTo(true);
+ }
+
+ @Test
+ public void hasField_protoMessageLite_unsetMapField_returnsFalse() {
+ ProtoMessageLiteValue message =
+ ProtoMessageLiteValue.create(
+ TestAllTypes.getDefaultInstance(),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ CONVERTER);
+
+ Object result =
+ LiteAttributeStep.hasField(
+ message, ImmutableList.of(ImmutableList.of(61, "map_string_string")), CONVERTER);
+
+ assertThat(result).isEqualTo(false);
+ }
+
+ @Test
+ public void hasField_unsupportedTarget_throwsException() {
+ String unsupportedTarget = "unsupported_string";
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(1, "field"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> LiteAttributeStep.hasField(unsupportedTarget, qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("field");
+ }
+
+ @Test
+ public void hasField_intermediateUnsupportedTarget_throwsException() {
+ String unsupportedTarget = "unsupported_string";
+ ImmutableList> qualifiers =
+ ImmutableList.of(ImmutableList.of(1, "submessage"), ImmutableList.of(2, "field"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> LiteAttributeStep.hasField(unsupportedTarget, qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("submessage");
+ }
+
+ @Test
+ public void hasField_invalidQualifierThrowsException() {
+ ImmutableMap target = ImmutableMap.of("field", "val");
+ ImmutableList> invalidQualifiers = ImmutableList.of("invalid_non_list_qualifier");
+
+ IllegalArgumentException thrown =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LiteAttributeStep.hasField(target, invalidQualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("Expected qualifier list");
+ }
+
+ @Test
+ public void hasField_malformedQualifierFormatThrowsException() {
+ ImmutableMap target = ImmutableMap.of("field", "val");
+ ImmutableList> malformedQualifiers = ImmutableList.of(ImmutableList.of(1));
+
+ IllegalArgumentException thrown =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LiteAttributeStep.hasField(target, malformedQualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("Invalid qualifier format");
+ }
+
+ @Test
+ public void hasField_emptyQualifiersReturnsFalse() {
+ ImmutableMap target = ImmutableMap.of("field", "val");
+
+ Object result = LiteAttributeStep.hasField(target, ImmutableList.of(), CONVERTER);
+
+ assertThat(result).isEqualTo(false);
+ }
+
+ @Test
+ public void hasField_multiStepChaining_intermediateRawProto_present() throws Exception {
+ ByteArrayOutputStream leafBaos = new ByteArrayOutputStream();
+ CodedOutputStream leafCos = CodedOutputStream.newInstance(leafBaos);
+ leafCos.writeInt64(20, 100L);
+ leafCos.flush();
+ ByteString leafBytes = ByteString.copyFrom(leafBaos.toByteArray());
+ ByteArrayOutputStream childBaos = new ByteArrayOutputStream();
+ CodedOutputStream childCos = CodedOutputStream.newInstance(childBaos);
+ childCos.writeBytes(15, leafBytes);
+ childCos.flush();
+ ByteString childBytes = ByteString.copyFrom(childBaos.toByteArray());
+ ByteArrayOutputStream parentBaos = new ByteArrayOutputStream();
+ CodedOutputStream parentCos = CodedOutputStream.newInstance(parentBaos);
+ parentCos.writeBytes(10, childBytes);
+ parentCos.flush();
+ RawProtoMessageLiteValue parent =
+ RawProtoMessageLiteValue.create(ByteString.copyFrom(parentBaos.toByteArray()));
+
+ Object result =
+ LiteAttributeStep.hasField(
+ parent,
+ ImmutableList.of(
+ ImmutableList.of(10, "child"),
+ ImmutableList.of(15, "leaf"),
+ ImmutableList.of(20, "val_field")),
+ CONVERTER);
+
+ assertThat(result).isEqualTo(true);
+ }
+
+ @Test
+ public void hasField_multiStepChaining_intermediateRawProto_absent() throws Exception {
+ ByteArrayOutputStream childBaos = new ByteArrayOutputStream();
+ CodedOutputStream childCos = CodedOutputStream.newInstance(childBaos);
+ childCos.writeString(99, "other");
+ childCos.flush();
+ ByteString childBytes = ByteString.copyFrom(childBaos.toByteArray());
+ ByteArrayOutputStream parentBaos = new ByteArrayOutputStream();
+ CodedOutputStream parentCos = CodedOutputStream.newInstance(parentBaos);
+ parentCos.writeBytes(10, childBytes);
+ parentCos.flush();
+ RawProtoMessageLiteValue parent =
+ RawProtoMessageLiteValue.create(ByteString.copyFrom(parentBaos.toByteArray()));
+
+ Object result =
+ LiteAttributeStep.hasField(
+ parent,
+ ImmutableList.of(
+ ImmutableList.of(10, "child"),
+ ImmutableList.of(15, "missing_leaf"),
+ ImmutableList.of(20, "val_field")),
+ CONVERTER);
+
+ assertThat(result).isEqualTo(false);
+ }
+
+ @Test
+ public void hasField_multiStepChaining_intermediateProtoMessageLite_absent() {
+ ProtoMessageLiteValue rootMessage =
+ ProtoMessageLiteValue.create(
+ TestAllTypes.getDefaultInstance(),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ CONVERTER);
+
+ Object result =
+ LiteAttributeStep.hasField(
+ rootMessage,
+ ImmutableList.of(
+ ImmutableList.of(9999, "missing_sub_message"), ImmutableList.of(20, "field")),
+ CONVERTER);
+
+ assertThat(result).isEqualTo(false);
+ }
+
+ @Test
+ public void hasField_multiStepChaining_intermediateSelectableValue_present() {
+ TestSelectableValue child = new TestSelectableValue(ImmutableMap.of("leaf", "val"));
+ TestSelectableValue parent = new TestSelectableValue(ImmutableMap.of("child", child));
+
+ Object result =
+ LiteAttributeStep.hasField(
+ parent,
+ ImmutableList.of(ImmutableList.of(1, "child"), ImmutableList.of(2, "leaf")),
+ CONVERTER);
+
+ assertThat(result).isEqualTo(true);
+ }
+
+ @Test
+ public void hasField_multiStepChaining_intermediateSelectableValue_absent() {
+ TestSelectableValue parent = new TestSelectableValue(ImmutableMap.of());
+
+ Object result =
+ LiteAttributeStep.hasField(
+ parent,
+ ImmutableList.of(ImmutableList.of(1, "missing_child"), ImmutableList.of(2, "leaf")),
+ CONVERTER);
+
+ assertThat(result).isEqualTo(false);
+ }
+
+ @Test
+ public void hasField_multiStepChaining_intermediateMap_present() {
+ ImmutableMap child = ImmutableMap.of("leaf", "val");
+ ImmutableMap parent = ImmutableMap.of("child", child);
+
+ Object result =
+ LiteAttributeStep.hasField(
+ parent,
+ ImmutableList.of(ImmutableList.of(1, "child"), ImmutableList.of(2, "leaf")),
+ CONVERTER);
+
+ assertThat(result).isEqualTo(true);
+ }
+
+ @Test
+ public void hasField_multiStepChaining_intermediateMap_absent() {
+ ImmutableMap parent = ImmutableMap.of();
+
+ Object result =
+ LiteAttributeStep.hasField(
+ parent,
+ ImmutableList.of(ImmutableList.of(1, "missing_child"), ImmutableList.of(2, "leaf")),
+ CONVERTER);
+
+ assertThat(result).isEqualTo(false);
+ }
+
+ @Test
+ public void qualifyAttribute_rawProto_withKnownTypeName_resolvesChildType() throws Exception {
+ ByteArrayOutputStream subBaos = new ByteArrayOutputStream();
+ CodedOutputStream subCos = CodedOutputStream.newInstance(subBaos);
+ subCos.writeInt32(1, 42);
+ subCos.flush();
+ ByteArrayOutputStream rootBaos = new ByteArrayOutputStream();
+ CodedOutputStream rootCos = CodedOutputStream.newInstance(rootBaos);
+ rootCos.writeBytes(21, ByteString.copyFrom(subBaos.toByteArray()));
+ rootCos.flush();
+ RawProtoMessageLiteValue rawParent =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(rootBaos.toByteArray()),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ CONVERTER);
+
+ Object result =
+ LiteAttributeStep.qualifyAttribute(
+ rawParent,
+ ImmutableList.of(ImmutableList.of(21, "single_nested_message", 11)),
+ CONVERTER);
+
+ assertThat(result).isInstanceOf(RawProtoMessageLiteValue.class);
+ RawProtoMessageLiteValue child = (RawProtoMessageLiteValue) result;
+ assertThat(child.celType().name())
+ .isEqualTo("cel.expr.conformance.proto3.TestAllTypes.NestedMessage");
+ }
+
+ @Test
+ public void qualifyAttribute_celUnknownSetTarget_propagatesUnknown() {
+ CelUnknownSet unknown = CelUnknownSet.create(1L);
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(1, "field", 9, "default"));
+
+ Object result = LiteAttributeStep.qualifyAttribute(unknown, qualifiers, CONVERTER);
+
+ assertThat(result).isSameInstanceAs(unknown);
+ }
+
+ @Test
+ public void qualifyAttribute_errorValueTarget_propagatesError() {
+ ErrorValue error = ErrorValue.create(1L, new RuntimeException("test error"));
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(1, "field", 9, "default"));
+
+ Object result = LiteAttributeStep.qualifyAttribute(error, qualifiers, CONVERTER);
+
+ assertThat(result).isSameInstanceAs(error);
+ }
+
+ @Test
+ public void qualifyAttribute_floatingPointFieldNumber_throwsIllegalArgumentException() {
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(1.5, "field", 9, "default"));
+
+ IllegalArgumentException thrown =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LiteAttributeStep.qualifyAttribute(ImmutableMap.of(), qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("Invalid qualifier format");
+ }
+
+ @Test
+ public void hasField_intermediateMapWithNullValue_throwsCelAttributeNotFoundException() {
+ ImmutableMap map = ImmutableMap.of("sub", NullValue.NULL_VALUE);
+ ImmutableList> qualifiers =
+ ImmutableList.of(ImmutableList.of(1, "sub"), ImmutableList.of(2, "leaf"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> LiteAttributeStep.hasField(map, qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("leaf");
+ }
+
+ @Test
+ public void hasField_celUnknownSetTarget_propagatesUnknown() {
+ CelUnknownSet unknown = CelUnknownSet.create(1L);
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(1, "field"));
+
+ Object result = LiteAttributeStep.hasField(unknown, qualifiers, CONVERTER);
+
+ assertThat(result).isSameInstanceAs(unknown);
+ }
+
+ @Test
+ public void hasField_errorValueTarget_propagatesError() {
+ ErrorValue error = ErrorValue.create(1L, new RuntimeException("test error"));
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(1, "field"));
+
+ Object result = LiteAttributeStep.hasField(error, qualifiers, CONVERTER);
+
+ assertThat(result).isSameInstanceAs(error);
+ }
+
+ @Test
+ public void qualifyAttribute_zeroFieldNumber_throwsIllegalArgumentException() {
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(0, "field", 9, "default"));
+
+ IllegalArgumentException thrown =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LiteAttributeStep.qualifyAttribute(ImmutableMap.of(), qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("Invalid protobuf field number");
+ }
+
+ @Test
+ public void qualifyAttribute_outOfRangeFieldNumber_throwsIllegalArgumentException() {
+ ImmutableList> qualifiers =
+ ImmutableList.of(ImmutableList.of(536870912L, "field", 9, "default"));
+
+ IllegalArgumentException thrown =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LiteAttributeStep.qualifyAttribute(ImmutableMap.of(), qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("Invalid protobuf field number");
+ }
+
+ @Test
+ public void hasField_zeroFieldNumber_throwsIllegalArgumentException() {
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(0, "field"));
+
+ IllegalArgumentException thrown =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LiteAttributeStep.hasField(ImmutableMap.of(), qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("Invalid protobuf field number");
+ }
+
+ @Test
+ public void hasField_outOfRangeFieldNumber_throwsIllegalArgumentException() {
+ ImmutableList> qualifiers = ImmutableList.of(ImmutableList.of(536870912L, "field"));
+
+ IllegalArgumentException thrown =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LiteAttributeStep.hasField(ImmutableMap.of(), qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("Invalid protobuf field number");
+ }
+
+ @Test
+ public void qualifyAttribute_mapContainingProtoMessage_resolvesField() {
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleInt64(42L).build();
+ ImmutableMap map = ImmutableMap.of("proto", proto);
+ ImmutableList> qualifiers =
+ ImmutableList.of(
+ ImmutableList.of(1, "proto", 11), ImmutableList.of(2, "single_int64", 3, 0L));
+
+ Object result = LiteAttributeStep.qualifyAttribute(map, qualifiers, CONVERTER);
+
+ assertThat(result).isEqualTo(42L);
+ }
+
+ @Test
+ public void hasField_mapContainingProtoMessage_resolvesPresence() {
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleInt64(42L).build();
+ ImmutableMap map = ImmutableMap.of("proto", proto);
+ ImmutableList> presentQualifiers =
+ ImmutableList.of(ImmutableList.of(1, "proto"), ImmutableList.of(2, "single_int64"));
+ ImmutableList> absentQualifiers =
+ ImmutableList.of(ImmutableList.of(1, "proto"), ImmutableList.of(14, "single_string"));
+
+ Object presentResult = LiteAttributeStep.hasField(map, presentQualifiers, CONVERTER);
+ Object absentResult = LiteAttributeStep.hasField(map, absentQualifiers, CONVERTER);
+
+ assertThat(presentResult).isEqualTo(true);
+ assertThat(absentResult).isEqualTo(false);
+ }
+
+ @Test
+ public void qualifyAttribute_protoMessageContainingMap_resolvesMapKey() {
+ TestAllTypes proto = TestAllTypes.newBuilder().putMapStringString("k", "v").build();
+ ImmutableList> qualifiers =
+ ImmutableList.of(
+ ImmutableList.of(61, "map_string_string", -1, ImmutableMap.of()),
+ ImmutableList.of(1, "k", 9, ""));
+
+ Object result = LiteAttributeStep.qualifyAttribute(proto, qualifiers, CONVERTER);
+
+ assertThat(result).isEqualTo("v");
+ }
+
+ @Test
+ public void hasField_protoMessageContainingMap_resolvesPresence() {
+ TestAllTypes proto = TestAllTypes.newBuilder().putMapStringString("k", "v").build();
+ ImmutableList> presentQualifiers =
+ ImmutableList.of(ImmutableList.of(61, "map_string_string"), ImmutableList.of(1, "k"));
+ ImmutableList> absentQualifiers =
+ ImmutableList.of(ImmutableList.of(61, "map_string_string"), ImmutableList.of(1, "missing"));
+
+ Object presentResult = LiteAttributeStep.hasField(proto, presentQualifiers, CONVERTER);
+ Object absentResult = LiteAttributeStep.hasField(proto, absentQualifiers, CONVERTER);
+
+ assertThat(presentResult).isEqualTo(true);
+ assertThat(absentResult).isEqualTo(false);
+ }
+
+ @Test
+ public void hasField_emptyPackedRepeated_withoutDescriptor_returnsFalse() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeBytes(31, ByteString.EMPTY);
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()), "cel.expr.conformance.proto3.TestAllTypes");
+ ImmutableList> qualifiers =
+ ImmutableList.of(ImmutableList.of(31, "repeated_int32", 5, ImmutableList.of()));
+
+ Object result =
+ LiteAttributeStep.hasField(raw, qualifiers, ProtoLiteCelValueConverter.newInstance());
+
+ assertThat(result).isEqualTo(false);
+ }
+
+ @Test
+ public void hasField_intermediateMapWithJavaNullValue_throwsCelAttributeNotFoundException() {
+ Map map = Collections.singletonMap("sub", null);
+ ImmutableList> qualifiers =
+ ImmutableList.of(ImmutableList.of(1, "sub"), ImmutableList.of(2, "leaf"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> LiteAttributeStep.hasField(map, qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("leaf");
+ }
+
+ @Test
+ public void qualifyAttribute_typeCodeOutOfIntRange_throwsIllegalArgumentException() {
+ ImmutableList> qualifiers =
+ ImmutableList.of(ImmutableList.of(1, "field", (1L << 32) + 9, "default"));
+
+ IllegalArgumentException thrown =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LiteAttributeStep.qualifyAttribute(ImmutableMap.of(), qualifiers, CONVERTER));
+
+ assertThat(thrown).hasMessageThat().contains("Invalid protobuf type code");
+ }
+}