From 8e811deb36334b119a3cba13434e5e91380fb345 Mon Sep 17 00:00:00 2001 From: Jonathan Tatum Date: Wed, 26 Aug 2026 15:16:55 -0700 Subject: [PATCH] Split out utility for wrapping values into WKTs. PiperOrigin-RevId: 971530712 --- common/BUILD | 2 +- common/legacy_value.cc | 7 +- common/legacy_value.h | 4 + common/values/legacy_map_value.cc | 56 +- common/values/legacy_struct_value_test.cc | 12 + common/values/parsed_map_field_value.h | 5 + common/values/parsed_message_value.h | 6 + eval/internal/BUILD | 3 +- eval/internal/cel_value_equal_test.cc | 13 +- eval/public/structs/BUILD | 77 +- eval/public/structs/cel_proto_wrap_util.cc | 826 ---------------- eval/public/structs/cel_proto_wrap_util.h | 12 - .../structs/cel_proto_wrap_util_test.cc | 528 ----------- .../cel_proto_wrap_value_to_message.cc | 895 ++++++++++++++++++ .../structs/cel_proto_wrap_value_to_message.h | 40 + .../cel_proto_wrap_value_to_message_test.cc | 616 ++++++++++++ eval/public/structs/cel_proto_wrapper.cc | 89 +- eval/public/structs/cel_proto_wrapper.h | 21 +- eval/public/structs/cel_proto_wrapper_test.cc | 60 +- eval/public/structs/field_access_impl.cc | 1 + 20 files changed, 1846 insertions(+), 1427 deletions(-) create mode 100644 eval/public/structs/cel_proto_wrap_value_to_message.cc create mode 100644 eval/public/structs/cel_proto_wrap_value_to_message.h create mode 100644 eval/public/structs/cel_proto_wrap_value_to_message_test.cc diff --git a/common/BUILD b/common/BUILD index 185af4e27..f34f07fcf 100644 --- a/common/BUILD +++ b/common/BUILD @@ -793,7 +793,7 @@ cc_library( "//eval/internal:cel_value_equal", "//eval/public:cel_value", "//eval/public:message_wrapper", - "//eval/public/structs:cel_proto_wrap_util", + "//eval/public/structs:cel_proto_wrap_value_to_message", "//eval/public/structs:legacy_type_info_apis", "//eval/public/structs:proto_message_type_adapter", "//eval/public/structs:trivial_legacy_type_info_internal", diff --git a/common/legacy_value.cc b/common/legacy_value.cc index a08b8317a..3a2108c4d 100644 --- a/common/legacy_value.cc +++ b/common/legacy_value.cc @@ -51,7 +51,7 @@ #include "eval/internal/cel_value_equal.h" #include "eval/public/cel_value.h" #include "eval/public/message_wrapper.h" -#include "eval/public/structs/cel_proto_wrap_util.h" +#include "eval/public/structs/cel_proto_wrap_value_to_message.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" @@ -260,6 +260,11 @@ CelValue LegacyTrivialStructValue(google::protobuf::Arena* absl_nonnull arena, } if (auto parsed_message_value = value.AsParsedMessage(); parsed_message_value) { + if (interop_internal::IsUnsafeParsedMessageValue(*parsed_message_value)) { + return CelValue::CreateMessageWrapper( + AsMessageWrapper(cel::to_address(*parsed_message_value), + &GetGenericProtoTypeInfoInstance())); + } auto maybe_cloned = parsed_message_value->Clone(arena); return CelValue::CreateMessageWrapper(MessageWrapper( cel::to_address(maybe_cloned), &GetGenericProtoTypeInfoInstance())); diff --git a/common/legacy_value.h b/common/legacy_value.h index a89eb0412..e71eb85a7 100644 --- a/common/legacy_value.h +++ b/common/legacy_value.h @@ -65,6 +65,10 @@ class MessageFactory; namespace cel::interop_internal { +inline bool IsUnsafeParsedMessageValue(const cel::ParsedMessageValue& value) { + return value.is_unsafe(); +} + // 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); diff --git a/common/values/legacy_map_value.cc b/common/values/legacy_map_value.cc index e287df076..25be43ab9 100644 --- a/common/values/legacy_map_value.cc +++ b/common/values/legacy_map_value.cc @@ -218,21 +218,33 @@ class LegacyParsedMapFieldMapValue final 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())) { + // Legacy to modern should succeed for a valid CelValue. 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) { + // Call custom map Find directly. MapValue normally handles wrapping + // non-ok result to error value types, so emulate that here. + // + // Use the descriptor pool and message factory from the value. This is not + // totally consistent with modern APIs, but this should behave the same as + // the legacy map did. + const google::protobuf::Message* msg = value_.message_; + ABSL_DCHECK(msg->GetDescriptor() != nullptr); + ABSL_DCHECK(msg->GetReflection() != nullptr); + + const google::protobuf::DescriptorPool* descriptor_pool = + msg->GetDescriptor()->file()->pool(); + google::protobuf::MessageFactory* message_factory = + msg->GetReflection()->GetMessageFactory(); + auto found = + Find(modern_key, descriptor_pool, message_factory, arena, &modern_val); + if (!found.ok()) { + return google::api::expr::runtime::CreateErrorValue(arena, + found.status()); + } + if (!(*found) && !modern_val.IsError()) { return std::nullopt; } return UnsafeLegacyValue(modern_val, /*stable=*/false, arena); @@ -401,21 +413,25 @@ class LegacyParsedJsonMapValue final 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())) { + // Legacy to modern should succeed for a valid CelValue. 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) { + // Call custom map Find directly. MapValue normally handles wrapping + // non-ok result to error value types, so emulate that here. + // + // We know that the descriptor pool and message factory aren't needed here, + // so fine to use generated. + auto found = + Find(modern_key, google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), arena, &modern_val); + if (!found.ok()) { + return google::api::expr::runtime::CreateErrorValue(arena, + found.status()); + } + if (!(*found) && !modern_val.IsError()) { return std::nullopt; } return UnsafeLegacyValue(modern_val, /*stable=*/false, arena); diff --git a/common/values/legacy_struct_value_test.cc b/common/values/legacy_struct_value_test.cc index 1170f2824..2e1949c0b 100644 --- a/common/values/legacy_struct_value_test.cc +++ b/common/values/legacy_struct_value_test.cc @@ -190,6 +190,18 @@ TEST_F(LegacyStructValueTest, MapFieldKeyTypeValidation) { 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)); + + auto invalid_get_res = cel_map->Get(arena(), str_key); + ASSERT_TRUE(invalid_get_res.has_value()); + ASSERT_TRUE(invalid_get_res->IsError()); + EXPECT_THAT(*invalid_get_res->ErrorOrDie(), + StatusIs(absl::StatusCode::kInvalidArgument)); + + auto invalid_subscript_res = (*cel_map)[str_key]; + ASSERT_TRUE(invalid_subscript_res.has_value()); + ASSERT_TRUE(invalid_subscript_res->IsError()); + EXPECT_THAT(*invalid_subscript_res->ErrorOrDie(), + StatusIs(absl::StatusCode::kInvalidArgument)); } TEST_F(LegacyStructValueTest, JsonStructAccess) { diff --git a/common/values/parsed_map_field_value.h b/common/values/parsed_map_field_value.h index 21d686bfd..f31f2e070 100644 --- a/common/values/parsed_map_field_value.h +++ b/common/values/parsed_map_field_value.h @@ -47,6 +47,10 @@ class ValueIterator; class ListValue; class ParsedJsonMapValue; +namespace common_internal { +class LegacyParsedMapFieldMapValue; +} // namespace common_internal + // ParsedMapFieldValue is a MapValue over a map field of a parsed protocol // buffer message. class ParsedMapFieldValue final @@ -192,6 +196,7 @@ class ParsedMapFieldValue final friend class ParsedJsonMapValue; friend class common_internal::ValueMixin; friend class common_internal::MapValueMixin; + friend class common_internal::LegacyParsedMapFieldMapValue; friend ParsedMapFieldValue UnsafeParsedMapFieldValue( const google::protobuf::Message* absl_nonnull message, const google::protobuf::FieldDescriptor* absl_nonnull field); diff --git a/common/values/parsed_message_value.h b/common/values/parsed_message_value.h index 3cad912e7..1d02a4369 100644 --- a/common/values/parsed_message_value.h +++ b/common/values/parsed_message_value.h @@ -48,6 +48,10 @@ namespace cel { +namespace interop_internal { +bool IsUnsafeParsedMessageValue(const ParsedMessageValue& value); +} + class MessageValue; class StructValue; class Value; @@ -189,6 +193,8 @@ class ParsedMessageValue final friend class common_internal::StructValueMixin; friend ParsedMessageValue UnsafeParsedMessageValue( const google::protobuf::Message* absl_nonnull value); + friend bool interop_internal::IsUnsafeParsedMessageValue( + const ParsedMessageValue& value); explicit ParsedMessageValue( const google::protobuf::Message* absl_nonnull value ABSL_ATTRIBUTE_LIFETIME_BOUND) diff --git a/eval/internal/BUILD b/eval/internal/BUILD index d6f31493e..c168ce3fa 100644 --- a/eval/internal/BUILD +++ b/eval/internal/BUILD @@ -56,11 +56,10 @@ cc_test( "//eval/public/structs:trivial_legacy_type_info", "//eval/testutil:test_message_cc_proto", "//internal:testing", - "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/log:absl_check", "@com_google_absl//absl/strings", "@com_google_absl//absl/time", "@com_google_absl//absl/types:span", - "@com_google_absl//absl/types:variant", "@com_google_googleapis//google/rpc/context:attribute_context_cc_proto", "@com_google_protobuf//:any_cc_proto", "@com_google_protobuf//:protobuf", diff --git a/eval/internal/cel_value_equal_test.cc b/eval/internal/cel_value_equal_test.cc index 109a63795..13787e973 100644 --- a/eval/internal/cel_value_equal_test.cc +++ b/eval/internal/cel_value_equal_test.cc @@ -18,20 +18,21 @@ #include #include #include +#include #include #include #include +#include #include #include "google/protobuf/any.pb.h" #include "google/rpc/context/attribute_context.pb.h" #include "google/protobuf/descriptor.pb.h" -#include "absl/status/statusor.h" +#include "absl/log/absl_check.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" #include "absl/types/span.h" -#include "absl/types/variant.h" #include "eval/public/cel_value.h" #include "eval/public/containers/container_backed_list_impl.h" #include "eval/public/containers/container_backed_map_impl.h" @@ -131,6 +132,8 @@ const std::vector& ValueExamples1() { result->push_back(CelValue::CreateMap(&CelMapExample1())); result->push_back(CelValue::CreateCelTypeView("type")); + ABSL_CHECK_EQ(arena.SpaceUsed(), 0) << "Arena should not be used."; + return result.release(); }(); return *examples; @@ -185,7 +188,7 @@ std::string CelValueEqualTestName( } TEST_P(CelValueEqualImplTypesTest, Basic) { - absl::optional result = CelValueEqualImpl(lhs(), rhs()); + std::optional result = CelValueEqualImpl(lhs(), rhs()); if (lhs().IsNull() || rhs().IsNull()) { if (lhs().IsNull() && rhs().IsNull()) { @@ -267,7 +270,7 @@ const std::vector& NumericValuesNotEqualExample() { using NumericInequalityTest = testing::TestWithParam; TEST_P(NumericInequalityTest, NumericValues) { NumericInequalityTestCase test_case = GetParam(); - absl::optional result = CelValueEqualImpl(test_case.a, test_case.b); + std::optional result = CelValueEqualImpl(test_case.a, test_case.b); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, false); } @@ -280,7 +283,7 @@ INSTANTIATE_TEST_SUITE_P( }); TEST(CelValueEqualImplTest, LossyNumericEquality) { - absl::optional result = CelValueEqualImpl( + std::optional result = CelValueEqualImpl( CelValue::CreateDouble( static_cast(std::numeric_limits::max()) - 1), CelValue::CreateInt64(std::numeric_limits::max())); diff --git a/eval/public/structs/BUILD b/eval/public/structs/BUILD index 468867294..107451e01 100644 --- a/eval/public/structs/BUILD +++ b/eval/public/structs/BUILD @@ -28,12 +28,19 @@ cc_library( "cel_proto_wrapper.h", ], deps = [ - ":cel_proto_wrap_util", + ":cel_proto_wrap_value_to_message", ":proto_message_type_adapter", + ":trivial_legacy_type_info_internal", + "//common:value", "//eval/public:cel_value", "//eval/public:message_wrapper", "//internal:proto_time_encoding", - "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/base:no_destructor", + "@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", + "@com_google_absl//absl/status:statusor", "@com_google_protobuf//:duration_cc_proto", "@com_google_protobuf//:protobuf", "@com_google_protobuf//:timestamp_cc_proto", @@ -62,16 +69,13 @@ cc_library( deps = [ ":protobuf_value_factory", "//eval/public:cel_value", - "//internal:overflow", "//internal:proto_time_encoding", "//internal:status_macros", - "//internal:time", "//internal:well_known_types", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/base:nullability", "@com_google_absl//absl/functional:overload", "@com_google_absl//absl/log:absl_check", - "@com_google_absl//absl/log:absl_log", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", @@ -97,19 +101,70 @@ cc_test( ], deps = [ ":cel_proto_wrap_util", - ":protobuf_value_factory", ":trivial_legacy_type_info", "//eval/public:cel_value", - "//eval/public:message_wrapper", - "//eval/public/containers:container_backed_list_impl", - "//eval/public/containers:container_backed_map_impl", "//eval/testutil:test_message_cc_proto", "//internal:proto_time_encoding", + "//internal:testing", + "//testutil:util", + "@com_google_absl//absl/status", + "@com_google_protobuf//:any_cc_proto", + "@com_google_protobuf//:duration_cc_proto", + "@com_google_protobuf//:empty_cc_proto", + "@com_google_protobuf//:field_mask_cc_proto", + "@com_google_protobuf//:protobuf", + "@com_google_protobuf//:struct_cc_proto", + "@com_google_protobuf//:wrappers_cc_proto", + ], +) + +cc_library( + name = "cel_proto_wrap_value_to_message", + srcs = [ + "cel_proto_wrap_value_to_message.cc", + ], + hdrs = [ + "cel_proto_wrap_value_to_message.h", + ], + deps = [ + "//eval/public:cel_value", + "//internal:overflow", + "//internal:proto_time_encoding", "//internal:status_macros", + "//internal:time", + "//internal:well_known_types", + "@com_google_absl//absl/log:absl_log", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:cord", + "@com_google_absl//absl/time", + "@com_google_protobuf//:any_cc_proto", + "@com_google_protobuf//:duration_cc_proto", + "@com_google_protobuf//:protobuf", + "@com_google_protobuf//:struct_cc_proto", + "@com_google_protobuf//:timestamp_cc_proto", + "@com_google_protobuf//:wrappers_cc_proto", + ], +) + +cc_test( + name = "cel_proto_wrap_value_to_message_test", + size = "small", + srcs = [ + "cel_proto_wrap_value_to_message_test.cc", + ], + deps = [ + ":cel_proto_wrap_value_to_message", + ":trivial_legacy_type_info", + "//eval/public:cel_value", + "//eval/public/containers:container_backed_list_impl", + "//eval/public/containers:container_backed_map_impl", + "//eval/testutil:test_message_cc_proto", "//internal:testing", "//testutil:util", - "@com_google_absl//absl/base:no_destructor", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:status_matchers", "@com_google_absl//absl/strings", "@com_google_absl//absl/time", "@com_google_absl//absl/types:span", @@ -119,6 +174,7 @@ cc_test( "@com_google_protobuf//:field_mask_cc_proto", "@com_google_protobuf//:protobuf", "@com_google_protobuf//:struct_cc_proto", + "@com_google_protobuf//:timestamp_cc_proto", "@com_google_protobuf//:wrappers_cc_proto", ], ) @@ -133,6 +189,7 @@ cc_library( ], deps = [ ":cel_proto_wrap_util", + ":cel_proto_wrap_value_to_message", ":protobuf_value_factory", "//eval/public:cel_options", "//eval/public:cel_value", diff --git a/eval/public/structs/cel_proto_wrap_util.cc b/eval/public/structs/cel_proto_wrap_util.cc index 490686fcb..87d05ee3f 100644 --- a/eval/public/structs/cel_proto_wrap_util.cc +++ b/eval/public/structs/cel_proto_wrap_util.cc @@ -16,7 +16,6 @@ #include #include -#include #include #include #include @@ -32,11 +31,9 @@ #include "absl/base/optimization.h" #include "absl/functional/overload.h" #include "absl/log/absl_check.h" -#include "absl/log/absl_log.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/cord.h" -#include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/synchronization/mutex.h" @@ -45,16 +42,12 @@ #include "absl/types/variant.h" #include "eval/public/cel_value.h" #include "eval/public/structs/protobuf_value_factory.h" -#include "internal/overflow.h" #include "internal/proto_time_encoding.h" #include "internal/status_macros.h" -#include "internal/time.h" #include "internal/well_known_types.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" -#include "google/protobuf/json/json.h" #include "google/protobuf/message.h" -#include "google/protobuf/message_lite.h" namespace google::api::expr::runtime::internal { @@ -82,55 +75,6 @@ using google::protobuf::Descriptor; using google::protobuf::DescriptorPool; using google::protobuf::Message; using google::protobuf::MessageFactory; -using google::protobuf::json::MessageToJsonString; -using google::protobuf::json::PrintOptions; - -// kMaxIntJSON is defined as the Number.MAX_SAFE_INTEGER value per EcmaScript 6. -constexpr int64_t kMaxIntJSON = (1ll << 53) - 1; - -// kMinIntJSON is defined as the Number.MIN_SAFE_INTEGER value per EcmaScript 6. -constexpr int64_t kMinIntJSON = -kMaxIntJSON; - -// IsJSONSafe indicates whether the int is safely representable as a floating -// point value in JSON. -static bool IsJSONSafe(int64_t i) { - return i >= kMinIntJSON && i <= kMaxIntJSON; -} - -// IsJSONSafe indicates whether the uint is safely representable as a floating -// point value in JSON. -static bool IsJSONSafe(uint64_t i) { - return i <= static_cast(kMaxIntJSON); -} - -static bool IsEmptyProto(const google::protobuf::Descriptor* descriptor) { - return descriptor->full_name() == "google.protobuf.Empty"; -} - -static bool IsFieldMaskProto(const google::protobuf::Descriptor* descriptor) { - return descriptor->full_name() == "google.protobuf.FieldMask"; -} - -static std::optional GetFieldMaskJsonString( - const google::protobuf::Message& message) { - // TODO(b/540507668): Refactor to pipe descriptor_pool through - // ValueFromValue to use internal::MessageToJson. - PrintOptions json_options; - std::string json_str; - auto status = MessageToJsonString(message, &json_str, json_options); - if (!status.ok()) { - ABSL_LOG(ERROR) << "Failed to convert FieldMask to JSON: " << status; - return std::nullopt; - } - // If JSON marshalling is correct, we know we'll always get a plain - // JSON string value and it shouldn't contain any escapes that we need - // to interpret. - if (json_str.size() >= 2 && json_str.front() == '"' && - json_str.back() == '"') { - return json_str.substr(1, json_str.size() - 2); - } - return json_str; -} // Map implementation wrapping google.protobuf.ListValue class DynamicList : public CelList { @@ -756,768 +700,6 @@ absl::optional DynamicMap::operator[](CelValue key) const { return ValueManager(factory_, arena_).ValueFromMessage(&it->second); } -google::protobuf::Message* DurationFromValue(const google::protobuf::Message* prototype, - const CelValue& value, - google::protobuf::Arena* arena) { - absl::Duration val; - if (!value.GetValue(&val)) { - return nullptr; - } - if (!cel::internal::ValidateDuration(val).ok()) { - return nullptr; - } - auto* message = prototype->New(arena); - CEL_ASSIGN_OR_RETURN( - auto reflection, - cel::well_known_types::GetDurationReflection(message->GetDescriptor()), - _.With(IgnoreErrorAndReturnNullptr())); - reflection.UnsafeSetFromAbslDuration(message, val); - return message; -} - -google::protobuf::Message* BoolFromValue(const google::protobuf::Message* prototype, - const CelValue& value, google::protobuf::Arena* arena) { - bool val; - if (!value.GetValue(&val)) { - return nullptr; - } - auto* message = prototype->New(arena); - CEL_ASSIGN_OR_RETURN( - auto reflection, - cel::well_known_types::GetBoolValueReflection(message->GetDescriptor()), - _.With(IgnoreErrorAndReturnNullptr())); - reflection.SetValue(message, val); - return message; -} - -google::protobuf::Message* BytesFromValue(const google::protobuf::Message* prototype, - const CelValue& value, google::protobuf::Arena* arena) { - CelValue::BytesHolder view_val; - if (!value.GetValue(&view_val)) { - return nullptr; - } - auto* message = prototype->New(arena); - CEL_ASSIGN_OR_RETURN( - auto reflection, - cel::well_known_types::GetBytesValueReflection(message->GetDescriptor()), - _.With(IgnoreErrorAndReturnNullptr())); - reflection.SetValue(message, view_val.value()); - return message; -} - -google::protobuf::Message* DoubleFromValue(const google::protobuf::Message* prototype, - const CelValue& value, google::protobuf::Arena* arena) { - double val; - if (!value.GetValue(&val)) { - return nullptr; - } - auto* message = prototype->New(arena); - CEL_ASSIGN_OR_RETURN( - auto reflection, - cel::well_known_types::GetDoubleValueReflection(message->GetDescriptor()), - _.With(IgnoreErrorAndReturnNullptr())); - reflection.SetValue(message, val); - return message; -} - -google::protobuf::Message* FloatFromValue(const google::protobuf::Message* prototype, - const CelValue& value, google::protobuf::Arena* arena) { - double val; - if (!value.GetValue(&val)) { - return nullptr; - } - float fval = val; - // Abort the conversion if the value is outside the float range. - if (val > std::numeric_limits::max()) { - fval = std::numeric_limits::infinity(); - } else if (val < std::numeric_limits::lowest()) { - fval = -std::numeric_limits::infinity(); - } - auto* message = prototype->New(arena); - CEL_ASSIGN_OR_RETURN( - auto reflection, - cel::well_known_types::GetFloatValueReflection(message->GetDescriptor()), - _.With(IgnoreErrorAndReturnNullptr())); - reflection.SetValue(message, static_cast(fval)); - return message; -} - -google::protobuf::Message* Int32FromValue(const google::protobuf::Message* prototype, - const CelValue& value, google::protobuf::Arena* arena) { - int64_t val; - if (!value.GetValue(&val)) { - return nullptr; - } - if (!cel::internal::CheckedInt64ToInt32(val).ok()) { - return nullptr; - } - int32_t ival = static_cast(val); - auto* message = prototype->New(arena); - CEL_ASSIGN_OR_RETURN( - auto reflection, - cel::well_known_types::GetInt32ValueReflection(message->GetDescriptor()), - _.With(IgnoreErrorAndReturnNullptr())); - reflection.SetValue(message, ival); - return message; -} - -google::protobuf::Message* Int64FromValue(const google::protobuf::Message* prototype, - const CelValue& value, google::protobuf::Arena* arena) { - int64_t val; - if (!value.GetValue(&val)) { - return nullptr; - } - auto* message = prototype->New(arena); - CEL_ASSIGN_OR_RETURN( - auto reflection, - cel::well_known_types::GetInt64ValueReflection(message->GetDescriptor()), - _.With(IgnoreErrorAndReturnNullptr())); - reflection.SetValue(message, val); - return message; -} - -google::protobuf::Message* StringFromValue(const google::protobuf::Message* prototype, - const CelValue& value, google::protobuf::Arena* arena) { - CelValue::StringHolder view_val; - if (!value.GetValue(&view_val)) { - return nullptr; - } - auto* message = prototype->New(arena); - CEL_ASSIGN_OR_RETURN( - auto reflection, - cel::well_known_types::GetStringValueReflection(message->GetDescriptor()), - _.With(IgnoreErrorAndReturnNullptr())); - reflection.SetValue(message, view_val.value()); - return message; -} - -google::protobuf::Message* TimestampFromValue(const google::protobuf::Message* prototype, - const CelValue& value, - google::protobuf::Arena* arena) { - absl::Time val; - if (!value.GetValue(&val)) { - return nullptr; - } - if (!cel::internal::ValidateTimestamp(val).ok()) { - return nullptr; - } - auto* message = prototype->New(arena); - CEL_ASSIGN_OR_RETURN( - auto reflection, - cel::well_known_types::GetTimestampReflection(message->GetDescriptor()), - _.With(IgnoreErrorAndReturnNullptr())); - reflection.UnsafeSetFromAbslTime(message, val); - return message; -} - -google::protobuf::Message* UInt32FromValue(const google::protobuf::Message* prototype, - const CelValue& value, google::protobuf::Arena* arena) { - uint64_t val; - if (!value.GetValue(&val)) { - return nullptr; - } - if (!cel::internal::CheckedUint64ToUint32(val).ok()) { - return nullptr; - } - uint32_t ival = static_cast(val); - auto* message = prototype->New(arena); - CEL_ASSIGN_OR_RETURN( - auto reflection, - cel::well_known_types::GetUInt32ValueReflection(message->GetDescriptor()), - _.With(IgnoreErrorAndReturnNullptr())); - reflection.SetValue(message, ival); - return message; -} - -google::protobuf::Message* UInt64FromValue(const google::protobuf::Message* prototype, - const CelValue& value, google::protobuf::Arena* arena) { - uint64_t val; - if (!value.GetValue(&val)) { - return nullptr; - } - auto* message = prototype->New(arena); - CEL_ASSIGN_OR_RETURN( - auto reflection, - cel::well_known_types::GetUInt64ValueReflection(message->GetDescriptor()), - _.With(IgnoreErrorAndReturnNullptr())); - reflection.SetValue(message, val); - return message; -} - -google::protobuf::Message* ValueFromValue(google::protobuf::Message* message, const CelValue& value, - google::protobuf::Arena* arena); - -google::protobuf::Message* ValueFromValue(const google::protobuf::Message* prototype, - const CelValue& value, google::protobuf::Arena* arena) { - return ValueFromValue(prototype->New(arena), value, arena); -} - -google::protobuf::Message* ListFromValue(google::protobuf::Message* message, const CelValue& value, - google::protobuf::Arena* arena) { - if (!value.IsList()) { - return nullptr; - } - const CelList& list = *value.ListOrDie(); - CEL_ASSIGN_OR_RETURN( - auto reflection, - cel::well_known_types::GetListValueReflection(message->GetDescriptor()), - _.With(IgnoreErrorAndReturnNullptr())); - for (int i = 0; i < list.size(); i++) { - auto e = list.Get(arena, i); - auto* elem = reflection.AddValues(message); - if (ValueFromValue(elem, e, arena) == nullptr) { - return nullptr; - } - } - return message; -} - -google::protobuf::Message* ListFromValue(const google::protobuf::Message* prototype, - const CelValue& value, google::protobuf::Arena* arena) { - if (!value.IsList()) { - return nullptr; - } - return ListFromValue(prototype->New(arena), value, arena); -} - -google::protobuf::Message* StructFromValue(google::protobuf::Message* message, - const CelValue& value, google::protobuf::Arena* arena) { - if (!value.IsMap()) { - return nullptr; - } - const CelMap& map = *value.MapOrDie(); - absl::StatusOr keys_or = map.ListKeys(arena); - if (!keys_or.ok()) { - // If map doesn't support listing keys, it can't pack into a Struct value. - // This will surface as a CEL error when the object creation expression - // fails. - return nullptr; - } - const CelList& keys = **keys_or; - CEL_ASSIGN_OR_RETURN( - auto reflection, - cel::well_known_types::GetStructReflection(message->GetDescriptor()), - _.With(IgnoreErrorAndReturnNullptr())); - for (int i = 0; i < keys.size(); i++) { - auto k = keys.Get(arena, i); - // If the key is not a string type, abort the conversion. - if (!k.IsString()) { - return nullptr; - } - absl::string_view key = k.StringOrDie().value(); - - auto v = map.Get(arena, k); - if (!v.has_value()) { - return nullptr; - } - auto* field = reflection.InsertField(message, key); - if (ValueFromValue(field, *v, arena) == nullptr) { - return nullptr; - } - } - return message; -} - -google::protobuf::Message* StructFromValue(const google::protobuf::Message* prototype, - const CelValue& value, google::protobuf::Arena* arena) { - if (!value.IsMap()) { - return nullptr; - } - return StructFromValue(prototype->New(arena), value, arena); -} - -google::protobuf::Message* ValueFromValue(google::protobuf::Message* message, const CelValue& value, - google::protobuf::Arena* arena) { - CEL_ASSIGN_OR_RETURN( - auto reflection, - cel::well_known_types::GetValueReflection(message->GetDescriptor()), - _.With(IgnoreErrorAndReturnNullptr())); - switch (value.type()) { - case CelValue::Type::kBool: { - bool val; - if (value.GetValue(&val)) { - reflection.SetBoolValue(message, val); - return message; - } - } break; - case CelValue::Type::kBytes: { - // Base64 encode byte strings to ensure they can safely be transported - // in a JSON string. - CelValue::BytesHolder val; - if (value.GetValue(&val)) { - reflection.SetStringValueFromBytes(message, val.value()); - return message; - } - } break; - case CelValue::Type::kDouble: { - double val; - if (value.GetValue(&val)) { - reflection.SetNumberValue(message, val); - return message; - } - } break; - case CelValue::Type::kDuration: { - // Convert duration values to a protobuf JSON format. - absl::Duration val; - if (value.GetValue(&val)) { - CEL_RETURN_IF_ERROR(cel::internal::ValidateDuration(val)) - .With(IgnoreErrorAndReturnNullptr()); - reflection.SetStringValueFromDuration(message, val); - return message; - } - } break; - case CelValue::Type::kInt64: { - int64_t val; - // Convert int64_t values within the int53 range to doubles, otherwise - // serialize the value to a string. - if (value.GetValue(&val)) { - reflection.SetNumberValue(message, val); - return message; - } - } break; - case CelValue::Type::kString: { - CelValue::StringHolder val; - if (value.GetValue(&val)) { - reflection.SetStringValue(message, val.value()); - return message; - } - } break; - case CelValue::Type::kTimestamp: { - // Convert timestamp values to a protobuf JSON format. - absl::Time val; - if (value.GetValue(&val)) { - CEL_RETURN_IF_ERROR(cel::internal::ValidateTimestamp(val)) - .With(IgnoreErrorAndReturnNullptr()); - reflection.SetStringValueFromTimestamp(message, val); - return message; - } - } break; - case CelValue::Type::kUint64: { - uint64_t val; - // Convert uint64_t values within the int53 range to doubles, otherwise - // serialize the value to a string. - if (value.GetValue(&val)) { - reflection.SetNumberValue(message, val); - return message; - } - } break; - case CelValue::Type::kList: { - if (ListFromValue(reflection.MutableListValue(message), value, arena) != - nullptr) { - return message; - } - } break; - case CelValue::Type::kMap: { - if (StructFromValue(reflection.MutableStructValue(message), value, - arena) != nullptr) { - return message; - } - } break; - case CelValue::Type::kMessage: { - const google::protobuf::Message* message_ptr = value.MessageOrDie(); - if (IsEmptyProto(message_ptr->GetDescriptor())) { - reflection.MutableStructValue(message); - return message; - } - if (IsFieldMaskProto(message_ptr->GetDescriptor())) { - std::optional fm_str = - GetFieldMaskJsonString(*message_ptr); - if (fm_str.has_value()) { - reflection.SetStringValue(message, *fm_str); - return message; - } - return nullptr; - } - return nullptr; - } break; - case CelValue::Type::kNullType: - reflection.SetNullValue(message); - return message; - break; - default: - return nullptr; - } - return nullptr; -} - -bool ValueFromValue(Value* json, const CelValue& value, google::protobuf::Arena* arena); - -bool ListFromValue(ListValue* json_list, const CelValue& value, - google::protobuf::Arena* arena) { - if (!value.IsList()) { - return false; - } - const CelList& list = *value.ListOrDie(); - for (int i = 0; i < list.size(); i++) { - auto e = list.Get(arena, i); - Value* elem = json_list->add_values(); - if (!ValueFromValue(elem, e, arena)) { - return false; - } - } - return true; -} - -bool StructFromValue(Struct* json_struct, const CelValue& value, - google::protobuf::Arena* arena) { - if (!value.IsMap()) { - return false; - } - const CelMap& map = *value.MapOrDie(); - absl::StatusOr keys_or = map.ListKeys(arena); - if (!keys_or.ok()) { - // If map doesn't support listing keys, it can't pack into a Struct value. - // This will surface as a CEL error when the object creation expression - // fails. - return false; - } - const CelList& keys = **keys_or; - auto fields = json_struct->mutable_fields(); - for (int i = 0; i < keys.size(); i++) { - auto k = keys.Get(arena, i); - // If the key is not a string type, abort the conversion. - if (!k.IsString()) { - return false; - } - absl::string_view key = k.StringOrDie().value(); - - auto v = map.Get(arena, k); - if (!v.has_value()) { - return false; - } - Value field_value; - if (!ValueFromValue(&field_value, *v, arena)) { - return false; - } - (*fields)[std::string(key)] = field_value; - } - return true; -} - -bool ValueFromValue(Value* json, const CelValue& value, google::protobuf::Arena* arena) { - switch (value.type()) { - case CelValue::Type::kBool: { - bool val; - if (value.GetValue(&val)) { - json->set_bool_value(val); - return true; - } - } break; - case CelValue::Type::kBytes: { - // Base64 encode byte strings to ensure they can safely be transported - // in a JSON string. - CelValue::BytesHolder val; - if (value.GetValue(&val)) { - json->set_string_value(absl::Base64Escape(val.value())); - return true; - } - } break; - case CelValue::Type::kDouble: { - double val; - if (value.GetValue(&val)) { - json->set_number_value(val); - return true; - } - } break; - case CelValue::Type::kDuration: { - // Convert duration values to a protobuf JSON format. - absl::Duration val; - if (value.GetValue(&val)) { - auto encode = cel::internal::EncodeDurationToString(val); - if (!encode.ok()) { - return false; - } - json->set_string_value(*encode); - return true; - } - } break; - case CelValue::Type::kInt64: { - int64_t val; - // Convert int64_t values within the int53 range to doubles, otherwise - // serialize the value to a string. - if (value.GetValue(&val)) { - if (IsJSONSafe(val)) { - json->set_number_value(val); - } else { - json->set_string_value(absl::StrCat(val)); - } - return true; - } - } break; - case CelValue::Type::kString: { - CelValue::StringHolder val; - if (value.GetValue(&val)) { - json->set_string_value(val.value()); - return true; - } - } break; - case CelValue::Type::kTimestamp: { - // Convert timestamp values to a protobuf JSON format. - absl::Time val; - if (value.GetValue(&val)) { - auto encode = cel::internal::EncodeTimeToString(val); - if (!encode.ok()) { - return false; - } - json->set_string_value(*encode); - return true; - } - } break; - case CelValue::Type::kUint64: { - uint64_t val; - // Convert uint64_t values within the int53 range to doubles, otherwise - // serialize the value to a string. - if (value.GetValue(&val)) { - if (IsJSONSafe(val)) { - json->set_number_value(val); - } else { - json->set_string_value(absl::StrCat(val)); - } - return true; - } - } break; - case CelValue::Type::kList: - return ListFromValue(json->mutable_list_value(), value, arena); - case CelValue::Type::kMap: - return StructFromValue(json->mutable_struct_value(), value, arena); - case CelValue::Type::kMessage: { - const google::protobuf::Message* message_ptr = value.MessageOrDie(); - if (IsEmptyProto(message_ptr->GetDescriptor())) { - json->mutable_struct_value(); - return true; - } - if (IsFieldMaskProto(message_ptr->GetDescriptor())) { - std::optional fm_str = - GetFieldMaskJsonString(*message_ptr); - if (fm_str.has_value()) { - json->set_string_value(*fm_str); - return true; - } - return false; - } - return false; - } - case CelValue::Type::kNullType: - json->set_null_value(protobuf::NULL_VALUE); - return true; - default: - return false; - } - return false; -} - -google::protobuf::Message* AnyFromValue(const google::protobuf::Message* prototype, - const CelValue& value, google::protobuf::Arena* arena) { - std::string type_name; - absl::Cord payload; - - // In open source, any->PackFrom() returns void rather than boolean. - switch (value.type()) { - case CelValue::Type::kBool: { - BoolValue v; - type_name = v.GetTypeName(); - v.set_value(value.BoolOrDie()); - payload = v.SerializeAsCord(); - } break; - case CelValue::Type::kBytes: { - BytesValue v; - type_name = v.GetTypeName(); - v.set_value(value.BytesOrDie().value()); - payload = v.SerializeAsCord(); - } break; - case CelValue::Type::kDouble: { - DoubleValue v; - type_name = v.GetTypeName(); - v.set_value(value.DoubleOrDie()); - payload = v.SerializeAsCord(); - } break; - case CelValue::Type::kDuration: { - Duration v; - if (!cel::internal::EncodeDuration(value.DurationOrDie(), &v).ok()) { - return nullptr; - } - type_name = v.GetTypeName(); - payload = v.SerializeAsCord(); - } break; - case CelValue::Type::kInt64: { - Int64Value v; - type_name = v.GetTypeName(); - v.set_value(value.Int64OrDie()); - payload = v.SerializeAsCord(); - } break; - case CelValue::Type::kString: { - StringValue v; - type_name = v.GetTypeName(); - v.set_value(std::string(value.StringOrDie().value())); - payload = v.SerializeAsCord(); - } break; - case CelValue::Type::kTimestamp: { - Timestamp v; - if (!cel::internal::EncodeTime(value.TimestampOrDie(), &v).ok()) { - return nullptr; - } - type_name = v.GetTypeName(); - payload = v.SerializeAsCord(); - } break; - case CelValue::Type::kUint64: { - UInt64Value v; - type_name = v.GetTypeName(); - v.set_value(value.Uint64OrDie()); - payload = v.SerializeAsCord(); - } break; - case CelValue::Type::kList: { - ListValue v; - if (!ListFromValue(&v, value, arena)) { - return nullptr; - } - type_name = v.GetTypeName(); - payload = v.SerializeAsCord(); - } break; - case CelValue::Type::kMap: { - Struct v; - if (!StructFromValue(&v, value, arena)) { - return nullptr; - } - type_name = v.GetTypeName(); - payload = v.SerializeAsCord(); - } break; - case CelValue::Type::kNullType: { - Value v; - type_name = v.GetTypeName(); - v.set_null_value(google::protobuf::NULL_VALUE); - payload = v.SerializeAsCord(); - } break; - case CelValue::Type::kMessage: { - type_name = value.MessageWrapperOrDie().message_ptr()->GetTypeName(); - payload = value.MessageWrapperOrDie().message_ptr()->SerializeAsCord(); - } break; - default: - return nullptr; - } - - auto* message = prototype->New(arena); - CEL_ASSIGN_OR_RETURN( - auto reflection, - cel::well_known_types::GetAnyReflection(message->GetDescriptor()), - _.With(IgnoreErrorAndReturnNullptr())); - reflection.SetTypeUrl(message, - absl::StrCat("type.googleapis.com/", type_name)); - reflection.SetValue(message, payload); - return message; -} - -bool IsAlreadyWrapped(google::protobuf::Descriptor::WellKnownType wkt, - const CelValue& value) { - if (value.IsMessage()) { - const auto* msg = value.MessageOrDie(); - if (wkt == msg->GetDescriptor()->well_known_type()) { - return true; - } - } - return false; -} - -// MessageFromValueMaker makes a specific protobuf Message instance based on -// the desired protobuf type name and an input CelValue. -// -// It holds a registry of CelValue factories for specific subtypes of Message. -// If message does not match any of types stored in registry, an the factory -// returns an absent value. -class MessageFromValueMaker { - public: - // Non-copyable, non-assignable - MessageFromValueMaker(const MessageFromValueMaker&) = delete; - MessageFromValueMaker& operator=(const MessageFromValueMaker&) = delete; - - static google::protobuf::Message* MaybeWrapMessage(const google::protobuf::Descriptor* descriptor, - google::protobuf::MessageFactory* factory, - const CelValue& value, - Arena* arena) { - switch (descriptor->well_known_type()) { - case google::protobuf::Descriptor::WELLKNOWNTYPE_DOUBLEVALUE: - if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { - return nullptr; - } - return DoubleFromValue(factory->GetPrototype(descriptor), value, arena); - case google::protobuf::Descriptor::WELLKNOWNTYPE_FLOATVALUE: - if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { - return nullptr; - } - return FloatFromValue(factory->GetPrototype(descriptor), value, arena); - case google::protobuf::Descriptor::WELLKNOWNTYPE_INT64VALUE: - if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { - return nullptr; - } - return Int64FromValue(factory->GetPrototype(descriptor), value, arena); - case google::protobuf::Descriptor::WELLKNOWNTYPE_UINT64VALUE: - if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { - return nullptr; - } - return UInt64FromValue(factory->GetPrototype(descriptor), value, arena); - case google::protobuf::Descriptor::WELLKNOWNTYPE_INT32VALUE: - if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { - return nullptr; - } - return Int32FromValue(factory->GetPrototype(descriptor), value, arena); - case google::protobuf::Descriptor::WELLKNOWNTYPE_UINT32VALUE: - if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { - return nullptr; - } - return UInt32FromValue(factory->GetPrototype(descriptor), value, arena); - case google::protobuf::Descriptor::WELLKNOWNTYPE_STRINGVALUE: - if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { - return nullptr; - } - return StringFromValue(factory->GetPrototype(descriptor), value, arena); - case google::protobuf::Descriptor::WELLKNOWNTYPE_BYTESVALUE: - if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { - return nullptr; - } - return BytesFromValue(factory->GetPrototype(descriptor), value, arena); - case google::protobuf::Descriptor::WELLKNOWNTYPE_BOOLVALUE: - if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { - return nullptr; - } - return BoolFromValue(factory->GetPrototype(descriptor), value, arena); - case google::protobuf::Descriptor::WELLKNOWNTYPE_ANY: - if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { - return nullptr; - } - return AnyFromValue(factory->GetPrototype(descriptor), value, arena); - case google::protobuf::Descriptor::WELLKNOWNTYPE_DURATION: - if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { - return nullptr; - } - return DurationFromValue(factory->GetPrototype(descriptor), value, - arena); - case google::protobuf::Descriptor::WELLKNOWNTYPE_TIMESTAMP: - if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { - return nullptr; - } - return TimestampFromValue(factory->GetPrototype(descriptor), value, - arena); - case google::protobuf::Descriptor::WELLKNOWNTYPE_VALUE: - if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { - return nullptr; - } - return ValueFromValue(factory->GetPrototype(descriptor), value, arena); - case google::protobuf::Descriptor::WELLKNOWNTYPE_LISTVALUE: - if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { - return nullptr; - } - return ListFromValue(factory->GetPrototype(descriptor), value, arena); - case google::protobuf::Descriptor::WELLKNOWNTYPE_STRUCT: - if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { - return nullptr; - } - return StructFromValue(factory->GetPrototype(descriptor), value, arena); - // WELLKNOWNTYPE_FIELDMASK has no special CelValue type - default: - return nullptr; - } - } -}; - } // namespace CelValue UnwrapMessageToValue(const google::protobuf::Message* value, @@ -1536,12 +718,4 @@ CelValue UnwrapMessageToValue(const google::protobuf::Message* value, return factory(value); } -const google::protobuf::Message* MaybeWrapValueToMessage( - const google::protobuf::Descriptor* descriptor, google::protobuf::MessageFactory* factory, - const CelValue& value, Arena* arena) { - google::protobuf::Message* msg = MessageFromValueMaker::MaybeWrapMessage( - descriptor, factory, value, arena); - return msg; -} - } // namespace google::api::expr::runtime::internal diff --git a/eval/public/structs/cel_proto_wrap_util.h b/eval/public/structs/cel_proto_wrap_util.h index 508985209..1d15c9ead 100644 --- a/eval/public/structs/cel_proto_wrap_util.h +++ b/eval/public/structs/cel_proto_wrap_util.h @@ -28,18 +28,6 @@ CelValue UnwrapMessageToValue(const google::protobuf::Message* value, const ProtobufValueFactory& factory, google::protobuf::Arena* arena); -// MaybeWrapValue attempts to wrap the input value in a proto message with -// the given type_name. If the value can be wrapped, it is returned as a -// protobuf message. Otherwise, the result will be nullptr. -// -// This method is the complement to MaybeUnwrapValue which may unwrap a protobuf -// message to native CelValue representation during a protobuf field read. -// Just as CreateMessage should only be used when reading protobuf values, -// MaybeWrapValue should only be used when assigning protobuf fields. -const google::protobuf::Message* MaybeWrapValueToMessage( - const google::protobuf::Descriptor* descriptor, google::protobuf::MessageFactory* factory, - const CelValue& value, google::protobuf::Arena* arena); - } // namespace google::api::expr::runtime::internal #endif // THIRD_PARTY_CEL_CPP_EVAL_PUBLIC_STRUCTS_CEL_PROTO_WRAP_UTIL_H_ diff --git a/eval/public/structs/cel_proto_wrap_util_test.cc b/eval/public/structs/cel_proto_wrap_util_test.cc index b196d1dd3..bbcb64060 100644 --- a/eval/public/structs/cel_proto_wrap_util_test.cc +++ b/eval/public/structs/cel_proto_wrap_util_test.cc @@ -16,10 +16,8 @@ #include #include -#include #include #include -#include #include #include "google/protobuf/any.pb.h" @@ -28,20 +26,11 @@ #include "google/protobuf/field_mask.pb.h" #include "google/protobuf/struct.pb.h" #include "google/protobuf/wrappers.pb.h" -#include "absl/base/no_destructor.h" #include "absl/status/status.h" -#include "absl/strings/str_cat.h" -#include "absl/time/time.h" -#include "absl/types/span.h" #include "eval/public/cel_value.h" -#include "eval/public/containers/container_backed_list_impl.h" -#include "eval/public/containers/container_backed_map_impl.h" -#include "eval/public/message_wrapper.h" -#include "eval/public/structs/protobuf_value_factory.h" #include "eval/public/structs/trivial_legacy_type_info.h" #include "eval/testutil/test_message.pb.h" #include "internal/proto_time_encoding.h" -#include "internal/status_macros.h" #include "internal/testing.h" #include "testutil/util.h" #include "google/protobuf/arena.h" @@ -52,12 +41,10 @@ namespace google::api::expr::runtime::internal { namespace { -using ::google::protobuf::TextFormat; using ::testing::Eq; using ::testing::UnorderedPointwise; using google::protobuf::Duration; -using google::protobuf::FieldMask; using google::protobuf::ListValue; using google::protobuf::Struct; using google::protobuf::Timestamp; @@ -85,38 +72,7 @@ class CelProtoWrapperTest : public ::testing::Test { protected: CelProtoWrapperTest() = default; - void ExpectWrappedMessage(const CelValue& value, - const google::protobuf::Message& message) { - // Test the input value wraps to the destination message type. - auto* result = MaybeWrapValueToMessage( - message.GetDescriptor(), message.GetReflection()->GetMessageFactory(), - value, arena()); - EXPECT_TRUE(result != nullptr); - EXPECT_THAT(result, testutil::EqualsProto(message)); - - // Ensure that double wrapping results in the object being wrapped once. - auto* identity = MaybeWrapValueToMessage( - message.GetDescriptor(), message.GetReflection()->GetMessageFactory(), - ProtobufValueFactoryImpl(result), arena()); - EXPECT_TRUE(identity == nullptr); - - // Check to make sure that even dynamic messages can be used as input to - // the wrapping call. - result = MaybeWrapValueToMessage( - ReflectedCopy(message)->GetDescriptor(), - ReflectedCopy(message)->GetReflection()->GetMessageFactory(), value, - arena()); - EXPECT_TRUE(result != nullptr); - EXPECT_THAT(result, testutil::EqualsProto(message)); - } - void ExpectNotWrapped(const CelValue& value, const google::protobuf::Message& message) { - // Test the input value does not wrap by asserting value == result. - auto result = MaybeWrapValueToMessage( - message.GetDescriptor(), message.GetReflection()->GetMessageFactory(), - value, arena()); - EXPECT_TRUE(result == nullptr); - } template void ExpectUnwrappedPrimitive(const google::protobuf::Message& message, T result) { @@ -442,120 +398,6 @@ TEST_F(CelProtoWrapperTest, UnwrapInvalidAny) { UnwrapMessageToValue(&any, &ProtobufValueFactoryImpl, arena()).IsError()); } -TEST_F(CelProtoWrapperTest, WrapFieldMaskToValue) { - FieldMask field_mask; - ASSERT_TRUE(TextFormat::ParseFromString(R"pb( - paths: "foo.bar" paths: "baz" - )pb", - &field_mask)); - CelValue value = ProtobufValueFactoryImpl(&field_mask); - - Value expected_message; - ASSERT_TRUE(TextFormat::ParseFromString(R"pb(string_value: "foo.bar,baz")pb", - &expected_message)); - - ExpectWrappedMessage(value, expected_message); -} - -TEST_F(CelProtoWrapperTest, WrapMapWithFieldMaskToAny) { - const std::string kField = "field_mask"; - FieldMask field_mask; - ASSERT_TRUE(TextFormat::ParseFromString(R"pb( - paths: "foo.bar" paths: "baz" - )pb", - &field_mask)); - CelValue value = ProtobufValueFactoryImpl(&field_mask); - - std::vector> args = { - {CelValue::CreateString(CelValue::StringHolder(&kField)), value}}; - ASSERT_OK_AND_ASSIGN( - std::unique_ptr cel_map, - CreateContainerBackedMap( - absl::Span>(args.data(), args.size()))); - CelValue cel_value = CelValue::CreateMap(cel_map.get()); - - Struct expected_struct; - ASSERT_TRUE( - TextFormat::ParseFromString(R"pb( - fields { - key: "field_mask" - value { string_value: "foo.bar,baz" } - } - )pb", - &expected_struct)); - Any expected_message; - ASSERT_TRUE(expected_message.PackFrom(expected_struct)); - - ExpectWrappedMessage(cel_value, expected_message); -} - -TEST_F(CelProtoWrapperTest, WrapListWithFieldMaskToAny) { - FieldMask field_mask; - ASSERT_TRUE(TextFormat::ParseFromString(R"pb( - paths: "foo.bar" paths: "baz" - )pb", - &field_mask)); - CelValue value = ProtobufValueFactoryImpl(&field_mask); - - std::vector list_entries = {value}; - ContainerBackedListImpl cel_list(list_entries); - CelValue list_value = CelValue::CreateList(&cel_list); - - ListValue expected_list; - ASSERT_TRUE(TextFormat::ParseFromString( - R"pb(values { string_value: "foo.bar,baz" })pb", &expected_list)); - Any expected_message; - ASSERT_TRUE(expected_message.PackFrom(expected_list)); - - ExpectWrappedMessage(list_value, expected_message); -} - -TEST_F(CelProtoWrapperTest, WrapEmptyToValue) { - google::protobuf::Empty empty; - CelValue value = ProtobufValueFactoryImpl(&empty); - - Value expected_message; - expected_message.mutable_struct_value(); - - ExpectWrappedMessage(value, expected_message); -} - -TEST_F(CelProtoWrapperTest, WrapMapWithEmptyToAny) { - const std::string kField = "empty"; - google::protobuf::Empty empty; - CelValue value = ProtobufValueFactoryImpl(&empty); - - std::vector> args = { - {CelValue::CreateString(CelValue::StringHolder(&kField)), value}}; - ASSERT_OK_AND_ASSIGN( - std::unique_ptr cel_map, - CreateContainerBackedMap( - absl::Span>(args.data(), args.size()))); - auto cel_value = CelValue::CreateMap(cel_map.get()); - - Struct expected_struct; - (*expected_struct.mutable_fields())[kField].mutable_struct_value(); - Any expected_message; - expected_message.PackFrom(expected_struct); - - ExpectWrappedMessage(cel_value, expected_message); -} - -TEST_F(CelProtoWrapperTest, WrapListWithEmptyToAny) { - google::protobuf::Empty empty; - CelValue value = ProtobufValueFactoryImpl(&empty); - - std::vector list_entries = {value}; - ContainerBackedListImpl cel_list(list_entries); - CelValue list_value = CelValue::CreateList(&cel_list); - - ListValue expected_list; - expected_list.add_values()->mutable_struct_value(); - Any expected_message; - expected_message.PackFrom(expected_list); - - ExpectWrappedMessage(list_value, expected_message); -} // Test support of google.protobuf.Value wrappers in CelValue. TEST_F(CelProtoWrapperTest, UnwrapBoolWrapper) { bool value = true; @@ -631,376 +473,6 @@ TEST_F(CelProtoWrapperTest, UnwrapBytesWrapper) { ExpectUnwrappedPrimitive(wrapper, value); } -TEST_F(CelProtoWrapperTest, WrapNull) { - auto cel_value = CelValue::CreateNull(); - - Value json; - json.set_null_value(protobuf::NULL_VALUE); - ExpectWrappedMessage(cel_value, json); - - Any any; - any.PackFrom(json); - ExpectWrappedMessage(cel_value, any); -} - -TEST_F(CelProtoWrapperTest, WrapBool) { - auto cel_value = CelValue::CreateBool(true); - - Value json; - json.set_bool_value(true); - ExpectWrappedMessage(cel_value, json); - - BoolValue wrapper; - wrapper.set_value(true); - ExpectWrappedMessage(cel_value, wrapper); - - Any any; - any.PackFrom(wrapper); - ExpectWrappedMessage(cel_value, any); -} - -TEST_F(CelProtoWrapperTest, WrapBytes) { - std::string str = "hello world"; - auto cel_value = CelValue::CreateBytes(CelValue::BytesHolder(&str)); - - BytesValue wrapper; - wrapper.set_value(str); - ExpectWrappedMessage(cel_value, wrapper); - - Any any; - any.PackFrom(wrapper); - ExpectWrappedMessage(cel_value, any); -} - -TEST_F(CelProtoWrapperTest, WrapBytesToValue) { - std::string str = "hello world"; - auto cel_value = CelValue::CreateBytes(CelValue::BytesHolder(&str)); - - Value json; - json.set_string_value("aGVsbG8gd29ybGQ="); - ExpectWrappedMessage(cel_value, json); -} - -TEST_F(CelProtoWrapperTest, WrapDuration) { - auto cel_value = CelValue::CreateDuration(absl::Seconds(300)); - - Duration d; - d.set_seconds(300); - ExpectWrappedMessage(cel_value, d); - - Any any; - any.PackFrom(d); - ExpectWrappedMessage(cel_value, any); -} - -TEST_F(CelProtoWrapperTest, WrapDurationToValue) { - auto cel_value = CelValue::CreateDuration(absl::Seconds(300)); - - Value json; - json.set_string_value("300s"); - ExpectWrappedMessage(cel_value, json); -} - -TEST_F(CelProtoWrapperTest, WrapDouble) { - double num = 1.5; - auto cel_value = CelValue::CreateDouble(num); - - Value json; - json.set_number_value(num); - ExpectWrappedMessage(cel_value, json); - - DoubleValue wrapper; - wrapper.set_value(num); - ExpectWrappedMessage(cel_value, wrapper); - - Any any; - any.PackFrom(wrapper); - ExpectWrappedMessage(cel_value, any); -} - -TEST_F(CelProtoWrapperTest, WrapDoubleToFloatValue) { - double num = 1.5; - auto cel_value = CelValue::CreateDouble(num); - - FloatValue wrapper; - wrapper.set_value(num); - ExpectWrappedMessage(cel_value, wrapper); - - // Imprecise double -> float representation results in truncation. - double small_num = -9.9e-100; - wrapper.set_value(small_num); - cel_value = CelValue::CreateDouble(small_num); - ExpectWrappedMessage(cel_value, wrapper); -} - -TEST_F(CelProtoWrapperTest, WrapDoubleOverflow) { - double lowest_double = std::numeric_limits::lowest(); - auto cel_value = CelValue::CreateDouble(lowest_double); - - // Double exceeds float precision, overflow to -infinity. - FloatValue wrapper; - wrapper.set_value(-std::numeric_limits::infinity()); - ExpectWrappedMessage(cel_value, wrapper); - - double max_double = std::numeric_limits::max(); - cel_value = CelValue::CreateDouble(max_double); - - wrapper.set_value(std::numeric_limits::infinity()); - ExpectWrappedMessage(cel_value, wrapper); -} - -TEST_F(CelProtoWrapperTest, WrapInt64) { - int32_t num = std::numeric_limits::lowest(); - auto cel_value = CelValue::CreateInt64(num); - - Value json; - json.set_number_value(static_cast(num)); - ExpectWrappedMessage(cel_value, json); - - Int64Value wrapper; - wrapper.set_value(num); - ExpectWrappedMessage(cel_value, wrapper); - - Any any; - any.PackFrom(wrapper); - ExpectWrappedMessage(cel_value, any); -} - -TEST_F(CelProtoWrapperTest, WrapInt64ToInt32Value) { - int32_t num = std::numeric_limits::lowest(); - auto cel_value = CelValue::CreateInt64(num); - - Int32Value wrapper; - wrapper.set_value(num); - ExpectWrappedMessage(cel_value, wrapper); -} - -TEST_F(CelProtoWrapperTest, WrapFailureInt64ToInt32Value) { - int64_t num = std::numeric_limits::lowest(); - auto cel_value = CelValue::CreateInt64(num); - - Int32Value wrapper; - ExpectNotWrapped(cel_value, wrapper); -} - -TEST_F(CelProtoWrapperTest, WrapInt64ToValue) { - int64_t max = std::numeric_limits::max(); - auto cel_value = CelValue::CreateInt64(max); - - Value json; - json.set_string_value(absl::StrCat(max)); - ExpectWrappedMessage(cel_value, json); - - int64_t min = std::numeric_limits::min(); - cel_value = CelValue::CreateInt64(min); - - json.set_string_value(absl::StrCat(min)); - ExpectWrappedMessage(cel_value, json); -} - -TEST_F(CelProtoWrapperTest, WrapUint64) { - uint32_t num = std::numeric_limits::max(); - auto cel_value = CelValue::CreateUint64(num); - - Value json; - json.set_number_value(static_cast(num)); - ExpectWrappedMessage(cel_value, json); - - UInt64Value wrapper; - wrapper.set_value(num); - ExpectWrappedMessage(cel_value, wrapper); - - Any any; - any.PackFrom(wrapper); - ExpectWrappedMessage(cel_value, any); -} - -TEST_F(CelProtoWrapperTest, WrapUint64ToUint32Value) { - uint32_t num = std::numeric_limits::max(); - auto cel_value = CelValue::CreateUint64(num); - - UInt32Value wrapper; - wrapper.set_value(num); - ExpectWrappedMessage(cel_value, wrapper); -} - -TEST_F(CelProtoWrapperTest, WrapUint64ToValue) { - uint64_t num = std::numeric_limits::max(); - auto cel_value = CelValue::CreateUint64(num); - - Value json; - json.set_string_value(absl::StrCat(num)); - ExpectWrappedMessage(cel_value, json); -} - -TEST_F(CelProtoWrapperTest, WrapFailureUint64ToUint32Value) { - uint64_t num = std::numeric_limits::max(); - auto cel_value = CelValue::CreateUint64(num); - - UInt32Value wrapper; - ExpectNotWrapped(cel_value, wrapper); -} - -TEST_F(CelProtoWrapperTest, WrapString) { - std::string str = "test"; - auto cel_value = CelValue::CreateString(CelValue::StringHolder(&str)); - - Value json; - json.set_string_value(str); - ExpectWrappedMessage(cel_value, json); - - StringValue wrapper; - wrapper.set_value(str); - ExpectWrappedMessage(cel_value, wrapper); - - Any any; - any.PackFrom(wrapper); - ExpectWrappedMessage(cel_value, any); -} - -TEST_F(CelProtoWrapperTest, WrapTimestamp) { - absl::Time ts = absl::FromUnixSeconds(1615852799); - auto cel_value = CelValue::CreateTimestamp(ts); - - Timestamp t; - t.set_seconds(1615852799); - ExpectWrappedMessage(cel_value, t); - - Any any; - any.PackFrom(t); - ExpectWrappedMessage(cel_value, any); -} - -TEST_F(CelProtoWrapperTest, WrapTimestampToValue) { - absl::Time ts = absl::FromUnixSeconds(1615852799); - auto cel_value = CelValue::CreateTimestamp(ts); - - Value json; - json.set_string_value("2021-03-15T23:59:59Z"); - ExpectWrappedMessage(cel_value, json); -} - -TEST_F(CelProtoWrapperTest, WrapList) { - std::vector list_elems = { - CelValue::CreateDouble(1.5), - CelValue::CreateInt64(-2L), - }; - ContainerBackedListImpl list(std::move(list_elems)); - auto cel_value = CelValue::CreateList(&list); - - Value json; - json.mutable_list_value()->add_values()->set_number_value(1.5); - json.mutable_list_value()->add_values()->set_number_value(-2.); - ExpectWrappedMessage(cel_value, json); - ExpectWrappedMessage(cel_value, json.list_value()); - - Any any; - any.PackFrom(json.list_value()); - ExpectWrappedMessage(cel_value, any); -} - -TEST_F(CelProtoWrapperTest, WrapFailureListValueBadJSON) { - TestMessage message; - std::vector list_elems = { - CelValue::CreateDouble(1.5), - UnwrapMessageToValue(&message, &ProtobufValueFactoryImpl, arena()), - }; - ContainerBackedListImpl list(std::move(list_elems)); - auto cel_value = CelValue::CreateList(&list); - - Value json; - ExpectNotWrapped(cel_value, json); -} - -TEST_F(CelProtoWrapperTest, WrapStruct) { - const std::string kField1 = "field1"; - std::vector> args = { - {CelValue::CreateString(CelValue::StringHolder(&kField1)), - CelValue::CreateBool(true)}}; - ASSERT_OK_AND_ASSIGN( - std::unique_ptr cel_map, - CreateContainerBackedMap( - absl::Span>(args.data(), args.size()))); - auto cel_value = CelValue::CreateMap(cel_map.get()); - - Value json; - (*json.mutable_struct_value()->mutable_fields())[kField1].set_bool_value( - true); - ExpectWrappedMessage(cel_value, json); - ExpectWrappedMessage(cel_value, json.struct_value()); - - Any any; - any.PackFrom(json.struct_value()); - ExpectWrappedMessage(cel_value, any); -} - -TEST_F(CelProtoWrapperTest, WrapFailureStructBadKeyType) { - std::vector> args = { - {CelValue::CreateInt64(1L), CelValue::CreateBool(true)}}; - ASSERT_OK_AND_ASSIGN( - std::unique_ptr cel_map, - CreateContainerBackedMap( - absl::Span>(args.data(), args.size()))); - auto cel_value = CelValue::CreateMap(cel_map.get()); - - Value json; - ExpectNotWrapped(cel_value, json); -} - -TEST_F(CelProtoWrapperTest, WrapFailureStructBadValueType) { - const std::string kField1 = "field1"; - TestMessage bad_value; - std::vector> args = { - {CelValue::CreateString(CelValue::StringHolder(&kField1)), - UnwrapMessageToValue(&bad_value, &ProtobufValueFactoryImpl, arena())}}; - ASSERT_OK_AND_ASSIGN( - std::unique_ptr cel_map, - CreateContainerBackedMap( - absl::Span>(args.data(), args.size()))); - auto cel_value = CelValue::CreateMap(cel_map.get()); - Value json; - ExpectNotWrapped(cel_value, json); -} - -class TestMap : public CelMapBuilder { - public: - absl::StatusOr ListKeys() const override { - return absl::UnimplementedError("test"); - } -}; - -TEST_F(CelProtoWrapperTest, WrapFailureStructListKeysUnimplemented) { - const std::string kField1 = "field1"; - TestMap map; - ASSERT_OK(map.Add(CelValue::CreateString(CelValue::StringHolder(&kField1)), - CelValue::CreateString(CelValue::StringHolder(&kField1)))); - - auto cel_value = CelValue::CreateMap(&map); - Value json; - ExpectNotWrapped(cel_value, json); -} - -TEST_F(CelProtoWrapperTest, WrapFailureWrongType) { - auto cel_value = CelValue::CreateNull(); - std::vector wrong_types = { - &BoolValue::default_instance(), &BytesValue::default_instance(), - &DoubleValue::default_instance(), &Duration::default_instance(), - &FloatValue::default_instance(), &Int32Value::default_instance(), - &Int64Value::default_instance(), &ListValue::default_instance(), - &StringValue::default_instance(), &Struct::default_instance(), - &Timestamp::default_instance(), &UInt32Value::default_instance(), - &UInt64Value::default_instance(), - }; - for (const auto* wrong_type : wrong_types) { - ExpectNotWrapped(cel_value, *wrong_type); - } -} - -TEST_F(CelProtoWrapperTest, WrapFailureErrorToAny) { - auto cel_value = CreateNoSuchFieldError(arena(), "error_field"); - ExpectNotWrapped(cel_value, Any::default_instance()); -} - TEST_F(CelProtoWrapperTest, DebugString) { google::protobuf::Empty e; // Note: the value factory is trivial so the debug string for a message-typed diff --git a/eval/public/structs/cel_proto_wrap_value_to_message.cc b/eval/public/structs/cel_proto_wrap_value_to_message.cc new file mode 100644 index 000000000..0b04e93d2 --- /dev/null +++ b/eval/public/structs/cel_proto_wrap_value_to_message.cc @@ -0,0 +1,895 @@ +// 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 "eval/public/structs/cel_proto_wrap_value_to_message.h" + +#include +#include +#include +#include +#include + +#include "google/protobuf/any.pb.h" +#include "google/protobuf/duration.pb.h" +#include "google/protobuf/struct.pb.h" +#include "google/protobuf/timestamp.pb.h" +#include "google/protobuf/wrappers.pb.h" +#include "absl/log/absl_log.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/cord.h" +#include "absl/strings/escaping.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "eval/public/cel_value.h" +#include "internal/overflow.h" +#include "internal/proto_time_encoding.h" +#include "internal/status_macros.h" +#include "internal/time.h" +#include "internal/well_known_types.h" +#include "google/protobuf/arena.h" +#include "google/protobuf/descriptor.h" +#include "google/protobuf/json/json.h" +#include "google/protobuf/message.h" + +namespace google::api::expr::runtime::internal { + +namespace { + +using google::protobuf::BoolValue; +using google::protobuf::BytesValue; +using google::protobuf::DoubleValue; +using google::protobuf::Duration; +using google::protobuf::Int64Value; +using google::protobuf::ListValue; +using google::protobuf::StringValue; +using google::protobuf::Struct; +using google::protobuf::Timestamp; +using google::protobuf::UInt64Value; +using google::protobuf::Value; +using google::protobuf::Arena; +using google::protobuf::Descriptor; +using google::protobuf::Message; +using google::protobuf::MessageFactory; +using google::protobuf::json::MessageToJsonString; +using google::protobuf::json::PrintOptions; + +// kMaxIntJSON is defined as the Number.MAX_SAFE_INTEGER value per EcmaScript 6. +constexpr int64_t kMaxIntJSON = (1ll << 53) - 1; + +// kMinIntJSON is defined as the Number.MIN_SAFE_INTEGER value per EcmaScript 6. +constexpr int64_t kMinIntJSON = -kMaxIntJSON; + +// IsJSONSafe indicates whether the int is safely representable as a floating +// point value in JSON. +static bool IsJSONSafe(int64_t i) { + return i >= kMinIntJSON && i <= kMaxIntJSON; +} + +// IsJSONSafe indicates whether the uint is safely representable as a floating +// point value in JSON. +static bool IsJSONSafe(uint64_t i) { + return i <= static_cast(kMaxIntJSON); +} + +static bool IsEmptyProto(const google::protobuf::Descriptor* descriptor) { + return descriptor->full_name() == "google.protobuf.Empty"; +} + +static bool IsFieldMaskProto(const google::protobuf::Descriptor* descriptor) { + return descriptor->full_name() == "google.protobuf.FieldMask"; +} + +static std::optional GetFieldMaskJsonString( + const google::protobuf::Message& message) { + // TODO(b/540507668): Refactor to pipe descriptor_pool through + // ValueFromValue to use internal::MessageToJson. + PrintOptions json_options; + std::string json_str; + auto status = MessageToJsonString(message, &json_str, json_options); + if (!status.ok()) { + ABSL_LOG(ERROR) << "Failed to convert FieldMask to JSON: " << status; + return std::nullopt; + } + // If JSON marshalling is correct, we know we'll always get a plain + // JSON string value and it shouldn't contain any escapes that we need + // to interpret. + if (json_str.size() >= 2 && json_str.front() == '"' && + json_str.back() == '"') { + return json_str.substr(1, json_str.size() - 2); + } + return json_str; +} + +struct IgnoreErrorAndReturnNullptr { + std::nullptr_t operator()(const absl::Status& status) const { + status.IgnoreError(); + return nullptr; + } +}; + +google::protobuf::Message* DurationFromValue(const google::protobuf::Message* prototype, + const CelValue& value, + google::protobuf::Arena* arena) { + absl::Duration val; + if (!value.GetValue(&val)) { + return nullptr; + } + if (!cel::internal::ValidateDuration(val).ok()) { + return nullptr; + } + auto* message = prototype->New(arena); + CEL_ASSIGN_OR_RETURN( + auto reflection, + cel::well_known_types::GetDurationReflection(message->GetDescriptor()), + _.With(IgnoreErrorAndReturnNullptr())); + reflection.UnsafeSetFromAbslDuration(message, val); + return message; +} + +google::protobuf::Message* BoolFromValue(const google::protobuf::Message* prototype, + const CelValue& value, google::protobuf::Arena* arena) { + bool val; + if (!value.GetValue(&val)) { + return nullptr; + } + auto* message = prototype->New(arena); + CEL_ASSIGN_OR_RETURN( + auto reflection, + cel::well_known_types::GetBoolValueReflection(message->GetDescriptor()), + _.With(IgnoreErrorAndReturnNullptr())); + reflection.SetValue(message, val); + return message; +} + +google::protobuf::Message* BytesFromValue(const google::protobuf::Message* prototype, + const CelValue& value, google::protobuf::Arena* arena) { + CelValue::BytesHolder view_val; + if (!value.GetValue(&view_val)) { + return nullptr; + } + auto* message = prototype->New(arena); + CEL_ASSIGN_OR_RETURN( + auto reflection, + cel::well_known_types::GetBytesValueReflection(message->GetDescriptor()), + _.With(IgnoreErrorAndReturnNullptr())); + reflection.SetValue(message, view_val.value()); + return message; +} + +google::protobuf::Message* DoubleFromValue(const google::protobuf::Message* prototype, + const CelValue& value, google::protobuf::Arena* arena) { + double val; + if (!value.GetValue(&val)) { + return nullptr; + } + auto* message = prototype->New(arena); + CEL_ASSIGN_OR_RETURN( + auto reflection, + cel::well_known_types::GetDoubleValueReflection(message->GetDescriptor()), + _.With(IgnoreErrorAndReturnNullptr())); + reflection.SetValue(message, val); + return message; +} + +google::protobuf::Message* FloatFromValue(const google::protobuf::Message* prototype, + const CelValue& value, google::protobuf::Arena* arena) { + double val; + if (!value.GetValue(&val)) { + return nullptr; + } + float fval = val; + // Abort the conversion if the value is outside the float range. + if (val > std::numeric_limits::max()) { + fval = std::numeric_limits::infinity(); + } else if (val < std::numeric_limits::lowest()) { + fval = -std::numeric_limits::infinity(); + } + auto* message = prototype->New(arena); + CEL_ASSIGN_OR_RETURN( + auto reflection, + cel::well_known_types::GetFloatValueReflection(message->GetDescriptor()), + _.With(IgnoreErrorAndReturnNullptr())); + reflection.SetValue(message, static_cast(fval)); + return message; +} + +google::protobuf::Message* Int32FromValue(const google::protobuf::Message* prototype, + const CelValue& value, google::protobuf::Arena* arena) { + int64_t val; + if (!value.GetValue(&val)) { + return nullptr; + } + if (!cel::internal::CheckedInt64ToInt32(val).ok()) { + return nullptr; + } + int32_t ival = static_cast(val); + auto* message = prototype->New(arena); + CEL_ASSIGN_OR_RETURN( + auto reflection, + cel::well_known_types::GetInt32ValueReflection(message->GetDescriptor()), + _.With(IgnoreErrorAndReturnNullptr())); + reflection.SetValue(message, ival); + return message; +} + +google::protobuf::Message* Int64FromValue(const google::protobuf::Message* prototype, + const CelValue& value, google::protobuf::Arena* arena) { + int64_t val; + if (!value.GetValue(&val)) { + return nullptr; + } + auto* message = prototype->New(arena); + CEL_ASSIGN_OR_RETURN( + auto reflection, + cel::well_known_types::GetInt64ValueReflection(message->GetDescriptor()), + _.With(IgnoreErrorAndReturnNullptr())); + reflection.SetValue(message, val); + return message; +} + +google::protobuf::Message* StringFromValue(const google::protobuf::Message* prototype, + const CelValue& value, google::protobuf::Arena* arena) { + CelValue::StringHolder view_val; + if (!value.GetValue(&view_val)) { + return nullptr; + } + auto* message = prototype->New(arena); + CEL_ASSIGN_OR_RETURN( + auto reflection, + cel::well_known_types::GetStringValueReflection(message->GetDescriptor()), + _.With(IgnoreErrorAndReturnNullptr())); + reflection.SetValue(message, view_val.value()); + return message; +} + +google::protobuf::Message* TimestampFromValue(const google::protobuf::Message* prototype, + const CelValue& value, + google::protobuf::Arena* arena) { + absl::Time val; + if (!value.GetValue(&val)) { + return nullptr; + } + if (!cel::internal::ValidateTimestamp(val).ok()) { + return nullptr; + } + auto* message = prototype->New(arena); + CEL_ASSIGN_OR_RETURN( + auto reflection, + cel::well_known_types::GetTimestampReflection(message->GetDescriptor()), + _.With(IgnoreErrorAndReturnNullptr())); + reflection.UnsafeSetFromAbslTime(message, val); + return message; +} + +google::protobuf::Message* UInt32FromValue(const google::protobuf::Message* prototype, + const CelValue& value, google::protobuf::Arena* arena) { + uint64_t val; + if (!value.GetValue(&val)) { + return nullptr; + } + if (!cel::internal::CheckedUint64ToUint32(val).ok()) { + return nullptr; + } + uint32_t ival = static_cast(val); + auto* message = prototype->New(arena); + CEL_ASSIGN_OR_RETURN( + auto reflection, + cel::well_known_types::GetUInt32ValueReflection(message->GetDescriptor()), + _.With(IgnoreErrorAndReturnNullptr())); + reflection.SetValue(message, ival); + return message; +} + +google::protobuf::Message* UInt64FromValue(const google::protobuf::Message* prototype, + const CelValue& value, google::protobuf::Arena* arena) { + uint64_t val; + if (!value.GetValue(&val)) { + return nullptr; + } + auto* message = prototype->New(arena); + CEL_ASSIGN_OR_RETURN( + auto reflection, + cel::well_known_types::GetUInt64ValueReflection(message->GetDescriptor()), + _.With(IgnoreErrorAndReturnNullptr())); + reflection.SetValue(message, val); + return message; +} + +google::protobuf::Message* ValueFromValue(google::protobuf::Message* message, const CelValue& value, + google::protobuf::Arena* arena); + +google::protobuf::Message* ValueFromValue(const google::protobuf::Message* prototype, + const CelValue& value, google::protobuf::Arena* arena) { + return ValueFromValue(prototype->New(arena), value, arena); +} + +google::protobuf::Message* ListFromValue(google::protobuf::Message* message, const CelValue& value, + google::protobuf::Arena* arena) { + if (!value.IsList()) { + return nullptr; + } + const CelList& list = *value.ListOrDie(); + CEL_ASSIGN_OR_RETURN( + auto reflection, + cel::well_known_types::GetListValueReflection(message->GetDescriptor()), + _.With(IgnoreErrorAndReturnNullptr())); + for (int i = 0; i < list.size(); i++) { + auto e = list.Get(arena, i); + auto* elem = reflection.AddValues(message); + if (ValueFromValue(elem, e, arena) == nullptr) { + return nullptr; + } + } + return message; +} + +google::protobuf::Message* ListFromValue(const google::protobuf::Message* prototype, + const CelValue& value, google::protobuf::Arena* arena) { + if (!value.IsList()) { + return nullptr; + } + return ListFromValue(prototype->New(arena), value, arena); +} + +google::protobuf::Message* StructFromValue(google::protobuf::Message* message, + const CelValue& value, google::protobuf::Arena* arena) { + if (!value.IsMap()) { + return nullptr; + } + const CelMap& map = *value.MapOrDie(); + absl::StatusOr keys_or = map.ListKeys(arena); + if (!keys_or.ok()) { + // If map doesn't support listing keys, it can't pack into a Struct value. + // This will surface as a CEL error when the object creation expression + // fails. + return nullptr; + } + const CelList& keys = **keys_or; + CEL_ASSIGN_OR_RETURN( + auto reflection, + cel::well_known_types::GetStructReflection(message->GetDescriptor()), + _.With(IgnoreErrorAndReturnNullptr())); + for (int i = 0; i < keys.size(); i++) { + auto k = keys.Get(arena, i); + // If the key is not a string type, abort the conversion. + if (!k.IsString()) { + return nullptr; + } + absl::string_view key = k.StringOrDie().value(); + + auto v = map.Get(arena, k); + if (!v.has_value()) { + return nullptr; + } + auto* field = reflection.InsertField(message, key); + if (ValueFromValue(field, *v, arena) == nullptr) { + return nullptr; + } + } + return message; +} + +google::protobuf::Message* StructFromValue(const google::protobuf::Message* prototype, + const CelValue& value, google::protobuf::Arena* arena) { + if (!value.IsMap()) { + return nullptr; + } + return StructFromValue(prototype->New(arena), value, arena); +} + +google::protobuf::Message* ValueFromValue(google::protobuf::Message* message, const CelValue& value, + google::protobuf::Arena* arena) { + CEL_ASSIGN_OR_RETURN( + auto reflection, + cel::well_known_types::GetValueReflection(message->GetDescriptor()), + _.With(IgnoreErrorAndReturnNullptr())); + switch (value.type()) { + case CelValue::Type::kBool: { + bool val; + if (value.GetValue(&val)) { + reflection.SetBoolValue(message, val); + return message; + } + } break; + case CelValue::Type::kBytes: { + // Base64 encode byte strings to ensure they can safely be transported + // in a JSON string. + CelValue::BytesHolder val; + if (value.GetValue(&val)) { + reflection.SetStringValueFromBytes(message, val.value()); + return message; + } + } break; + case CelValue::Type::kDouble: { + double val; + if (value.GetValue(&val)) { + reflection.SetNumberValue(message, val); + return message; + } + } break; + case CelValue::Type::kDuration: { + // Convert duration values to a protobuf JSON format. + absl::Duration val; + if (value.GetValue(&val)) { + CEL_RETURN_IF_ERROR(cel::internal::ValidateDuration(val)) + .With(IgnoreErrorAndReturnNullptr()); + reflection.SetStringValueFromDuration(message, val); + return message; + } + } break; + case CelValue::Type::kInt64: { + int64_t val; + // Convert int64_t values within the int53 range to doubles, otherwise + // serialize the value to a string. + if (value.GetValue(&val)) { + reflection.SetNumberValue(message, val); + return message; + } + } break; + case CelValue::Type::kString: { + CelValue::StringHolder val; + if (value.GetValue(&val)) { + reflection.SetStringValue(message, val.value()); + return message; + } + } break; + case CelValue::Type::kTimestamp: { + // Convert timestamp values to a protobuf JSON format. + absl::Time val; + if (value.GetValue(&val)) { + CEL_RETURN_IF_ERROR(cel::internal::ValidateTimestamp(val)) + .With(IgnoreErrorAndReturnNullptr()); + reflection.SetStringValueFromTimestamp(message, val); + return message; + } + } break; + case CelValue::Type::kUint64: { + uint64_t val; + // Convert uint64_t values within the int53 range to doubles, otherwise + // serialize the value to a string. + if (value.GetValue(&val)) { + reflection.SetNumberValue(message, val); + return message; + } + } break; + case CelValue::Type::kList: { + if (ListFromValue(reflection.MutableListValue(message), value, arena) != + nullptr) { + return message; + } + } break; + case CelValue::Type::kMap: { + if (StructFromValue(reflection.MutableStructValue(message), value, + arena) != nullptr) { + return message; + } + } break; + case CelValue::Type::kMessage: { + const google::protobuf::Message* message_ptr = value.MessageOrDie(); + if (IsEmptyProto(message_ptr->GetDescriptor())) { + reflection.MutableStructValue(message); + return message; + } + if (IsFieldMaskProto(message_ptr->GetDescriptor())) { + std::optional fm_str = + GetFieldMaskJsonString(*message_ptr); + if (fm_str.has_value()) { + reflection.SetStringValue(message, *fm_str); + return message; + } + return nullptr; + } + return nullptr; + } break; + case CelValue::Type::kNullType: + reflection.SetNullValue(message); + return message; + break; + default: + return nullptr; + } + return nullptr; +} + +bool ValueFromValue(Value* json, const CelValue& value, google::protobuf::Arena* arena); + +bool ListFromValue(ListValue* json_list, const CelValue& value, + google::protobuf::Arena* arena) { + if (!value.IsList()) { + return false; + } + const CelList& list = *value.ListOrDie(); + for (int i = 0; i < list.size(); i++) { + auto e = list.Get(arena, i); + Value* elem = json_list->add_values(); + if (!ValueFromValue(elem, e, arena)) { + return false; + } + } + return true; +} + +bool StructFromValue(Struct* json_struct, const CelValue& value, + google::protobuf::Arena* arena) { + if (!value.IsMap()) { + return false; + } + const CelMap& map = *value.MapOrDie(); + absl::StatusOr keys_or = map.ListKeys(arena); + if (!keys_or.ok()) { + // If map doesn't support listing keys, it can't pack into a Struct value. + // This will surface as a CEL error when the object creation expression + // fails. + return false; + } + const CelList& keys = **keys_or; + auto fields = json_struct->mutable_fields(); + for (int i = 0; i < keys.size(); i++) { + auto k = keys.Get(arena, i); + // If the key is not a string type, abort the conversion. + if (!k.IsString()) { + return false; + } + absl::string_view key = k.StringOrDie().value(); + + auto v = map.Get(arena, k); + if (!v.has_value()) { + return false; + } + Value field_value; + if (!ValueFromValue(&field_value, *v, arena)) { + return false; + } + (*fields)[std::string(key)] = field_value; + } + return true; +} + +bool ValueFromValue(Value* json, const CelValue& value, google::protobuf::Arena* arena) { + switch (value.type()) { + case CelValue::Type::kBool: { + bool val; + if (value.GetValue(&val)) { + json->set_bool_value(val); + return true; + } + } break; + case CelValue::Type::kBytes: { + // Base64 encode byte strings to ensure they can safely be transported + // in a JSON string. + CelValue::BytesHolder val; + if (value.GetValue(&val)) { + json->set_string_value(absl::Base64Escape(val.value())); + return true; + } + } break; + case CelValue::Type::kDouble: { + double val; + if (value.GetValue(&val)) { + json->set_number_value(val); + return true; + } + } break; + case CelValue::Type::kDuration: { + // Convert duration values to a protobuf JSON format. + absl::Duration val; + if (value.GetValue(&val)) { + auto encode = cel::internal::EncodeDurationToString(val); + if (!encode.ok()) { + return false; + } + json->set_string_value(*encode); + return true; + } + } break; + case CelValue::Type::kInt64: { + int64_t val; + // Convert int64_t values within the int53 range to doubles, otherwise + // serialize the value to a string. + if (value.GetValue(&val)) { + if (IsJSONSafe(val)) { + json->set_number_value(val); + } else { + json->set_string_value(absl::StrCat(val)); + } + return true; + } + } break; + case CelValue::Type::kString: { + CelValue::StringHolder val; + if (value.GetValue(&val)) { + json->set_string_value(val.value()); + return true; + } + } break; + case CelValue::Type::kTimestamp: { + // Convert timestamp values to a protobuf JSON format. + absl::Time val; + if (value.GetValue(&val)) { + auto encode = cel::internal::EncodeTimeToString(val); + if (!encode.ok()) { + return false; + } + json->set_string_value(*encode); + return true; + } + } break; + case CelValue::Type::kUint64: { + uint64_t val; + // Convert uint64_t values within the int53 range to doubles, otherwise + // serialize the value to a string. + if (value.GetValue(&val)) { + if (IsJSONSafe(val)) { + json->set_number_value(val); + } else { + json->set_string_value(absl::StrCat(val)); + } + return true; + } + } break; + case CelValue::Type::kList: + return ListFromValue(json->mutable_list_value(), value, arena); + case CelValue::Type::kMap: + return StructFromValue(json->mutable_struct_value(), value, arena); + case CelValue::Type::kMessage: { + const google::protobuf::Message* message_ptr = value.MessageOrDie(); + if (IsEmptyProto(message_ptr->GetDescriptor())) { + json->mutable_struct_value(); + return true; + } + if (IsFieldMaskProto(message_ptr->GetDescriptor())) { + std::optional fm_str = + GetFieldMaskJsonString(*message_ptr); + if (fm_str.has_value()) { + json->set_string_value(*fm_str); + return true; + } + return false; + } + return false; + } + case CelValue::Type::kNullType: + json->set_null_value(protobuf::NULL_VALUE); + return true; + default: + return false; + } + return false; +} + +google::protobuf::Message* AnyFromValue(const google::protobuf::Message* prototype, + const CelValue& value, google::protobuf::Arena* arena) { + std::string type_name; + absl::Cord payload; + + // In open source, any->PackFrom() returns void rather than boolean. + switch (value.type()) { + case CelValue::Type::kBool: { + BoolValue v; + type_name = v.GetTypeName(); + v.set_value(value.BoolOrDie()); + payload = v.SerializeAsCord(); + } break; + case CelValue::Type::kBytes: { + BytesValue v; + type_name = v.GetTypeName(); + v.set_value(value.BytesOrDie().value()); + payload = v.SerializeAsCord(); + } break; + case CelValue::Type::kDouble: { + DoubleValue v; + type_name = v.GetTypeName(); + v.set_value(value.DoubleOrDie()); + payload = v.SerializeAsCord(); + } break; + case CelValue::Type::kDuration: { + Duration v; + if (!cel::internal::EncodeDuration(value.DurationOrDie(), &v).ok()) { + return nullptr; + } + type_name = v.GetTypeName(); + payload = v.SerializeAsCord(); + } break; + case CelValue::Type::kInt64: { + Int64Value v; + type_name = v.GetTypeName(); + v.set_value(value.Int64OrDie()); + payload = v.SerializeAsCord(); + } break; + case CelValue::Type::kString: { + StringValue v; + type_name = v.GetTypeName(); + v.set_value(std::string(value.StringOrDie().value())); + payload = v.SerializeAsCord(); + } break; + case CelValue::Type::kTimestamp: { + Timestamp v; + if (!cel::internal::EncodeTime(value.TimestampOrDie(), &v).ok()) { + return nullptr; + } + type_name = v.GetTypeName(); + payload = v.SerializeAsCord(); + } break; + case CelValue::Type::kUint64: { + UInt64Value v; + type_name = v.GetTypeName(); + v.set_value(value.Uint64OrDie()); + payload = v.SerializeAsCord(); + } break; + case CelValue::Type::kList: { + ListValue v; + if (!ListFromValue(&v, value, arena)) { + return nullptr; + } + type_name = v.GetTypeName(); + payload = v.SerializeAsCord(); + } break; + case CelValue::Type::kMap: { + Struct v; + if (!StructFromValue(&v, value, arena)) { + return nullptr; + } + type_name = v.GetTypeName(); + payload = v.SerializeAsCord(); + } break; + case CelValue::Type::kNullType: { + Value v; + type_name = v.GetTypeName(); + v.set_null_value(google::protobuf::NULL_VALUE); + payload = v.SerializeAsCord(); + } break; + case CelValue::Type::kMessage: { + type_name = value.MessageWrapperOrDie().message_ptr()->GetTypeName(); + payload = value.MessageWrapperOrDie().message_ptr()->SerializeAsCord(); + } break; + default: + return nullptr; + } + + auto* message = prototype->New(arena); + CEL_ASSIGN_OR_RETURN( + auto reflection, + cel::well_known_types::GetAnyReflection(message->GetDescriptor()), + _.With(IgnoreErrorAndReturnNullptr())); + reflection.SetTypeUrl(message, + absl::StrCat("type.googleapis.com/", type_name)); + reflection.SetValue(message, payload); + return message; +} + +bool IsAlreadyWrapped(google::protobuf::Descriptor::WellKnownType wkt, + const CelValue& value) { + if (value.IsMessage()) { + const auto* msg = value.MessageOrDie(); + if (wkt == msg->GetDescriptor()->well_known_type()) { + return true; + } + } + return false; +} + +// MessageFromValueMaker makes a specific protobuf Message instance based on +// the desired protobuf type name and an input CelValue. +// +// It holds a registry of CelValue factories for specific subtypes of Message. +// If message does not match any of types stored in registry, an the factory +// returns an absent value. +class MessageFromValueMaker { + public: + // Non-copyable, non-assignable + MessageFromValueMaker(const MessageFromValueMaker&) = delete; + MessageFromValueMaker& operator=(const MessageFromValueMaker&) = delete; + + static google::protobuf::Message* MaybeWrapMessage(const google::protobuf::Descriptor* descriptor, + google::protobuf::MessageFactory* factory, + const CelValue& value, + Arena* arena) { + switch (descriptor->well_known_type()) { + case google::protobuf::Descriptor::WELLKNOWNTYPE_DOUBLEVALUE: + if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { + return nullptr; + } + return DoubleFromValue(factory->GetPrototype(descriptor), value, arena); + case google::protobuf::Descriptor::WELLKNOWNTYPE_FLOATVALUE: + if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { + return nullptr; + } + return FloatFromValue(factory->GetPrototype(descriptor), value, arena); + case google::protobuf::Descriptor::WELLKNOWNTYPE_INT64VALUE: + if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { + return nullptr; + } + return Int64FromValue(factory->GetPrototype(descriptor), value, arena); + case google::protobuf::Descriptor::WELLKNOWNTYPE_UINT64VALUE: + if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { + return nullptr; + } + return UInt64FromValue(factory->GetPrototype(descriptor), value, arena); + case google::protobuf::Descriptor::WELLKNOWNTYPE_INT32VALUE: + if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { + return nullptr; + } + return Int32FromValue(factory->GetPrototype(descriptor), value, arena); + case google::protobuf::Descriptor::WELLKNOWNTYPE_UINT32VALUE: + if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { + return nullptr; + } + return UInt32FromValue(factory->GetPrototype(descriptor), value, arena); + case google::protobuf::Descriptor::WELLKNOWNTYPE_STRINGVALUE: + if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { + return nullptr; + } + return StringFromValue(factory->GetPrototype(descriptor), value, arena); + case google::protobuf::Descriptor::WELLKNOWNTYPE_BYTESVALUE: + if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { + return nullptr; + } + return BytesFromValue(factory->GetPrototype(descriptor), value, arena); + case google::protobuf::Descriptor::WELLKNOWNTYPE_BOOLVALUE: + if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { + return nullptr; + } + return BoolFromValue(factory->GetPrototype(descriptor), value, arena); + case google::protobuf::Descriptor::WELLKNOWNTYPE_ANY: + if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { + return nullptr; + } + return AnyFromValue(factory->GetPrototype(descriptor), value, arena); + case google::protobuf::Descriptor::WELLKNOWNTYPE_DURATION: + if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { + return nullptr; + } + return DurationFromValue(factory->GetPrototype(descriptor), value, + arena); + case google::protobuf::Descriptor::WELLKNOWNTYPE_TIMESTAMP: + if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { + return nullptr; + } + return TimestampFromValue(factory->GetPrototype(descriptor), value, + arena); + case google::protobuf::Descriptor::WELLKNOWNTYPE_VALUE: + if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { + return nullptr; + } + return ValueFromValue(factory->GetPrototype(descriptor), value, arena); + case google::protobuf::Descriptor::WELLKNOWNTYPE_LISTVALUE: + if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { + return nullptr; + } + return ListFromValue(factory->GetPrototype(descriptor), value, arena); + case google::protobuf::Descriptor::WELLKNOWNTYPE_STRUCT: + if (IsAlreadyWrapped(descriptor->well_known_type(), value)) { + return nullptr; + } + return StructFromValue(factory->GetPrototype(descriptor), value, arena); + // WELLKNOWNTYPE_FIELDMASK has no special CelValue type + default: + return nullptr; + } + } +}; + +} // namespace + +const google::protobuf::Message* MaybeWrapValueToMessage( + const google::protobuf::Descriptor* descriptor, google::protobuf::MessageFactory* factory, + const CelValue& value, Arena* arena) { + google::protobuf::Message* msg = MessageFromValueMaker::MaybeWrapMessage( + descriptor, factory, value, arena); + return msg; +} + +} // namespace google::api::expr::runtime::internal diff --git a/eval/public/structs/cel_proto_wrap_value_to_message.h b/eval/public/structs/cel_proto_wrap_value_to_message.h new file mode 100644 index 000000000..9bc8581e1 --- /dev/null +++ b/eval/public/structs/cel_proto_wrap_value_to_message.h @@ -0,0 +1,40 @@ +// 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. + +#ifndef THIRD_PARTY_CEL_CPP_EVAL_PUBLIC_STRUCTS_CEL_PROTO_WRAP_VALUE_TO_MESSAGE_H_ +#define THIRD_PARTY_CEL_CPP_EVAL_PUBLIC_STRUCTS_CEL_PROTO_WRAP_VALUE_TO_MESSAGE_H_ + +#include "eval/public/cel_value.h" +#include "google/protobuf/arena.h" +#include "google/protobuf/descriptor.h" +#include "google/protobuf/message.h" + +namespace google::api::expr::runtime::internal { + +// MaybeWrapValue attempts to wrap the input value in a proto message with +// the given type_name. If the value can be wrapped, it is returned as a +// protobuf message. Otherwise, the result will be nullptr. +// +// This method is the complement to UnwrapMessageToValue which may unwrap a +// protobuf message to native CelValue representation during a protobuf field +// read. +// Just as CreateMessage should only be used when reading protobuf values, +// MaybeWrapValueToMessage should only be used when assigning protobuf fields. +const google::protobuf::Message* MaybeWrapValueToMessage( + const google::protobuf::Descriptor* descriptor, google::protobuf::MessageFactory* factory, + const CelValue& value, google::protobuf::Arena* arena); + +} // namespace google::api::expr::runtime::internal + +#endif // THIRD_PARTY_CEL_CPP_EVAL_PUBLIC_STRUCTS_CEL_PROTO_WRAP_VALUE_TO_MESSAGE_H_ diff --git a/eval/public/structs/cel_proto_wrap_value_to_message_test.cc b/eval/public/structs/cel_proto_wrap_value_to_message_test.cc new file mode 100644 index 000000000..c05d1203d --- /dev/null +++ b/eval/public/structs/cel_proto_wrap_value_to_message_test.cc @@ -0,0 +1,616 @@ +// 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 "eval/public/structs/cel_proto_wrap_value_to_message.h" + +#include +#include +#include +#include +#include +#include + +#include "google/protobuf/any.pb.h" +#include "google/protobuf/duration.pb.h" +#include "google/protobuf/empty.pb.h" +#include "google/protobuf/field_mask.pb.h" +#include "google/protobuf/struct.pb.h" +#include "google/protobuf/timestamp.pb.h" +#include "google/protobuf/wrappers.pb.h" +#include "absl/status/status.h" +#include "absl/status/status_matchers.h" +#include "absl/strings/str_cat.h" +#include "absl/time/time.h" +#include "absl/types/span.h" +#include "eval/public/cel_value.h" +#include "eval/public/containers/container_backed_list_impl.h" +#include "eval/public/containers/container_backed_map_impl.h" +#include "eval/public/structs/trivial_legacy_type_info.h" +#include "eval/testutil/test_message.pb.h" +#include "internal/testing.h" +#include "testutil/util.h" +#include "google/protobuf/arena.h" +#include "google/protobuf/dynamic_message.h" +#include "google/protobuf/message.h" +#include "google/protobuf/text_format.h" + +namespace google::api::expr::runtime::internal { + +namespace { + +using google::protobuf::Any; +using google::protobuf::BoolValue; +using google::protobuf::BytesValue; +using google::protobuf::DoubleValue; +using google::protobuf::Duration; +using google::protobuf::FieldMask; +using google::protobuf::FloatValue; +using google::protobuf::Int32Value; +using google::protobuf::Int64Value; +using google::protobuf::ListValue; +using google::protobuf::StringValue; +using google::protobuf::Struct; +using google::protobuf::Timestamp; +using google::protobuf::UInt32Value; +using google::protobuf::UInt64Value; +using google::protobuf::Value; +using google::protobuf::Arena; +using ::google::protobuf::TextFormat; + +CelValue MessageToCelValue(const google::protobuf::Message* m) { + return CelValue::CreateMessageWrapper( + CelValue::MessageWrapper(m, TrivialTypeInfo::GetInstance())); +} + +class CelProtoWrapValueToMessageTest : public ::testing::Test { + protected: + CelProtoWrapValueToMessageTest() = default; + + void ExpectWrappedMessage(const CelValue& value, + const google::protobuf::Message& message) { + // Test the input value wraps to the destination message type. + auto* result = MaybeWrapValueToMessage( + message.GetDescriptor(), message.GetReflection()->GetMessageFactory(), + value, arena()); + EXPECT_TRUE(result != nullptr); + EXPECT_THAT(result, testutil::EqualsProto(message)); + + // Ensure that double wrapping results in the object being wrapped once. + auto* identity = MaybeWrapValueToMessage( + message.GetDescriptor(), message.GetReflection()->GetMessageFactory(), + MessageToCelValue(result), arena()); + EXPECT_TRUE(identity == nullptr); + + // Check to make sure that even dynamic messages can be used as input to + // the wrapping call. + result = MaybeWrapValueToMessage( + ReflectedCopy(message)->GetDescriptor(), + ReflectedCopy(message)->GetReflection()->GetMessageFactory(), value, + arena()); + EXPECT_TRUE(result != nullptr); + EXPECT_THAT(result, testutil::EqualsProto(message)); + } + + void ExpectNotWrapped(const CelValue& value, const google::protobuf::Message& message) { + // Test the input value does not wrap by asserting value == result. + auto result = MaybeWrapValueToMessage( + message.GetDescriptor(), message.GetReflection()->GetMessageFactory(), + value, arena()); + EXPECT_TRUE(result == nullptr); + } + + std::unique_ptr ReflectedCopy( + const google::protobuf::Message& message) { + std::unique_ptr dynamic_value( + factory_.GetPrototype(message.GetDescriptor())->New()); + dynamic_value->CopyFrom(message); + return dynamic_value; + } + + Arena* arena() { return &arena_; } + + private: + Arena arena_; + google::protobuf::DynamicMessageFactory factory_; +}; + +TEST_F(CelProtoWrapValueToMessageTest, WrapNull) { + auto cel_value = CelValue::CreateNull(); + + Value json; + json.set_null_value(protobuf::NULL_VALUE); + ExpectWrappedMessage(cel_value, json); + + Any any; + any.PackFrom(json); + ExpectWrappedMessage(cel_value, any); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapBool) { + auto cel_value = CelValue::CreateBool(true); + + Value json; + json.set_bool_value(true); + ExpectWrappedMessage(cel_value, json); + + BoolValue wrapper; + wrapper.set_value(true); + ExpectWrappedMessage(cel_value, wrapper); + + Any any; + any.PackFrom(wrapper); + ExpectWrappedMessage(cel_value, any); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapBytes) { + std::string str = "hello world"; + auto cel_value = CelValue::CreateBytes(CelValue::BytesHolder(&str)); + + BytesValue wrapper; + wrapper.set_value(str); + ExpectWrappedMessage(cel_value, wrapper); + + Any any; + any.PackFrom(wrapper); + ExpectWrappedMessage(cel_value, any); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapBytesToValue) { + std::string str = "hello world"; + auto cel_value = CelValue::CreateBytes(CelValue::BytesHolder(&str)); + + Value json; + json.set_string_value("aGVsbG8gd29ybGQ="); + ExpectWrappedMessage(cel_value, json); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapDuration) { + auto cel_value = CelValue::CreateDuration(absl::Seconds(300)); + + Duration d; + d.set_seconds(300); + ExpectWrappedMessage(cel_value, d); + + Any any; + any.PackFrom(d); + ExpectWrappedMessage(cel_value, any); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapDurationToValue) { + auto cel_value = CelValue::CreateDuration(absl::Seconds(300)); + + Value json; + json.set_string_value("300s"); + ExpectWrappedMessage(cel_value, json); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapDouble) { + double num = 1.5; + auto cel_value = CelValue::CreateDouble(num); + + Value json; + json.set_number_value(num); + ExpectWrappedMessage(cel_value, json); + + DoubleValue wrapper; + wrapper.set_value(num); + ExpectWrappedMessage(cel_value, wrapper); + + Any any; + any.PackFrom(wrapper); + ExpectWrappedMessage(cel_value, any); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapDoubleToFloatValue) { + double num = 1.5; + auto cel_value = CelValue::CreateDouble(num); + + FloatValue wrapper; + wrapper.set_value(num); + ExpectWrappedMessage(cel_value, wrapper); + + // Imprecise double -> float representation results in truncation. + double small_num = -9.9e-100; + wrapper.set_value(small_num); + cel_value = CelValue::CreateDouble(small_num); + ExpectWrappedMessage(cel_value, wrapper); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapDoubleOverflow) { + double lowest_double = std::numeric_limits::lowest(); + auto cel_value = CelValue::CreateDouble(lowest_double); + + // Double exceeds float precision, overflow to -infinity. + FloatValue wrapper; + wrapper.set_value(-std::numeric_limits::infinity()); + ExpectWrappedMessage(cel_value, wrapper); + + double max_double = std::numeric_limits::max(); + cel_value = CelValue::CreateDouble(max_double); + + wrapper.set_value(std::numeric_limits::infinity()); + ExpectWrappedMessage(cel_value, wrapper); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapInt64) { + int32_t num = std::numeric_limits::lowest(); + auto cel_value = CelValue::CreateInt64(num); + + Value json; + json.set_number_value(static_cast(num)); + ExpectWrappedMessage(cel_value, json); + + Int64Value wrapper; + wrapper.set_value(num); + ExpectWrappedMessage(cel_value, wrapper); + + Any any; + any.PackFrom(wrapper); + ExpectWrappedMessage(cel_value, any); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapInt64ToInt32Value) { + int32_t num = std::numeric_limits::lowest(); + auto cel_value = CelValue::CreateInt64(num); + + Int32Value wrapper; + wrapper.set_value(num); + ExpectWrappedMessage(cel_value, wrapper); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapFailureInt64ToInt32Value) { + int64_t num = std::numeric_limits::lowest(); + auto cel_value = CelValue::CreateInt64(num); + + Int32Value wrapper; + ExpectNotWrapped(cel_value, wrapper); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapInt64ToValue) { + int64_t max = std::numeric_limits::max(); + auto cel_value = CelValue::CreateInt64(max); + + Value json; + json.set_string_value(absl::StrCat(max)); + ExpectWrappedMessage(cel_value, json); + + int64_t min = std::numeric_limits::min(); + cel_value = CelValue::CreateInt64(min); + + json.set_string_value(absl::StrCat(min)); + ExpectWrappedMessage(cel_value, json); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapUint64) { + uint32_t num = std::numeric_limits::max(); + auto cel_value = CelValue::CreateUint64(num); + + Value json; + json.set_number_value(static_cast(num)); + ExpectWrappedMessage(cel_value, json); + + UInt64Value wrapper; + wrapper.set_value(num); + ExpectWrappedMessage(cel_value, wrapper); + + Any any; + any.PackFrom(wrapper); + ExpectWrappedMessage(cel_value, any); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapUint64ToUint32Value) { + uint32_t num = std::numeric_limits::max(); + auto cel_value = CelValue::CreateUint64(num); + + UInt32Value wrapper; + wrapper.set_value(num); + ExpectWrappedMessage(cel_value, wrapper); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapUint64ToValue) { + uint64_t num = std::numeric_limits::max(); + auto cel_value = CelValue::CreateUint64(num); + + Value json; + json.set_string_value(absl::StrCat(num)); + ExpectWrappedMessage(cel_value, json); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapFailureUint64ToUint32Value) { + uint64_t num = std::numeric_limits::max(); + auto cel_value = CelValue::CreateUint64(num); + + UInt32Value wrapper; + ExpectNotWrapped(cel_value, wrapper); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapString) { + std::string str = "test"; + auto cel_value = CelValue::CreateString(CelValue::StringHolder(&str)); + + Value json; + json.set_string_value(str); + ExpectWrappedMessage(cel_value, json); + + StringValue wrapper; + wrapper.set_value(str); + ExpectWrappedMessage(cel_value, wrapper); + + Any any; + any.PackFrom(wrapper); + ExpectWrappedMessage(cel_value, any); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapTimestamp) { + absl::Time ts = absl::FromUnixSeconds(1615852799); + auto cel_value = CelValue::CreateTimestamp(ts); + + Timestamp t; + t.set_seconds(1615852799); + ExpectWrappedMessage(cel_value, t); + + Any any; + any.PackFrom(t); + ExpectWrappedMessage(cel_value, any); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapTimestampToValue) { + absl::Time ts = absl::FromUnixSeconds(1615852799); + auto cel_value = CelValue::CreateTimestamp(ts); + + Value json; + json.set_string_value("2021-03-15T23:59:59Z"); + ExpectWrappedMessage(cel_value, json); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapList) { + std::vector list_elems = { + CelValue::CreateDouble(1.5), + CelValue::CreateInt64(-2L), + }; + ContainerBackedListImpl list(std::move(list_elems)); + auto cel_value = CelValue::CreateList(&list); + + Value json; + json.mutable_list_value()->add_values()->set_number_value(1.5); + json.mutable_list_value()->add_values()->set_number_value(-2.); + ExpectWrappedMessage(cel_value, json); + ExpectWrappedMessage(cel_value, json.list_value()); + + Any any; + any.PackFrom(json.list_value()); + ExpectWrappedMessage(cel_value, any); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapFailureListValueBadJSON) { + TestMessage message; + std::vector list_elems = { + CelValue::CreateDouble(1.5), + MessageToCelValue(&message), + }; + ContainerBackedListImpl list(std::move(list_elems)); + auto cel_value = CelValue::CreateList(&list); + + Value json; + ExpectNotWrapped(cel_value, json); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapStruct) { + const std::string kField1 = "field1"; + std::vector> args = { + {CelValue::CreateString(CelValue::StringHolder(&kField1)), + CelValue::CreateBool(true)}}; + ASSERT_OK_AND_ASSIGN( + std::unique_ptr cel_map, + CreateContainerBackedMap( + absl::Span>(args.data(), args.size()))); + auto cel_value = CelValue::CreateMap(cel_map.get()); + + Value json; + (*json.mutable_struct_value()->mutable_fields())[kField1].set_bool_value( + true); + ExpectWrappedMessage(cel_value, json); + ExpectWrappedMessage(cel_value, json.struct_value()); + + Any any; + any.PackFrom(json.struct_value()); + ExpectWrappedMessage(cel_value, any); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapFailureStructBadKeyType) { + std::vector> args = { + {CelValue::CreateInt64(1L), CelValue::CreateBool(true)}}; + ASSERT_OK_AND_ASSIGN( + std::unique_ptr cel_map, + CreateContainerBackedMap( + absl::Span>(args.data(), args.size()))); + auto cel_value = CelValue::CreateMap(cel_map.get()); + + Value json; + ExpectNotWrapped(cel_value, json); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapFailureStructBadValueType) { + const std::string kField1 = "field1"; + TestMessage bad_value; + std::vector> args = { + {CelValue::CreateString(CelValue::StringHolder(&kField1)), + MessageToCelValue(&bad_value)}}; + ASSERT_OK_AND_ASSIGN( + std::unique_ptr cel_map, + CreateContainerBackedMap( + absl::Span>(args.data(), args.size()))); + auto cel_value = CelValue::CreateMap(cel_map.get()); + Value json; + ExpectNotWrapped(cel_value, json); +} + +class TestMap : public CelMapBuilder { + public: + absl::StatusOr ListKeys() const override { + return absl::UnimplementedError("test"); + } +}; + +TEST_F(CelProtoWrapValueToMessageTest, WrapFailureStructListKeysUnimplemented) { + const std::string kField1 = "field1"; + TestMap map; + ASSERT_THAT(map.Add(CelValue::CreateString(CelValue::StringHolder(&kField1)), + CelValue::CreateString(CelValue::StringHolder(&kField1))), + absl_testing::IsOk()); + + auto cel_value = CelValue::CreateMap(&map); + Value json; + ExpectNotWrapped(cel_value, json); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapFailureWrongType) { + auto cel_value = CelValue::CreateNull(); + std::vector wrong_types = { + &BoolValue::default_instance(), &BytesValue::default_instance(), + &DoubleValue::default_instance(), &Duration::default_instance(), + &FloatValue::default_instance(), &Int32Value::default_instance(), + &Int64Value::default_instance(), &ListValue::default_instance(), + &StringValue::default_instance(), &Struct::default_instance(), + &Timestamp::default_instance(), &UInt32Value::default_instance(), + &UInt64Value::default_instance(), + }; + for (const auto* wrong_type : wrong_types) { + ExpectNotWrapped(cel_value, *wrong_type); + } +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapFailureErrorToAny) { + auto cel_value = CreateNoSuchFieldError(arena(), "error_field"); + ExpectNotWrapped(cel_value, Any::default_instance()); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapFieldMaskToValue) { + FieldMask field_mask; + ASSERT_TRUE(TextFormat::ParseFromString(R"pb( + paths: "foo.bar" paths: "baz" + )pb", + &field_mask)); + CelValue value = MessageToCelValue(&field_mask); + + Value expected_message; + ASSERT_TRUE(TextFormat::ParseFromString(R"pb(string_value: "foo.bar,baz")pb", + &expected_message)); + + ExpectWrappedMessage(value, expected_message); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapMapWithFieldMaskToAny) { + const std::string kField = "field_mask"; + FieldMask field_mask; + ASSERT_TRUE(TextFormat::ParseFromString(R"pb( + paths: "foo.bar" paths: "baz" + )pb", + &field_mask)); + CelValue value = MessageToCelValue(&field_mask); + + std::vector> args = { + {CelValue::CreateString(CelValue::StringHolder(&kField)), value}}; + ASSERT_OK_AND_ASSIGN( + std::unique_ptr cel_map, + CreateContainerBackedMap( + absl::Span>(args.data(), args.size()))); + CelValue cel_value = CelValue::CreateMap(cel_map.get()); + + Struct expected_struct; + ASSERT_TRUE( + TextFormat::ParseFromString(R"pb( + fields { + key: "field_mask" + value { string_value: "foo.bar,baz" } + } + )pb", + &expected_struct)); + Any expected_message; + ASSERT_TRUE(expected_message.PackFrom(expected_struct)); + + ExpectWrappedMessage(cel_value, expected_message); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapListWithFieldMaskToAny) { + FieldMask field_mask; + ASSERT_TRUE(TextFormat::ParseFromString(R"pb( + paths: "foo.bar" paths: "baz" + )pb", + &field_mask)); + CelValue value = MessageToCelValue(&field_mask); + + std::vector list_entries = {value}; + ContainerBackedListImpl cel_list(list_entries); + CelValue list_value = CelValue::CreateList(&cel_list); + + ListValue expected_list; + ASSERT_TRUE(TextFormat::ParseFromString( + R"pb(values { string_value: "foo.bar,baz" })pb", &expected_list)); + Any expected_message; + ASSERT_TRUE(expected_message.PackFrom(expected_list)); + + ExpectWrappedMessage(list_value, expected_message); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapEmptyToValue) { + google::protobuf::Empty empty; + CelValue value = MessageToCelValue(&empty); + + Value expected_message; + expected_message.mutable_struct_value(); + + ExpectWrappedMessage(value, expected_message); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapMapWithEmptyToAny) { + const std::string kField = "empty"; + google::protobuf::Empty empty; + CelValue value = MessageToCelValue(&empty); + + std::vector> args = { + {CelValue::CreateString(CelValue::StringHolder(&kField)), value}}; + ASSERT_OK_AND_ASSIGN( + std::unique_ptr cel_map, + CreateContainerBackedMap( + absl::Span>(args.data(), args.size()))); + auto cel_value = CelValue::CreateMap(cel_map.get()); + + Struct expected_struct; + (*expected_struct.mutable_fields())[kField].mutable_struct_value(); + Any expected_message; + ASSERT_TRUE(expected_message.PackFrom(expected_struct)); + + ExpectWrappedMessage(cel_value, expected_message); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapListWithEmptyToAny) { + google::protobuf::Empty empty; + CelValue value = MessageToCelValue(&empty); + + std::vector list_entries = {value}; + ContainerBackedListImpl cel_list(list_entries); + CelValue list_value = CelValue::CreateList(&cel_list); + + ListValue expected_list; + expected_list.add_values()->mutable_struct_value(); + Any expected_message; + ASSERT_TRUE(expected_message.PackFrom(expected_list)); + + ExpectWrappedMessage(list_value, expected_message); +} + +} // namespace + +} // namespace google::api::expr::runtime::internal diff --git a/eval/public/structs/cel_proto_wrapper.cc b/eval/public/structs/cel_proto_wrapper.cc index 6fad6aee3..8b85aafc2 100644 --- a/eval/public/structs/cel_proto_wrapper.cc +++ b/eval/public/structs/cel_proto_wrapper.cc @@ -14,11 +14,21 @@ #include "eval/public/structs/cel_proto_wrapper.h" -#include "absl/types/optional.h" +#include + +#include "absl/base/no_destructor.h" +#include "absl/base/nullability.h" +#include "absl/log/absl_check.h" +#include "absl/log/absl_log.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "common/legacy_value.h" +#include "common/value.h" #include "eval/public/cel_value.h" #include "eval/public/message_wrapper.h" -#include "eval/public/structs/cel_proto_wrap_util.h" +#include "eval/public/structs/cel_proto_wrap_value_to_message.h" #include "eval/public/structs/proto_message_type_adapter.h" +#include "eval/public/structs/trivial_legacy_type_info_internal.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" #include "google/protobuf/message.h" @@ -27,9 +37,36 @@ namespace google::api::expr::runtime { namespace { +using ::cel::interop_internal::TrivialTypeInfo; using ::google::protobuf::Arena; using ::google::protobuf::Descriptor; +using ::google::protobuf::DescriptorPool; using ::google::protobuf::Message; +using ::google::protobuf::MessageFactory; + +// Returns the arena for the given message, or the fallback arena if the +// message does not have an arena. +// +// A global fallback arena is used to avoid allocation when the arena is not +// specified. This is effectively a memory leak, but won't trigger leak +// check analyzers. +// +// This emulates the old behavior of tolerating a nullptr arena without +// triggering a crash. +google::protobuf::Arena* GetArena(const Message* absl_nonnull message, + google::protobuf::Arena* absl_nullable arena) { + if (arena != nullptr) { + return arena; + } + if (message->GetArena() != nullptr) { + return message->GetArena(); + } + static absl::NoDestructor fallback_arena; + ABSL_LOG(WARNING) + << "CelValue: using fallback global arena for wrapping message: " + << message->GetTypeName(); + return fallback_arena.get(); +} } // namespace @@ -38,14 +75,50 @@ CelValue CelProtoWrapper::InternalWrapMessage(const Message* message) { MessageWrapper(message, &GetGenericProtoTypeInfoInstance())); } -// CreateMessage creates CelValue from google::protobuf::Message. -// As some of CEL basic types are subclassing google::protobuf::Message, -// this method contains type checking and downcasts. -CelValue CelProtoWrapper::CreateMessage(const Message* value, Arena* arena) { - return internal::UnwrapMessageToValue(value, &InternalWrapMessage, arena); +CelValue CelProtoWrapper::CreateMessage( + const Message* absl_nonnull value, + const google::protobuf::DescriptorPool* absl_nonnull pool, + MessageFactory* absl_nonnull factory, Arena* absl_nonnull arena) { + ABSL_DCHECK(value != nullptr); + if (value->GetDescriptor() == nullptr || value->GetReflection() == nullptr) { + // This only happens for custom google::protobuf::Message subclasses that CEL can't + // support. + return CelValue::CreateMessageWrapper( + MessageWrapper(value, TrivialTypeInfo::GetInstance())); + } + + auto modern_value = + cel::Value::WrapMessageUnsafe(value, pool, factory, arena); + + absl::StatusOr cel_value = cel::LegacyValue(arena, modern_value); + if (!cel_value.ok()) { + // This only happens for custom google::protobuf::Message subclasses that CEL can't + // support. + auto* status = + google::protobuf::Arena::Create(arena, cel_value.status()); + return CelValue::CreateError(status); + } + return *cel_value; +} + +CelValue CelProtoWrapper::CreateMessage(const Message* absl_nullable value, + Arena* absl_nullable arena) { + if (value == nullptr) { + return CelValue::CreateNull(); + } + + if (value->GetDescriptor() == nullptr || value->GetReflection() == nullptr) { + // This only happens for custom messages subclasses that CEL can't support. + return CelValue::CreateMessageWrapper( + MessageWrapper(value, TrivialTypeInfo::GetInstance())); + } + const auto* pool = value->GetDescriptor()->file()->pool(); + auto* factory = value->GetReflection()->GetMessageFactory(); + arena = GetArena(value, arena); + return CreateMessage(value, pool, factory, arena); } -absl::optional CelProtoWrapper::MaybeWrapValue( +std::optional CelProtoWrapper::MaybeWrapValue( const Descriptor* descriptor, google::protobuf::MessageFactory* factory, const CelValue& value, Arena* arena) { const Message* msg = diff --git a/eval/public/structs/cel_proto_wrapper.h b/eval/public/structs/cel_proto_wrapper.h index 73942c253..8d334961c 100644 --- a/eval/public/structs/cel_proto_wrapper.h +++ b/eval/public/structs/cel_proto_wrapper.h @@ -1,9 +1,11 @@ #ifndef THIRD_PARTY_CEL_CPP_EVAL_PUBLIC_STRUCTS_CEL_PROTO_WRAPPER_H_ #define THIRD_PARTY_CEL_CPP_EVAL_PUBLIC_STRUCTS_CEL_PROTO_WRAPPER_H_ +#include + #include "google/protobuf/duration.pb.h" #include "google/protobuf/timestamp.pb.h" -#include "absl/types/optional.h" +#include "absl/base/nullability.h" #include "eval/public/cel_value.h" #include "internal/proto_time_encoding.h" #include "google/protobuf/arena.h" @@ -17,8 +19,19 @@ class CelProtoWrapper { // CreateMessage creates CelValue from google::protobuf::Message. // As some of CEL basic types are subclassing google::protobuf::Message, // this method contains type checking and downcasts. - static CelValue CreateMessage(const google::protobuf::Message* value, - google::protobuf::Arena* arena); + static CelValue CreateMessage(const google::protobuf::Message* absl_nonnull value, + const google::protobuf::DescriptorPool* absl_nonnull pool, + google::protobuf::MessageFactory* absl_nonnull factory, + google::protobuf::Arena* absl_nonnull arena); + + // Prefer using the overload that takes an explicit descriptor pool and + // message factory instead. This overload will use the ones associated with + // the value. + // + // For backward compatibility, nullptr message is allowed and will result in + // the CEL null_type value. + static CelValue CreateMessage(const google::protobuf::Message* absl_nullable value, + google::protobuf::Arena* absl_nullable arena); // Internal utility for creating a CelValue wrapping a user defined type. // Assumes that the message has been properly unpacked. @@ -43,7 +56,7 @@ class CelProtoWrapper { // message to native CelValue representation during a protobuf field read. // Just as CreateMessage should only be used when reading protobuf values, // MaybeWrapValue should only be used when assigning protobuf fields. - static absl::optional MaybeWrapValue( + static std::optional MaybeWrapValue( const google::protobuf::Descriptor* descriptor, google::protobuf::MessageFactory* factory, const CelValue& value, google::protobuf::Arena* arena); }; diff --git a/eval/public/structs/cel_proto_wrapper_test.cc b/eval/public/structs/cel_proto_wrapper_test.cc index 408e33284..597943f7a 100644 --- a/eval/public/structs/cel_proto_wrapper_test.cc +++ b/eval/public/structs/cel_proto_wrapper_test.cc @@ -104,7 +104,7 @@ class CelProtoWrapperTest : public ::testing::Test { T dyn_value; CelValue cel_dyn_value = - CelProtoWrapper::CreateMessage(ReflectedCopy(message).get(), arena()); + CelProtoWrapper::CreateMessage(ReflectedCopy(message), arena()); EXPECT_THAT(cel_dyn_value.type(), Eq(cel_value.type())); EXPECT_TRUE(cel_dyn_value.GetValue(&dyn_value)); EXPECT_THAT(value, Eq(dyn_value)); @@ -121,10 +121,9 @@ class CelProtoWrapperTest : public ::testing::Test { EXPECT_THAT(cel_value.MessageOrDie(), testutil::EqualsProto(*result)); } - std::unique_ptr ReflectedCopy( - const google::protobuf::Message& message) { - std::unique_ptr dynamic_value( - factory_.GetPrototype(message.GetDescriptor())->New()); + google::protobuf::Message* ReflectedCopy(const google::protobuf::Message& message) { + google::protobuf::Message* dynamic_value = + factory_.GetPrototype(message.GetDescriptor())->New(&arena_); dynamic_value->CopyFrom(message); return dynamic_value; } @@ -213,7 +212,7 @@ TEST_F(CelProtoWrapperTest, UnwrapDynamicValueNull) { value_msg.set_null_value(protobuf::NULL_VALUE); CelValue value = - CelProtoWrapper::CreateMessage(ReflectedCopy(value_msg).get(), arena()); + CelProtoWrapper::CreateMessage(ReflectedCopy(value_msg), arena()); EXPECT_TRUE(value.IsNull()); } @@ -314,8 +313,8 @@ TEST_F(CelProtoWrapperTest, UnwrapDynamicStruct) { const std::string kFieldBool = "field_bool"; (*struct_msg.mutable_fields())[kFieldInt].set_number_value(1.); (*struct_msg.mutable_fields())[kFieldBool].set_bool_value(true); - CelValue value = - CelProtoWrapper::CreateMessage(ReflectedCopy(struct_msg).get(), arena()); + auto reflected_copy = ReflectedCopy(struct_msg); + CelValue value = CelProtoWrapper::CreateMessage(reflected_copy, arena()); EXPECT_TRUE(value.IsMap()); const CelMap* cel_map = value.MapOrDie(); ASSERT_TRUE(cel_map != nullptr); @@ -355,7 +354,7 @@ TEST_F(CelProtoWrapperTest, UnwrapDynamicValueStruct) { .set_number_value(2); CelValue value = - CelProtoWrapper::CreateMessage(ReflectedCopy(value_msg).get(), arena()); + CelProtoWrapper::CreateMessage(ReflectedCopy(value_msg), arena()); EXPECT_TRUE(value.IsMap()); EXPECT_TRUE( (*value.MapOrDie())[CelValue::CreateString(&kField1)].has_value()); @@ -398,7 +397,7 @@ TEST_F(CelProtoWrapperTest, UnwrapDynamicValueListValue) { value_msg.mutable_list_value()->add_values()->set_number_value(2.); CelValue value = - CelProtoWrapper::CreateMessage(ReflectedCopy(value_msg).get(), arena()); + CelProtoWrapper::CreateMessage(ReflectedCopy(value_msg), arena()); EXPECT_TRUE(value.IsList()); EXPECT_THAT((*value.ListOrDie())[0].DoubleOrDie(), testing::DoubleEq(1)); EXPECT_THAT((*value.ListOrDie())[1].DoubleOrDie(), testing::DoubleEq(2)); @@ -426,6 +425,47 @@ TEST_F(CelProtoWrapperTest, UnwrapInvalidAny) { ASSERT_TRUE(CelProtoWrapper::CreateMessage(&any, arena()).IsError()); } +TEST_F(CelProtoWrapperTest, CreateMessageExplicitPoolAndFactory) { + TestMessage test_message; + test_message.set_string_value("test"); + + CelValue value = CelProtoWrapper::CreateMessage( + &test_message, google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), arena()); + ASSERT_TRUE(value.IsMessage()); + EXPECT_THAT(value.MessageOrDie(), testutil::EqualsProto(test_message)); +} + +TEST_F(CelProtoWrapperTest, CreateMessageExplicitPoolAndFactoryUnpackAny) { + TestMessage test_message; + test_message.set_string_value("test"); + + Any any; + any.PackFrom(test_message); + + google::protobuf::DynamicMessageFactory factory( + google::protobuf::DescriptorPool::generated_pool()); + CelValue value = CelProtoWrapper::CreateMessage( + &any, google::protobuf::DescriptorPool::generated_pool(), &factory, arena()); + ASSERT_TRUE(value.IsMessage()); + EXPECT_THAT(value.MessageOrDie(), testutil::EqualsProto(test_message)); +} + +TEST_F(CelProtoWrapperTest, + CreateMessageExplicitPoolAndFactoryUnpackAnyNotFound) { + TestMessage test_message; + test_message.set_string_value("test"); + + Any any; + any.PackFrom(test_message); + + google::protobuf::DescriptorPool empty_pool; + google::protobuf::DynamicMessageFactory factory(&empty_pool); + CelValue value = + CelProtoWrapper::CreateMessage(&any, &empty_pool, &factory, arena()); + EXPECT_TRUE(value.IsError()); +} + // Test support of google.protobuf.Value wrappers in CelValue. TEST_F(CelProtoWrapperTest, UnwrapBoolWrapper) { bool value = true; diff --git a/eval/public/structs/field_access_impl.cc b/eval/public/structs/field_access_impl.cc index 2bd9fff9d..652bcf778 100644 --- a/eval/public/structs/field_access_impl.cc +++ b/eval/public/structs/field_access_impl.cc @@ -29,6 +29,7 @@ #include "absl/strings/string_view.h" #include "absl/strings/substitute.h" #include "eval/public/structs/cel_proto_wrap_util.h" +#include "eval/public/structs/cel_proto_wrap_value_to_message.h" #include "internal/casts.h" #include "internal/overflow.h" #include "google/protobuf/arena.h"