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 c39eaaa73..51fa12e98 100644
--- a/common/src/main/java/dev/cel/common/values/BUILD.bazel
+++ b/common/src/main/java/dev/cel/common/values/BUILD.bazel
@@ -323,6 +323,8 @@ java_library(
],
deps = [
":base_proto_cel_value_converter",
+ ":optimized_selectable",
+ ":select_field",
":values",
"//:auto_value",
"//common/annotations",
@@ -351,6 +353,8 @@ cel_android_library(
],
deps = [
":base_proto_cel_value_converter_android",
+ ":optimized_selectable_android",
+ ":select_field_android",
":values_android",
"//:auto_value",
"//common/annotations",
@@ -434,3 +438,85 @@ cel_android_library(
"@maven//:com_google_errorprone_error_prone_annotations",
],
)
+
+java_library(
+ name = "select_field",
+ srcs = ["SelectField.java"],
+ tags = [
+ ],
+ deps = [
+ "//:auto_value",
+ "//common/annotations",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:com_google_guava_guava",
+ "@maven//:org_jspecify_jspecify",
+ ],
+)
+
+cel_android_library(
+ name = "select_field_android",
+ srcs = ["SelectField.java"],
+ tags = [
+ ],
+ deps = [
+ "//:auto_value",
+ "//common/annotations",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:org_jspecify_jspecify",
+ "@maven_android//:com_google_guava_guava",
+ ],
+)
+
+java_library(
+ name = "optimized_selectable",
+ srcs = ["OptimizedSelectable.java"],
+ tags = [
+ ],
+ deps = [
+ ":select_field",
+ "//common/annotations",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ ],
+)
+
+cel_android_library(
+ name = "optimized_selectable_android",
+ srcs = ["OptimizedSelectable.java"],
+ tags = [
+ ],
+ deps = [
+ ":select_field_android",
+ "//common/annotations",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ ],
+)
+
+java_library(
+ name = "optimized_select_traversal",
+ srcs = ["OptimizedSelectTraversal.java"],
+ tags = [
+ ],
+ deps = [
+ ":optimized_selectable",
+ ":select_field",
+ ":values",
+ "//common/annotations",
+ "//common/exceptions:attribute_not_found",
+ "@maven//:com_google_guava_guava",
+ ],
+)
+
+cel_android_library(
+ name = "optimized_select_traversal_android",
+ srcs = ["OptimizedSelectTraversal.java"],
+ tags = [
+ ],
+ deps = [
+ ":optimized_selectable_android",
+ ":select_field_android",
+ ":values_android",
+ "//common/annotations",
+ "//common/exceptions:attribute_not_found",
+ "@maven_android//:com_google_guava_guava",
+ ],
+)
diff --git a/common/src/main/java/dev/cel/common/values/OptimizedSelectTraversal.java b/common/src/main/java/dev/cel/common/values/OptimizedSelectTraversal.java
new file mode 100644
index 000000000..2c6b2bc08
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/values/OptimizedSelectTraversal.java
@@ -0,0 +1,116 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dev.cel.common.values;
+
+import com.google.common.collect.ImmutableList;
+import dev.cel.common.annotations.Internal;
+import dev.cel.common.exceptions.CelAttributeNotFoundException;
+import java.util.Optional;
+
+/**
+ * Walks a sequence of {@link SelectField} selections, dispatching each field over {@link
+ * OptimizedSelectable} or {@link SelectableValue}.
+ *
+ *
CEL Library Internals. Do Not Use.
+ */
+@Internal
+public final class OptimizedSelectTraversal {
+
+ /**
+ * Qualifies {@code target} through every field in {@code fields} and returns the terminal value.
+ */
+ public static Object qualify(Object target, ImmutableList fields) {
+ Object current = target;
+ for (int i = 0; i < fields.size(); i++) {
+ current = qualifyField(current, fields.get(i));
+ }
+ return current;
+ }
+
+ /**
+ * Presence tests the terminal field of {@code fields}, navigating through all preceding fields.
+ *
+ * Absence of any intermediate field short-circuits to {@code false}.
+ */
+ public static boolean hasField(Object target, ImmutableList fields) {
+ if (fields.isEmpty()) {
+ return false;
+ }
+ Object current = target;
+ int terminalIndex = fields.size() - 1;
+ for (int i = 0; i < terminalIndex; i++) {
+ Optional next = navigateField(current, fields.get(i));
+ if (!next.isPresent()) {
+ return false;
+ }
+ current = next.get();
+ }
+ return hasTerminalField(current, fields.get(terminalIndex));
+ }
+
+ // SelectableValue is only ever instantiated with String keys in the select path.
+ @SuppressWarnings("unchecked")
+ private static Object qualifyField(Object target, SelectField field) {
+ if (target instanceof ErrorValue) {
+ return target;
+ }
+ if (target instanceof OptimizedSelectable) {
+ return ((OptimizedSelectable) target).selectByFieldNumber(field);
+ }
+ if (target instanceof SelectableValue) {
+ SelectableValue selectable = (SelectableValue) target;
+ if (field.defaultValue() != null) {
+ return selectable
+ .find(field.fieldName())
+ .map(Object.class::cast)
+ .orElse(field.defaultValue());
+ }
+ return selectable.select(field.fieldName());
+ }
+ throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
+ }
+
+ // SelectableValue is only ever instantiated with String keys in the select path.
+ @SuppressWarnings("unchecked")
+ private static Optional navigateField(Object target, SelectField field) {
+ if (target instanceof ErrorValue) {
+ return Optional.of(target);
+ }
+ if (target instanceof OptimizedSelectable) {
+ return ((OptimizedSelectable) target).findByFieldNumber(field);
+ }
+ if (target instanceof SelectableValue) {
+ return ((SelectableValue) target).find(field.fieldName()).map(Object.class::cast);
+ }
+ throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
+ }
+
+ // SelectableValue is only ever instantiated with String keys in the select path.
+ @SuppressWarnings("unchecked")
+ private static boolean hasTerminalField(Object target, SelectField field) {
+ if (target instanceof ErrorValue) {
+ return false;
+ }
+ if (target instanceof OptimizedSelectable) {
+ return ((OptimizedSelectable) target).hasFieldByNumber(field);
+ }
+ if (target instanceof SelectableValue) {
+ return ((SelectableValue) target).find(field.fieldName()).isPresent();
+ }
+ throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
+ }
+
+ private OptimizedSelectTraversal() {}
+}
diff --git a/common/src/main/java/dev/cel/common/values/OptimizedSelectable.java b/common/src/main/java/dev/cel/common/values/OptimizedSelectable.java
new file mode 100644
index 000000000..828d15227
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/values/OptimizedSelectable.java
@@ -0,0 +1,45 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dev.cel.common.values;
+
+import com.google.errorprone.annotations.Immutable;
+import dev.cel.common.annotations.Internal;
+import java.util.Optional;
+
+/**
+ * Resolves an optimized field selection within a selection chain rewritten by the select optimizer.
+ *
+ * Implementations resolve individual field selections against themselves by protobuf field
+ * number. Walking the chain across multiple fields and heterogeneous values belongs to {@link
+ * OptimizedSelectTraversal}.
+ *
+ *
CEL Library Internals. Do Not Use.
+ */
+@Internal
+@Immutable
+public interface OptimizedSelectable {
+
+ /** Selects {@code field}, falling back to its default value or an empty submessage if absent. */
+ Object selectByFieldNumber(SelectField field);
+
+ /** Returns whether {@code field} is present. */
+ boolean hasFieldByNumber(SelectField field);
+
+ /**
+ * Returns the value of the field at {@code field} (a scalar or submessage) for an intermediate
+ * step of a presence test, or empty if absent.
+ */
+ Optional findByFieldNumber(SelectField field);
+}
diff --git a/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java b/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java
index 093819198..0d53903a5 100644
--- a/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java
+++ b/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java
@@ -17,7 +17,6 @@
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;
@@ -26,6 +25,7 @@
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;
@@ -62,6 +62,9 @@
@Immutable
@Internal
public final class ProtoLiteCelValueConverter extends BaseProtoCelValueConverter {
+ static final String MAP_KEY_FIELD_NAME = "key";
+ static final String MAP_VALUE_FIELD_NAME = "value";
+
private final CelLiteDescriptorPool descriptorPool;
public static ProtoLiteCelValueConverter newInstance(
@@ -69,6 +72,10 @@ public static ProtoLiteCelValueConverter newInstance(
return new ProtoLiteCelValueConverter(celLiteDescriptorPool);
}
+ boolean hasDescriptor(String protoTypeName) {
+ return descriptorPool.findDescriptor(protoTypeName).isPresent();
+ }
+
private static Object readPrimitiveField(
CodedInputStream inputStream, FieldLiteDescriptor fieldDescriptor) throws IOException {
switch (fieldDescriptor.getProtoFieldType()) {
@@ -155,22 +162,45 @@ private MessageLite.Builder getDefaultMessageBuilder(String protoTypeName) {
Object getDefaultCelValue(String protoTypeName, String fieldName) {
MessageLiteDescriptor messageDescriptor = descriptorPool.getDescriptorOrThrow(protoTypeName);
- FieldLiteDescriptor fieldDescriptor = messageDescriptor.getByFieldNameOrThrow(fieldName);
-
- Object defaultValue = getDefaultValue(fieldDescriptor);
+ return getDefaultCelValue(messageDescriptor.getByFieldNameOrThrow(fieldName));
+ }
- return toRuntimeValue(defaultValue);
+ Object getDefaultCelValue(FieldLiteDescriptor fieldDescriptor) {
+ return toRuntimeValue(getDefaultValue(fieldDescriptor));
}
- public Optional findFieldDescriptor(String protoTypeName, int fieldNumber) {
+ Optional findFieldDescriptor(String protoTypeName, int fieldNumber) {
return descriptorPool
.findDescriptor(protoTypeName)
.flatMap(desc -> desc.findByFieldNumber(fieldNumber));
}
- public Optional findDefaultCelValue(String protoTypeName, int fieldNumber) {
- return findFieldDescriptor(protoTypeName, fieldNumber)
- .map(fieldDescriptor -> toRuntimeValue(getDefaultValue(fieldDescriptor)));
+ Optional tryDecodeWellKnownProto(ByteString bytes, String protoTypeName) {
+ Optional wellKnownProto = WellKnownProto.getByTypeName(protoTypeName);
+ if (!wellKnownProto.isPresent()) {
+ return Optional.empty();
+ }
+
+ return descriptorPool
+ .findDescriptor(protoTypeName)
+ .map(
+ descriptor ->
+ decodeWellKnownProto(bytes, protoTypeName, descriptor, wellKnownProto.get()));
+ }
+
+ private Object decodeWellKnownProto(
+ ByteString bytes,
+ String protoTypeName,
+ MessageLiteDescriptor descriptor,
+ WellKnownProto wellKnownProto) {
+ try {
+ MessageLite.Builder builder = descriptor.newMessageBuilder();
+ builder.mergeFrom(bytes, ExtensionRegistryLite.getEmptyRegistry());
+ return fromWellKnownProto(builder.build(), wellKnownProto);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Failed to decode well-known proto of type: " + protoTypeName, e);
+ }
}
@Override
@@ -276,16 +306,21 @@ private ImmutableList readPackedRepeatedFields(
private Map.Entry readSingleMapEntry(
CodedInputStream inputStream, FieldLiteDescriptor fieldDescriptor) throws IOException {
+ String entryTypeName = fieldDescriptor.getFieldProtoTypeName();
ImmutableMap singleMapEntry =
- readAllFields(inputStream.readByteArray(), fieldDescriptor.getFieldProtoTypeName())
- .values();
- Object key = checkNotNull(singleMapEntry.get("key"));
- Object value = checkNotNull(singleMapEntry.get("value"));
+ readAllFields(inputStream.readByteArray(), entryTypeName).values();
+ Object key = singleMapEntry.get(MAP_KEY_FIELD_NAME);
+ if (key == null) {
+ key = getDefaultCelValue(entryTypeName, MAP_KEY_FIELD_NAME);
+ }
+ Object value = singleMapEntry.get(MAP_VALUE_FIELD_NAME);
+ if (value == null) {
+ value = getDefaultCelValue(entryTypeName, MAP_VALUE_FIELD_NAME);
+ }
return new AbstractMap.SimpleEntry<>(key, value);
}
- @VisibleForTesting
MessageFields readAllFields(byte[] bytes, String protoTypeName) throws IOException {
MessageLiteDescriptor messageDescriptor = descriptorPool.getDescriptorOrThrow(protoTypeName);
CodedInputStream inputStream = CodedInputStream.newInstance(bytes);
@@ -360,19 +395,16 @@ MessageFields readAllFields(byte[] bytes, String protoTypeName) throws IOExcepti
if (fieldDescriptor.getEncodingType().equals(EncodingType.LIST)) {
String fieldName = fieldDescriptor.getFieldName();
List repeatedValues =
- repeatedFieldValues.computeIfAbsent(
- fieldNumber,
- (unused) -> {
- List newList = new ArrayList<>();
- fieldValues.put(fieldName, newList);
- return newList;
- });
+ repeatedFieldValues.computeIfAbsent(fieldNumber, (unused) -> new ArrayList<>());
if (payload instanceof Collection) {
repeatedValues.addAll((Collection>) payload);
} else {
repeatedValues.add(payload);
}
+ if (!repeatedValues.isEmpty()) {
+ fieldValues.put(fieldName, repeatedValues);
+ }
} else {
fieldValues.put(fieldDescriptor.getFieldName(), payload);
}
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 99e95ebd3..fffd35794 100644
--- a/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java
+++ b/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java
@@ -14,19 +14,21 @@
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.base.Preconditions;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.Immutable;
import com.google.protobuf.MessageLite;
-import dev.cel.common.annotations.Internal;
import dev.cel.common.types.CelType;
import dev.cel.common.types.StructTypeReference;
import dev.cel.common.values.ProtoLiteCelValueConverter.MessageFields;
+import 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,10 +37,23 @@
*
* If the codebase has access to full protobuf messages with descriptors, use {@code
* ProtoMessageValue} instead.
+ *
+ *
Implements {@link OptimizedSelectable} so that select chains can address fields by number:
+ *
+ *
+ * Field renames: If a protobuf field is renamed in schema after an AST was compiled,
+ * resolving by {@link SelectField#fieldNumber()} maps the number to the runtime descriptor's
+ * current field name, preventing {@code CelAttributeNotFoundException}.
+ * Version skew / unknown fields: When evaluating payloads serialized by a newer binary
+ * containing fields absent from the local {@code CelLiteDescriptor}, the unknown wire bytes
+ * are preserved in {@link #unknownFields()} and decoded on demand using the compile-time wire
+ * type and default metadata in {@link SelectField}.
+ *
*/
@AutoValue
@Immutable
-public abstract class ProtoMessageLiteValue extends StructValue {
+public abstract class ProtoMessageLiteValue extends StructValue
+ implements OptimizedSelectable {
@Override
public abstract MessageLite value();
@@ -57,12 +72,11 @@ MessageFields messageFields() {
}
}
- @Internal
- public ImmutableMap fieldValues() {
+ ImmutableMap fieldValues() {
return messageFields().values();
}
- public ImmutableListMultimap unknownFields() {
+ ImmutableListMultimap unknownFields() {
return messageFields().unknowns();
}
@@ -84,11 +98,59 @@ public Optional find(String field) {
.map(value -> protoLiteCelValueConverter().toRuntimeValue(fieldValue));
}
+ @Override
+ public Object selectByFieldNumber(SelectField field) {
+ FieldLiteDescriptor fd = findFieldDescriptor(field);
+ Object known = findKnownFieldValue(fd);
+ if (known != null) {
+ return protoLiteCelValueConverter().toRuntimeValue(known);
+ }
+ return RawProtoMessageLiteValue.selectWireOrDefault(
+ field, fd, unknownFields().get(field.fieldNumber()), protoLiteCelValueConverter());
+ }
+
+ @Override
+ public boolean hasFieldByNumber(SelectField field) {
+ FieldLiteDescriptor fd = findFieldDescriptor(field);
+ if (findKnownFieldValue(fd) != null) {
+ return true;
+ }
+ return RawProtoMessageLiteValue.isPresentInWire(
+ field, fd, unknownFields().get(field.fieldNumber()));
+ }
+
+ @Override
+ public Optional findByFieldNumber(SelectField field) {
+ FieldLiteDescriptor fd = findFieldDescriptor(field);
+ Object known = findKnownFieldValue(fd);
+ if (known != null) {
+ return Optional.of(protoLiteCelValueConverter().toRuntimeValue(known));
+ }
+ return RawProtoMessageLiteValue.navigateWire(
+ field, fd, unknownFields().get(field.fieldNumber()), protoLiteCelValueConverter());
+ }
+
+ private @Nullable FieldLiteDescriptor findFieldDescriptor(SelectField field) {
+ return protoLiteCelValueConverter()
+ .findFieldDescriptor(celType().name(), field.fieldNumber())
+ .orElse(null);
+ }
+
+ private @Nullable Object findKnownFieldValue(@Nullable FieldLiteDescriptor fieldDescriptor) {
+ if (fieldDescriptor == null) {
+ return null;
+ }
+ return fieldValues().get(fieldDescriptor.getFieldName());
+ }
+
public static ProtoMessageLiteValue create(
MessageLite value, String typeName, ProtoLiteCelValueConverter protoLiteCelValueConverter) {
- Preconditions.checkNotNull(value);
- Preconditions.checkNotNull(typeName);
+ checkNotNull(value);
+ checkNotNull(typeName);
+ checkNotNull(protoLiteCelValueConverter);
return new AutoValue_ProtoMessageLiteValue(
value, StructTypeReference.create(typeName), protoLiteCelValueConverter);
}
+
+ 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
index cd8990be9..2a3bdf940 100644
--- a/common/src/main/java/dev/cel/common/values/RawProtoMessageLiteValue.java
+++ b/common/src/main/java/dev/cel/common/values/RawProtoMessageLiteValue.java
@@ -15,12 +15,15 @@
package dev.cel.common.values;
import static com.google.common.base.Preconditions.checkNotNull;
+import static dev.cel.common.values.ProtoLiteCelValueConverter.MAP_KEY_FIELD_NAME;
+import static dev.cel.common.values.ProtoLiteCelValueConverter.MAP_VALUE_FIELD_NAME;
import com.google.auto.value.AutoValue;
import com.google.auto.value.extension.memoized.Memoized;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
@@ -28,15 +31,20 @@
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.internal.WellKnownProto;
import dev.cel.common.types.CelType;
import dev.cel.common.types.StructTypeReference;
import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor;
+import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor.EncodingType;
+import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor.JavaType;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import org.jspecify.annotations.Nullable;
@@ -55,21 +63,25 @@
@Immutable
@SuppressWarnings("Immutable") // Immutable wire fields
@Internal
-public abstract class RawProtoMessageLiteValue
- extends StructValue {
+public abstract class RawProtoMessageLiteValue extends StructValue
+ implements OptimizedSelectable {
+
+ private static final String UNKNOWN_MESSAGE_TYPE_NAME = "cel.@unknownMessage";
abstract ByteString rawWireBytes();
+ @Override
+ public abstract CelType celType();
+
+ abstract ProtoLiteCelValueConverter protoLiteCelValueConverter();
+
@Override
public RawProtoMessageLiteValue value() {
return this;
}
- @Override
- public abstract CelType celType();
-
@Memoized
- public ImmutableListMultimap unknownFields() {
+ ImmutableListMultimap unknownFields() {
try {
CodedInputStream inputStream = rawWireBytes().newCodedInput();
Multimap fields = Multimaps.newMultimap(new TreeMap<>(), ArrayList::new);
@@ -85,10 +97,6 @@ public ImmutableListMultimap unknownFields() {
}
}
- public boolean hasField(int fieldNumber) {
- return unknownFields().containsKey(fieldNumber);
- }
-
@Override
public boolean isZeroValue() {
return rawWireBytes().isEmpty();
@@ -97,25 +105,232 @@ public boolean isZeroValue() {
/**
* 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);
}
+ /**
+ * Direct field presence testing by name is unsupported on {@link RawProtoMessageLiteValue}
+ * because raw wire bytes lack message descriptors and field names.
+ */
@Override
public Optional find(String field) {
- return Optional.empty();
+ throw CelAttributeNotFoundException.forFieldResolution(field);
+ }
+
+ @Override
+ public Object selectByFieldNumber(SelectField field) {
+ int fieldNumber = field.fieldNumber();
+ FieldLiteDescriptor fieldDescriptor =
+ protoLiteCelValueConverter()
+ .findFieldDescriptor(celType().name(), fieldNumber)
+ .orElse(null);
+ return selectWireOrDefault(
+ field, fieldDescriptor, unknownFields().get(fieldNumber), protoLiteCelValueConverter());
+ }
+
+ @Override
+ public boolean hasFieldByNumber(SelectField field) {
+ int fieldNumber = field.fieldNumber();
+ FieldLiteDescriptor fieldDescriptor =
+ protoLiteCelValueConverter()
+ .findFieldDescriptor(celType().name(), fieldNumber)
+ .orElse(null);
+ return isPresentInWire(field, fieldDescriptor, unknownFields().get(fieldNumber));
+ }
+
+ @Override
+ public Optional findByFieldNumber(SelectField field) {
+ int fieldNumber = field.fieldNumber();
+ FieldLiteDescriptor fieldDescriptor =
+ protoLiteCelValueConverter()
+ .findFieldDescriptor(celType().name(), fieldNumber)
+ .orElse(null);
+ return navigateWire(
+ field, fieldDescriptor, unknownFields().get(fieldNumber), protoLiteCelValueConverter());
+ }
+
+ /**
+ * Decodes a field value from preserved wire bytes, falling back to schema or default values.
+ *
+ * Package-private: shared with {@code ProtoMessageLiteValue} for unknown field resolution.
+ */
+ static Object selectWireOrDefault(
+ SelectField field,
+ @Nullable FieldLiteDescriptor fieldDescriptor,
+ ImmutableList unknowns,
+ ProtoLiteCelValueConverter converter) {
+ if (unknowns.isEmpty()) {
+ return resolveDefault(field, fieldDescriptor, converter);
+ }
+ return decodeWireField(field, fieldDescriptor, unknowns, converter);
+ }
+
+ private static Object decodeWireField(
+ SelectField field,
+ @Nullable FieldLiteDescriptor fieldDescriptor,
+ ImmutableList unknowns,
+ ProtoLiteCelValueConverter converter) {
+ if (fieldDescriptor != null && fieldDescriptor.getEncodingType() == EncodingType.MAP) {
+ return decodeMapEntries(unknowns, fieldDescriptor, converter);
+ }
+
+ int typeCode =
+ fieldDescriptor != null
+ ? fieldDescriptor.getProtoFieldType().getNumber()
+ : field.typeCode();
+ if (typeCode == SelectField.CEL_MAP_TYPE_CODE) {
+ throw new UnsupportedOperationException(
+ "Decoding unknown map field from wire bytes is unsupported: " + field.fieldName());
+ }
+ if (typeCode == SelectField.NO_TYPE_CODE) {
+ throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
+ }
+
+ boolean isRepeated =
+ fieldDescriptor != null
+ ? fieldDescriptor.getEncodingType() == EncodingType.LIST
+ : field.defaultValue() instanceof List;
+ String protoTypeName =
+ fieldDescriptor != null
+ ? fieldDescriptor.getFieldProtoTypeName()
+ : UNKNOWN_MESSAGE_TYPE_NAME;
+
+ return decodeWireEntries(unknowns, typeCode, protoTypeName, isRepeated, converter);
+ }
+
+ private static Object resolveDefault(
+ SelectField field,
+ @Nullable FieldLiteDescriptor fieldDescriptor,
+ ProtoLiteCelValueConverter converter) {
+ if (field.defaultValue() != null) {
+ return field.defaultValue();
+ }
+
+ if (fieldDescriptor == null) {
+ if (field.typeCode() == FieldLiteDescriptor.Type.MESSAGE.getNumber()) {
+ return create(ByteString.EMPTY, UNKNOWN_MESSAGE_TYPE_NAME, converter);
+ }
+ throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
+ }
+
+ String protoTypeName = fieldDescriptor.getFieldProtoTypeName();
+ if (fieldDescriptor.getEncodingType() == EncodingType.SINGULAR
+ && fieldDescriptor.getJavaType() == JavaType.MESSAGE
+ && !WellKnownProto.isWrapperType(protoTypeName)
+ && !converter.hasDescriptor(protoTypeName)) {
+ return create(ByteString.EMPTY, protoTypeName, converter);
+ }
+
+ return converter.getDefaultCelValue(fieldDescriptor);
+ }
+
+ /**
+ * Returns whether a field has presence in preserved wire bytes.
+ *
+ * Package-private: shared with {@code ProtoMessageLiteValue} for unknown field resolution.
+ */
+ static boolean isPresentInWire(
+ SelectField field,
+ @Nullable FieldLiteDescriptor fieldDescriptor,
+ ImmutableList unknowns) {
+ if (unknowns.isEmpty()) {
+ return false;
+ }
+
+ boolean isRepeated =
+ fieldDescriptor != null
+ ? fieldDescriptor.getEncodingType() == EncodingType.LIST
+ : field.defaultValue() instanceof List;
+ int typeCode =
+ fieldDescriptor != null
+ ? fieldDescriptor.getProtoFieldType().getNumber()
+ : field.typeCode();
+
+ if (!isRepeated) {
+ return true;
+ }
+
+ boolean isPackable =
+ typeCode != FieldLiteDescriptor.Type.STRING.getNumber()
+ && typeCode != FieldLiteDescriptor.Type.BYTES.getNumber()
+ && typeCode != FieldLiteDescriptor.Type.MESSAGE.getNumber()
+ && typeCode != FieldLiteDescriptor.Type.GROUP.getNumber();
+ if (!isPackable) {
+ return true;
+ }
+
+ for (Object raw : unknowns) {
+ if (!(raw instanceof ByteString) || !((ByteString) raw).isEmpty()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Navigates a field on preserved wire bytes, returning empty if absent.
+ *
+ * Package-private: shared with {@code ProtoMessageLiteValue} for unknown field resolution.
+ */
+ static Optional navigateWire(
+ SelectField field,
+ @Nullable FieldLiteDescriptor fieldDescriptor,
+ ImmutableList unknowns,
+ ProtoLiteCelValueConverter converter) {
+ if (!isPresentInWire(field, fieldDescriptor, unknowns)) {
+ return Optional.empty();
+ }
+ if (fieldDescriptor != null || field.typeCode() != SelectField.NO_TYPE_CODE) {
+ return Optional.of(selectWireOrDefault(field, fieldDescriptor, unknowns, converter));
+ }
+ Object lastEntry = unknowns.get(unknowns.size() - 1);
+ if (lastEntry instanceof ByteString) {
+ return Optional.of(
+ decodeWireEntries(
+ unknowns,
+ FieldLiteDescriptor.Type.MESSAGE.getNumber(),
+ UNKNOWN_MESSAGE_TYPE_NAME,
+ /* isRepeated= */ false,
+ converter));
+ }
+ return Optional.of(lastEntry);
+ }
+
+ private static ImmutableMap decodeMapEntries(
+ ImmutableList unknowns,
+ FieldLiteDescriptor mapFieldDescriptor,
+ ProtoLiteCelValueConverter converter) {
+ String entryTypeName = mapFieldDescriptor.getFieldProtoTypeName();
+ Object defaultKey = converter.getDefaultCelValue(entryTypeName, MAP_KEY_FIELD_NAME);
+ Object defaultValue = converter.getDefaultCelValue(entryTypeName, MAP_VALUE_FIELD_NAME);
+ Map resultMap = new LinkedHashMap<>();
+ for (Object raw : unknowns) {
+ ByteString bytes = requireType(raw, ByteString.class, WireFormat.FieldType.MESSAGE);
+ try {
+ ImmutableMap entryFields =
+ converter.readAllFields(bytes.toByteArray(), entryTypeName).values();
+ Object key = entryFields.get(MAP_KEY_FIELD_NAME);
+ key = (key == null) ? defaultKey : converter.toRuntimeValue(key);
+ Object value = entryFields.get(MAP_VALUE_FIELD_NAME);
+ value = (value == null) ? defaultValue : converter.toRuntimeValue(value);
+ resultMap.put(key, value);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Failed to decode map entry for field: " + mapFieldDescriptor.getFieldName(), e);
+ }
+ }
+ return ImmutableMap.copyOf(resultMap);
}
- public static @Nullable Object decodeWireEntries(
- ImmutableCollection entries, int typeCode, String protoTypeName, boolean isRepeated) {
+ 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) {
@@ -130,7 +345,7 @@ public Optional find(String field) {
if (fieldType.isPackable() && (raw instanceof ByteString)) {
listBuilder.addAll(decodePacked((ByteString) raw, fieldType));
} else {
- listBuilder.add(decodeWireValue(raw, fieldType, protoTypeName));
+ listBuilder.add(decodeWireValue(raw, fieldType, protoTypeName, converter));
}
}
return listBuilder.build();
@@ -140,18 +355,26 @@ public Optional find(String field) {
for (Object item : entries) {
mergedBytes = mergedBytes.concat(requireType(item, ByteString.class, fieldType));
}
- return decodeWireValue(mergedBytes, fieldType, protoTypeName);
+ return decodeWireValue(mergedBytes, fieldType, protoTypeName, converter);
}
// Protobuf "last one wins" semantics for non-repeated scalar fields
- return decodeWireValue(Iterables.getLast(entries), fieldType, protoTypeName);
+ return decodeWireValue(Iterables.getLast(entries), fieldType, protoTypeName, converter);
}
- static Object decodeWireValue(Object raw, int typeCode, String protoTypeName) {
+ static Object decodeWireValue(
+ Object raw, int typeCode, String protoTypeName, ProtoLiteCelValueConverter converter) {
return decodeWireValue(
- raw, FieldLiteDescriptor.Type.forNumber(typeCode).toWireFormatFieldType(), protoTypeName);
+ raw,
+ FieldLiteDescriptor.Type.forNumber(typeCode).toWireFormatFieldType(),
+ protoTypeName,
+ converter);
}
- static Object decodeWireValue(Object raw, WireFormat.FieldType fieldType, String protoTypeName) {
+ static Object decodeWireValue(
+ Object raw,
+ WireFormat.FieldType fieldType,
+ String protoTypeName,
+ ProtoLiteCelValueConverter converter) {
switch (fieldType) {
case DOUBLE:
return Double.longBitsToDouble(requireType(raw, Long.class, fieldType));
@@ -180,8 +403,10 @@ static Object decodeWireValue(Object raw, WireFormat.FieldType fieldType, String
case GROUP:
throw new UnsupportedOperationException("Groups are not supported");
case MESSAGE:
- return RawProtoMessageLiteValue.create(
- requireType(raw, ByteString.class, fieldType), protoTypeName);
+ ByteString msgBytes = requireType(raw, ByteString.class, fieldType);
+ return converter
+ .tryDecodeWellKnownProto(msgBytes, protoTypeName)
+ .orElseGet(() -> create(msgBytes, protoTypeName, converter));
case BYTES:
return CelByteString.of(requireType(raw, ByteString.class, fieldType).toByteArray());
case UINT32:
@@ -269,15 +494,20 @@ private static ImmutableList decodePacked(
}
}
- public static RawProtoMessageLiteValue create(ByteString rawWireBytes) {
- return create(rawWireBytes, "");
+ public static RawProtoMessageLiteValue create(
+ ByteString rawWireBytes, ProtoLiteCelValueConverter protoLiteCelValueConverter) {
+ return create(rawWireBytes, "", protoLiteCelValueConverter);
}
- public static RawProtoMessageLiteValue create(ByteString rawWireBytes, String protoTypeName) {
+ public static RawProtoMessageLiteValue create(
+ ByteString rawWireBytes,
+ String protoTypeName,
+ ProtoLiteCelValueConverter protoLiteCelValueConverter) {
checkNotNull(rawWireBytes);
checkNotNull(protoTypeName);
+ checkNotNull(protoLiteCelValueConverter);
return new AutoValue_RawProtoMessageLiteValue(
- rawWireBytes, StructTypeReference.create(protoTypeName));
+ 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..8bee5a0d7
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/values/SelectField.java
@@ -0,0 +1,124 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dev.cel.common.values;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.auto.value.AutoValue;
+import com.google.errorprone.annotations.Immutable;
+import dev.cel.common.annotations.Internal;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * Represents a single field selection in an optimized selection chain.
+ *
+ * CEL Library Internals. Do Not Use.
+ */
+@Internal
+@AutoValue
+@AutoValue.CopyAnnotations
+@Immutable
+@SuppressWarnings("Immutable") // Default value is an immutable CEL literal or null
+public abstract class SelectField {
+
+ private static final int MAX_FIELD_NUMBER = 536870911;
+
+ /** CEL-specific type code used to encode CEL maps on the wire. */
+ public static final int CEL_MAP_TYPE_CODE = -1;
+
+ /** Sentinel for a presence-test qualifier, whose 2-tuple carries no type code. */
+ public static final int NO_TYPE_CODE = 0;
+
+ /** Protobuf field type code for {@code TYPE_MESSAGE} ({@code FieldDescriptorProto.Type}). */
+ public static final int MESSAGE_TYPE_CODE = 11;
+
+ // Mirrors FieldDescriptorProto.Type. Not validated against a protobuf enum because the :values
+ // target is deliberately protobuf-free; keep in sync with CelLiteDescriptor.FieldLiteDescriptor.
+ private static final int MIN_PROTO_TYPE_CODE = 1; // TYPE_DOUBLE
+ private static final int MAX_PROTO_TYPE_CODE = 18; // TYPE_SINT64
+ private static final int GROUP_PROTO_TYPE_CODE = 10; // Unsupported by CEL.
+
+ /** Protobuf field number of this hop. */
+ public abstract int fieldNumber();
+
+ /** Protobuf field name or map key of this hop. */
+ public abstract String fieldName();
+
+ /**
+ * Protobuf wire type code (1..18, except 10), {@link #CEL_MAP_TYPE_CODE}, or {@link
+ * #NO_TYPE_CODE}.
+ */
+ public abstract int typeCode();
+
+ /**
+ * Default value for this hop, or null if unspecified. When non-null, this must be an immutable
+ * CEL literal value.
+ */
+ public abstract @Nullable Object defaultValue();
+
+ /**
+ * Creates a presence-test qualifier hop.
+ *
+ * @param fieldNumber Protobuf field number. Takes {@code long} for compatibility with CEL's int64
+ * constant representations.
+ * @param fieldName Protobuf field name.
+ */
+ public static SelectField create(long fieldNumber, String fieldName) {
+ checkArgument(
+ fieldNumber >= 1 && fieldNumber <= MAX_FIELD_NUMBER,
+ "Field number out of protobuf range: %s",
+ fieldNumber);
+ checkNotNull(fieldName);
+ return new AutoValue_SelectField(
+ (int) fieldNumber, fieldName, NO_TYPE_CODE, /* defaultValue= */ null);
+ }
+
+ /**
+ * Creates a fully-specified field selection hop with type code and optional default value.
+ *
+ * @param fieldNumber Protobuf field number. Takes {@code long} for compatibility with CEL's int64
+ * constant representations.
+ * @param fieldName Protobuf field name.
+ * @param typeCode Protobuf wire type code or {@link #CEL_MAP_TYPE_CODE}. Takes {@code long} for
+ * compatibility with CEL's int64 constant representations.
+ * @param defaultValue Default value for the field, or null if unspecified.
+ */
+ public static SelectField create(
+ long fieldNumber, String fieldName, long typeCode, @Nullable Object defaultValue) {
+ checkArgument(
+ fieldNumber >= 1 && fieldNumber <= MAX_FIELD_NUMBER,
+ "Field number out of protobuf range: %s",
+ fieldNumber);
+ checkNotNull(fieldName);
+ checkArgument(isSupportedTypeCode(typeCode), "Invalid protobuf type code: %s", typeCode);
+ return new AutoValue_SelectField((int) fieldNumber, fieldName, (int) typeCode, defaultValue);
+ }
+
+ /**
+ * Returns whether {@code typeCode} is a protobuf field type code CEL supports, or the {@link
+ * #CEL_MAP_TYPE_CODE} sentinel.
+ */
+ public static boolean isSupportedTypeCode(long typeCode) {
+ if (typeCode == CEL_MAP_TYPE_CODE) {
+ return true;
+ }
+ return typeCode >= MIN_PROTO_TYPE_CODE
+ && typeCode <= MAX_PROTO_TYPE_CODE
+ && typeCode != GROUP_PROTO_TYPE_CODE;
+ }
+
+ SelectField() {}
+}
diff --git a/common/src/test/java/dev/cel/common/values/BUILD.bazel b/common/src/test/java/dev/cel/common/values/BUILD.bazel
index baa33ebc3..1732c6667 100644
--- a/common/src/test/java/dev/cel/common/values/BUILD.bazel
+++ b/common/src/test/java/dev/cel/common/values/BUILD.bazel
@@ -22,6 +22,7 @@ java_library(
"//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",
@@ -29,10 +30,13 @@ java_library(
"//common/values:cel_value_provider",
"//common/values:combined_cel_value_converter",
"//common/values:combined_cel_value_provider",
+ "//common/values:optimized_select_traversal",
+ "//common/values:optimized_selectable",
"//common/values:proto_message_lite_value",
"//common/values:proto_message_lite_value_provider",
"//common/values:proto_message_value",
"//common/values:proto_message_value_provider",
+ "//common/values:select_field",
"//protobuf:cel_lite_descriptor",
"//testing/protos:test_all_types_cel_java_proto3",
"@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto",
diff --git a/common/src/test/java/dev/cel/common/values/OptimizedSelectTraversalTest.java b/common/src/test/java/dev/cel/common/values/OptimizedSelectTraversalTest.java
new file mode 100644
index 000000000..7206b72e9
--- /dev/null
+++ b/common/src/test/java/dev/cel/common/values/OptimizedSelectTraversalTest.java
@@ -0,0 +1,329 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dev.cel.common.values;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+import dev.cel.common.exceptions.CelAttributeNotFoundException;
+import java.util.Map;
+import java.util.Optional;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(TestParameterInjector.class)
+public final class OptimizedSelectTraversalTest {
+
+ private enum TargetType {
+ OPTIMIZED_SELECTABLE {
+ @Override
+ Object createTarget(Map data) {
+ return new FakeOptimizedSelectable(data);
+ }
+
+ @Override
+ Object createNestedTarget(Map innerData) {
+ return new FakeOptimizedSelectable(
+ ImmutableMap.of("outer_key", new FakeOptimizedSelectable(innerData)));
+ }
+ },
+ SELECTABLE_VALUE {
+ @Override
+ Object createTarget(Map data) {
+ return new FakeSelectableValue(data);
+ }
+
+ @Override
+ Object createNestedTarget(Map innerData) {
+ return new FakeSelectableValue(
+ ImmutableMap.of("outer_key", new FakeSelectableValue(innerData)));
+ }
+ };
+
+ abstract Object createTarget(Map data);
+
+ abstract Object createNestedTarget(Map innerData);
+ }
+
+ @SuppressWarnings("Immutable")
+ private enum NestedPresenceTestCase {
+ ALL_PRESENT(
+ ImmutableMap.of("inner_key", "nested_val"), "outer_key", "inner_key", /* expected= */ true),
+ INTERMEDIATE_MISSING(
+ ImmutableMap.of("inner_key", "nested_val"),
+ "missing_outer",
+ "inner_key",
+ /* expected= */ false),
+ TERMINAL_MISSING(
+ ImmutableMap.of("other_key", "nested_val"),
+ "outer_key",
+ "missing_terminal",
+ /* expected= */ false);
+
+ final ImmutableMap innerData;
+ final String outerField;
+ final String innerField;
+ final boolean expected;
+
+ NestedPresenceTestCase(
+ ImmutableMap innerData,
+ String outerField,
+ String innerField,
+ boolean expected) {
+ this.innerData = innerData;
+ this.outerField = outerField;
+ this.innerField = innerField;
+ this.expected = expected;
+ }
+ }
+
+ @Test
+ public void qualify_emptyFields_returnsTargetInstance() {
+ Object target = new Object();
+
+ Object result = OptimizedSelectTraversal.qualify(target, ImmutableList.of());
+
+ assertThat(result).isSameInstanceAs(target);
+ }
+
+ @Test
+ public void qualify_singleField_success(@TestParameter TargetType targetType) {
+ Object target = targetType.createTarget(ImmutableMap.of("key", "value"));
+ ImmutableList fields = ImmutableList.of(SelectField.create(1L, "key", 9, ""));
+
+ Object result = OptimizedSelectTraversal.qualify(target, fields);
+
+ assertThat(result).isEqualTo("value");
+ }
+
+ @Test
+ public void qualify_nested_success(@TestParameter TargetType targetType) {
+ Object target = targetType.createNestedTarget(ImmutableMap.of("inner_key", "nested_value"));
+ ImmutableList fields =
+ ImmutableList.of(
+ SelectField.create(1L, "outer_key", 11, null),
+ SelectField.create(2L, "inner_key", 9, ""));
+
+ Object result = OptimizedSelectTraversal.qualify(target, fields);
+
+ assertThat(result).isEqualTo("nested_value");
+ }
+
+ @Test
+ public void qualify_singleField_missingThrowsException(@TestParameter TargetType targetType) {
+ Object target = targetType.createTarget(ImmutableMap.of("present", "value"));
+ ImmutableList fields =
+ ImmutableList.of(SelectField.create(1L, "missing", 9, null));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> OptimizedSelectTraversal.qualify(target, fields));
+
+ assertThat(thrown).hasMessageThat().contains("missing");
+ }
+
+ @Test
+ public void qualify_optimizedSelectable_absentWithDefaultValue_returnsDefault() {
+ FakeOptimizedSelectable selectable = new FakeOptimizedSelectable(ImmutableMap.of());
+ ImmutableList fields =
+ ImmutableList.of(SelectField.create(1L, "absent", 9, "default_fallback"));
+
+ Object result = OptimizedSelectTraversal.qualify(selectable, fields);
+
+ assertThat(result).isEqualTo("default_fallback");
+ }
+
+ @Test
+ public void qualify_selectableValue_absentWithDefaultValue_returnsDefault() {
+ FakeSelectableValue selectable = new FakeSelectableValue(ImmutableMap.of());
+ ImmutableList fields =
+ ImmutableList.of(SelectField.create(1L, "absent", 9, "default_fallback"));
+
+ Object result = OptimizedSelectTraversal.qualify(selectable, fields);
+
+ assertThat(result).isEqualTo("default_fallback");
+ }
+
+ @Test
+ public void qualify_unsupportedTarget_throwsException() {
+ ImmutableList fields = ImmutableList.of(SelectField.create(1L, "invalid_field"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> OptimizedSelectTraversal.qualify(12345L, fields));
+
+ assertThat(thrown).hasMessageThat().contains("invalid_field");
+ }
+
+ @Test
+ public void qualify_intermediateUnsupportedTarget_throwsException() {
+ FakeOptimizedSelectable target =
+ new FakeOptimizedSelectable(ImmutableMap.of("scalar", 999L));
+ ImmutableList fields =
+ ImmutableList.of(
+ SelectField.create(1L, "scalar", 3, 0L), SelectField.create(2L, "unreachable", 9, ""));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> OptimizedSelectTraversal.qualify(target, fields));
+
+ assertThat(thrown).hasMessageThat().contains("unreachable");
+ }
+
+ @Test
+ public void hasField_emptyFields_returnsFalse() {
+ Object target = new Object();
+
+ boolean hasField = OptimizedSelectTraversal.hasField(target, ImmutableList.of());
+
+ assertThat(hasField).isFalse();
+ }
+
+ @Test
+ public void hasField_singleField(
+ @TestParameter TargetType targetType,
+ @TestParameter({"present_key", "missing_key"}) String queryKey) {
+ Object target = targetType.createTarget(ImmutableMap.of("present_key", "val"));
+ ImmutableList fields = ImmutableList.of(SelectField.create(1L, queryKey));
+
+ boolean hasField = OptimizedSelectTraversal.hasField(target, fields);
+
+ assertThat(hasField).isEqualTo(queryKey.equals("present_key"));
+ }
+
+ @Test
+ public void hasField_nestedFields(
+ @TestParameter TargetType targetType, @TestParameter NestedPresenceTestCase testCase) {
+ Object target = targetType.createNestedTarget(testCase.innerData);
+ ImmutableList fields =
+ ImmutableList.of(
+ SelectField.create(1L, testCase.outerField),
+ SelectField.create(2L, testCase.innerField));
+
+ boolean hasField = OptimizedSelectTraversal.hasField(target, fields);
+
+ assertThat(hasField).isEqualTo(testCase.expected);
+ }
+
+ @Test
+ public void hasField_intermediateUnsupportedTarget_throwsException() {
+ FakeOptimizedSelectable target =
+ new FakeOptimizedSelectable(ImmutableMap.of("scalar_key", 100L));
+ ImmutableList fields =
+ ImmutableList.of(SelectField.create(1L, "scalar_key"), SelectField.create(2L, "child_key"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> OptimizedSelectTraversal.hasField(target, fields));
+
+ assertThat(thrown).hasMessageThat().contains("child_key");
+ }
+
+ @Test
+ public void hasField_unsupportedTarget_throwsException() {
+ ImmutableList fields = ImmutableList.of(SelectField.create(1L, "invalid_field"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> OptimizedSelectTraversal.hasField(12345L, fields));
+
+ assertThat(thrown).hasMessageThat().contains("invalid_field");
+ }
+
+ @Test
+ public void qualify_errorValue_propagatesError() {
+ ErrorValue error = ErrorValue.create(1L, new RuntimeException("test error"));
+ ImmutableList fields =
+ ImmutableList.of(SelectField.create(1L, "field1"), SelectField.create(2L, "field2"));
+
+ Object result = OptimizedSelectTraversal.qualify(error, fields);
+
+ assertThat(result).isSameInstanceAs(error);
+ }
+
+ @Test
+ public void hasField_errorValue_returnsFalse() {
+ ErrorValue error = ErrorValue.create(1L, new RuntimeException("test error"));
+ ImmutableList fields =
+ ImmutableList.of(SelectField.create(1L, "field1"), SelectField.create(2L, "field2"));
+
+ boolean result = OptimizedSelectTraversal.hasField(error, fields);
+
+ assertThat(result).isFalse();
+ }
+
+ @SuppressWarnings("Immutable")
+ private static final class FakeOptimizedSelectable implements OptimizedSelectable {
+ private final ImmutableMap values;
+
+ @Override
+ public Object selectByFieldNumber(SelectField field) {
+ Object value = values.get(field.fieldName());
+ if (value != null) {
+ return value;
+ }
+ if (field.defaultValue() != null) {
+ return field.defaultValue();
+ }
+ throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
+ }
+
+ @Override
+ public boolean hasFieldByNumber(SelectField field) {
+ return values.containsKey(field.fieldName());
+ }
+
+ @Override
+ public Optional findByFieldNumber(SelectField field) {
+ return Optional.ofNullable(values.get(field.fieldName()));
+ }
+
+ FakeOptimizedSelectable(Map values) {
+ this.values = ImmutableMap.copyOf(values);
+ }
+ }
+
+ @SuppressWarnings("Immutable")
+ private static final class FakeSelectableValue implements SelectableValue {
+ private final ImmutableMap values;
+
+ @Override
+ public Object select(String field) {
+ Object value = values.get(field);
+ if (value != null) {
+ return value;
+ }
+ throw CelAttributeNotFoundException.forFieldResolution(field);
+ }
+
+ @Override
+ public Optional find(String field) {
+ return Optional.ofNullable(values.get(field));
+ }
+
+ FakeSelectableValue(Map values) {
+ this.values = ImmutableMap.copyOf(values);
+ }
+ }
+}
diff --git a/common/src/test/java/dev/cel/common/values/ProtoLiteCelValueConverterTest.java b/common/src/test/java/dev/cel/common/values/ProtoLiteCelValueConverterTest.java
index 3b66171e4..0a11c23e5 100644
--- a/common/src/test/java/dev/cel/common/values/ProtoLiteCelValueConverterTest.java
+++ b/common/src/test/java/dev/cel/common/values/ProtoLiteCelValueConverterTest.java
@@ -15,6 +15,7 @@
package dev.cel.common.values;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
@@ -44,13 +45,36 @@
import dev.cel.common.values.ProtoLiteCelValueConverter.MessageFields;
import dev.cel.expr.conformance.proto3.TestAllTypes;
import dev.cel.expr.conformance.proto3.TestAllTypesCelDescriptor;
+import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor;
+import dev.cel.protobuf.CelLiteDescriptor.MessageLiteDescriptor;
+import java.io.IOException;
import java.time.Instant;
import java.util.LinkedHashMap;
+import java.util.NoSuchElementException;
+import java.util.Optional;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(TestParameterInjector.class)
public class ProtoLiteCelValueConverterTest {
+ private static final CelLiteDescriptorPool EMPTY_DESCRIPTOR_POOL =
+ new CelLiteDescriptorPool() {
+ @Override
+ public Optional findDescriptor(String protoTypeName) {
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional findDescriptor(MessageLite messageLite) {
+ return Optional.empty();
+ }
+
+ @Override
+ public MessageLiteDescriptor getDescriptorOrThrow(String protoTypeName) {
+ throw new NoSuchElementException(protoTypeName);
+ }
+ };
+
private static final CelLiteDescriptorPool DESCRIPTOR_POOL =
DefaultLiteDescriptorPool.newInstance(
ImmutableSet.of(TestAllTypesCelDescriptor.getDescriptor()));
@@ -307,7 +331,7 @@ public void readAllFields_unknownFieldsWithValues() throws Exception {
LinkedHashMap mapBoolDoubleValues =
(LinkedHashMap) fields.values().get("map_bool_double");
assertThat(mapBoolDoubleValues).containsExactly(true, 1.5d, false, 2.5d).inOrder();
- Multimap unknownValues = fields.unknowns();
+ ImmutableListMultimap unknownValues = fields.unknowns();
assertThat(unknownValues)
.containsExactly(
2500,
@@ -326,4 +350,99 @@ public void readAllFields_unknownFieldsWithValues() throws Exception {
ByteString.copyFromUtf8("\n\003bar\020\005"))
.inOrder();
}
+
+ @Test
+ public void getDefaultCelValue_fieldDescriptor_returnsDefault() {
+ FieldLiteDescriptor fieldDescriptor =
+ DESCRIPTOR_POOL
+ .getDescriptorOrThrow("cel.expr.conformance.proto3.TestAllTypes")
+ .getByFieldNameOrThrow("single_string");
+
+ Object defaultValue = PROTO_LITE_CEL_VALUE_CONVERTER.getDefaultCelValue(fieldDescriptor);
+
+ assertThat(defaultValue).isEqualTo("");
+ }
+
+ @Test
+ public void getDefaultCelValue_nestedMessageWithoutDescriptor_throwsNoSuchElementException() {
+ FieldLiteDescriptor nestedMsgField =
+ DESCRIPTOR_POOL
+ .getDescriptorOrThrow("cel.expr.conformance.proto3.TestAllTypes")
+ .getByFieldNameOrThrow("single_nested_message");
+ ProtoLiteCelValueConverter converterWithoutNested =
+ ProtoLiteCelValueConverter.newInstance(EMPTY_DESCRIPTOR_POOL);
+
+ assertThrows(
+ NoSuchElementException.class,
+ () -> converterWithoutNested.getDefaultCelValue(nestedMsgField));
+ }
+
+ @Test
+ public void tryDecodeWellKnownProto_validBytes_returnsDecodedValue() {
+ Int32Value int32Value = Int32Value.of(42);
+
+ Optional decoded =
+ PROTO_LITE_CEL_VALUE_CONVERTER.tryDecodeWellKnownProto(
+ int32Value.toByteString(), "google.protobuf.Int32Value");
+
+ assertThat(decoded).hasValue(42L);
+ }
+
+ @Test
+ public void tryDecodeWellKnownProto_notWellKnownType_returnsEmpty() {
+ Optional decoded =
+ PROTO_LITE_CEL_VALUE_CONVERTER.tryDecodeWellKnownProto(
+ ByteString.EMPTY, "cel.expr.conformance.proto3.TestAllTypes");
+
+ assertThat(decoded).isEmpty();
+ }
+
+ @Test
+ public void tryDecodeWellKnownProto_missingDescriptor_returnsEmpty() {
+ ProtoLiteCelValueConverter converter =
+ ProtoLiteCelValueConverter.newInstance(EMPTY_DESCRIPTOR_POOL);
+
+ Optional decoded =
+ converter.tryDecodeWellKnownProto(ByteString.EMPTY, "google.protobuf.Int32Value");
+
+ assertThat(decoded).isEmpty();
+ }
+
+ @Test
+ public void tryDecodeWellKnownProto_invalidBytes_throwsIllegalArgumentException() {
+ ByteString corruptBytes = ByteString.copyFrom(new byte[] {(byte) 0xFF, (byte) 0xFF});
+
+ IllegalArgumentException exception =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ PROTO_LITE_CEL_VALUE_CONVERTER.tryDecodeWellKnownProto(
+ corruptBytes, "google.protobuf.Int32Value"));
+
+ assertThat(exception)
+ .hasMessageThat()
+ .contains("Failed to decode well-known proto of type: google.protobuf.Int32Value");
+ assertThat(exception).hasCauseThat().isInstanceOf(IOException.class);
+ }
+
+ @Test
+ public void tryDecodeWellKnownProto_anyType_throwsUnsupportedOperationException() {
+ UnsupportedOperationException exception =
+ assertThrows(
+ UnsupportedOperationException.class,
+ () ->
+ PROTO_LITE_CEL_VALUE_CONVERTER.tryDecodeWellKnownProto(
+ ByteString.EMPTY, "google.protobuf.Any"));
+
+ assertThat(exception).hasMessageThat().contains("ANY_VALUE");
+ }
+
+ @Test
+ public void hasDescriptor_returnsExpectedResult() {
+ assertThat(
+ PROTO_LITE_CEL_VALUE_CONVERTER.hasDescriptor(
+ "cel.expr.conformance.proto3.TestAllTypes"))
+ .isTrue();
+ assertThat(PROTO_LITE_CEL_VALUE_CONVERTER.hasDescriptor("unknown.Type")).isFalse();
+ }
}
diff --git a/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueTest.java b/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueTest.java
index 88799878e..165ba03cc 100644
--- a/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueTest.java
+++ b/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueTest.java
@@ -15,6 +15,7 @@
package dev.cel.common.values;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -37,6 +38,7 @@
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;
@@ -46,11 +48,12 @@
import java.io.ByteArrayOutputStream;
import java.time.Duration;
import java.time.Instant;
+import java.util.Optional;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(TestParameterInjector.class)
-public class ProtoMessageLiteValueTest {
+public final class ProtoMessageLiteValueTest {
private static final CelLiteDescriptorPool DESCRIPTOR_POOL =
DefaultLiteDescriptorPool.newInstance(
ImmutableSet.of(TestAllTypesCelDescriptor.getDescriptor()));
@@ -280,4 +283,322 @@ public void unknownFields_retainsUnknownWireFields() throws Exception {
.valuesForKey(1000)
.containsExactly(ByteString.copyFromUtf8("hello unknown"));
}
+
+ @Test
+ public void selectByFieldNumber_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.selectByFieldNumber(SelectField.create(14L, "single_string", 9, "default"));
+
+ assertThat(result).isEqualTo("foo");
+ }
+
+ @Test
+ public void selectByFieldNumber_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.selectByFieldNumber(SelectField.create(999L, "unknown_field", 3, 0L));
+
+ assertThat(result).isEqualTo(42L);
+ }
+
+ @Test
+ public void selectByFieldNumber_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.selectByFieldNumber(
+ SelectField.create(999L, "unknown_repeated", 3, ImmutableList.of()));
+
+ assertThat((Iterable>) result).containsExactly(10L, 20L).inOrder();
+ }
+
+ @Test
+ public void selectByFieldNumber_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.selectByFieldNumber(SelectField.create(14L, "renamed_string", 9, "default"));
+
+ assertThat(result).isEqualTo("foo");
+ }
+
+ @Test
+ public void selectByFieldNumber_unknownFieldCollidesWithKnownFieldName_returnsUnknownFieldValue()
+ 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())
+ .toBuilder()
+ .setSingleString("known_field_14")
+ .build();
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ Object result = val.selectByFieldNumber(SelectField.create(999L, "single_string", 3, 0L));
+
+ assertThat(result).isEqualTo(42L);
+ }
+
+ @Test
+ public void selectByFieldNumber_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.selectByFieldNumber(SelectField.create(61L, "renamed_map", -1, ImmutableMap.of()));
+
+ assertThat(result).isEqualTo(ImmutableMap.of("k", "v"));
+ }
+
+ @Test
+ public void selectByFieldNumber_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.selectByFieldNumber(SelectField.create(32L, "renamed_repeated", 3, ImmutableList.of()));
+
+ assertThat(result).isEqualTo(ImmutableList.of(10L, 20L));
+ }
+
+ @Test
+ public void findByFieldNumber_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);
+
+ Optional nav = val.findByFieldNumber(SelectField.create(998L, "unknown_submessage"));
+
+ assertThat(nav.map(v -> v instanceof RawProtoMessageLiteValue)).hasValue(true);
+ }
+
+ @Test
+ public void hasFieldByNumber_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.hasFieldByNumber(SelectField.create(14L, "single_string"))).isTrue();
+ }
+
+ @Test
+ public void hasFieldByNumber_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.hasFieldByNumber(SelectField.create(999L, "unknown_present"))).isTrue();
+ }
+
+ @Test
+ public void hasFieldByNumber_unknownFieldAbsent_returnsFalse() {
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ TestAllTypes.getDefaultInstance(),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ assertThat(val.hasFieldByNumber(SelectField.create(888L, "unknown_absent"))).isFalse();
+ }
+
+ @Test
+ public void hasFieldByNumber_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.hasFieldByNumber(SelectField.create(14L, "renamed_string"))).isTrue();
+ }
+
+ @Test
+ public void hasFieldByNumber_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.hasFieldByNumber(SelectField.create(61L, "renamed_map"))).isTrue();
+ }
+
+ @Test
+ public void hasFieldByNumber_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.hasFieldByNumber(SelectField.create(32L, "renamed_repeated"))).isTrue();
+ }
+
+ @Test
+ public void qualify_emptyList_returnsSameInstance() {
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ TestAllTypes.getDefaultInstance(),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ assertThat(OptimizedSelectTraversal.qualify(val, 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(OptimizedSelectTraversal.hasField(val, ImmutableList.of())).isFalse();
+ }
+
+ @Test
+ public void qualify_mapField_returnsMap() {
+ TestAllTypes proto = TestAllTypes.newBuilder().putMapStringString("k", "v").build();
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+ ImmutableList fields =
+ ImmutableList.of(
+ SelectField.create(
+ 61L, "map_string_string", SelectField.CEL_MAP_TYPE_CODE, ImmutableMap.of()));
+
+ Object result = OptimizedSelectTraversal.qualify(val, fields);
+
+ assertThat(result).isEqualTo(ImmutableMap.of("k", "v"));
+ }
+
+ @Test
+ public void qualify_nestedMessage_resolvesField() {
+ TestAllTypes proto =
+ TestAllTypes.newBuilder()
+ .setSingleNestedMessage(TestAllTypes.NestedMessage.newBuilder().setBb(42))
+ .build();
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+ ImmutableList fields =
+ ImmutableList.of(
+ SelectField.create(21L, "single_nested_message", 11, null),
+ SelectField.create(1L, "bb", 5, 0));
+
+ Object result = OptimizedSelectTraversal.qualify(val, fields);
+
+ assertThat(result).isEqualTo(42L);
+ }
+
+ @Test
+ public void hasField_nestedMessage_resolvesPresence() {
+ TestAllTypes proto =
+ TestAllTypes.newBuilder()
+ .setSingleNestedMessage(TestAllTypes.NestedMessage.newBuilder().setBb(42))
+ .build();
+ ProtoMessageLiteValue val =
+ ProtoMessageLiteValue.create(
+ proto, "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER);
+
+ assertThat(
+ OptimizedSelectTraversal.hasField(
+ val,
+ ImmutableList.of(
+ SelectField.create(21L, "single_nested_message"), SelectField.create(1L, "bb"))))
+ .isTrue();
+ assertThat(
+ OptimizedSelectTraversal.hasField(
+ val,
+ ImmutableList.of(
+ SelectField.create(21L, "single_nested_message"),
+ SelectField.create(99L, "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);
+ ImmutableList fields =
+ ImmutableList.of(
+ SelectField.create(2L, "single_int64", 3, 0L),
+ SelectField.create(3L, "leaf_field", 9, ""));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> OptimizedSelectTraversal.qualify(val, fields));
+
+ 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);
+ ImmutableList fields =
+ ImmutableList.of(
+ SelectField.create(2L, "single_int64"), SelectField.create(3L, "leaf_field"));
+
+ CelAttributeNotFoundException thrown =
+ assertThrows(
+ CelAttributeNotFoundException.class,
+ () -> OptimizedSelectTraversal.hasField(val, fields));
+
+ 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
index 8f5ac623a..180883f60 100644
--- a/common/src/test/java/dev/cel/common/values/RawProtoMessageLiteValueTest.java
+++ b/common/src/test/java/dev/cel/common/values/RawProtoMessageLiteValueTest.java
@@ -18,25 +18,64 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertThrows;
+import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.UnsignedLong;
import com.google.protobuf.ByteString;
import com.google.protobuf.CodedOutputStream;
+import com.google.protobuf.Int64Value;
+import com.google.protobuf.MessageLite;
import com.google.protobuf.WireFormat;
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import dev.cel.common.exceptions.CelAttributeNotFoundException;
+import dev.cel.common.internal.CelLiteDescriptorPool;
+import dev.cel.common.internal.DefaultLiteDescriptorPool;
+import dev.cel.common.internal.ProtoTimeUtils;
+import dev.cel.expr.conformance.proto3.TestAllTypes;
+import dev.cel.expr.conformance.proto3.TestAllTypesCelDescriptor;
import dev.cel.protobuf.CelLiteDescriptor.FieldLiteDescriptor;
+import dev.cel.protobuf.CelLiteDescriptor.MessageLiteDescriptor;
import java.io.ByteArrayOutputStream;
+import java.time.Duration;
+import java.util.NoSuchElementException;
+import java.util.Optional;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-@RunWith(JUnit4.class)
+@RunWith(TestParameterInjector.class)
public final class RawProtoMessageLiteValueTest {
+ private static final ProtoLiteCelValueConverter CONVERTER =
+ ProtoLiteCelValueConverter.newInstance(
+ DefaultLiteDescriptorPool.newInstance(
+ ImmutableSet.of(TestAllTypesCelDescriptor.getDescriptor())));
+
+ private static final ProtoLiteCelValueConverter EMPTY_CONVERTER =
+ ProtoLiteCelValueConverter.newInstance(DefaultLiteDescriptorPool.newInstance());
+
+ private static Object decodeWireEntries(
+ ImmutableCollection entries, int typeCode, String protoTypeName, boolean isRepeated) {
+ return RawProtoMessageLiteValue.decodeWireEntries(
+ entries, typeCode, protoTypeName, isRepeated, EMPTY_CONVERTER);
+ }
+
+ private static Object decodeWireValue(
+ Object raw, WireFormat.FieldType fieldType, String protoTypeName) {
+ return RawProtoMessageLiteValue.decodeWireValue(raw, fieldType, protoTypeName, EMPTY_CONVERTER);
+ }
+
+ private static Object decodeWireValue(Object raw, int typeCode, String protoTypeName) {
+ return RawProtoMessageLiteValue.decodeWireValue(raw, typeCode, protoTypeName, EMPTY_CONVERTER);
+ }
+
@Test
public void create_accessorsAndType() {
ByteString bytes = ByteString.copyFromUtf8("test");
- RawProtoMessageLiteValue value = RawProtoMessageLiteValue.create(bytes, "custom.Message");
+ RawProtoMessageLiteValue value =
+ RawProtoMessageLiteValue.create(bytes, "custom.Message", EMPTY_CONVERTER);
assertThat(value.rawWireBytes()).isEqualTo(bytes);
assertThat(value.value()).isSameInstanceAs(value);
@@ -44,9 +83,9 @@ public void create_accessorsAndType() {
}
@Test
- public void create_singleArgDefaultsEmptyTypeName() {
+ public void create_defaultsEmptyTypeName() {
ByteString bytes = ByteString.copyFromUtf8("test");
- RawProtoMessageLiteValue value = RawProtoMessageLiteValue.create(bytes);
+ RawProtoMessageLiteValue value = RawProtoMessageLiteValue.create(bytes, EMPTY_CONVERTER);
assertThat(value.rawWireBytes()).isEqualTo(bytes);
assertThat(value.celType().name()).isEmpty();
@@ -55,22 +94,23 @@ public void create_singleArgDefaultsEmptyTypeName() {
@Test
public void select_throwsCelAttributeNotFoundException() {
RawProtoMessageLiteValue value =
- RawProtoMessageLiteValue.create(ByteString.EMPTY, "custom.Message");
+ RawProtoMessageLiteValue.create(ByteString.EMPTY, "custom.Message", EMPTY_CONVERTER);
assertThrows(CelAttributeNotFoundException.class, () -> value.select("field"));
}
@Test
- public void find_returnsEmptyOptional() {
+ public void find_throwsCelAttributeNotFoundException() {
RawProtoMessageLiteValue value =
- RawProtoMessageLiteValue.create(ByteString.EMPTY, "custom.Message");
+ RawProtoMessageLiteValue.create(ByteString.EMPTY, "custom.Message", EMPTY_CONVERTER);
- assertThat(value.find("field")).isEmpty();
+ assertThrows(CelAttributeNotFoundException.class, () -> value.find("field"));
}
@Test
public void isZeroValue_emptyBytes_returnsTrue() {
- RawProtoMessageLiteValue value = RawProtoMessageLiteValue.create(ByteString.EMPTY);
+ RawProtoMessageLiteValue value =
+ RawProtoMessageLiteValue.create(ByteString.EMPTY, EMPTY_CONVERTER);
assertThat(value.isZeroValue()).isTrue();
}
@@ -78,22 +118,27 @@ public void isZeroValue_emptyBytes_returnsTrue() {
@Test
public void isZeroValue_nonEmptyBytes_returnsFalse() {
RawProtoMessageLiteValue value =
- RawProtoMessageLiteValue.create(ByteString.copyFromUtf8("data"));
+ RawProtoMessageLiteValue.create(ByteString.copyFromUtf8("data"), EMPTY_CONVERTER);
assertThat(value.isZeroValue()).isFalse();
}
@Test
- public void hasField_returnsExpectedPresence() throws Exception {
+ public void hasFieldByNumber_scalarField_returnsExpectedPresence() throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
CodedOutputStream cos = CodedOutputStream.newInstance(baos);
cos.writeInt64(1, 42L);
cos.flush();
RawProtoMessageLiteValue value =
- RawProtoMessageLiteValue.create(ByteString.copyFrom(baos.toByteArray()));
+ RawProtoMessageLiteValue.create(ByteString.copyFrom(baos.toByteArray()), EMPTY_CONVERTER);
+
+ SelectField field1 =
+ SelectField.create(1L, "single_int64", FieldLiteDescriptor.Type.INT64.getNumber(), 0L);
+ SelectField field2 =
+ SelectField.create(2L, "single_int64", FieldLiteDescriptor.Type.INT64.getNumber(), 0L);
- assertThat(value.hasField(1)).isTrue();
- assertThat(value.hasField(2)).isFalse();
+ assertThat(value.hasFieldByNumber(field1)).isTrue();
+ assertThat(value.hasFieldByNumber(field2)).isFalse();
}
@Test
@@ -107,7 +152,7 @@ public void unknownFields_parsesWireTags() throws Exception {
cos.flush();
RawProtoMessageLiteValue value =
- RawProtoMessageLiteValue.create(ByteString.copyFrom(baos.toByteArray()));
+ RawProtoMessageLiteValue.create(ByteString.copyFrom(baos.toByteArray()), EMPTY_CONVERTER);
assertThat(value.unknownFields()).valuesForKey(1).containsExactly(42L);
assertThat(value.unknownFields()).valuesForKey(2).containsExactly(100);
@@ -120,13 +165,13 @@ public void unknownFields_parsesWireTags() throws Exception {
@Test
public void decodeWireEntries_emptySingularEntries_returnsNull() {
Object intResult =
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(),
FieldLiteDescriptor.Type.INT64.getNumber(),
"custom.Message",
/* isRepeated= */ false);
Object messageResult =
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(),
FieldLiteDescriptor.Type.MESSAGE.getNumber(),
"custom.Message",
@@ -139,7 +184,7 @@ public void decodeWireEntries_emptySingularEntries_returnsNull() {
@Test
public void decodeWireEntries_emptyRepeatedEntries_returnsEmptyList() {
Object result =
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(),
FieldLiteDescriptor.Type.INT64.getNumber(),
"custom.Message",
@@ -151,7 +196,7 @@ public void decodeWireEntries_emptyRepeatedEntries_returnsEmptyList() {
@Test
public void decodeWireEntries_nonRepeated_lastOneWins() {
Object decoded =
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(10L, 20L, 30L),
FieldLiteDescriptor.Type.INT64.getNumber(),
"custom.Message",
@@ -163,7 +208,7 @@ public void decodeWireEntries_nonRepeated_lastOneWins() {
@Test
public void decodeWireEntries_repeatedUnpacked() {
Object decoded =
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(10L, 20L, 30L),
FieldLiteDescriptor.Type.INT64.getNumber(),
"custom.Message",
@@ -182,7 +227,7 @@ public void decodeWireEntries_packedInt32() throws Exception {
cos.flush();
Object decoded =
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(ByteString.copyFrom(baos.toByteArray())),
FieldLiteDescriptor.Type.INT32.getNumber(),
"custom.Message",
@@ -200,7 +245,7 @@ public void decodeWireEntries_packedInt64() throws Exception {
cos.flush();
Object decoded =
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(ByteString.copyFrom(baos.toByteArray())),
FieldLiteDescriptor.Type.INT64.getNumber(),
"custom.Message",
@@ -217,7 +262,7 @@ public void decodeWireEntries_packedUint32() throws Exception {
cos.flush();
Object decoded =
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(ByteString.copyFrom(baos.toByteArray())),
FieldLiteDescriptor.Type.UINT32.getNumber(),
"custom.Message",
@@ -234,7 +279,7 @@ public void decodeWireEntries_packedUint64() throws Exception {
cos.flush();
Object decoded =
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(ByteString.copyFrom(baos.toByteArray())),
FieldLiteDescriptor.Type.UINT64.getNumber(),
"custom.Message",
@@ -252,7 +297,7 @@ public void decodeWireEntries_packedSint32AndSint64() throws Exception {
cos32.flush();
Object decoded32 =
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(ByteString.copyFrom(baos32.toByteArray())),
FieldLiteDescriptor.Type.SINT32.getNumber(),
"custom.Message",
@@ -267,7 +312,7 @@ public void decodeWireEntries_packedSint32AndSint64() throws Exception {
cos64.flush();
Object decoded64 =
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(ByteString.copyFrom(baos64.toByteArray())),
FieldLiteDescriptor.Type.SINT64.getNumber(),
"custom.Message",
@@ -287,7 +332,7 @@ public void decodeWireEntries_packedFixedAndSFixed() throws Exception {
cos.flush();
assertThat(
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(ByteString.copyFrom(baos.toByteArray()).substring(0, 4)),
FieldLiteDescriptor.Type.FIXED32.getNumber(),
"custom.Message",
@@ -295,7 +340,7 @@ public void decodeWireEntries_packedFixedAndSFixed() throws Exception {
.isEqualTo(ImmutableList.of(UnsignedLong.fromLongBits(10L)));
assertThat(
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(ByteString.copyFrom(baos.toByteArray()).substring(4, 12)),
FieldLiteDescriptor.Type.FIXED64.getNumber(),
"custom.Message",
@@ -303,7 +348,7 @@ public void decodeWireEntries_packedFixedAndSFixed() throws Exception {
.isEqualTo(ImmutableList.of(UnsignedLong.fromLongBits(20L)));
assertThat(
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(ByteString.copyFrom(baos.toByteArray()).substring(12, 16)),
FieldLiteDescriptor.Type.SFIXED32.getNumber(),
"custom.Message",
@@ -311,7 +356,7 @@ public void decodeWireEntries_packedFixedAndSFixed() throws Exception {
.isEqualTo(ImmutableList.of(-30L));
assertThat(
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(ByteString.copyFrom(baos.toByteArray()).substring(16, 24)),
FieldLiteDescriptor.Type.SFIXED64.getNumber(),
"custom.Message",
@@ -328,7 +373,7 @@ public void decodeWireEntries_packedBoolFloatDoubleEnum() throws Exception {
cosBool.flush();
assertThat(
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(ByteString.copyFrom(baosBool.toByteArray())),
FieldLiteDescriptor.Type.BOOL.getNumber(),
"custom.Message",
@@ -341,7 +386,7 @@ public void decodeWireEntries_packedBoolFloatDoubleEnum() throws Exception {
cosFloat.flush();
assertThat(
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(ByteString.copyFrom(baosFloat.toByteArray())),
FieldLiteDescriptor.Type.FLOAT.getNumber(),
"custom.Message",
@@ -354,7 +399,7 @@ public void decodeWireEntries_packedBoolFloatDoubleEnum() throws Exception {
cosDouble.flush();
assertThat(
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(ByteString.copyFrom(baosDouble.toByteArray())),
FieldLiteDescriptor.Type.DOUBLE.getNumber(),
"custom.Message",
@@ -367,7 +412,7 @@ public void decodeWireEntries_packedBoolFloatDoubleEnum() throws Exception {
cosEnum.flush();
assertThat(
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(ByteString.copyFrom(baosEnum.toByteArray())),
FieldLiteDescriptor.Type.ENUM.getNumber(),
"custom.Message",
@@ -378,99 +423,72 @@ public void decodeWireEntries_packedBoolFloatDoubleEnum() throws Exception {
@Test
public void decodeWireValue_allScalarWireTypes() {
assertThat(
- RawProtoMessageLiteValue.decodeWireValue(
+ decodeWireValue(
Double.doubleToRawLongBits(2.5d), WireFormat.FieldType.DOUBLE, "custom.Message"))
.isEqualTo(2.5d);
assertThat(
- RawProtoMessageLiteValue.decodeWireValue(
+ 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(decodeWireValue(42L, WireFormat.FieldType.INT64, "custom.Message")).isEqualTo(42L);
- assertThat(
- RawProtoMessageLiteValue.decodeWireValue(
- 42L, WireFormat.FieldType.INT32, "custom.Message"))
- .isEqualTo(42L);
+ assertThat(decodeWireValue(42L, WireFormat.FieldType.INT32, "custom.Message")).isEqualTo(42L);
- assertThat(
- RawProtoMessageLiteValue.decodeWireValue(
- 42L, WireFormat.FieldType.UINT64, "custom.Message"))
+ assertThat(decodeWireValue(42L, WireFormat.FieldType.UINT64, "custom.Message"))
.isEqualTo(UnsignedLong.fromLongBits(42L));
- assertThat(
- RawProtoMessageLiteValue.decodeWireValue(
- 42L, WireFormat.FieldType.UINT32, "custom.Message"))
+ assertThat(decodeWireValue(42L, WireFormat.FieldType.UINT32, "custom.Message"))
.isEqualTo(UnsignedLong.fromLongBits(42L));
- assertThat(
- RawProtoMessageLiteValue.decodeWireValue(
- 100, WireFormat.FieldType.FIXED32, "custom.Message"))
+ assertThat(decodeWireValue(100, WireFormat.FieldType.FIXED32, "custom.Message"))
.isEqualTo(UnsignedLong.fromLongBits(100L));
- assertThat(
- RawProtoMessageLiteValue.decodeWireValue(
- 100L, WireFormat.FieldType.FIXED64, "custom.Message"))
+ assertThat(decodeWireValue(100L, WireFormat.FieldType.FIXED64, "custom.Message"))
.isEqualTo(UnsignedLong.fromLongBits(100L));
- assertThat(
- RawProtoMessageLiteValue.decodeWireValue(
- -50, WireFormat.FieldType.SFIXED32, "custom.Message"))
+ assertThat(decodeWireValue(-50, WireFormat.FieldType.SFIXED32, "custom.Message"))
.isEqualTo(-50L);
- assertThat(
- RawProtoMessageLiteValue.decodeWireValue(
- -50L, WireFormat.FieldType.SFIXED64, "custom.Message"))
+ assertThat(decodeWireValue(-50L, WireFormat.FieldType.SFIXED64, "custom.Message"))
.isEqualTo(-50L);
- assertThat(
- RawProtoMessageLiteValue.decodeWireValue(
- 1L, WireFormat.FieldType.BOOL, "custom.Message"))
- .isEqualTo(true);
+ assertThat(decodeWireValue(1L, WireFormat.FieldType.BOOL, "custom.Message")).isEqualTo(true);
- assertThat(
- RawProtoMessageLiteValue.decodeWireValue(
- 0L, WireFormat.FieldType.BOOL, "custom.Message"))
- .isEqualTo(false);
+ assertThat(decodeWireValue(0L, WireFormat.FieldType.BOOL, "custom.Message")).isEqualTo(false);
assertThat(
- RawProtoMessageLiteValue.decodeWireValue(
+ decodeWireValue(
ByteString.copyFromUtf8("hello"), WireFormat.FieldType.STRING, "custom.Message"))
.isEqualTo("hello");
assertThat(
- RawProtoMessageLiteValue.decodeWireValue(
+ decodeWireValue(
ByteString.copyFromUtf8("bytes"), WireFormat.FieldType.BYTES, "custom.Message"))
.isEqualTo(CelByteString.of("bytes".getBytes(UTF_8)));
assertThat(
- RawProtoMessageLiteValue.decodeWireValue(
+ decodeWireValue(
1L, // zigzag 1 -> -1
WireFormat.FieldType.SINT32,
"custom.Message"))
.isEqualTo(-1L);
assertThat(
- RawProtoMessageLiteValue.decodeWireValue(
+ decodeWireValue(
1L, // zigzag 1 -> -1
WireFormat.FieldType.SINT64,
"custom.Message"))
.isEqualTo(-1L);
- assertThat(
- RawProtoMessageLiteValue.decodeWireValue(
- 3L, WireFormat.FieldType.ENUM, "custom.Message"))
- .isEqualTo(3L);
+ assertThat(decodeWireValue(3L, WireFormat.FieldType.ENUM, "custom.Message")).isEqualTo(3L);
}
@Test
public void decodeWireValue_messageType_returnsRawProtoMessageLiteValue() {
Object submessage =
- RawProtoMessageLiteValue.decodeWireValue(
+ decodeWireValue(
ByteString.copyFromUtf8("raw"), WireFormat.FieldType.MESSAGE, "sub.Message");
assertThat(submessage).isInstanceOf(RawProtoMessageLiteValue.class);
@@ -484,9 +502,7 @@ public void decodeWireValue_groupType_throwsUnsupportedOperationException() {
UnsupportedOperationException thrown =
assertThrows(
UnsupportedOperationException.class,
- () ->
- RawProtoMessageLiteValue.decodeWireValue(
- rawBytes, WireFormat.FieldType.GROUP, "group.Message"));
+ () -> decodeWireValue(rawBytes, WireFormat.FieldType.GROUP, "group.Message"));
assertThat(thrown).hasMessageThat().contains("Groups are not supported");
}
@@ -500,7 +516,7 @@ public void decodeWireEntries_groupType_throwsUnsupportedOperationException() {
assertThrows(
UnsupportedOperationException.class,
() ->
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
rawEntries, groupTypeCode, "group.Message", /* isRepeated= */ false));
assertThat(thrown).hasMessageThat().contains("Groups are not supported");
@@ -512,30 +528,22 @@ public void decodeWireEntries_invalidTypeCode_throwsIllegalArgumentException() {
assertThrows(
IllegalArgumentException.class,
- () ->
- RawProtoMessageLiteValue.decodeWireEntries(
- rawEntries, 999, "custom.Message", /* isRepeated= */ false));
+ () -> 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, () -> decodeWireValue(42L, 0, "custom.Message"));
- assertThrows(
- IllegalArgumentException.class,
- () -> RawProtoMessageLiteValue.decodeWireValue(42L, 999, "custom.Message"));
+ assertThrows(IllegalArgumentException.class, () -> decodeWireValue(42L, 999, "custom.Message"));
}
@Test
public void decodeWireValue_int32HighBits_truncatedToSigned32Bit() {
Object decodedHigh =
- RawProtoMessageLiteValue.decodeWireValue(
- 0x100000005L, WireFormat.FieldType.INT32, "custom.Message");
+ decodeWireValue(0x100000005L, WireFormat.FieldType.INT32, "custom.Message");
Object decodedNegative =
- RawProtoMessageLiteValue.decodeWireValue(
- 0xFFFFFFFF80000000L, WireFormat.FieldType.INT32, "custom.Message");
+ decodeWireValue(0xFFFFFFFF80000000L, WireFormat.FieldType.INT32, "custom.Message");
assertThat(decodedHigh).isEqualTo(5L);
assertThat(decodedNegative).isEqualTo(-2147483648L);
@@ -543,9 +551,7 @@ public void decodeWireValue_int32HighBits_truncatedToSigned32Bit() {
@Test
public void decodeWireValue_enumHighBits_truncatedToSigned32Bit() {
- Object decodedHigh =
- RawProtoMessageLiteValue.decodeWireValue(
- 0x100000005L, WireFormat.FieldType.ENUM, "custom.Message");
+ Object decodedHigh = decodeWireValue(0x100000005L, WireFormat.FieldType.ENUM, "custom.Message");
assertThat(decodedHigh).isEqualTo(5L);
}
@@ -555,33 +561,25 @@ public void decodeWireValue_typeMismatch_throwsIllegalArgumentException() {
IllegalArgumentException thrownInt64 =
assertThrows(
IllegalArgumentException.class,
- () ->
- RawProtoMessageLiteValue.decodeWireValue(
- "not a long", WireFormat.FieldType.INT64, "custom.Message"));
+ () -> 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"));
+ () -> 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"));
+ () -> 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"));
+ () -> decodeWireValue(100L, WireFormat.FieldType.MESSAGE, "custom.Message"));
assertThat(thrownMessage)
.hasMessageThat()
.contains("Expected ByteString for wire type MESSAGE");
@@ -589,17 +587,13 @@ public void decodeWireValue_typeMismatch_throwsIllegalArgumentException() {
IllegalArgumentException thrownFloat =
assertThrows(
IllegalArgumentException.class,
- () ->
- RawProtoMessageLiteValue.decodeWireValue(
- 100L, WireFormat.FieldType.FLOAT, "custom.Message"));
+ () -> 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"));
+ () -> decodeWireValue(100, WireFormat.FieldType.DOUBLE, "custom.Message"));
assertThat(thrownDouble).hasMessageThat().contains("Expected Long for wire type DOUBLE");
}
@@ -610,9 +604,7 @@ public void decodeWireValue_invalidUtf8String_throwsIllegalArgumentException() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
- () ->
- RawProtoMessageLiteValue.decodeWireValue(
- invalidUtf8, WireFormat.FieldType.STRING, "custom.Message"));
+ () -> decodeWireValue(invalidUtf8, WireFormat.FieldType.STRING, "custom.Message"));
assertThat(thrown).hasMessageThat().contains("Invalid UTF-8 in string field");
}
@@ -631,7 +623,7 @@ public void decodeWireEntries_multiChunkPackedRepeated() throws Exception {
cos2.flush();
Object decoded =
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(
ByteString.copyFrom(baos1.toByteArray()), ByteString.copyFrom(baos2.toByteArray())),
FieldLiteDescriptor.Type.INT32.getNumber(),
@@ -650,7 +642,7 @@ public void decodeWireEntries_mixedPackedAndUnpackedRepeated() throws Exception
cos.flush();
Object decoded =
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(1L, ByteString.copyFrom(baos.toByteArray()), 4L),
FieldLiteDescriptor.Type.INT32.getNumber(),
"custom.Message",
@@ -672,7 +664,7 @@ public void decodeWireEntries_singularMessage_mergesChunks() throws Exception {
cos2.flush();
Object decoded =
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(
ByteString.copyFrom(baos1.toByteArray()), ByteString.copyFrom(baos2.toByteArray())),
FieldLiteDescriptor.Type.MESSAGE.getNumber(),
@@ -687,18 +679,14 @@ public void decodeWireEntries_singularMessage_mergesChunks() throws Exception {
@Test
public void decodeWireValue_uint32HighBit_correctUnsignedLong() {
- Object decoded =
- RawProtoMessageLiteValue.decodeWireValue(
- 0xFFFFFFFFL, WireFormat.FieldType.UINT32, "custom.Message");
+ Object decoded = 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");
+ Object decoded = decodeWireValue(-1, WireFormat.FieldType.FIXED32, "custom.Message");
assertThat(decoded).isEqualTo(UnsignedLong.valueOf(4294967295L));
}
@@ -706,7 +694,7 @@ public void decodeWireValue_fixed32HighBit_correctUnsignedLong() {
@Test
public void decodeWireEntries_repeatedString() {
Object decoded =
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(ByteString.copyFromUtf8("foo"), ByteString.copyFromUtf8("bar")),
FieldLiteDescriptor.Type.STRING.getNumber(),
"custom.Message",
@@ -718,7 +706,7 @@ public void decodeWireEntries_repeatedString() {
@Test
public void decodeWireEntries_repeatedBytes() {
Object decoded =
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(ByteString.copyFromUtf8("foo"), ByteString.copyFromUtf8("bar")),
FieldLiteDescriptor.Type.BYTES.getNumber(),
"custom.Message",
@@ -733,7 +721,7 @@ public void decodeWireEntries_repeatedBytes() {
@Test
public void decodeWireEntries_repeatedMessage() {
Object decoded =
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(ByteString.copyFromUtf8("msg1"), ByteString.copyFromUtf8("msg2")),
FieldLiteDescriptor.Type.MESSAGE.getNumber(),
"sub.Message",
@@ -741,8 +729,10 @@ public void decodeWireEntries_repeatedMessage() {
assertThat((Iterable>) decoded)
.containsExactly(
- RawProtoMessageLiteValue.create(ByteString.copyFromUtf8("msg1"), "sub.Message"),
- RawProtoMessageLiteValue.create(ByteString.copyFromUtf8("msg2"), "sub.Message"))
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFromUtf8("msg1"), "sub.Message", EMPTY_CONVERTER),
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFromUtf8("msg2"), "sub.Message", EMPTY_CONVERTER))
.inOrder();
}
@@ -755,7 +745,7 @@ public void decodeWireEntries_packedTruncated_throwsIllegalStateException() {
assertThrows(
IllegalStateException.class,
() ->
- RawProtoMessageLiteValue.decodeWireEntries(
+ decodeWireEntries(
ImmutableList.of(truncated),
FieldLiteDescriptor.Type.INT32.getNumber(),
"custom.Message",
@@ -763,4 +753,450 @@ public void decodeWireEntries_packedTruncated_throwsIllegalStateException() {
assertThat(thrown).hasMessageThat().contains("Failed to parse packed repeated field");
}
+
+ @Test
+ public void selectByFieldNumber_presentOnWire_decoded() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeString(14, "hello");
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ EMPTY_CONVERTER);
+
+ Object val = raw.selectByFieldNumber(SelectField.create(14L, "single_string", 9, ""));
+
+ assertThat(val).isEqualTo("hello");
+ }
+
+ @Test
+ public void selectByFieldNumber_absentWithDefaultValue_returnsDefault() {
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.EMPTY, "cel.expr.conformance.proto3.TestAllTypes", EMPTY_CONVERTER);
+
+ Object val = raw.selectByFieldNumber(SelectField.create(14L, "single_string", 9, "default"));
+
+ assertThat(val).isEqualTo("default");
+ }
+
+ @Test
+ public void selectByFieldNumber_withConverter_resolvesDescriptor() {
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.EMPTY, "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+
+ Object val = raw.selectByFieldNumber(SelectField.create(14L, "single_string"));
+
+ assertThat(val).isEqualTo("");
+ }
+
+ @Test
+ public void
+ selectByFieldNumber_absentSubmessageWithMissingChildDescriptor_returnsEmptyRawProtoMessageLiteValue() {
+ CelLiteDescriptorPool poolWithoutNested =
+ new CelLiteDescriptorPool() {
+ @Override
+ public Optional findDescriptor(String protoTypeName) {
+ if (protoTypeName.equals("cel.expr.conformance.proto3.TestAllTypes")) {
+ return DefaultLiteDescriptorPool.newInstance(
+ ImmutableSet.of(TestAllTypesCelDescriptor.getDescriptor()))
+ .findDescriptor(protoTypeName);
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional findDescriptor(MessageLite messageLite) {
+ return Optional.empty();
+ }
+
+ @Override
+ public MessageLiteDescriptor getDescriptorOrThrow(String protoTypeName) {
+ return findDescriptor(protoTypeName)
+ .orElseThrow(() -> new NoSuchElementException(protoTypeName));
+ }
+ };
+ ProtoLiteCelValueConverter converter =
+ ProtoLiteCelValueConverter.newInstance(poolWithoutNested);
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.EMPTY, "cel.expr.conformance.proto3.TestAllTypes", converter);
+
+ Object val = raw.selectByFieldNumber(SelectField.create(21L, "single_nested_message"));
+
+ assertThat(val).isInstanceOf(RawProtoMessageLiteValue.class);
+ RawProtoMessageLiteValue rawChild = (RawProtoMessageLiteValue) val;
+ assertThat(rawChild.rawWireBytes()).isEqualTo(ByteString.EMPTY);
+ assertThat(rawChild.celType().name())
+ .isEqualTo("cel.expr.conformance.proto3.TestAllTypes.NestedMessage");
+ }
+
+ @Test
+ public void
+ selectByFieldNumber_absentRepeatedMessageWithMissingChildDescriptor_returnsEmptyList() {
+ CelLiteDescriptorPool poolWithoutNested =
+ new CelLiteDescriptorPool() {
+ @Override
+ public Optional findDescriptor(String protoTypeName) {
+ if (protoTypeName.equals("cel.expr.conformance.proto3.TestAllTypes")) {
+ return DefaultLiteDescriptorPool.newInstance(
+ ImmutableSet.of(TestAllTypesCelDescriptor.getDescriptor()))
+ .findDescriptor(protoTypeName);
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional findDescriptor(MessageLite messageLite) {
+ return Optional.empty();
+ }
+
+ @Override
+ public MessageLiteDescriptor getDescriptorOrThrow(String protoTypeName) {
+ return findDescriptor(protoTypeName)
+ .orElseThrow(() -> new NoSuchElementException(protoTypeName));
+ }
+ };
+ ProtoLiteCelValueConverter converter =
+ ProtoLiteCelValueConverter.newInstance(poolWithoutNested);
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.EMPTY, "cel.expr.conformance.proto3.TestAllTypes", converter);
+
+ Object val =
+ raw.selectByFieldNumber(
+ SelectField.create(
+ TestAllTypes.REPEATED_NESTED_MESSAGE_FIELD_NUMBER, "repeated_nested_message"));
+
+ assertThat(val).isEqualTo(ImmutableList.of());
+ }
+
+ @Test
+ public void selectByFieldNumber_unknownFieldWithoutTypeCode_throwsCelAttributeNotFoundException()
+ throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeString(999, "unknown");
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ CONVERTER);
+ SelectField selectField = SelectField.create(999L, "unknown_field");
+
+ assertThrows(CelAttributeNotFoundException.class, () -> raw.selectByFieldNumber(selectField));
+ }
+
+ @Test
+ public void hasFieldByNumber_wirePresent_returnsTrue() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeString(TestAllTypes.SINGLE_STRING_FIELD_NUMBER, "present");
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ EMPTY_CONVERTER);
+
+ assertThat(
+ raw.hasFieldByNumber(
+ SelectField.create(TestAllTypes.SINGLE_STRING_FIELD_NUMBER, "single_string")))
+ .isTrue();
+ }
+
+ @Test
+ public void hasFieldByNumber_wireAbsent_returnsFalse() {
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.EMPTY, "cel.expr.conformance.proto3.TestAllTypes", EMPTY_CONVERTER);
+
+ assertThat(
+ raw.hasFieldByNumber(
+ SelectField.create(TestAllTypes.SINGLE_STRING_FIELD_NUMBER, "single_string")))
+ .isFalse();
+ }
+
+ @Test
+ public void hasFieldByNumber_emptyPackedRepeated_returnsFalse(
+ @TestParameter boolean withDescriptor) throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeBytes(TestAllTypes.REPEATED_INT32_FIELD_NUMBER, ByteString.EMPTY);
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ withDescriptor ? CONVERTER : EMPTY_CONVERTER);
+ SelectField selectField =
+ withDescriptor
+ ? SelectField.create(TestAllTypes.REPEATED_INT32_FIELD_NUMBER, "repeated_int32")
+ : SelectField.create(
+ TestAllTypes.REPEATED_INT32_FIELD_NUMBER,
+ "repeated_int32",
+ FieldLiteDescriptor.Type.INT32.getNumber(),
+ ImmutableList.of());
+
+ assertThat(raw.hasFieldByNumber(selectField)).isFalse();
+ }
+
+ @Test
+ public void hasFieldByNumber_nonEmptyPackedRepeated_returnsTrue() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ ByteArrayOutputStream packed = new ByteArrayOutputStream();
+ CodedOutputStream packedCos = CodedOutputStream.newInstance(packed);
+ packedCos.writeInt32NoTag(42);
+ packedCos.flush();
+ cos.writeBytes(
+ TestAllTypes.REPEATED_INT32_FIELD_NUMBER, ByteString.copyFrom(packed.toByteArray()));
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ CONVERTER);
+
+ assertThat(
+ raw.hasFieldByNumber(
+ SelectField.create(TestAllTypes.REPEATED_INT32_FIELD_NUMBER, "repeated_int32")))
+ .isTrue();
+ }
+
+ @Test
+ public void hasFieldByNumber_emptyByteStringOnScalarPackableField_returnsTrue(
+ @TestParameter boolean withDescriptor) throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeByteArray(TestAllTypes.SINGLE_INT32_FIELD_NUMBER, new byte[0]);
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ withDescriptor ? CONVERTER : EMPTY_CONVERTER);
+ SelectField selectField =
+ withDescriptor
+ ? SelectField.create(TestAllTypes.SINGLE_INT32_FIELD_NUMBER, "single_int32")
+ : SelectField.create(
+ TestAllTypes.SINGLE_INT32_FIELD_NUMBER,
+ "single_int32",
+ FieldLiteDescriptor.Type.INT32.getNumber(),
+ 0);
+
+ assertThat(raw.hasFieldByNumber(selectField)).isTrue();
+ }
+
+ @Test
+ public void findByFieldNumber_intermediatePresent_returnsSubmessage() throws Exception {
+ ByteArrayOutputStream subBaos1 = new ByteArrayOutputStream();
+ CodedOutputStream subCos1 = CodedOutputStream.newInstance(subBaos1);
+ subCos1.writeInt32(1, 42);
+ subCos1.flush();
+
+ ByteArrayOutputStream subBaos2 = new ByteArrayOutputStream();
+ CodedOutputStream subCos2 = CodedOutputStream.newInstance(subBaos2);
+ subCos2.writeInt32(2, 84);
+ subCos2.flush();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CodedOutputStream cos = CodedOutputStream.newInstance(baos);
+ cos.writeBytes(21, ByteString.copyFrom(subBaos1.toByteArray()));
+ cos.writeBytes(21, ByteString.copyFrom(subBaos2.toByteArray()));
+ cos.flush();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(baos.toByteArray()),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ EMPTY_CONVERTER);
+
+ Optional nav = raw.findByFieldNumber(SelectField.create(21L, "single_nested_message"));
+
+ RawProtoMessageLiteValue expected =
+ RawProtoMessageLiteValue.create(
+ ByteString.copyFrom(subBaos1.toByteArray())
+ .concat(ByteString.copyFrom(subBaos2.toByteArray())),
+ "cel.@unknownMessage",
+ EMPTY_CONVERTER);
+ assertThat(nav).hasValue(expected);
+ }
+
+ @Test
+ public void findByFieldNumber_intermediateAbsent_returnsEmpty() {
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.EMPTY, "cel.expr.conformance.proto3.TestAllTypes", EMPTY_CONVERTER);
+
+ Optional nav = raw.findByFieldNumber(SelectField.create(999L, "absent"));
+
+ assertThat(nav).isEmpty();
+ }
+
+ @SuppressWarnings("ImmutableEnumChecker") // Test only
+ private enum SelectByFieldNumberTestCase {
+ INT64(SelectField.create(TestAllTypes.SINGLE_INT64_FIELD_NUMBER, "single_int64"), 99L),
+ STRING(SelectField.create(TestAllTypes.SINGLE_STRING_FIELD_NUMBER, "single_string"), "hello"),
+ MAP_STRING_STRING(
+ SelectField.create(TestAllTypes.MAP_STRING_STRING_FIELD_NUMBER, "map_string_string"),
+ ImmutableMap.of("k1", "v1", "k2", "v2")),
+ MAP_INT32_BYTES(
+ SelectField.create(TestAllTypes.MAP_INT32_BYTES_FIELD_NUMBER, "map_int32_bytes"),
+ ImmutableMap.of(
+ 0L, CelByteString.copyFromUtf8("val_for_default_key"), 42L, CelByteString.EMPTY)),
+ DURATION(
+ SelectField.create(TestAllTypes.SINGLE_DURATION_FIELD_NUMBER, "single_duration"),
+ Duration.ofSeconds(10L, 500L)),
+ INT64_WRAPPER(
+ SelectField.create(TestAllTypes.SINGLE_INT64_WRAPPER_FIELD_NUMBER, "single_int64_wrapper"),
+ 12345L);
+
+ private final SelectField selectField;
+ private final Object expectedValue;
+
+ private SelectByFieldNumberTestCase(SelectField selectField, Object expectedValue) {
+ this.selectField = selectField;
+ this.expectedValue = expectedValue;
+ }
+ }
+
+ @Test
+ public void selectByFieldNumber_withDescriptor_decodesExpectedValue(
+ @TestParameter SelectByFieldNumberTestCase testCase) {
+ TestAllTypes proto =
+ TestAllTypes.newBuilder()
+ .setSingleInt64(99L)
+ .setSingleString("hello")
+ .putMapStringString("k1", "v1")
+ .putMapStringString("k2", "v2")
+ .putMapInt32Bytes(0, ByteString.copyFromUtf8("val_for_default_key"))
+ .putMapInt32Bytes(42, ByteString.EMPTY)
+ .setSingleDuration(ProtoTimeUtils.toProtoDuration(Duration.ofSeconds(10L, 500L)))
+ .setSingleInt64Wrapper(Int64Value.of(12345L))
+ .build();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ proto.toByteString(), "cel.expr.conformance.proto3.TestAllTypes", CONVERTER);
+
+ Object selected = raw.selectByFieldNumber(testCase.selectField);
+
+ assertThat(selected).isEqualTo(testCase.expectedValue);
+ }
+
+ @Test
+ public void findByFieldNumber_scalarField_returnsScalar(@TestParameter boolean withDescriptor) {
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleInt64(99L).build();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ proto.toByteString(),
+ "cel.expr.conformance.proto3.TestAllTypes",
+ withDescriptor ? CONVERTER : EMPTY_CONVERTER);
+
+ Optional nav =
+ raw.findByFieldNumber(
+ SelectField.create(TestAllTypes.SINGLE_INT64_FIELD_NUMBER, "single_int64"));
+
+ assertThat(nav).hasValue(99L);
+ }
+
+ @Test
+ public void selectByFieldNumber_unsetWrapperFieldWithoutWrapperDescriptor_returnsNullValue() {
+ MessageLiteDescriptor testAllTypesDesc =
+ TestAllTypesCelDescriptor.getDescriptor()
+ .getProtoTypeNamesToDescriptors()
+ .get("cel.expr.conformance.proto3.TestAllTypes");
+ CelLiteDescriptorPool poolWithoutWrappers =
+ new CelLiteDescriptorPool() {
+ @Override
+ public Optional findDescriptor(String protoTypeName) {
+ if (protoTypeName.equals(testAllTypesDesc.getProtoTypeName())) {
+ return Optional.of(testAllTypesDesc);
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional findDescriptor(MessageLite messageLite) {
+ return findDescriptor(messageLite.getClass().getName());
+ }
+
+ @Override
+ public MessageLiteDescriptor getDescriptorOrThrow(String protoTypeName) {
+ return findDescriptor(protoTypeName)
+ .orElseThrow(() -> new NoSuchElementException(protoTypeName));
+ }
+ };
+ ProtoLiteCelValueConverter converter =
+ ProtoLiteCelValueConverter.newInstance(poolWithoutWrappers);
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ ByteString.EMPTY, "cel.expr.conformance.proto3.TestAllTypes", converter);
+
+ Object result =
+ raw.selectByFieldNumber(
+ SelectField.create(
+ TestAllTypes.SINGLE_INT64_WRAPPER_FIELD_NUMBER, "single_int64_wrapper"));
+
+ assertThat(result).isEqualTo(NullValue.NULL_VALUE);
+ }
+
+ @Test
+ public void findByFieldNumber_typedFieldWithoutDescriptor_returnsSelectedValue() {
+ TestAllTypes proto = TestAllTypes.newBuilder().setSingleUint32(123).build();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ proto.toByteString(), "cel.expr.conformance.proto3.TestAllTypes", EMPTY_CONVERTER);
+
+ Optional nav =
+ raw.findByFieldNumber(
+ SelectField.create(
+ TestAllTypes.SINGLE_UINT32_FIELD_NUMBER,
+ "single_uint32",
+ FieldLiteDescriptor.Type.UINT32.getNumber(),
+ 0L));
+
+ assertThat(nav).hasValue(UnsignedLong.fromLongBits(123L));
+ }
+
+ @Test
+ public void selectByFieldNumber_absentMessageFieldWithoutDescriptor_returnsUnknownMessage() {
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(ByteString.EMPTY, EMPTY_CONVERTER);
+
+ Object selected =
+ raw.selectByFieldNumber(
+ SelectField.create(
+ 21L, "single_nested_message", FieldLiteDescriptor.Type.MESSAGE.getNumber(), null));
+
+ assertThat(selected).isInstanceOf(RawProtoMessageLiteValue.class);
+ RawProtoMessageLiteValue message = (RawProtoMessageLiteValue) selected;
+ assertThat(message.rawWireBytes()).isEqualTo(ByteString.EMPTY);
+ assertThat(message.celType().name()).isEqualTo("cel.@unknownMessage");
+ }
+
+ @Test
+ public void
+ selectByFieldNumber_unknownMapFieldWithWireEntries_throwsUnsupportedOperationException() {
+ TestAllTypes proto = TestAllTypes.newBuilder().putMapStringString("key", "val").build();
+ RawProtoMessageLiteValue raw =
+ RawProtoMessageLiteValue.create(
+ proto.toByteString(), "cel.expr.conformance.proto3.TestAllTypes", EMPTY_CONVERTER);
+ SelectField field =
+ SelectField.create(
+ TestAllTypes.MAP_STRING_STRING_FIELD_NUMBER,
+ "map_string_string",
+ SelectField.CEL_MAP_TYPE_CODE,
+ null);
+
+ UnsupportedOperationException e =
+ assertThrows(UnsupportedOperationException.class, () -> raw.selectByFieldNumber(field));
+
+ assertThat(e)
+ .hasMessageThat()
+ .contains("Decoding unknown map field from wire bytes is unsupported");
+ }
}
diff --git a/common/src/test/java/dev/cel/common/values/SelectFieldTest.java b/common/src/test/java/dev/cel/common/values/SelectFieldTest.java
new file mode 100644
index 000000000..ba9dc7008
--- /dev/null
+++ b/common/src/test/java/dev/cel/common/values/SelectFieldTest.java
@@ -0,0 +1,133 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dev.cel.common.values;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class SelectFieldTest {
+
+ @Test
+ public void create_twoArguments_success() {
+ SelectField field = SelectField.create(1L, "foo");
+
+ assertThat(field.fieldNumber()).isEqualTo(1);
+ assertThat(field.fieldName()).isEqualTo("foo");
+ assertThat(field.typeCode()).isEqualTo(SelectField.NO_TYPE_CODE);
+ assertThat(field.defaultValue()).isNull();
+ }
+
+ @Test
+ public void create_fourArguments_success() {
+ SelectField field = SelectField.create(2L, "bar", 9, "default_str");
+
+ assertThat(field.fieldNumber()).isEqualTo(2);
+ assertThat(field.fieldName()).isEqualTo("bar");
+ assertThat(field.typeCode()).isEqualTo(9);
+ assertThat(field.defaultValue()).isEqualTo("default_str");
+ }
+
+ @Test
+ public void create_mapTypeCode_success() {
+ SelectField field = SelectField.create(3L, "map_field", -1, null);
+
+ assertThat(field.typeCode()).isEqualTo(-1);
+ }
+
+ @Test
+ public void create_twoArgNullFieldName_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> SelectField.create(1L, null));
+ }
+
+ @Test
+ public void create_fourArgNullFieldName_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> SelectField.create(1L, null, 9, null));
+ }
+
+ @Test
+ public void create_fieldNumberBelowMinimum_throwsIllegalArgumentException() {
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> SelectField.create(0L, "foo"));
+
+ assertThat(thrown).hasMessageThat().contains("Field number out of protobuf range: 0");
+ }
+
+ @Test
+ public void create_fieldNumberNegative_throwsIllegalArgumentException() {
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> SelectField.create(-1L, "foo"));
+
+ assertThat(thrown).hasMessageThat().contains("Field number out of protobuf range: -1");
+ }
+
+ @Test
+ public void create_fieldNumberAboveMaximum_throwsIllegalArgumentException() {
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> SelectField.create(536870912L, "foo"));
+
+ assertThat(thrown).hasMessageThat().contains("Field number out of protobuf range: 536870912");
+ }
+
+ @Test
+ public void create_typeCodeZero_throwsIllegalArgumentException() {
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> SelectField.create(1L, "foo", 0, null));
+
+ assertThat(thrown).hasMessageThat().contains("Invalid protobuf type code: 0");
+ }
+
+ @Test
+ public void create_typeCodeAboveMaximum_throwsIllegalArgumentException() {
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> SelectField.create(1L, "foo", 19, null));
+
+ assertThat(thrown).hasMessageThat().contains("Invalid protobuf type code: 19");
+ }
+
+ @Test
+ public void create_typeCodeBelowSentinel_throwsIllegalArgumentException() {
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> SelectField.create(1L, "foo", -2, null));
+
+ assertThat(thrown).hasMessageThat().contains("Invalid protobuf type code: -2");
+ }
+
+ @Test
+ public void create_typeCodeGroupProto_throwsIllegalArgumentException() {
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> SelectField.create(1L, "foo", 10, null));
+
+ assertThat(thrown).hasMessageThat().contains("Invalid protobuf type code: 10");
+ }
+
+ @Test
+ public void equalsAndHashCode_testedProperly() {
+ new EqualsTester()
+ .addEqualityGroup(SelectField.create(1L, "foo"), SelectField.create(1L, "foo"))
+ .addEqualityGroup(SelectField.create(2L, "foo"), SelectField.create(2L, "foo"))
+ .addEqualityGroup(SelectField.create(1L, "bar"), SelectField.create(1L, "bar"))
+ .addEqualityGroup(
+ SelectField.create(1L, "foo", 9, "default"),
+ SelectField.create(1L, "foo", 9, "default"))
+ .addEqualityGroup(SelectField.create(1L, "foo", 9, "other_default"))
+ .testEquals();
+ }
+}
diff --git a/common/values/BUILD.bazel b/common/values/BUILD.bazel
index 9853289a9..192f01de8 100644
--- a/common/values/BUILD.bazel
+++ b/common/values/BUILD.bazel
@@ -126,3 +126,39 @@ cel_android_library(
name = "base_proto_message_value_provider_android",
exports = ["//common/src/main/java/dev/cel/common/values:base_proto_message_value_provider_android"],
)
+
+java_library(
+ name = "select_field",
+ visibility = ["//:internal"],
+ exports = ["//common/src/main/java/dev/cel/common/values:select_field"],
+)
+
+cel_android_library(
+ name = "select_field_android",
+ visibility = ["//:internal"],
+ exports = ["//common/src/main/java/dev/cel/common/values:select_field_android"],
+)
+
+java_library(
+ name = "optimized_selectable",
+ visibility = ["//:internal"],
+ exports = ["//common/src/main/java/dev/cel/common/values:optimized_selectable"],
+)
+
+cel_android_library(
+ name = "optimized_selectable_android",
+ visibility = ["//:internal"],
+ exports = ["//common/src/main/java/dev/cel/common/values:optimized_selectable_android"],
+)
+
+java_library(
+ name = "optimized_select_traversal",
+ visibility = ["//:internal"],
+ exports = ["//common/src/main/java/dev/cel/common/values:optimized_select_traversal"],
+)
+
+cel_android_library(
+ name = "optimized_select_traversal_android",
+ visibility = ["//:internal"],
+ exports = ["//common/src/main/java/dev/cel/common/values:optimized_select_traversal_android"],
+)