Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions common/src/main/java/dev/cel/common/values/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ CEL_VALUES_SOURCES = [
"ErrorValue.java",
"NullValue.java",
"OpaqueValue.java",
"OptimizedSelectTraversal.java",
"OptimizedSelectable.java",
"OptionalValue.java",
"SelectField.java",
"SelectableValue.java",
"StructValue.java",
]
Expand Down Expand Up @@ -167,6 +170,7 @@ java_library(
":preadapted_list",
"//:auto_value",
"//common/annotations",
"//common/exceptions:attribute_not_found",
"//common/types",
"//common/types:type_providers",
"@maven//:com_google_errorprone_error_prone_annotations",
Expand Down Expand Up @@ -218,6 +222,7 @@ cel_android_library(
":preadapted_list_android",
"//:auto_value",
"//common/annotations",
"//common/exceptions:attribute_not_found",
"//common/types:type_providers_android",
"//common/types:types_android",
"@maven//:com_google_errorprone_error_prone_annotations",
Expand Down Expand Up @@ -317,6 +322,7 @@ java_library(
srcs = [
"ProtoLiteCelValueConverter.java",
"ProtoMessageLiteValue.java",
"RawProtoMessageLiteValue.java",
],
tags = [
],
Expand All @@ -325,6 +331,7 @@ java_library(
":values",
"//:auto_value",
"//common/annotations",
"//common/exceptions:attribute_not_found",
"//common/internal:cel_lite_descriptor_pool",
"//common/internal:well_known_proto",
"//common/types",
Expand All @@ -333,6 +340,7 @@ java_library(
"//protobuf:cel_lite_descriptor",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:org_jspecify_jspecify",
"@maven_android//:com_google_protobuf_protobuf_javalite",
],
)
Expand All @@ -342,6 +350,7 @@ cel_android_library(
srcs = [
"ProtoLiteCelValueConverter.java",
"ProtoMessageLiteValue.java",
"RawProtoMessageLiteValue.java",
],
tags = [
],
Expand All @@ -350,6 +359,7 @@ cel_android_library(
":values_android",
"//:auto_value",
"//common/annotations",
"//common/exceptions:attribute_not_found",
"//common/internal:cel_lite_descriptor_pool_android",
"//common/internal:well_known_proto_android",
"//common/types:type_providers_android",
Expand All @@ -358,6 +368,7 @@ cel_android_library(
"//protobuf:cel_lite_descriptor",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:org_jspecify_jspecify",
"@maven_android//:com_google_guava_guava",
"@maven_android//:com_google_protobuf_protobuf_javalite",
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package dev.cel.common.values;

import com.google.common.collect.ImmutableList;
import dev.cel.common.annotations.Internal;
import dev.cel.common.exceptions.CelAttributeNotFoundException;
import java.util.Map;
import java.util.Optional;
import org.jspecify.annotations.Nullable;

/**
* Walks a chain of {@link SelectField} hops, dispatching each hop over {@link OptimizedSelectable},
* {@link SelectableValue} or {@link Map}.
*
* <p>CEL Library Internals. Do Not Use.
*/
@Internal
public final class OptimizedSelectTraversal {

/**
* Qualifies {@code target} through every hop in {@code fields} and returns the terminal value.
*
* @param celValueConverter Converter for hops not resolved by an {@link OptimizedSelectable};
* superseded by {@link OptimizedSelectable#celValueConverter} once the chain crosses one.
*/
public static Object qualify(
Object target, ImmutableList<SelectField> fields, CelValueConverter celValueConverter) {
Object current = target;
CelValueConverter converter = celValueConverter;
for (int i = 0; i < fields.size(); i++) {
converter = converterFor(current, converter);
current = qualifyHop(current, fields.get(i), converter);
}
return current;
}

/**
* Presence tests the terminal hop of {@code fields}, navigating through all preceding hops.
*
* <p>Absence of any intermediate hop short-circuits to {@code false}.
*/
public static boolean hasField(
Object target, ImmutableList<SelectField> fields, CelValueConverter celValueConverter) {
if (fields.isEmpty()) {
return false;
}
Object current = target;
CelValueConverter converter = celValueConverter;
int terminalIndex = fields.size() - 1;
for (int i = 0; i < terminalIndex; i++) {
converter = converterFor(current, converter);
current = navigateHop(current, fields.get(i), converter);
if (current == null) {
return false;
}
}
return hasTerminalHop(current, fields.get(terminalIndex));
}

private static Object qualifyHop(
Object target, SelectField field, CelValueConverter celValueConverter) {
if (target instanceof OptimizedSelectable) {
return ((OptimizedSelectable) target).optimizedSelect(field);
}
if (target instanceof SelectableValue) {
Optional<Object> found =
SelectField.findField((SelectableValue<?>) target, field.fieldName());
if (found.isPresent()) {
return SelectField.toStepTarget(found.get(), celValueConverter);
}
if (field.defaultValue() != null) {
return field.defaultValue();
}
throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
}
if (target instanceof Map) {
Map<?, ?> map = (Map<?, ?>) target;
Object mapValue = map.get(field.fieldName());
if (mapValue != null) {
return SelectField.toStepTarget(mapValue, celValueConverter);
}
if (map.containsKey(field.fieldName())) {
return NullValue.NULL_VALUE;
}
throw CelAttributeNotFoundException.forMissingMapKey(field.fieldName());
}
throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
}

private static @Nullable Object navigateHop(
Object target, SelectField field, CelValueConverter celValueConverter) {
if (target instanceof OptimizedSelectable) {
return ((OptimizedSelectable) target).optimizedFind(field).orElse(null);
}
if (target instanceof SelectableValue) {
Optional<Object> found =
SelectField.findField((SelectableValue<?>) target, field.fieldName());
return found.isPresent() ? SelectField.toStepTarget(found.get(), celValueConverter) : null;
}
if (target instanceof Map) {
Map<?, ?> map = (Map<?, ?>) target;
Object mapValue = map.get(field.fieldName());
if (mapValue != null) {
return SelectField.toStepTarget(mapValue, celValueConverter);
}
return map.containsKey(field.fieldName()) ? NullValue.NULL_VALUE : null;
}
throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
}

private static boolean hasTerminalHop(Object target, SelectField field) {
if (target instanceof OptimizedSelectable) {
return ((OptimizedSelectable) target).optimizedHasField(field);
}
if (target instanceof SelectableValue) {
return SelectField.findField((SelectableValue<?>) target, field.fieldName()).isPresent();
}
if (target instanceof Map) {
return ((Map<?, ?>) target).containsKey(field.fieldName());
}
throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
}

/** Returns {@code target}'s own converter if it has one, otherwise {@code fallback}. */
private static CelValueConverter converterFor(Object target, CelValueConverter fallback) {
return target instanceof OptimizedSelectable
? ((OptimizedSelectable) target).celValueConverter()
: fallback;
}

private OptimizedSelectTraversal() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// 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 a single hop of an optimized selection chain, where a hop is one field selection within
* a chain rewritten by the select optimizer ({@code a.b.c} has two hops, {@code .b} and {@code
* .c}).
*
* <p>Implementations only resolve a hop against themselves. Walking the chain, including across
* values that do not implement this interface, belongs to {@link OptimizedSelectTraversal}.
*
* <p>CEL Library Internals. Do Not Use.
*/
@Internal
@Immutable
public interface OptimizedSelectable {

/**
* Returns the converter this value's fields were decoded with. {@link OptimizedSelectTraversal}
* adopts it for the rest of the chain, so values produced downstream are adapted with the same
* descriptor pool that produced them.
*/
CelValueConverter celValueConverter();

/** Selects {@code field}, falling back to its default value or an empty submessage if absent. */
Object optimizedSelect(SelectField field);

/** Returns whether {@code field} is present. */
boolean optimizedHasField(SelectField field);

/**
* Returns the submessage at {@code field} for an intermediate hop of a presence test, or empty if
* absent.
*/
Optional<Object> optimizedFind(SelectField field);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Defaults;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
Expand All @@ -45,6 +46,7 @@
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.TreeMap;

/**
Expand Down Expand Up @@ -80,7 +82,7 @@ private static Object readPrimitiveField(
case INT64:
return inputStream.readInt64();
case UINT32:
return UnsignedLong.fromLongBits(inputStream.readUInt32());
return UnsignedLong.fromLongBits(Integer.toUnsignedLong(inputStream.readUInt32()));
case UINT64:
return UnsignedLong.fromLongBits(inputStream.readUInt64());
case BOOL:
Expand Down Expand Up @@ -160,6 +162,17 @@ Object getDefaultCelValue(String protoTypeName, String fieldName) {
return toRuntimeValue(defaultValue);
}

public Optional<FieldLiteDescriptor> findFieldDescriptor(String protoTypeName, int fieldNumber) {
return descriptorPool
.findDescriptor(protoTypeName)
.flatMap(desc -> desc.findByFieldNumber(fieldNumber));
}

public Optional<Object> findDefaultCelValue(String protoTypeName, int fieldNumber) {
return findFieldDescriptor(protoTypeName, fieldNumber)
.map(fieldDescriptor -> toRuntimeValue(getDefaultValue(fieldDescriptor)));
}

@Override
@SuppressWarnings("LiteProtoToString") // No alternative identifier to use. Debug only info is OK.
public Object toRuntimeValue(Object value) {
Expand Down Expand Up @@ -193,7 +206,10 @@ protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wel
descriptorPool
.findDescriptor(message)
.orElseThrow(
() -> new NoSuchElementException("Could not find a descriptor for: " + message));
() ->
new NoSuchElementException(
"Could not find a descriptor for message of type: "
+ message.getClass().getName()));
return ProtoMessageLiteValue.create(message, descriptor.getProtoTypeName(), this);
}

Expand Down Expand Up @@ -367,13 +383,11 @@ MessageFields readAllFields(byte[] bytes, String protoTypeName) throws IOExcepti
return MessageFields.create(fieldValues.buildKeepingLast(), unknownFields);
}

ImmutableMap<String, Object> readAllFields(MessageLite msg, String protoTypeName)
throws IOException {
return readAllFields(msg.toByteArray(), protoTypeName).values();
MessageFields readMessageFields(MessageLite msg, String protoTypeName) throws IOException {
return readAllFields(msg.toByteArray(), protoTypeName);
}

private static Object readUnknownField(int tagWireType, CodedInputStream inputStream)
throws IOException {
static Object readUnknownField(int tagWireType, CodedInputStream inputStream) throws IOException {
switch (tagWireType) {
case WireFormat.WIRETYPE_VARINT:
return inputStream.readInt64();
Expand All @@ -393,16 +407,19 @@ private static Object readUnknownField(int tagWireType, CodedInputStream inputSt
}

@AutoValue
@SuppressWarnings("AutoValueImmutableFields") // Unknowns are inaccessible to users.
@AutoValue.CopyAnnotations
@Immutable
@SuppressWarnings("Immutable") // Safe immutable fields
abstract static class MessageFields {

abstract ImmutableMap<String, Object> values();

abstract Multimap<Integer, Object> unknowns();
abstract ImmutableListMultimap<Integer, Object> unknowns();

static MessageFields create(
ImmutableMap<String, Object> fieldValues, Multimap<Integer, Object> unknownFields) {
return new AutoValue_ProtoLiteCelValueConverter_MessageFields(fieldValues, unknownFields);
return new AutoValue_ProtoLiteCelValueConverter_MessageFields(
fieldValues, ImmutableListMultimap.copyOf(unknownFields));
}
}

Expand Down
Loading
Loading