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..a61b9c476 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 = true; }; // 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( "