From efa2b1c924141892cbd82db263560cf2d56cc18c Mon Sep 17 00:00:00 2001 From: Jonathan Tatum Date: Thu, 20 Aug 2026 12:39:46 -0700 Subject: [PATCH] Add option to switch internal reflection implementation to new version. Update legacy get field and qualify implementations to use new proto reflection. Uses ParsedMessageValue for field access and qualify on legacy struct values, wrapping repeated and map field containers with the legacy specialization types. PiperOrigin-RevId: 967990553 --- common/BUILD | 5 +- common/legacy_value.cc | 146 +++-- common/legacy_value.h | 15 +- common/values/legacy_list_value.cc | 278 +++++++++ common/values/legacy_list_value.h | 11 + common/values/legacy_map_value.cc | 462 ++++++++++++++- common/values/legacy_map_value.h | 12 +- common/values/legacy_struct_value_test.cc | 528 ++++++++++++++++++ eval/eval/BUILD | 4 + eval/eval/select_step.cc | 73 ++- eval/public/cel_options.cc | 1 + eval/public/cel_options.h | 9 + .../proto_message_type_adapter_test.cc | 10 +- extensions/BUILD | 5 +- extensions/select_optimization.cc | 97 +++- runtime/runtime_options.h | 9 + 16 files changed, 1566 insertions(+), 99 deletions(-) create mode 100644 common/values/legacy_struct_value_test.cc diff --git a/common/BUILD b/common/BUILD index 5b91f419e..185af4e27 100644 --- a/common/BUILD +++ b/common/BUILD @@ -793,10 +793,7 @@ cc_library( "//eval/internal:cel_value_equal", "//eval/public:cel_value", "//eval/public:message_wrapper", - "//eval/public/containers:field_backed_list_impl", - "//eval/public/containers:field_backed_map_impl", "//eval/public/structs:cel_proto_wrap_util", - "//eval/public/structs:legacy_type_adapter", "//eval/public/structs:legacy_type_info_apis", "//eval/public/structs:proto_message_type_adapter", "//eval/public/structs:trivial_legacy_type_info_internal", @@ -865,6 +862,8 @@ cc_test( ":value_kind", ":value_testing", "//base:attributes", + "//eval/public:cel_value", + "//eval/public/structs:proto_message_type_adapter", "//internal:parse_text_proto", "//internal:status_macros", "//internal:testing", diff --git a/common/legacy_value.cc b/common/legacy_value.cc index 184a09c0b..a08b8317a 100644 --- a/common/legacy_value.cc +++ b/common/legacy_value.cc @@ -43,22 +43,20 @@ #include "common/unknown.h" #include "common/value.h" #include "common/value_kind.h" +#include "common/values/legacy_list_value.h" +#include "common/values/legacy_map_value.h" #include "common/values/list_value_builder.h" #include "common/values/map_value_builder.h" #include "common/values/values.h" #include "eval/internal/cel_value_equal.h" #include "eval/public/cel_value.h" -#include "eval/public/containers/field_backed_list_impl.h" -#include "eval/public/containers/field_backed_map_impl.h" #include "eval/public/message_wrapper.h" #include "eval/public/structs/cel_proto_wrap_util.h" -#include "eval/public/structs/legacy_type_adapter.h" #include "eval/public/structs/legacy_type_info_apis.h" #include "eval/public/structs/proto_message_type_adapter.h" #include "eval/public/structs/trivial_legacy_type_info_internal.h" #include "internal/json.h" #include "internal/status_macros.h" -#include "internal/well_known_types.h" #include "runtime/runtime_options.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" @@ -76,13 +74,9 @@ using ::cel::interop_internal::TrivialTypeInfo; using ::google::api::expr::runtime::CelList; using ::google::api::expr::runtime::CelMap; using ::google::api::expr::runtime::CelValue; -using ::google::api::expr::runtime::CreateCelValueFromField; -using ::google::api::expr::runtime::FieldBackedListImpl; -using ::google::api::expr::runtime::FieldBackedMapImpl; using ::google::api::expr::runtime::GetGenericProtoTypeInfoInstance; using ::google::api::expr::runtime::LegacyTypeInfoApis; using ::google::api::expr::runtime::MessageWrapper; -using ::google::api::expr::runtime::internal::GetGenericProtoAccessApisInstance; using ::google::api::expr::runtime::internal::MaybeWrapValueToMessage; absl::Status InvalidMapKeyTypeError(ValueKind kind) { @@ -284,19 +278,17 @@ CelValue LegacyTrivialListValue(google::protobuf::Arena* absl_nonnull arena, } if (auto parsed_repeated_field_value = value.AsParsedRepeatedField(); parsed_repeated_field_value) { - auto maybe_cloned = parsed_repeated_field_value->Clone(arena); - return CelValue::CreateList(google::protobuf::Arena::Create( - arena, &maybe_cloned.message(), maybe_cloned.field(), arena)); + auto wrapped = common_internal::WrapLegacyParsedRepeatedField( + *parsed_repeated_field_value, arena); + return CelValue::CreateList( + common_internal::AsLegacyListValue(wrapped)->cel_list()); } if (auto parsed_json_list_value = value.AsParsedJsonList(); parsed_json_list_value) { - auto maybe_cloned = parsed_json_list_value->Clone(arena); - return CelValue::CreateList(google::protobuf::Arena::Create( - arena, cel::to_address(maybe_cloned), - well_known_types::GetListValueReflectionOrDie( - maybe_cloned->GetDescriptor()) - .GetValuesDescriptor(), - arena)); + auto wrapped = common_internal::WrapLegacyParsedJsonList( + *parsed_json_list_value, arena); + return CelValue::CreateList( + common_internal::AsLegacyListValue(wrapped)->cel_list()); } if (auto custom_list_value = value.AsCustomList(); custom_list_value) { auto status_or_compat_list = common_internal::MakeCompatListValue( @@ -322,19 +314,17 @@ CelValue LegacyTrivialMapValue(google::protobuf::Arena* absl_nonnull arena, } if (auto parsed_map_field_value = value.AsParsedMapField(); parsed_map_field_value) { - auto maybe_cloned = parsed_map_field_value->Clone(arena); - return CelValue::CreateMap(google::protobuf::Arena::Create( - arena, &maybe_cloned.message(), maybe_cloned.field(), arena)); + auto wrapped = common_internal::WrapLegacyParsedMapField( + *parsed_map_field_value, arena); + return CelValue::CreateMap( + common_internal::AsLegacyMapValue(wrapped)->cel_map()); } if (auto parsed_json_map_value = value.AsParsedJsonMap(); parsed_json_map_value) { - auto maybe_cloned = parsed_json_map_value->Clone(arena); - return CelValue::CreateMap(google::protobuf::Arena::Create( - arena, cel::to_address(maybe_cloned), - well_known_types::GetStructReflectionOrDie( - maybe_cloned->GetDescriptor()) - .GetFieldsDescriptor(), - arena)); + auto wrapped = + common_internal::WrapLegacyParsedJsonMap(*parsed_json_map_value, arena); + return CelValue::CreateMap( + common_internal::AsLegacyMapValue(wrapped)->cel_map()); } if (auto custom_map_value = value.AsCustomMap(); custom_map_value) { auto status_or_compat_map = common_internal::MakeCompatMapValue( @@ -352,6 +342,25 @@ CelValue LegacyTrivialMapValue(google::protobuf::Arena* absl_nonnull arena, value.GetRuntimeType().DebugString())))); } +LegacyStructValue ParsedMessageToLegacyStructValue( + const ParsedMessageValue& parsed_message) { + return LegacyStructValue(cel::to_address(parsed_message), + &GetGenericProtoTypeInfoInstance()); +} + +LegacyStructValue MakeLegacyStructValue( + const google::protobuf::Message* absl_nonnull message, + const LegacyTypeInfoApis* legacy_type_info) { + // Guard against edge cases where a custom implementation of Message + // misbehaves. + // Modern value handles this with DCHECKs on value creation, legacy value + // would allow it and just report an ErrorValue on accesses. + if (message->GetReflection() == nullptr || legacy_type_info == nullptr) { + legacy_type_info = TrivialTypeInfo::GetInstance(); + } + return LegacyStructValue(message, legacy_type_info); +} + } // namespace google::api::expr::runtime::CelValue UnsafeLegacyValue( @@ -394,10 +403,6 @@ google::api::expr::runtime::CelValue UnsafeLegacyValue( } } -} // namespace common_internal - -namespace common_internal { - std::string LegacyListValue::DebugString() const { return CelValue::CreateList(impl_).DebugString(); } @@ -837,10 +842,8 @@ absl::Status LegacyStructValue::SerializeTo( ABSL_DCHECK(message_factory != nullptr); ABSL_DCHECK(output != nullptr); - auto message_wrapper = AsMessageWrapper(message_ptr_, legacy_type_info_); if (ABSL_PREDICT_TRUE( - message_wrapper.message_ptr()->SerializePartialToZeroCopyStream( - output))) { + message_ptr_->SerializePartialToZeroCopyStream(output))) { return absl::OkStatus(); } return absl::UnknownError("failed to serialize protocol buffer message"); @@ -918,17 +921,26 @@ absl::Status LegacyStructValue::GetFieldByName( const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { - auto message_wrapper = AsMessageWrapper(message_ptr_, legacy_type_info_); if (ABSL_PREDICT_FALSE(legacy_type_info_ == TrivialTypeInfo::GetInstance())) { *result = NoSuchFieldError(name); return absl::OkStatus(); } - CEL_ASSIGN_OR_RETURN(auto cel_value, - GetGenericProtoAccessApisInstance().GetField( - name, message_wrapper, unboxing_options, - MemoryManagerRef::Pooling(arena))); - CEL_RETURN_IF_ERROR(ModernValue(arena, cel_value, *result)); - return absl::OkStatus(); + + ParsedMessageValue parsed_message = UnsafeParsedMessageValue(message_ptr_); + const auto* descriptor = parsed_message.GetDescriptor(); + const auto* field = descriptor->FindFieldByName(name); + if (field == nullptr) { + field = descriptor->file()->pool()->FindExtensionByPrintableName(descriptor, + name); + if (field == nullptr) { + *result = NoSuchFieldError(name); + return absl::OkStatus(); + } + } + + return interop_internal::WrapLegacyMessageField( + message_ptr_, field, unboxing_options, descriptor_pool, message_factory, + arena, result); } absl::Status LegacyStructValue::GetFieldByNumber( @@ -980,7 +992,6 @@ absl::Status LegacyStructValue::Qualify( if (ABSL_PREDICT_FALSE(qualifiers.empty())) { return absl::InvalidArgumentError("invalid select qualifier path."); } - auto message_wrapper = AsMessageWrapper(message_ptr_, legacy_type_info_); if (ABSL_PREDICT_FALSE(legacy_type_info_ == TrivialTypeInfo::GetInstance())) { absl::string_view field_name = absl::visit( absl::Overload( @@ -995,12 +1006,13 @@ absl::Status LegacyStructValue::Qualify( *count = -1; return absl::OkStatus(); } - CEL_ASSIGN_OR_RETURN(auto legacy_result, - GetGenericProtoAccessApisInstance().Qualify( - qualifiers, message_wrapper, presence_test, - MemoryManager::Pooling(arena))); - CEL_RETURN_IF_ERROR(ModernValue(arena, legacy_result.value, *result)); - *count = legacy_result.qualifier_count; + + ParsedMessageValue parsed_message = UnsafeParsedMessageValue(message_ptr_); + CEL_RETURN_IF_ERROR(parsed_message.Qualify(qualifiers, presence_test, + descriptor_pool, message_factory, + arena, result, count)); + + interop_internal::WrapLegacyFieldAccessResult(arena, result); return absl::OkStatus(); } @@ -1035,7 +1047,7 @@ absl::Status ModernValue(google::protobuf::Arena* arena, return absl::OkStatus(); case CelValue::Type::kMessage: { auto message_wrapper = legacy_value.MessageWrapperOrDie(); - result = common_internal::LegacyStructValue( + result = common_internal::MakeLegacyStructValue( google::protobuf::DownCastMessage( message_wrapper.message_ptr()), message_wrapper.legacy_type_info()); @@ -1153,7 +1165,7 @@ absl::StatusOr FromLegacyValue(google::protobuf::Arena* arena, legacy_value.BytesOrDie().value()); case CelValue::Type::kMessage: { auto message_wrapper = legacy_value.MessageWrapperOrDie(); - return common_internal::LegacyStructValue( + return common_internal::MakeLegacyStructValue( google::protobuf::DownCastMessage( message_wrapper.message_ptr()), message_wrapper.legacy_type_info()); @@ -1262,6 +1274,23 @@ google::api::expr::runtime::CelValue ModernValueToLegacyValueOrDie( return std::move(*status_or_value); } +void WrapLegacyFieldAccessResult(google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) { + if (result->IsParsedMessage()) { + *result = common_internal::ParsedMessageToLegacyStructValue( + result->GetParsedMessage()); + } else if (result->IsParsedRepeatedField()) { + *result = + WrapLegacyParsedRepeatedField(result->GetParsedRepeatedField(), arena); + } else if (result->IsParsedJsonList()) { + *result = WrapLegacyParsedJsonList(result->GetParsedJsonList(), arena); + } else if (result->IsParsedMapField()) { + *result = WrapLegacyParsedMapField(result->GetParsedMapField(), arena); + } else if (result->IsParsedJsonMap()) { + *result = WrapLegacyParsedJsonMap(result->GetParsedJsonMap(), arena); + } +} + TypeValue CreateTypeValueFromView(google::protobuf::Arena* arena, absl::string_view input) { return TypeValue(common_internal::LegacyRuntimeType(input)); @@ -1289,12 +1318,17 @@ const google::protobuf::Message* absl_nullable GetLegacyMessage(const Value& val absl::Status WrapLegacyMessageField( const google::protobuf::Message* absl_nonnull message, const google::protobuf::FieldDescriptor* absl_nonnull field_descriptor, - ProtoWrapperTypeOptions unboxing_option, google::protobuf::Arena* arena, + ProtoWrapperTypeOptions unboxing_option, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, google::protobuf::Arena* arena, Value* absl_nonnull out) { - CEL_ASSIGN_OR_RETURN(CelValue result, - CreateCelValueFromField(message, field_descriptor, - unboxing_option, arena)); - return ModernValue(arena, result, *out); + ParsedMessageValue parsed_message = UnsafeParsedMessageValue(message); + CEL_RETURN_IF_ERROR(parsed_message.GetField(field_descriptor, unboxing_option, + descriptor_pool, message_factory, + arena, out)); + WrapLegacyFieldAccessResult(arena, out); + + return absl::OkStatus(); } } // namespace interop_internal diff --git a/common/legacy_value.h b/common/legacy_value.h index 8d0392f7a..a89eb0412 100644 --- a/common/legacy_value.h +++ b/common/legacy_value.h @@ -59,19 +59,32 @@ google::api::expr::runtime::CelValue UnsafeLegacyValue( } // namespace cel +namespace proto2 { +class MessageFactory; +} // namespace proto2 + namespace cel::interop_internal { // Returns the underlying `google::protobuf::Message` of a `cel::Value` if it is a legacy // message with the default type info, or `nullptr` otherwise. const google::protobuf::Message* absl_nullable GetLegacyMessage(const Value& value); +// Helper for wrapping a field accesses for the legacy runtime. +// +// Adapts the output to avoid further allocations when converting to a legacy +// value when possible. +void WrapLegacyFieldAccessResult(google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result); + // Access a field on a legacy message value, writing the result to `out`. // Prefers wrapping legacy values instead of using the modern value // representation. absl::Status WrapLegacyMessageField( const google::protobuf::Message* absl_nonnull message, const google::protobuf::FieldDescriptor* absl_nonnull field_descriptor, - ProtoWrapperTypeOptions unboxing_option, google::protobuf::Arena* arena, + ProtoWrapperTypeOptions unboxing_option, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, google::protobuf::Arena* arena, Value* absl_nonnull out); absl::StatusOr FromLegacyValue( diff --git a/common/values/legacy_list_value.cc b/common/values/legacy_list_value.cc index 60e571452..1152df715 100644 --- a/common/values/legacy_list_value.cc +++ b/common/values/legacy_list_value.cc @@ -14,22 +14,288 @@ #include "common/values/legacy_list_value.h" +#include +#include +#include + #include "absl/base/nullability.h" +#include "absl/base/optimization.h" #include "absl/log/absl_check.h" #include "absl/status/status.h" #include "absl/types/optional.h" +#include "common/legacy_value.h" #include "common/native_type.h" #include "common/value.h" +#include "common/values/legacy_struct_value.h" #include "common/values/list_value_builder.h" #include "common/values/values.h" #include "eval/public/cel_value.h" #include "internal/casts.h" +#include "internal/status_macros.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" #include "google/protobuf/message.h" namespace cel::common_internal { +class LegacyParsedRepeatedFieldListValue final + : public CustomListValueInterface, + public google::api::expr::runtime::CelList { + public: + // `arena` is expected to be the same arena as the one that the object is + // allocated on. + explicit LegacyParsedRepeatedFieldListValue(ParsedRepeatedFieldValue value, + google::protobuf::Arena* absl_nonnull arena) + : value_(std::move(value)), arena_(arena) { + ABSL_DCHECK(arena != nullptr); + } + + // CelList implementation + int size() const override { return static_cast(value_.Size()); } + + bool empty() const override { return value_.IsEmpty(); } + + google::api::expr::runtime::CelValue operator[](int index) const override { + return Get(arena_, index); + } + + google::api::expr::runtime::CelValue Get(google::protobuf::Arena* arena, + int index) const override { + if (arena == nullptr) { + arena = arena_; + } + if (ABSL_PREDICT_FALSE(index < 0 || index >= size())) { + return google::api::expr::runtime::CelValue::CreateError( + google::protobuf::Arena::Create( + arena, IndexOutOfBoundsError(index).ToStatus())); + } + Value result; + auto status = value_.Get( + static_cast(index), google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), arena, &result); + if (ABSL_PREDICT_FALSE(!status.ok())) { + return google::api::expr::runtime::CelValue::CreateError( + google::protobuf::Arena::Create(arena, std::move(status))); + } + return UnsafeLegacyValue(result, /*stable=*/false, arena); + } + + protected: + std::string DebugString() const override { return value_.DebugString(); } + + absl::Status SerializeTo( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output) const override { + return value_.SerializeTo(descriptor_pool, message_factory, output); + } + + absl::Status ConvertToJsonArray( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json) const override { + return value_.ConvertToJsonArray(descriptor_pool, message_factory, json); + } + + absl::Status Equal(const ListValue& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + return value_.Equal(other, descriptor_pool, message_factory, arena, result); + } + + bool IsZeroValue() const override { return value_.IsZeroValue(); } + + bool IsEmpty() const override { return value_.IsEmpty(); } + + size_t Size() const override { return value_.Size(); } + + absl::Status Get(size_t index, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + CEL_RETURN_IF_ERROR( + value_.Get(index, descriptor_pool, message_factory, arena, result)); + interop_internal::WrapLegacyFieldAccessResult(arena, result); + return absl::OkStatus(); + } + + absl::Status ForEach( + ForEachWithIndexCallback callback, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) const override { + return value_.ForEach(callback, descriptor_pool, message_factory, arena); + } + + absl::StatusOr NewIterator() const override { + return value_.NewIterator(); + } + + absl::Status Contains( + const Value& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + return value_.Contains(other, descriptor_pool, message_factory, arena, + result); + } + + CustomListValue Clone(google::protobuf::Arena* absl_nonnull arena) const override { + return CustomListValue( + google::protobuf::Arena::Create( + arena, value_.Clone(arena), arena), + arena); + } + + private: + NativeTypeId GetNativeTypeId() const override { + return NativeTypeId::For(); + } + + ParsedRepeatedFieldValue value_; + google::protobuf::Arena* const arena_; +}; + +class LegacyParsedJsonListValue final + : public CustomListValueInterface, + public google::api::expr::runtime::CelList { + public: + // `arena` is expected to be the same arena as the one that the object is + // allocated on. + explicit LegacyParsedJsonListValue(ParsedJsonListValue value, + google::protobuf::Arena* absl_nonnull arena) + : value_(std::move(value)), arena_(arena) { + ABSL_DCHECK(arena != nullptr); + } + + // CelList implementation + int size() const override { return static_cast(value_.Size()); } + + bool empty() const override { return value_.IsEmpty(); } + + google::api::expr::runtime::CelValue operator[](int index) const override { + return Get(arena_, index); + } + + google::api::expr::runtime::CelValue Get(google::protobuf::Arena* arena, + int index) const override { + if (arena == nullptr) { + arena = arena_; + } + if (ABSL_PREDICT_FALSE(index < 0 || index >= size())) { + return google::api::expr::runtime::CelValue::CreateError( + google::protobuf::Arena::Create( + arena, IndexOutOfBoundsError(index).ToStatus())); + } + Value result; + auto status = value_.Get( + static_cast(index), google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), arena, &result); + if (ABSL_PREDICT_FALSE(!status.ok())) { + return google::api::expr::runtime::CelValue::CreateError( + google::protobuf::Arena::Create(arena, std::move(status))); + } + return UnsafeLegacyValue(result, /*stable=*/false, arena); + } + + protected: + std::string DebugString() const override { return value_.DebugString(); } + + absl::Status SerializeTo( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output) const override { + return value_.SerializeTo(descriptor_pool, message_factory, output); + } + + absl::Status ConvertToJsonArray( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json) const override { + return value_.ConvertToJsonArray(descriptor_pool, message_factory, json); + } + + absl::Status Equal(const ListValue& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + return value_.Equal(other, descriptor_pool, message_factory, arena, result); + } + + bool IsZeroValue() const override { return value_.IsZeroValue(); } + + bool IsEmpty() const override { return value_.IsEmpty(); } + + size_t Size() const override { return value_.Size(); } + + absl::Status Get(size_t index, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + CEL_RETURN_IF_ERROR( + value_.Get(index, descriptor_pool, message_factory, arena, result)); + interop_internal::WrapLegacyFieldAccessResult(arena, result); + return absl::OkStatus(); + } + + absl::Status ForEach( + ForEachWithIndexCallback callback, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) const override { + return value_.ForEach(callback, descriptor_pool, message_factory, arena); + } + + absl::StatusOr NewIterator() const override { + return value_.NewIterator(); + } + + absl::Status Contains( + const Value& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + return value_.Contains(other, descriptor_pool, message_factory, arena, + result); + } + + CustomListValue Clone(google::protobuf::Arena* absl_nonnull arena) const override { + return CustomListValue(google::protobuf::Arena::Create( + arena, value_.Clone(arena), arena), + arena); + } + + private: + NativeTypeId GetNativeTypeId() const override { + return NativeTypeId::For(); + } + + ParsedJsonListValue value_; + google::protobuf::Arena* const arena_; +}; + +CustomListValue WrapLegacyParsedRepeatedField( + ParsedRepeatedFieldValue value, google::protobuf::Arena* absl_nonnull arena) { + return CustomListValue( + google::protobuf::Arena::Create( + arena, std::move(value), arena), + arena); +} + +CustomListValue WrapLegacyParsedJsonList(ParsedJsonListValue value, + google::protobuf::Arena* absl_nonnull arena) { + return CustomListValue(google::protobuf::Arena::Create( + arena, std::move(value), arena), + arena); +} + absl::Status LegacyListValue::Equal( const Value& other, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, @@ -68,6 +334,18 @@ absl::optional AsLegacyListValue(const Value& value) { static_cast( cel::internal::down_cast( custom_list_value->interface()))); + } else if (native_type_id == + NativeTypeId::For()) { + return LegacyListValue(static_cast< + const google::api::expr::runtime::CelList*>( + cel::internal::down_cast( + custom_list_value->interface()))); + } else if (native_type_id == + NativeTypeId::For()) { + return LegacyListValue( + static_cast( + cel::internal::down_cast( + custom_list_value->interface()))); } } return std::nullopt; diff --git a/common/values/legacy_list_value.h b/common/values/legacy_list_value.h index caffcbc25..6dd6a568e 100644 --- a/common/values/legacy_list_value.h +++ b/common/values/legacy_list_value.h @@ -30,6 +30,8 @@ #include "absl/types/optional.h" #include "common/value_kind.h" #include "common/values/custom_list_value.h" +#include "common/values/parsed_json_list_value.h" +#include "common/values/parsed_repeated_field_value.h" #include "common/values/values.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" @@ -160,6 +162,15 @@ LegacyListValue GetLegacyListValue(const Value& value); absl::optional AsLegacyListValue(const Value& value); +class LegacyParsedRepeatedFieldListValue; +class LegacyParsedJsonListValue; + +CustomListValue WrapLegacyParsedRepeatedField( + ParsedRepeatedFieldValue value, google::protobuf::Arena* absl_nonnull arena); + +CustomListValue WrapLegacyParsedJsonList(ParsedJsonListValue value, + google::protobuf::Arena* absl_nonnull arena); + } // namespace common_internal } // namespace cel diff --git a/common/values/legacy_map_value.cc b/common/values/legacy_map_value.cc index 5b24b34ba..e287df076 100644 --- a/common/values/legacy_map_value.cc +++ b/common/values/legacy_map_value.cc @@ -14,22 +14,468 @@ #include "common/values/legacy_map_value.h" +#include +#include +#include +#include +#include +#include + #include "absl/base/nullability.h" +#include "absl/base/optimization.h" #include "absl/log/absl_check.h" #include "absl/status/status.h" -#include "absl/types/optional.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "common/legacy_value.h" +#include "common/memory.h" #include "common/native_type.h" #include "common/value.h" +#include "common/value_kind.h" +#include "common/values/legacy_list_value.h" +#include "common/values/legacy_struct_value.h" #include "common/values/map_value_builder.h" #include "common/values/values.h" #include "eval/public/cel_value.h" +#include "eval/public/structs/proto_message_type_adapter.h" #include "internal/casts.h" +#include "internal/status_macros.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" #include "google/protobuf/message.h" namespace cel::common_internal { +namespace { + +LegacyStructValue ParsedMessageToLegacyStructValue( + const ParsedMessageValue& parsed_message) { + return LegacyStructValue( + cel::to_address(parsed_message), + &google::api::expr::runtime::GetGenericProtoTypeInfoInstance()); +} + +bool MatchesMapKeyType(const google::protobuf::FieldDescriptor* absl_nonnull key_desc, + const Value& key) { + switch (key_desc->cpp_type()) { + case google::protobuf::FieldDescriptor::CPPTYPE_BOOL: + return key.IsBool(); + case google::protobuf::FieldDescriptor::CPPTYPE_INT32: + if (key.IsInt()) { + auto val = key.GetInt().NativeValue(); + return val >= std::numeric_limits::min() && + val <= std::numeric_limits::max(); + } + return false; + case google::protobuf::FieldDescriptor::CPPTYPE_INT64: + return key.IsInt(); + case google::protobuf::FieldDescriptor::CPPTYPE_UINT32: + if (key.IsUint()) { + auto val = key.GetUint().NativeValue(); + return val <= std::numeric_limits::max(); + } + return false; + case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: + return key.IsUint(); + case google::protobuf::FieldDescriptor::CPPTYPE_STRING: + return key.IsString(); + default: + return false; + } +} + +absl::Status InvalidMapKeyType(absl::string_view key_type) { + return absl::InvalidArgumentError( + absl::StrCat("Invalid map key type: '", key_type, "'")); +} + +} // namespace + +class LegacyParsedMapFieldMapValue final + : public CustomMapValueInterface, + public google::api::expr::runtime::CelMap { + public: + // `arena` is expected to be the same arena as the one that the object is + // allocated on. + explicit LegacyParsedMapFieldMapValue(ParsedMapFieldValue value, + google::protobuf::Arena* absl_nonnull arena) + : value_(std::move(value)), arena_(arena) { + ABSL_DCHECK(arena != nullptr); + ABSL_DCHECK(value_.field() != nullptr); + } + + std::string DebugString() const override { return value_.DebugString(); } + + absl::Status SerializeTo( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output) const override { + return value_.SerializeTo(descriptor_pool, message_factory, output); + } + + absl::Status ConvertToJsonObject( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json) const override { + return value_.ConvertToJsonObject(descriptor_pool, message_factory, json); + } + + absl::Status Equal(const MapValue& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + return value_.Equal(other, descriptor_pool, message_factory, arena, result); + } + + bool IsZeroValue() const override { return value_.IsZeroValue(); } + + bool IsEmpty() const override { return value_.IsEmpty(); } + + size_t Size() const override { return value_.Size(); } + + absl::StatusOr Find( + const Value& key, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + // Mimic the legacy behavior of complaining about unexpected key type. + const auto* key_field = value_.field()->message_type()->map_key(); + if (!MatchesMapKeyType(key_field, key)) { + return InvalidMapKeyType(cel::ValueKindToString(key.kind())); + } + + CEL_ASSIGN_OR_RETURN( + auto found, + value_.Find(key, descriptor_pool, message_factory, arena, result)); + if (found) { + interop_internal::WrapLegacyFieldAccessResult(arena, result); + } + return found; + } + + absl::StatusOr Has( + const Value& key, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) const override { + const auto* key_field = value_.field()->message_type()->map_key(); + if (!MatchesMapKeyType(key_field, key)) { + return InvalidMapKeyType(key_field->cpp_type_name()); + } + Value result; + CEL_RETURN_IF_ERROR( + value_.Has(key, descriptor_pool, message_factory, arena, &result)); + if (result.IsBool()) { + return result.GetBool().NativeValue(); + } + if (result.IsError()) { + return result.GetError().NativeValue(); + } + return false; + } + + absl::Status ListKeys( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + ListValue* absl_nonnull result) const override { + return value_.ListKeys(descriptor_pool, message_factory, arena, result); + } + + absl::Status ForEach( + ForEachCallback callback, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) const override { + return value_.ForEach(callback, descriptor_pool, message_factory, arena); + } + + absl::StatusOr NewIterator() const override { + return value_.NewIterator(); + } + + CustomMapValue Clone(google::protobuf::Arena* absl_nonnull arena) const override { + return CustomMapValue(google::protobuf::Arena::Create( + arena, value_.Clone(arena), arena), + arena); + } + + // CelMap implementation + int size() const override { return static_cast(value_.Size()); } + + bool empty() const override { return value_.IsEmpty(); } + + std::optional operator[]( + google::api::expr::runtime::CelValue key) const override { + return Get(arena_, key); + } + + std::optional Get( + google::protobuf::Arena* arena, + google::api::expr::runtime::CelValue key) const override { + if (arena == nullptr) { + arena = arena_; + } + if (auto status = + google::api::expr::runtime::CelValue::CheckMapKeyType(key); + !status.ok()) { + status.IgnoreError(); + return std::nullopt; + } + Value modern_key; + if (ABSL_PREDICT_FALSE(!ModernValue(arena, key, modern_key).ok())) { + return std::nullopt; + } + Value modern_val; + auto status_or_found = + Find(modern_key, google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), arena, &modern_val); + if (!status_or_found.ok() || !*status_or_found) { + return std::nullopt; + } + return UnsafeLegacyValue(modern_val, /*stable=*/false, arena); + } + + absl::StatusOr Has( + const google::api::expr::runtime::CelValue& key) const override { + CEL_RETURN_IF_ERROR( + google::api::expr::runtime::CelValue::CheckMapKeyType(key)); + google::protobuf::Arena scratch_arena; + Value modern_key; + CEL_RETURN_IF_ERROR(ModernValue(&scratch_arena, key, modern_key)); + return Has(modern_key, google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), &scratch_arena); + } + + absl::StatusOr ListKeys() + const override { + return ListKeys(arena_); + } + + absl::StatusOr ListKeys( + google::protobuf::Arena* arena) const override { + if (arena == nullptr) { + arena = arena_; + } + ListValue keys; + CEL_RETURN_IF_ERROR(value_.ListKeys( + google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), arena, &keys)); + auto legacy_list = AsLegacyListValue(keys); + if (!legacy_list.has_value()) { + return absl::InternalError("failed to convert list keys to legacy list"); + } + return legacy_list->cel_list(); + } + + private: + NativeTypeId GetNativeTypeId() const override { + return NativeTypeId::For(); + } + + ParsedMapFieldValue value_; + google::protobuf::Arena* const arena_; +}; + +class LegacyParsedJsonMapValue final + : public CustomMapValueInterface, + public google::api::expr::runtime::CelMap { + public: + // `arena` is expected to be the same arena as the one that the object is + // allocated on. + explicit LegacyParsedJsonMapValue(ParsedJsonMapValue value, + google::protobuf::Arena* absl_nonnull arena) + : value_(std::move(value)), arena_(arena) { + ABSL_DCHECK(arena != nullptr); + } + + std::string DebugString() const override { return value_.DebugString(); } + + absl::Status SerializeTo( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output) const override { + return value_.SerializeTo(descriptor_pool, message_factory, output); + } + + absl::Status ConvertToJsonObject( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json) const override { + return value_.ConvertToJsonObject(descriptor_pool, message_factory, json); + } + + absl::Status Equal(const MapValue& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + return value_.Equal(other, descriptor_pool, message_factory, arena, result); + } + + bool IsZeroValue() const override { return value_.IsZeroValue(); } + + bool IsEmpty() const override { return value_.IsEmpty(); } + + size_t Size() const override { return value_.Size(); } + + absl::StatusOr Find( + const Value& key, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + if (!key.IsString()) { + return InvalidMapKeyType(cel::ValueKindToString(key.kind())); + } + CEL_ASSIGN_OR_RETURN( + auto found, + value_.Find(key, descriptor_pool, message_factory, arena, result)); + if (found && result->IsParsedMessage()) { + *result = ParsedMessageToLegacyStructValue(result->GetParsedMessage()); + } + return found; + } + + absl::StatusOr Has( + const Value& key, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) const override { + if (!key.IsString()) { + return InvalidMapKeyType(cel::ValueKindToString(key.kind())); + } + Value result; + CEL_RETURN_IF_ERROR( + value_.Has(key, descriptor_pool, message_factory, arena, &result)); + if (result.IsBool()) { + return result.GetBool().NativeValue(); + } + if (result.IsError()) { + return result.GetError().NativeValue(); + } + return false; + } + + absl::Status ListKeys( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + ListValue* absl_nonnull result) const override { + return value_.ListKeys(descriptor_pool, message_factory, arena, result); + } + + absl::Status ForEach( + ForEachCallback callback, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) const override { + return value_.ForEach(callback, descriptor_pool, message_factory, arena); + } + + absl::StatusOr NewIterator() const override { + return value_.NewIterator(); + } + + CustomMapValue Clone(google::protobuf::Arena* absl_nonnull arena) const override { + return CustomMapValue(google::protobuf::Arena::Create( + arena, value_.Clone(arena), arena), + arena); + } + + // CelMap implementation + int size() const override { return static_cast(value_.Size()); } + + bool empty() const override { return value_.IsEmpty(); } + + std::optional operator[]( + google::api::expr::runtime::CelValue key) const override { + return Get(arena_, key); + } + + std::optional Get( + google::protobuf::Arena* arena, + google::api::expr::runtime::CelValue key) const override { + if (arena == nullptr) { + arena = arena_; + } + if (auto status = + google::api::expr::runtime::CelValue::CheckMapKeyType(key); + !status.ok()) { + status.IgnoreError(); + return std::nullopt; + } + Value modern_key; + if (ABSL_PREDICT_FALSE(!ModernValue(arena, key, modern_key).ok())) { + return std::nullopt; + } + Value modern_val; + auto status_or_found = value_.Find( + modern_key, google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), arena, &modern_val); + if (!status_or_found.ok() || !*status_or_found) { + return std::nullopt; + } + return UnsafeLegacyValue(modern_val, /*stable=*/false, arena); + } + + absl::StatusOr Has( + const google::api::expr::runtime::CelValue& key) const override { + CEL_RETURN_IF_ERROR( + google::api::expr::runtime::CelValue::CheckMapKeyType(key)); + google::protobuf::Arena scratch_arena; + Value modern_key; + CEL_RETURN_IF_ERROR(ModernValue(&scratch_arena, key, modern_key)); + return Has(modern_key, google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), &scratch_arena); + } + + absl::StatusOr ListKeys() + const override { + return ListKeys(arena_); + } + + absl::StatusOr ListKeys( + google::protobuf::Arena* arena) const override { + if (arena == nullptr) { + arena = arena_; + } + ListValue keys; + CEL_RETURN_IF_ERROR(value_.ListKeys( + google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), arena, &keys)); + auto legacy_list = AsLegacyListValue(keys); + if (!legacy_list.has_value()) { + return absl::InternalError("failed to convert list keys to legacy list"); + } + return legacy_list->cel_list(); + } + + private: + NativeTypeId GetNativeTypeId() const override { + return NativeTypeId::For(); + } + + ParsedJsonMapValue value_; + google::protobuf::Arena* const arena_; +}; + +CustomMapValue WrapLegacyParsedMapField(ParsedMapFieldValue value, + google::protobuf::Arena* absl_nonnull arena) { + return CustomMapValue(google::protobuf::Arena::Create( + arena, std::move(value), arena), + arena); +} + +CustomMapValue WrapLegacyParsedJsonMap(ParsedJsonMapValue value, + google::protobuf::Arena* absl_nonnull arena) { + return CustomMapValue(google::protobuf::Arena::Create( + arena, std::move(value), arena), + arena); +} + absl::Status LegacyMapValue::Equal( const Value& other, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, @@ -52,7 +498,7 @@ LegacyMapValue GetLegacyMapValue(const Value& value) { return value.variant_.Get(); } -absl::optional AsLegacyMapValue(const Value& value) { +std::optional AsLegacyMapValue(const Value& value) { if (IsLegacyMapValue(value)) { return GetLegacyMapValue(value); } @@ -68,6 +514,18 @@ absl::optional AsLegacyMapValue(const Value& value) { static_cast( cel::internal::down_cast( custom_map_value->interface()))); + } else if (native_type_id == + NativeTypeId::For()) { + return LegacyMapValue( + static_cast( + cel::internal::down_cast( + custom_map_value->interface()))); + } else if (native_type_id == + NativeTypeId::For()) { + return LegacyMapValue( + static_cast( + cel::internal::down_cast( + custom_map_value->interface()))); } } return std::nullopt; diff --git a/common/values/legacy_map_value.h b/common/values/legacy_map_value.h index c83b7fc2f..53f39a8de 100644 --- a/common/values/legacy_map_value.h +++ b/common/values/legacy_map_value.h @@ -25,11 +25,12 @@ #include "absl/base/nullability.h" #include "absl/status/status.h" #include "absl/status/statusor.h" -#include "absl/strings/cord.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "common/value_kind.h" #include "common/values/custom_map_value.h" +#include "common/values/parsed_json_map_value.h" +#include "common/values/parsed_map_field_value.h" #include "common/values/values.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" @@ -178,6 +179,15 @@ LegacyMapValue GetLegacyMapValue(const Value& value); absl::optional AsLegacyMapValue(const Value& value); +class LegacyParsedMapFieldMapValue; +class LegacyParsedJsonMapValue; + +CustomMapValue WrapLegacyParsedMapField(ParsedMapFieldValue value, + google::protobuf::Arena* absl_nonnull arena); + +CustomMapValue WrapLegacyParsedJsonMap(ParsedJsonMapValue value, + google::protobuf::Arena* absl_nonnull arena); + } // namespace common_internal } // namespace cel diff --git a/common/values/legacy_struct_value_test.cc b/common/values/legacy_struct_value_test.cc new file mode 100644 index 000000000..307889e33 --- /dev/null +++ b/common/values/legacy_struct_value_test.cc @@ -0,0 +1,528 @@ +// 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. + +#include "common/values/legacy_struct_value.h" + +#include +#include +#include + +#include "google/protobuf/struct.pb.h" +#include "absl/status/status.h" +#include "absl/status/status_matchers.h" +#include "absl/strings/string_view.h" +#include "common/legacy_value.h" +#include "common/value.h" +#include "common/value_testing.h" +#include "common/values/legacy_list_value.h" +#include "common/values/legacy_map_value.h" +#include "eval/public/cel_value.h" +#include "eval/public/structs/proto_message_type_adapter.h" +#include "internal/testing.h" +#include "runtime/runtime_options.h" +#include "cel/expr/conformance/proto3/test_all_types.pb.h" + +namespace cel { +namespace { + +using ::absl_testing::IsOk; +using ::absl_testing::IsOkAndHolds; +using ::absl_testing::StatusIs; +using ::cel::expr::conformance::proto3::TestAllTypes; +using ::cel::test::BoolValueIs; +using ::cel::test::ErrorValueIs; +using ::cel::test::IntValueIs; +using ::cel::test::StringValueIs; +using ::google::api::expr::runtime::CelValue; +using ::testing::NotNull; + +using LegacyStructValueTest = common_internal::ValueTest<>; + +TEST_F(LegacyStructValueTest, RepeatedFieldAccess) { + TestAllTypes message; + message.add_repeated_int32(10); + message.add_repeated_int32(20); + + common_internal::LegacyStructValue struct_value( + &message, &google::api::expr::runtime::GetGenericProtoTypeInfoInstance()); + + Value field_value; + ASSERT_THAT(struct_value.GetFieldByName( + "repeated_int32", ProtoWrapperTypeOptions::kUnsetProtoDefault, + descriptor_pool(), message_factory(), arena(), &field_value), + IsOk()); + + EXPECT_TRUE(field_value.IsList()); + auto list_value = field_value.GetList(); + EXPECT_THAT(list_value.Size(), IsOkAndHolds(2)); + + // Verify legacy CelList interface + auto legacy_list = common_internal::AsLegacyListValue(field_value); + ASSERT_TRUE(legacy_list.has_value()); + const auto* cel_list = legacy_list->cel_list(); + ASSERT_THAT(cel_list, NotNull()); + EXPECT_EQ(cel_list->size(), 2); + + CelValue elem0 = cel_list->Get(arena(), 0); + ASSERT_TRUE(elem0.IsInt64()); + EXPECT_EQ(elem0.Int64OrDie(), 10); + + CelValue elem1 = cel_list->Get(arena(), 1); + ASSERT_TRUE(elem1.IsInt64()); + EXPECT_EQ(elem1.Int64OrDie(), 20); +} + +TEST_F(LegacyStructValueTest, RepeatedMessageFieldAccess) { + TestAllTypes message; + auto* elem0 = message.add_repeated_nested_message(); + elem0->set_bb(42); + + common_internal::LegacyStructValue struct_value( + &message, &google::api::expr::runtime::GetGenericProtoTypeInfoInstance()); + + Value field_value; + ASSERT_THAT(struct_value.GetFieldByName( + "repeated_nested_message", + ProtoWrapperTypeOptions::kUnsetProtoDefault, + descriptor_pool(), message_factory(), arena(), &field_value), + IsOk()); + + EXPECT_TRUE(field_value.IsList()); + auto list_value = field_value.GetList(); + EXPECT_THAT(list_value.Size(), IsOkAndHolds(1)); + + Value first_elem; + ASSERT_THAT(list_value.Get(0, descriptor_pool(), message_factory(), arena(), + &first_elem), + IsOk()); + EXPECT_TRUE(common_internal::IsLegacyStructValue(first_elem)); + + // Verify via CelList + auto legacy_list = common_internal::AsLegacyListValue(field_value); + ASSERT_TRUE(legacy_list.has_value()); + const auto* cel_list = legacy_list->cel_list(); + ASSERT_THAT(cel_list, NotNull()); + + CelValue cel_elem = cel_list->Get(arena(), 0); + ASSERT_TRUE(cel_elem.IsMessage()); + EXPECT_EQ(cel_elem.MessageOrDie()->GetDescriptor(), elem0->GetDescriptor()); + EXPECT_EQ( + static_cast(cel_elem.MessageOrDie()) + ->bb(), + 42); +} + +TEST_F(LegacyStructValueTest, MapFieldAccess) { + TestAllTypes message; + (*message.mutable_map_string_string())["hello"] = "world"; + + common_internal::LegacyStructValue struct_value( + &message, &google::api::expr::runtime::GetGenericProtoTypeInfoInstance()); + + Value field_value; + ASSERT_THAT( + struct_value.GetFieldByName( + "map_string_string", ProtoWrapperTypeOptions::kUnsetProtoDefault, + descriptor_pool(), message_factory(), arena(), &field_value), + IsOk()); + + EXPECT_TRUE(field_value.IsMap()); + auto map_value = field_value.GetMap(); + EXPECT_THAT(map_value.Size(), IsOkAndHolds(1)); + + // Verify legacy CelMap interface + auto legacy_map = common_internal::AsLegacyMapValue(field_value); + ASSERT_TRUE(legacy_map.has_value()); + const auto* cel_map = legacy_map->cel_map(); + ASSERT_THAT(cel_map, NotNull()); + EXPECT_EQ(cel_map->size(), 1); + + std::string key_str = "hello"; + CelValue cel_key = CelValue::CreateString(&key_str); + auto cel_result = cel_map->Get(arena(), cel_key); + ASSERT_TRUE(cel_result.has_value()); + ASSERT_TRUE(cel_result->IsString()); + EXPECT_EQ(cel_result->StringOrDie().value(), "world"); + + auto has_res = cel_map->Has(cel_key); + ASSERT_THAT(has_res, IsOk()); + EXPECT_TRUE(*has_res); +} + +TEST_F(LegacyStructValueTest, MapFieldKeyTypeValidation) { + TestAllTypes message; + (*message.mutable_map_int32_int32())[1] = 2; + + common_internal::LegacyStructValue struct_value( + &message, &google::api::expr::runtime::GetGenericProtoTypeInfoInstance()); + + Value field_value; + ASSERT_THAT( + struct_value.GetFieldByName( + "map_int32_int32", ProtoWrapperTypeOptions::kUnsetProtoDefault, + descriptor_pool(), message_factory(), arena(), &field_value), + IsOk()); + + auto legacy_map = common_internal::AsLegacyMapValue(field_value); + ASSERT_TRUE(legacy_map.has_value()); + const auto* cel_map = legacy_map->cel_map(); + ASSERT_THAT(cel_map, NotNull()); + + // Valid key + CelValue int_key = CelValue::CreateInt64(1); + auto has_res = cel_map->Has(int_key); + ASSERT_THAT(has_res, IsOk()); + EXPECT_TRUE(*has_res); + + // Invalid key type (string key on int32 map) + std::string str_key_val = "1"; + CelValue str_key = CelValue::CreateString(&str_key_val); + auto invalid_has_res = cel_map->Has(str_key); + EXPECT_THAT(invalid_has_res, StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST_F(LegacyStructValueTest, JsonStructAccess) { + TestAllTypes message; + auto* struct_field = message.mutable_single_struct(); + (*struct_field->mutable_fields())["key"].set_string_value("value"); + + common_internal::LegacyStructValue struct_value( + &message, &google::api::expr::runtime::GetGenericProtoTypeInfoInstance()); + + Value field_value; + ASSERT_THAT(struct_value.GetFieldByName( + "single_struct", ProtoWrapperTypeOptions::kUnsetProtoDefault, + descriptor_pool(), message_factory(), arena(), &field_value), + IsOk()); + + EXPECT_TRUE(field_value.IsMap()); + auto map_value = field_value.GetMap(); + EXPECT_THAT(map_value.Size(), IsOkAndHolds(1)); + + // Verify legacy CelMap interface + auto legacy_map = common_internal::AsLegacyMapValue(field_value); + ASSERT_TRUE(legacy_map.has_value()); + const auto* cel_map = legacy_map->cel_map(); + ASSERT_THAT(cel_map, NotNull()); + EXPECT_EQ(cel_map->size(), 1); + + std::string key_str = "key"; + CelValue cel_key = CelValue::CreateString(&key_str); + auto cel_result = cel_map->Get(arena(), cel_key); + ASSERT_TRUE(cel_result.has_value()); + ASSERT_TRUE(cel_result->IsString()); + EXPECT_EQ(cel_result->StringOrDie().value(), "value"); +} + +TEST_F(LegacyStructValueTest, JsonListAccess) { + TestAllTypes message; + auto* list_field = message.mutable_single_value()->mutable_list_value(); + list_field->add_values()->set_string_value("item"); + + common_internal::LegacyStructValue struct_value( + &message, &google::api::expr::runtime::GetGenericProtoTypeInfoInstance()); + + Value field_value; + ASSERT_THAT(struct_value.GetFieldByName( + "single_value", ProtoWrapperTypeOptions::kUnsetProtoDefault, + descriptor_pool(), message_factory(), arena(), &field_value), + IsOk()); + + EXPECT_TRUE(field_value.IsList()); + auto list_value = field_value.GetList(); + EXPECT_THAT(list_value.Size(), IsOkAndHolds(1)); + + // Verify legacy CelList interface + auto legacy_list = common_internal::AsLegacyListValue(field_value); + ASSERT_TRUE(legacy_list.has_value()); + const auto* cel_list = legacy_list->cel_list(); + ASSERT_THAT(cel_list, NotNull()); + EXPECT_EQ(cel_list->size(), 1); + + CelValue elem = cel_list->Get(arena(), 0); + ASSERT_TRUE(elem.IsString()); + EXPECT_EQ(elem.StringOrDie().value(), "item"); +} + +TEST_F(LegacyStructValueTest, SingularMessageAccess) { + TestAllTypes message; + message.mutable_single_nested_message()->set_bb(100); + + common_internal::LegacyStructValue struct_value( + &message, &google::api::expr::runtime::GetGenericProtoTypeInfoInstance()); + + Value field_value; + ASSERT_THAT( + struct_value.GetFieldByName( + "single_nested_message", ProtoWrapperTypeOptions::kUnsetProtoDefault, + descriptor_pool(), message_factory(), arena(), &field_value), + IsOk()); + + EXPECT_TRUE(common_internal::IsLegacyStructValue(field_value)); + auto nested_struct = common_internal::GetLegacyStructValue(field_value); + + Value bb_value; + ASSERT_THAT(nested_struct.GetFieldByName( + "bb", ProtoWrapperTypeOptions::kUnsetProtoDefault, + descriptor_pool(), message_factory(), arena(), &bb_value), + IsOk()); + EXPECT_TRUE(bb_value.IsInt()); + EXPECT_EQ(bb_value.GetInt().NativeValue(), 100); +} + +TEST_F(LegacyStructValueTest, WrapLegacyFieldAccessResultParsedRepeatedField) { + TestAllTypes message; + message.add_repeated_int32(10); + message.add_repeated_int32(20); + + const auto* field_desc = + message.GetDescriptor()->FindFieldByName("repeated_int32"); + Value val = ParsedRepeatedFieldValue(&message, field_desc, arena()); + interop_internal::WrapLegacyFieldAccessResult(arena(), &val); + + EXPECT_TRUE(val.IsList()); + auto list_val = val.GetList(); + EXPECT_THAT(list_val.IsEmpty(), IsOkAndHolds(false)); + EXPECT_FALSE(list_val.IsZeroValue()); + EXPECT_THAT(list_val.Size(), IsOkAndHolds(2)); + EXPECT_THAT(list_val.Contains(IntValue(10), descriptor_pool(), + message_factory(), arena()), + IsOkAndHolds(BoolValueIs(true))); + + Value elem; + ASSERT_THAT( + list_val.Get(0, descriptor_pool(), message_factory(), arena(), &elem), + IsOk()); + EXPECT_THAT(elem, IntValueIs(10)); + + std::vector elements; + ASSERT_THAT(list_val.ForEach( + [&](const Value& v) -> absl::StatusOr { + elements.push_back(v.GetInt().NativeValue()); + return true; + }, + descriptor_pool(), message_factory(), arena()), + IsOk()); + EXPECT_THAT(elements, testing::ElementsAre(10, 20)); + + auto legacy_list = common_internal::AsLegacyListValue(val); + ASSERT_TRUE(legacy_list.has_value()); + const auto* cel_list = legacy_list->cel_list(); + ASSERT_THAT(cel_list, NotNull()); + EXPECT_EQ(cel_list->size(), 2); + EXPECT_FALSE(cel_list->empty()); + EXPECT_EQ(cel_list->Get(arena(), 0).Int64OrDie(), 10); + EXPECT_EQ((*cel_list)[1].Int64OrDie(), 20); + + Value cloned = val.Clone(arena()); + EXPECT_TRUE(cloned.IsList()); + EXPECT_THAT(cloned.GetList().Size(), IsOkAndHolds(2)); +} + +TEST_F(LegacyStructValueTest, WrapLegacyFieldAccessResultParsedJsonList) { + google::protobuf::ListValue list_proto; + list_proto.add_values()->set_string_value("item1"); + list_proto.add_values()->set_string_value("item2"); + + Value val = ParsedJsonListValue(&list_proto, arena()); + interop_internal::WrapLegacyFieldAccessResult(arena(), &val); + + EXPECT_TRUE(val.IsList()); + auto list_val = val.GetList(); + EXPECT_THAT(list_val.IsEmpty(), IsOkAndHolds(false)); + EXPECT_THAT(list_val.Size(), IsOkAndHolds(2)); + EXPECT_THAT(list_val.Contains(StringValue("item1"), descriptor_pool(), + message_factory(), arena()), + IsOkAndHolds(BoolValueIs(true))); + + Value elem; + ASSERT_THAT( + list_val.Get(0, descriptor_pool(), message_factory(), arena(), &elem), + IsOk()); + EXPECT_THAT(elem, StringValueIs("item1")); + + std::vector elements; + ASSERT_THAT(list_val.ForEach( + [&](const Value& v) -> absl::StatusOr { + elements.push_back(v.GetString().ToString()); + return true; + }, + descriptor_pool(), message_factory(), arena()), + IsOk()); + EXPECT_THAT(elements, testing::ElementsAre("item1", "item2")); + + auto legacy_list = common_internal::AsLegacyListValue(val); + ASSERT_TRUE(legacy_list.has_value()); + const auto* cel_list = legacy_list->cel_list(); + ASSERT_THAT(cel_list, NotNull()); + EXPECT_EQ(cel_list->size(), 2); + EXPECT_FALSE(cel_list->empty()); + EXPECT_EQ(cel_list->Get(arena(), 0).StringOrDie().value(), "item1"); + EXPECT_EQ((*cel_list)[1].StringOrDie().value(), "item2"); + + Value cloned = val.Clone(arena()); + EXPECT_TRUE(cloned.IsList()); + EXPECT_THAT(cloned.GetList().Size(), IsOkAndHolds(2)); +} + +TEST_F(LegacyStructValueTest, WrapLegacyFieldAccessResultParsedMapField) { + TestAllTypes message; + (*message.mutable_map_string_string())["key1"] = "val1"; + (*message.mutable_map_string_string())["key2"] = "val2"; + + const auto* field_desc = + message.GetDescriptor()->FindFieldByName("map_string_string"); + Value val = ParsedMapFieldValue(&message, field_desc, arena()); + interop_internal::WrapLegacyFieldAccessResult(arena(), &val); + + EXPECT_TRUE(val.IsMap()); + auto map_val = val.GetMap(); + EXPECT_THAT(map_val.IsEmpty(), IsOkAndHolds(false)); + EXPECT_FALSE(map_val.IsZeroValue()); + EXPECT_THAT(map_val.Size(), IsOkAndHolds(2)); + EXPECT_THAT(map_val.Has(StringValue("key1"), descriptor_pool(), + message_factory(), arena()), + IsOkAndHolds(BoolValueIs(true))); + EXPECT_THAT(map_val.Has(StringValue("missing"), descriptor_pool(), + message_factory(), arena()), + IsOkAndHolds(BoolValueIs(false))); + EXPECT_THAT( + map_val.Has(IntValue(1), descriptor_pool(), message_factory(), arena()), + IsOkAndHolds(ErrorValueIs(StatusIs(absl::StatusCode::kInvalidArgument)))); + + Value found_val; + ASSERT_THAT(map_val.Find(StringValue("key1"), descriptor_pool(), + message_factory(), arena(), &found_val), + IsOkAndHolds(true)); + EXPECT_THAT(found_val, StringValueIs("val1")); + + Value get_val; + ASSERT_THAT(map_val.Get(StringValue("key2"), descriptor_pool(), + message_factory(), arena(), &get_val), + IsOk()); + EXPECT_THAT(get_val, StringValueIs("val2")); + + ListValue keys; + ASSERT_THAT( + map_val.ListKeys(descriptor_pool(), message_factory(), arena(), &keys), + IsOk()); + EXPECT_THAT(keys.Size(), IsOkAndHolds(2)); + + auto legacy_map = common_internal::AsLegacyMapValue(val); + ASSERT_TRUE(legacy_map.has_value()); + const auto* cel_map = legacy_map->cel_map(); + ASSERT_THAT(cel_map, NotNull()); + EXPECT_EQ(cel_map->size(), 2); + EXPECT_FALSE(cel_map->empty()); + + std::string k1 = "key1"; + CelValue cel_k1 = CelValue::CreateString(&k1); + auto cel_find = cel_map->Get(arena(), cel_k1); + ASSERT_TRUE(cel_find.has_value()); + EXPECT_EQ(cel_find->StringOrDie().value(), "val1"); + + auto has_res = cel_map->Has(cel_k1); + ASSERT_THAT(has_res, IsOk()); + EXPECT_TRUE(*has_res); + + Value cloned = val.Clone(arena()); + EXPECT_TRUE(cloned.IsMap()); + EXPECT_THAT(cloned.GetMap().Size(), IsOkAndHolds(2)); +} + +TEST_F(LegacyStructValueTest, WrapLegacyFieldAccessResultParsedJsonMap) { + google::protobuf::Struct struct_proto; + (*struct_proto.mutable_fields())["k1"].set_string_value("v1"); + (*struct_proto.mutable_fields())["k2"].set_string_value("v2"); + + Value val = ParsedJsonMapValue(&struct_proto, arena()); + interop_internal::WrapLegacyFieldAccessResult(arena(), &val); + + EXPECT_TRUE(val.IsMap()); + auto map_val = val.GetMap(); + EXPECT_THAT(map_val.IsEmpty(), IsOkAndHolds(false)); + EXPECT_FALSE(map_val.IsZeroValue()); + EXPECT_THAT(map_val.Size(), IsOkAndHolds(2)); + EXPECT_THAT(map_val.Has(StringValue("k1"), descriptor_pool(), + message_factory(), arena()), + IsOkAndHolds(BoolValueIs(true))); + EXPECT_THAT(map_val.Has(StringValue("missing"), descriptor_pool(), + message_factory(), arena()), + IsOkAndHolds(BoolValueIs(false))); + EXPECT_THAT( + map_val.Has(IntValue(1), descriptor_pool(), message_factory(), arena()), + IsOkAndHolds(ErrorValueIs(StatusIs(absl::StatusCode::kInvalidArgument)))); + + Value found_val; + ASSERT_THAT(map_val.Find(StringValue("k1"), descriptor_pool(), + message_factory(), arena(), &found_val), + IsOkAndHolds(true)); + EXPECT_THAT(found_val, StringValueIs("v1")); + + Value get_val; + ASSERT_THAT(map_val.Get(StringValue("k2"), descriptor_pool(), + message_factory(), arena(), &get_val), + IsOk()); + EXPECT_THAT(get_val, StringValueIs("v2")); + + ListValue keys; + ASSERT_THAT( + map_val.ListKeys(descriptor_pool(), message_factory(), arena(), &keys), + IsOk()); + EXPECT_THAT(keys.Size(), IsOkAndHolds(2)); + + auto legacy_map = common_internal::AsLegacyMapValue(val); + ASSERT_TRUE(legacy_map.has_value()); + const auto* cel_map = legacy_map->cel_map(); + ASSERT_THAT(cel_map, NotNull()); + EXPECT_EQ(cel_map->size(), 2); + EXPECT_FALSE(cel_map->empty()); + + std::string k1 = "k1"; + CelValue cel_k1 = CelValue::CreateString(&k1); + auto cel_find = cel_map->Get(arena(), cel_k1); + ASSERT_TRUE(cel_find.has_value()); + EXPECT_EQ(cel_find->StringOrDie().value(), "v1"); + + auto has_res = cel_map->Has(cel_k1); + ASSERT_THAT(has_res, IsOk()); + EXPECT_TRUE(*has_res); + + Value cloned = val.Clone(arena()); + EXPECT_TRUE(cloned.IsMap()); + EXPECT_THAT(cloned.GetMap().Size(), IsOkAndHolds(2)); +} + +TEST_F(LegacyStructValueTest, WrapLegacyFieldAccessResultEmptyContainers) { + Value empty_list = ListValue(); + interop_internal::WrapLegacyFieldAccessResult(arena(), &empty_list); + EXPECT_TRUE(empty_list.IsList()); + EXPECT_THAT(empty_list.GetList().Size(), IsOkAndHolds(0)); + EXPECT_THAT(empty_list.GetList().IsEmpty(), IsOkAndHolds(true)); + auto legacy_list = common_internal::AsLegacyListValue(empty_list); + ASSERT_TRUE(legacy_list.has_value()); + EXPECT_EQ(legacy_list->cel_list()->size(), 0); + + Value empty_map = MapValue(); + interop_internal::WrapLegacyFieldAccessResult(arena(), &empty_map); + EXPECT_TRUE(empty_map.IsMap()); + EXPECT_THAT(empty_map.GetMap().Size(), IsOkAndHolds(0)); + EXPECT_THAT(empty_map.GetMap().IsEmpty(), IsOkAndHolds(true)); + auto legacy_map = common_internal::AsLegacyMapValue(empty_map); + ASSERT_TRUE(legacy_map.has_value()); + EXPECT_EQ(legacy_map->cel_map()->size(), 0); +} + +} // namespace +} // namespace cel diff --git a/eval/eval/BUILD b/eval/eval/BUILD index 329ee71f4..f6ce6e221 100644 --- a/eval/eval/BUILD +++ b/eval/eval/BUILD @@ -314,11 +314,15 @@ cc_library( ":direct_expression_step", ":evaluator_core", ":expression_step_base", + "//common:memory", "//common:type", "//common:value", "//common:value_kind", + "//eval/public:cel_value", + "//eval/public/structs:proto_message_type_adapter", "//internal:status_macros", "//runtime:runtime_options", + "@com_google_absl//absl/base:nullability", "@com_google_absl//absl/log:absl_check", "@com_google_absl//absl/log:absl_log", "@com_google_absl//absl/status", diff --git a/eval/eval/select_step.cc b/eval/eval/select_step.cc index 0b31c3c13..10426fee9 100644 --- a/eval/eval/select_step.cc +++ b/eval/eval/select_step.cc @@ -2,9 +2,11 @@ #include #include +#include #include #include +#include "absl/base/nullability.h" #include "absl/log/absl_check.h" #include "absl/log/absl_log.h" #include "absl/status/status.h" @@ -12,6 +14,7 @@ #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "common/legacy_value.h" +#include "common/memory.h" #include "common/type.h" #include "common/value.h" #include "common/value_kind.h" @@ -19,6 +22,8 @@ #include "eval/eval/direct_expression_step.h" #include "eval/eval/evaluator_core.h" #include "eval/eval/expression_step_base.h" +#include "eval/public/cel_value.h" +#include "eval/public/structs/proto_message_type_adapter.h" #include "internal/status_macros.h" #include "runtime/runtime_options.h" #include "google/protobuf/arena.h" @@ -74,6 +79,33 @@ absl::optional CheckForMarkedAttributes(const AttributeTrail& trail, return std::nullopt; } +// Helper for StructValue::GetFieldByName. Used for opting out of old reflection +// implementation. +absl::Status WrappedStructGet( + const Value& target, absl::string_view field, + ProtoWrapperTypeOptions unboxing_option, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, bool enable_updated_proto_reflection, + Value* absl_nonnull result) { + if (!enable_updated_proto_reflection) { + if (const google::protobuf::Message* message = + cel::interop_internal::GetLegacyMessage(target); + message != nullptr) { + CelValue::MessageWrapper message_wrapper( + message, &GetGenericProtoTypeInfoInstance()); + CEL_ASSIGN_OR_RETURN( + CelValue cel_value, + internal::GetGenericProtoAccessApisInstance().GetField( + field, message_wrapper, unboxing_option, + cel::MemoryManagerRef::Pooling(arena))); + return cel::ModernValue(arena, cel_value, *result); + } + } + return target.GetStruct().GetFieldByName( + field, unboxing_option, descriptor_pool, message_factory, arena, result); +} + absl::Status PerformHas(const Value& target, absl::string_view field, const StringValue& field_value, const google::protobuf::DescriptorPool* descriptor_pool, @@ -104,7 +136,8 @@ absl::Status PerformGet(const Value& target, absl::string_view field, ProtoWrapperTypeOptions unboxing_option, const google::protobuf::DescriptorPool* descriptor_pool, google::protobuf::MessageFactory* message_factory, - google::protobuf::Arena* arena, Value& result) { + google::protobuf::Arena* arena, + bool enable_updated_proto_reflection, Value& result) { switch (target.kind()) { case ValueKind::kMap: { auto status = target.GetMap().Get(field_value, descriptor_pool, @@ -115,9 +148,9 @@ absl::Status PerformGet(const Value& target, absl::string_view field, return absl::OkStatus(); } case ValueKind::kStruct: { - auto status = target.GetStruct().GetFieldByName( - field, unboxing_option, descriptor_pool, message_factory, arena, - &result); + auto status = WrappedStructGet(target, field, unboxing_option, + descriptor_pool, message_factory, arena, + enable_updated_proto_reflection, &result); if (!status.ok()) { result = ErrorValue(std::move(status)); } @@ -133,7 +166,9 @@ absl::Status PerformOptionalGet(const Value& target, absl::string_view field, ProtoWrapperTypeOptions unboxing_option, const google::protobuf::DescriptorPool* descriptor_pool, google::protobuf::MessageFactory* message_factory, - google::protobuf::Arena* arena, Value& result) { + google::protobuf::Arena* arena, + bool enable_updated_proto_reflection, + Value& result) { switch (target.kind()) { case ValueKind::kMap: { CEL_ASSIGN_OR_RETURN( @@ -154,9 +189,9 @@ absl::Status PerformOptionalGet(const Value& target, absl::string_view field, result = OptionalValue::None(); return absl::OkStatus(); } - CEL_RETURN_IF_ERROR(target.GetStruct().GetFieldByName( - field, unboxing_option, descriptor_pool, message_factory, arena, - &result)); + CEL_RETURN_IF_ERROR(WrappedStructGet( + target, field, unboxing_option, descriptor_pool, message_factory, + arena, enable_updated_proto_reflection, &result)); ABSL_DCHECK(!result.IsUnknown()); result = OptionalValue::Of(std::move(result), arena); @@ -219,7 +254,7 @@ absl::Status SelectStep::Evaluate(ExecutionFrame* frame) const { optional_arg = arg.GetOptional(); } - if (!(optional_arg || arg->Is() || arg->Is())) { + if (!(optional_arg || arg.IsMap() || arg.IsStruct())) { frame->value_stack().PopAndPush(cel::ErrorValue(InvalidSelectTargetError()), std::move(result_trail)); return absl::OkStatus(); @@ -262,7 +297,8 @@ absl::Status SelectStep::Evaluate(ExecutionFrame* frame) const { optional_arg->Value(&value); auto status = PerformOptionalGet( value, field_, field_value_, unboxing_option_, frame->descriptor_pool(), - frame->message_factory(), frame->arena(), result); + frame->message_factory(), frame->arena(), + frame->options().enable_updated_proto_reflection, result); if (!status.ok()) { result = ErrorValue(std::move(status)); } @@ -272,7 +308,8 @@ absl::Status SelectStep::Evaluate(ExecutionFrame* frame) const { CEL_RETURN_IF_ERROR(PerformGet( arg, field_, field_value_, unboxing_option_, frame->descriptor_pool(), - frame->message_factory(), frame->arena(), result)); + frame->message_factory(), frame->arena(), + frame->options().enable_updated_proto_reflection, result)); frame->value_stack().PopAndPush(std::move(result), std::move(result_trail)); return absl::OkStatus(); } @@ -352,10 +389,10 @@ class DirectSelectStep : public DirectExpressionStep { } Value value; optional_arg->Value(&value); - auto status = - PerformOptionalGet(value, field_, field_value_, unboxing_option_, - frame.descriptor_pool(), frame.message_factory(), - frame.arena(), result); + auto status = PerformOptionalGet( + value, field_, field_value_, unboxing_option_, + frame.descriptor_pool(), frame.message_factory(), frame.arena(), + frame.options().enable_updated_proto_reflection, result); if (!status.ok()) { result = ErrorValue(std::move(status)); } @@ -364,7 +401,8 @@ class DirectSelectStep : public DirectExpressionStep { return PerformGet(result, field_, field_value_, unboxing_option_, frame.descriptor_pool(), frame.message_factory(), - frame.arena(), result); + frame.arena(), + frame.options().enable_updated_proto_reflection, result); } private: @@ -467,7 +505,8 @@ absl::Status ProtoSelectStep::EvaluateLegacyMessageGetField( return absl::OkStatus(); } return cel::interop_internal::WrapLegacyMessageField( - legacy_message, field_descriptor_, unboxing_option_, frame->arena(), + legacy_message, field_descriptor_, unboxing_option_, + frame->descriptor_pool(), frame->message_factory(), frame->arena(), &frame->value_stack().Peek()); } diff --git a/eval/public/cel_options.cc b/eval/public/cel_options.cc index 93b67ad35..645ae24e3 100644 --- a/eval/public/cel_options.cc +++ b/eval/public/cel_options.cc @@ -45,6 +45,7 @@ cel::RuntimeOptions ConvertToRuntimeOptions(const InterpreterOptions& options) { options.enable_fast_builtins, options.enable_precision_preserving_double_format, options.enable_typed_field_access, + options.enable_updated_proto_reflection, }; } diff --git a/eval/public/cel_options.h b/eval/public/cel_options.h index 001990431..50b7011fe 100644 --- a/eval/public/cel_options.h +++ b/eval/public/cel_options.h @@ -223,6 +223,15 @@ struct InterpreterOptions { // path for field access when the type is known at plan time, instead of using // the generic field access implementation. bool enable_typed_field_access = false; + + // Temporary flag to gate using updated proto reflection APIs. + // + // For the cel::Runtime APIs, this is a no-op. + // + // For google::api::expr::runtime::CelExprssion, this will enable updated + // implementations for proto reflection APIs, aligned with the cel::Value + // implementation. + bool enable_updated_proto_reflection = false; }; // LINT.ThenChange(//depot/google3/runtime/runtime_options.h) diff --git a/eval/public/structs/proto_message_type_adapter_test.cc b/eval/public/structs/proto_message_type_adapter_test.cc index 529052025..b44c17062 100644 --- a/eval/public/structs/proto_message_type_adapter_test.cc +++ b/eval/public/structs/proto_message_type_adapter_test.cc @@ -1188,10 +1188,12 @@ TEST(ProtoMesssageTypeAdapter, InteropFieldAccess) { message.GetDescriptor()->FindFieldByName("string_value"); ASSERT_NE(field, nullptr); cel::Value field_value; - ASSERT_THAT(cel::interop_internal::WrapLegacyMessageField( - &message, field, ProtoWrapperTypeOptions::kUnsetNull, &arena, - &field_value), - IsOk()); + ASSERT_THAT( + cel::interop_internal::WrapLegacyMessageField( + &message, field, ProtoWrapperTypeOptions::kUnsetNull, + google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), &arena, &field_value), + IsOk()); EXPECT_THAT(field_value, cel::test::StringValueIs("hello")); } diff --git a/extensions/BUILD b/extensions/BUILD index faf9f08c8..df5477112 100644 --- a/extensions/BUILD +++ b/extensions/BUILD @@ -331,6 +331,7 @@ cc_library( "//common:expr", "//common:function_descriptor", "//common:kind", + "//common:memory", "//common:native_type", "//common:type", "//common:value", @@ -340,10 +341,13 @@ cc_library( "//eval/eval:direct_expression_step", "//eval/eval:evaluator_core", "//eval/eval:expression_step_base", + "//eval/public:cel_value", + "//eval/public/structs:proto_message_type_adapter", "//internal:casts", "//internal:number", "//internal:status_macros", "//runtime:runtime_builder", + "//runtime:runtime_options", "//runtime/internal:errors", "//runtime/internal:runtime_friend_access", "//runtime/internal:runtime_impl", @@ -355,7 +359,6 @@ cc_library( "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", - "@com_google_absl//absl/types:optional", "@com_google_absl//absl/types:span", "@com_google_absl//absl/types:variant", "@com_google_protobuf//:protobuf", diff --git a/extensions/select_optimization.cc b/extensions/select_optimization.cc index 0cc64311a..14dfe6412 100644 --- a/extensions/select_optimization.cc +++ b/extensions/select_optimization.cc @@ -18,8 +18,10 @@ #include #include #include +#include #include #include +#include #include #include "absl/algorithm/container.h" @@ -31,7 +33,6 @@ #include "absl/status/statusor.h" #include "absl/strings/match.h" #include "absl/strings/string_view.h" -#include "absl/types/optional.h" #include "absl/types/span.h" #include "absl/types/variant.h" #include "base/attribute.h" @@ -43,6 +44,8 @@ #include "common/expr.h" #include "common/function_descriptor.h" #include "common/kind.h" +#include "common/legacy_value.h" +#include "common/memory.h" #include "common/native_type.h" #include "common/type.h" #include "common/value.h" @@ -52,6 +55,8 @@ #include "eval/eval/direct_expression_step.h" #include "eval/eval/evaluator_core.h" #include "eval/eval/expression_step_base.h" +#include "eval/public/cel_value.h" +#include "eval/public/structs/proto_message_type_adapter.h" #include "internal/casts.h" #include "internal/number.h" #include "internal/status_macros.h" @@ -59,6 +64,7 @@ #include "runtime/internal/runtime_friend_access.h" #include "runtime/internal/runtime_impl.h" #include "runtime/runtime_builder.h" +#include "runtime/runtime_options.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" #include "google/protobuf/message.h" @@ -74,12 +80,15 @@ using ::cel::Expr; using ::cel::ExprKind; using ::cel::SelectExpr; using ::google::api::expr::runtime::AttributeTrail; +using ::google::api::expr::runtime::CelValue; using ::google::api::expr::runtime::DirectExpressionStep; using ::google::api::expr::runtime::ExecutionFrame; using ::google::api::expr::runtime::ExecutionFrameBase; using ::google::api::expr::runtime::ExpressionStepBase; +using ::google::api::expr::runtime::GetGenericProtoTypeInfoInstance; using ::google::api::expr::runtime::PlannerContext; using ::google::api::expr::runtime::ProgramOptimizer; +using ::google::api::expr::runtime::internal::GetGenericProtoAccessApisInstance; // Represents a single select operation (field access or indexing). // For struct-typed field accesses, includes the field name and the field @@ -267,11 +276,66 @@ absl::StatusOr MapKeyFromQualifier(const AttributeQualifier& qual, } } +// // Helper for StructValue::GetFieldByName. Used for opting out of old +// reflection implementation. +absl::StatusOr WrappedStructGet( + const Value& target, absl::string_view field, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, bool enable_updated_proto_reflection) { + if (!enable_updated_proto_reflection) { + if (const google::protobuf::Message* message = + cel::interop_internal::GetLegacyMessage(target); + message != nullptr) { + CelValue::MessageWrapper message_wrapper( + message, &GetGenericProtoTypeInfoInstance()); + CEL_ASSIGN_OR_RETURN(CelValue cel_value, + GetGenericProtoAccessApisInstance().GetField( + field, message_wrapper, + ProtoWrapperTypeOptions::kUnsetProtoDefault, + MemoryManagerRef::Pooling(arena))); + Value result; + CEL_RETURN_IF_ERROR(cel::ModernValue(arena, cel_value, result)); + return result; + } + } + return target.GetStruct().GetFieldByName(field, descriptor_pool, + message_factory, arena); +} + +// Helper for StructValue::Qualify. Used for opting out of old reflection +// implementation. +absl::StatusOr> WrappedStructQualify( + const StructValue& struct_value, + absl::Span qualifiers, bool presence_test, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, bool enable_updated_proto_reflection) { + if (!enable_updated_proto_reflection) { + if (const google::protobuf::Message* message = + cel::interop_internal::GetLegacyMessage(struct_value); + message != nullptr) { + CelValue::MessageWrapper message_wrapper( + message, &GetGenericProtoTypeInfoInstance()); + CEL_ASSIGN_OR_RETURN(auto legacy_result, + GetGenericProtoAccessApisInstance().Qualify( + qualifiers, message_wrapper, presence_test, + MemoryManagerRef::Pooling(arena))); + Value result; + CEL_RETURN_IF_ERROR(cel::ModernValue(arena, legacy_result.value, result)); + return std::pair{std::move(result), + legacy_result.qualifier_count}; + } + } + return struct_value.Qualify(qualifiers, presence_test, descriptor_pool, + message_factory, arena); +} + absl::StatusOr ApplyQualifier( const Value& operand, const SelectQualifier& qualifier, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena) { + google::protobuf::Arena* absl_nonnull arena, bool enable_updated_proto_reflection) { return absl::visit( absl::Overload( [&](const FieldSpecifier& field_specifier) -> absl::StatusOr { @@ -280,8 +344,9 @@ absl::StatusOr ApplyQualifier( cel::runtime_internal::CreateNoMatchingOverloadError( "