diff --git a/conformance/policy/BUILD b/conformance/policy/BUILD index e5d503d88..42adbcf90 100644 --- a/conformance/policy/BUILD +++ b/conformance/policy/BUILD @@ -36,7 +36,6 @@ cc_library( "//env:env_std_extensions", "//env:env_yaml", "//env:runtime_std_extensions", - "//extensions/protobuf:bind_proto_to_activation", "//extensions/protobuf:enum_adapter", "//internal:runfiles", "//internal:status_macros", @@ -50,6 +49,7 @@ cc_library( "//policy:yaml_policy_parser", "//runtime", "//runtime:activation", + "//runtime:bind_proto_to_activation", "//runtime:function_adapter", "@com_google_absl//absl/flags:flag", "@com_google_absl//absl/log:absl_check", diff --git a/conformance/policy/policy_conformance_test.cc b/conformance/policy/policy_conformance_test.cc index ef53ab34e..53fbcb419 100644 --- a/conformance/policy/policy_conformance_test.cc +++ b/conformance/policy/policy_conformance_test.cc @@ -40,7 +40,6 @@ #include "env/env_std_extensions.h" #include "env/env_yaml.h" #include "env/runtime_std_extensions.h" -#include "extensions/protobuf/bind_proto_to_activation.h" #include "extensions/protobuf/enum_adapter.h" #include "internal/runfiles.h" #include "internal/status_macros.h" @@ -53,6 +52,7 @@ #include "policy/test_util.h" #include "policy/yaml_policy_parser.h" #include "runtime/activation.h" +#include "runtime/bind_proto_to_activation.h" #include "runtime/function_adapter.h" #include "runtime/runtime.h" #include "cel/expr/conformance/test/suite.pb.h" @@ -384,9 +384,8 @@ absl::Status PopulateActivation( "Failed to resolve context message for test case"); } - return cel::extensions::BindProtoToActivation( - *context_message, - cel::extensions::BindProtoUnsetFieldBehavior::kBindDefaultValue, + return cel::BindProtoToActivation( + *context_message, cel::BindProtoUnsetFieldBehavior::kBindDefaultValue, descriptor_pool, message_factory, arena, &activation); } diff --git a/extensions/protobuf/BUILD b/extensions/protobuf/BUILD index 3f4081b09..d814057e7 100644 --- a/extensions/protobuf/BUILD +++ b/extensions/protobuf/BUILD @@ -165,11 +165,12 @@ cc_library( srcs = ["bind_proto_to_activation.cc"], hdrs = ["bind_proto_to_activation.h"], deps = [ - ":value", "//common:casting", "//common:value", "//internal:status_macros", "//runtime:activation", + "//runtime:bind_proto_to_activation", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/base:nullability", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", diff --git a/extensions/protobuf/bind_proto_to_activation.cc b/extensions/protobuf/bind_proto_to_activation.cc index aa151cb85..bdebd08eb 100644 --- a/extensions/protobuf/bind_proto_to_activation.cc +++ b/extensions/protobuf/bind_proto_to_activation.cc @@ -13,80 +13,3 @@ // limitations under the License. #include "extensions/protobuf/bind_proto_to_activation.h" - -#include - -#include "absl/base/nullability.h" -#include "absl/status/status.h" -#include "absl/status/statusor.h" -#include "common/value.h" -#include "internal/status_macros.h" -#include "runtime/activation.h" -#include "google/protobuf/arena.h" -#include "google/protobuf/descriptor.h" -#include "google/protobuf/message.h" - -namespace cel::extensions::protobuf_internal { - -namespace { - -using ::google::protobuf::Descriptor; - -absl::StatusOr ShouldBindField( - const google::protobuf::FieldDescriptor* field_desc, const StructValue& struct_value, - BindProtoUnsetFieldBehavior unset_field_behavior) { - if (unset_field_behavior == BindProtoUnsetFieldBehavior::kBindDefaultValue || - field_desc->is_repeated()) { - return true; - } - return struct_value.HasFieldByNumber(field_desc->number()); -} - -absl::StatusOr GetFieldValue( - const google::protobuf::FieldDescriptor* field_desc, const StructValue& struct_value, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena) { - // Special case unset any. - if (field_desc->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE && - field_desc->message_type()->well_known_type() == - Descriptor::WELLKNOWNTYPE_ANY) { - CEL_ASSIGN_OR_RETURN(bool present, - struct_value.HasFieldByNumber(field_desc->number())); - if (!present) { - return NullValue(); - } - } - - return struct_value.GetFieldByNumber(field_desc->number(), descriptor_pool, - message_factory, arena); -} - -} // namespace - -absl::Status BindProtoToActivation( - const Descriptor& descriptor, const StructValue& struct_value, - BindProtoUnsetFieldBehavior unset_field_behavior, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena, Activation* absl_nonnull activation) { - for (int i = 0; i < descriptor.field_count(); i++) { - const google::protobuf::FieldDescriptor* field_desc = descriptor.field(i); - CEL_ASSIGN_OR_RETURN( - bool should_bind, - ShouldBindField(field_desc, struct_value, unset_field_behavior)); - if (!should_bind) { - continue; - } - - CEL_ASSIGN_OR_RETURN( - Value field, GetFieldValue(field_desc, struct_value, descriptor_pool, - message_factory, arena)); - - activation->InsertOrAssignValue(field_desc->name(), std::move(field)); - } - - return absl::OkStatus(); -} - -} // namespace cel::extensions::protobuf_internal diff --git a/extensions/protobuf/bind_proto_to_activation.h b/extensions/protobuf/bind_proto_to_activation.h index 61f43c13d..97dfc6c0c 100644 --- a/extensions/protobuf/bind_proto_to_activation.h +++ b/extensions/protobuf/bind_proto_to_activation.h @@ -15,116 +15,23 @@ #ifndef THIRD_PARTY_CEL_CPP_EXTENSIONS_PROTOBUF_BIND_PROTO_TO_ACTIVATION_H_ #define THIRD_PARTY_CEL_CPP_EXTENSIONS_PROTOBUF_BIND_PROTO_TO_ACTIVATION_H_ -#include - -#include "absl/base/nullability.h" -#include "absl/status/status.h" -#include "common/casting.h" -#include "common/value.h" -#include "extensions/protobuf/value.h" -#include "internal/status_macros.h" -#include "runtime/activation.h" -#include "google/protobuf/arena.h" -#include "google/protobuf/descriptor.h" -#include "google/protobuf/message.h" +#include "absl/base/attributes.h" +#include "runtime/bind_proto_to_activation.h" namespace cel::extensions { -// Option for handling unset fields on the context proto. -enum class BindProtoUnsetFieldBehavior { - // Bind the message defined default or zero value. - kBindDefaultValue, - // Skip binding unset fields, no value is bound for the corresponding - // variable. - kSkip -}; +using BindProtoUnsetFieldBehavior ABSL_DEPRECATED( + "Use cel::BindProtoUnsetFieldBehavior instead") = + ::cel::BindProtoUnsetFieldBehavior; + +using ::cel::BindProtoToActivation; namespace protobuf_internal { -// Implements binding provided the context message has already -// been adapted to a suitable struct value. -absl::Status BindProtoToActivation( - const google::protobuf::Descriptor& descriptor, const StructValue& struct_value, - BindProtoUnsetFieldBehavior unset_field_behavior, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena, Activation* absl_nonnull activation); +using ::cel::runtime_internal::BindProtoToActivation; } // namespace protobuf_internal -// Utility method, that takes a protobuf Message and interprets it as a -// namespace, binding its fields to Activation. This is often referred to as a -// context message. -// -// Field names and values become respective names and values of parameters -// bound to the Activation object. -// Example: -// Assume we have a protobuf message of type: -// message Person { -// int age = 1; -// string name = 2; -// } -// -// The sample code snippet will look as follows: -// -// Person person; -// person.set_name("John Doe"); -// person.age(42); -// -// CEL_RETURN_IF_ERROR(BindProtoToActivation(person, value_factory, -// activation)); -// -// After this snippet, activation will have two parameters bound: -// "name", with string value of "John Doe" -// "age", with int value of 42. -// -// The default behavior for unset fields is to skip them. E.g. if the name field -// is not set on the Person message, it will not be bound in to the activation. -// BindProtoUnsetFieldBehavior::kBindDefault, will bind the cc proto api default -// for the field (either an explicit default value or a type specific default). -// -// For repeated fields, an unset field is bound as an empty list. -template -absl::Status BindProtoToActivation( - const T& context, BindProtoUnsetFieldBehavior unset_field_behavior, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena, Activation* absl_nonnull activation) { - static_assert(std::is_base_of_v); - // TODO(uncreated-issue/68): for simplicity, just convert the whole message to a - // struct value. For performance, may be better to convert members as needed. - CEL_ASSIGN_OR_RETURN( - Value parent, - ProtoMessageToValue(context, descriptor_pool, message_factory, arena)); - - if (!InstanceOf(parent)) { - return absl::InvalidArgumentError( - absl::StrCat("context is a well-known type: ", context.GetTypeName())); - } - const StructValue& struct_value = Cast(parent); - - const google::protobuf::Descriptor* descriptor = context.GetDescriptor(); - - if (descriptor == nullptr) { - return absl::InvalidArgumentError( - absl::StrCat("context missing descriptor: ", context.GetTypeName())); - } - - return protobuf_internal::BindProtoToActivation( - *descriptor, struct_value, unset_field_behavior, descriptor_pool, - message_factory, arena, activation); -} -template -absl::Status BindProtoToActivation( - const T& context, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena, Activation* absl_nonnull activation) { - return BindProtoToActivation(context, BindProtoUnsetFieldBehavior::kSkip, - descriptor_pool, message_factory, arena, - activation); -} - } // namespace cel::extensions #endif // THIRD_PARTY_CEL_CPP_EXTENSIONS_PROTOBUF_BIND_PROTO_TO_ACTIVATION_H_ diff --git a/runtime/BUILD b/runtime/BUILD index 34ff411a1..da2192439 100644 --- a/runtime/BUILD +++ b/runtime/BUILD @@ -110,6 +110,42 @@ cc_test( ], ) +cc_library( + name = "bind_proto_to_activation", + srcs = ["bind_proto_to_activation.cc"], + hdrs = ["bind_proto_to_activation.h"], + deps = [ + ":activation", + "//common:casting", + "//common:value", + "//internal:status_macros", + "@com_google_absl//absl/base:nullability", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_protobuf//:protobuf", + ], +) + +cc_test( + name = "bind_proto_to_activation_test", + srcs = ["bind_proto_to_activation_test.cc"], + deps = [ + ":activation", + ":bind_proto_to_activation", + "//common:casting", + "//common:value", + "//common:value_testing", + "//internal:testing", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:status_matchers", + "@com_google_absl//absl/types:optional", + "@com_google_cel_spec//proto/cel/expr/conformance/proto2:test_all_types_cc_proto", + "@com_google_protobuf//:protobuf", + "@com_google_protobuf//:wrappers_cc_proto", + ], +) + cc_library( name = "register_function_helper", hdrs = ["register_function_helper.h"], diff --git a/runtime/bind_proto_to_activation.cc b/runtime/bind_proto_to_activation.cc new file mode 100644 index 000000000..011feea7c --- /dev/null +++ b/runtime/bind_proto_to_activation.cc @@ -0,0 +1,92 @@ +// Copyright 2023 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 "runtime/bind_proto_to_activation.h" + +#include + +#include "absl/base/nullability.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "common/value.h" +#include "internal/status_macros.h" +#include "runtime/activation.h" +#include "google/protobuf/arena.h" +#include "google/protobuf/descriptor.h" +#include "google/protobuf/message.h" + +namespace cel::runtime_internal { + +namespace { + +using ::google::protobuf::Descriptor; + +absl::StatusOr ShouldBindField( + const google::protobuf::FieldDescriptor* field_desc, const StructValue& struct_value, + BindProtoUnsetFieldBehavior unset_field_behavior) { + if (unset_field_behavior == BindProtoUnsetFieldBehavior::kBindDefaultValue || + field_desc->is_repeated()) { + return true; + } + return struct_value.HasFieldByNumber(field_desc->number()); +} + +absl::StatusOr GetFieldValue( + const google::protobuf::FieldDescriptor* field_desc, const StructValue& struct_value, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) { + // Special case unset any. + if (field_desc->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE && + field_desc->message_type()->well_known_type() == + Descriptor::WELLKNOWNTYPE_ANY) { + CEL_ASSIGN_OR_RETURN(bool present, + struct_value.HasFieldByNumber(field_desc->number())); + if (!present) { + return NullValue(); + } + } + + return struct_value.GetFieldByNumber(field_desc->number(), descriptor_pool, + message_factory, arena); +} + +} // namespace + +absl::Status BindProtoToActivation( + const Descriptor& descriptor, const StructValue& struct_value, + BindProtoUnsetFieldBehavior unset_field_behavior, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Activation* absl_nonnull activation) { + for (int i = 0; i < descriptor.field_count(); i++) { + const google::protobuf::FieldDescriptor* field_desc = descriptor.field(i); + CEL_ASSIGN_OR_RETURN( + bool should_bind, + ShouldBindField(field_desc, struct_value, unset_field_behavior)); + if (!should_bind) { + continue; + } + + CEL_ASSIGN_OR_RETURN( + Value field, GetFieldValue(field_desc, struct_value, descriptor_pool, + message_factory, arena)); + + activation->InsertOrAssignValue(field_desc->name(), std::move(field)); + } + + return absl::OkStatus(); +} + +} // namespace cel::runtime_internal diff --git a/runtime/bind_proto_to_activation.h b/runtime/bind_proto_to_activation.h new file mode 100644 index 000000000..9bc866937 --- /dev/null +++ b/runtime/bind_proto_to_activation.h @@ -0,0 +1,127 @@ +// Copyright 2023 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_RUNTIME_BIND_PROTO_TO_ACTIVATION_H_ +#define THIRD_PARTY_CEL_CPP_RUNTIME_BIND_PROTO_TO_ACTIVATION_H_ + +#include + +#include "absl/base/nullability.h" +#include "absl/status/status.h" +#include "absl/strings/str_cat.h" +#include "common/casting.h" +#include "common/value.h" +#include "runtime/activation.h" +#include "google/protobuf/arena.h" +#include "google/protobuf/descriptor.h" +#include "google/protobuf/message.h" + +namespace cel { + +// Option for handling unset fields on the context proto. +enum class BindProtoUnsetFieldBehavior { + // Bind the message defined default or zero value. + kBindDefaultValue, + // Skip binding unset fields, no value is bound for the corresponding + // variable. + kSkip +}; + +namespace runtime_internal { + +// Implements binding provided the context message has already +// been adapted to a suitable struct value. +absl::Status BindProtoToActivation( + const google::protobuf::Descriptor& descriptor, const StructValue& struct_value, + BindProtoUnsetFieldBehavior unset_field_behavior, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Activation* absl_nonnull activation); + +} // namespace runtime_internal + +// Utility method, that takes a protobuf Message and interprets it as a +// namespace, binding its fields to Activation. This is often referred to as a +// context message. +// +// Field names and values become respective names and values of parameters +// bound to the Activation object. +// Example: +// Assume we have a protobuf message of type: +// message Person { +// int age = 1; +// string name = 2; +// } +// +// The sample code snippet will look as follows: +// +// Person person; +// person.set_name("John Doe"); +// person.age(42); +// +// CEL_RETURN_IF_ERROR(BindProtoToActivation(person, value_factory, +// activation)); +// +// After this snippet, activation will have two parameters bound: +// "name", with string value of "John Doe" +// "age", with int value of 42. +// +// The default behavior for unset fields is to skip them. E.g. if the name field +// is not set on the Person message, it will not be bound in to the activation. +// BindProtoUnsetFieldBehavior::kBindDefault, will bind the cc proto api default +// for the field (either an explicit default value or a type specific default). +// +// For repeated fields, an unset field is bound as an empty list. +template +absl::Status BindProtoToActivation( + const T& context, BindProtoUnsetFieldBehavior unset_field_behavior, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Activation* absl_nonnull activation) { + static_assert(std::is_base_of_v); + Value parent = + Value::FromMessage(context, descriptor_pool, message_factory, arena); + + if (!InstanceOf(parent)) { + return absl::InvalidArgumentError( + absl::StrCat("context is a well-known type: ", context.GetTypeName())); + } + const StructValue& struct_value = Cast(parent); + + const google::protobuf::Descriptor* descriptor = context.GetDescriptor(); + + if (descriptor == nullptr) { + return absl::InvalidArgumentError( + absl::StrCat("context missing descriptor: ", context.GetTypeName())); + } + + return runtime_internal::BindProtoToActivation( + *descriptor, struct_value, unset_field_behavior, descriptor_pool, + message_factory, arena, activation); +} + +template +absl::Status BindProtoToActivation( + const T& context, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Activation* absl_nonnull activation) { + return BindProtoToActivation(context, BindProtoUnsetFieldBehavior::kSkip, + descriptor_pool, message_factory, arena, + activation); +} + +} // namespace cel + +#endif // THIRD_PARTY_CEL_CPP_RUNTIME_BIND_PROTO_TO_ACTIVATION_H_ diff --git a/runtime/bind_proto_to_activation_test.cc b/runtime/bind_proto_to_activation_test.cc new file mode 100644 index 000000000..2242f47c0 --- /dev/null +++ b/runtime/bind_proto_to_activation_test.cc @@ -0,0 +1,244 @@ +// Copyright 2023 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 "runtime/bind_proto_to_activation.h" + +#include "google/protobuf/wrappers.pb.h" +#include "absl/status/status.h" +#include "absl/status/status_matchers.h" +#include "absl/types/optional.h" +#include "common/casting.h" +#include "common/value.h" +#include "common/value_testing.h" +#include "internal/testing.h" +#include "runtime/activation.h" +#include "cel/expr/conformance/proto2/test_all_types.pb.h" +#include "google/protobuf/arena.h" + +namespace cel { +namespace { + +using ::absl_testing::IsOk; +using ::absl_testing::IsOkAndHolds; +using ::absl_testing::StatusIs; +using ::cel::expr::conformance::proto2::TestAllTypes; +using ::cel::test::IntValueIs; +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::Optional; + +using BindProtoToActivationTest = common_internal::ValueTest<>; + +TEST_F(BindProtoToActivationTest, BindProtoToActivation) { + TestAllTypes test_all_types; + test_all_types.set_single_int64(123); + Activation activation; + + ASSERT_THAT(BindProtoToActivation(test_all_types, descriptor_pool(), + message_factory(), arena(), &activation), + IsOk()); + + EXPECT_THAT(activation.FindVariable("single_int64", descriptor_pool(), + message_factory(), arena()), + IsOkAndHolds(Optional(IntValueIs(123)))); +} + +TEST_F(BindProtoToActivationTest, BindProtoToActivationWktUnsupported) { + google::protobuf::Int64Value int64_value; + int64_value.set_value(123); + Activation activation; + + EXPECT_THAT(BindProtoToActivation(int64_value, descriptor_pool(), + message_factory(), arena(), &activation), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("google.protobuf.Int64Value"))); +} + +TEST_F(BindProtoToActivationTest, BindProtoToActivationSkip) { + TestAllTypes test_all_types; + test_all_types.set_single_int64(123); + Activation activation; + + ASSERT_THAT(BindProtoToActivation(test_all_types, descriptor_pool(), + message_factory(), arena(), &activation), + IsOk()); + + EXPECT_THAT(activation.FindVariable("single_int32", descriptor_pool(), + message_factory(), arena()), + IsOkAndHolds(Eq(std::nullopt))); + EXPECT_THAT(activation.FindVariable("single_sint32", descriptor_pool(), + message_factory(), arena()), + IsOkAndHolds(Eq(std::nullopt))); +} + +TEST_F(BindProtoToActivationTest, BindProtoToActivationDefault) { + TestAllTypes test_all_types; + test_all_types.set_single_int64(123); + Activation activation; + + ASSERT_THAT( + BindProtoToActivation( + test_all_types, BindProtoUnsetFieldBehavior::kBindDefaultValue, + descriptor_pool(), message_factory(), arena(), &activation), + IsOk()); + + // from test_all_types.proto + // optional int32 single_int32 = 1 [default = -32]; + EXPECT_THAT(activation.FindVariable("single_int32", descriptor_pool(), + message_factory(), arena()), + IsOkAndHolds(Optional(IntValueIs(-32)))); + EXPECT_THAT(activation.FindVariable("single_sint32", descriptor_pool(), + message_factory(), arena()), + IsOkAndHolds(Optional(IntValueIs(0)))); +} + +TEST_F(BindProtoToActivationTest, BindProtoToActivationDefaultAny) { + TestAllTypes test_all_types; + test_all_types.set_single_int64(123); + Activation activation; + + ASSERT_THAT( + BindProtoToActivation( + test_all_types, BindProtoUnsetFieldBehavior::kBindDefaultValue, + descriptor_pool(), message_factory(), arena(), &activation), + IsOk()); + + EXPECT_THAT(activation.FindVariable("single_any", descriptor_pool(), + message_factory(), arena()), + IsOkAndHolds(Optional(test::IsNullValue()))); +} + +MATCHER_P(IsListValueOfSize, size, "") { + const Value& v = arg; + + auto value = As(v); + if (!value) { + return false; + } + auto s = value->Size(); + return s.ok() && *s == size; +} + +TEST_F(BindProtoToActivationTest, BindProtoToActivationRepeated) { + TestAllTypes test_all_types; + test_all_types.add_repeated_int64(123); + test_all_types.add_repeated_int64(456); + test_all_types.add_repeated_int64(789); + + Activation activation; + + ASSERT_THAT(BindProtoToActivation(test_all_types, descriptor_pool(), + message_factory(), arena(), &activation), + IsOk()); + + EXPECT_THAT(activation.FindVariable("repeated_int64", descriptor_pool(), + message_factory(), arena()), + IsOkAndHolds(Optional(IsListValueOfSize(3)))); +} + +TEST_F(BindProtoToActivationTest, BindProtoToActivationRepeatedEmpty) { + TestAllTypes test_all_types; + test_all_types.set_single_int64(123); + Activation activation; + + ASSERT_THAT(BindProtoToActivation(test_all_types, descriptor_pool(), + message_factory(), arena(), &activation), + IsOk()); + + EXPECT_THAT(activation.FindVariable("repeated_int32", descriptor_pool(), + message_factory(), arena()), + IsOkAndHolds(Optional(IsListValueOfSize(0)))); +} + +TEST_F(BindProtoToActivationTest, BindProtoToActivationRepeatedComplex) { + TestAllTypes test_all_types; + auto* nested = test_all_types.add_repeated_nested_message(); + nested->set_bb(123); + nested = test_all_types.add_repeated_nested_message(); + nested->set_bb(456); + nested = test_all_types.add_repeated_nested_message(); + nested->set_bb(789); + Activation activation; + + ASSERT_THAT(BindProtoToActivation(test_all_types, descriptor_pool(), + message_factory(), arena(), &activation), + IsOk()); + + EXPECT_THAT( + activation.FindVariable("repeated_nested_message", descriptor_pool(), + message_factory(), arena()), + IsOkAndHolds(Optional(IsListValueOfSize(3)))); +} + +MATCHER_P(IsMapValueOfSize, size, "") { + const Value& v = arg; + + auto value = As(v); + if (!value) { + return false; + } + auto s = value->Size(); + return s.ok() && *s == size; +} + +TEST_F(BindProtoToActivationTest, BindProtoToActivationMap) { + TestAllTypes test_all_types; + (*test_all_types.mutable_map_int64_int64())[1] = 2; + (*test_all_types.mutable_map_int64_int64())[2] = 4; + + Activation activation; + + ASSERT_THAT(BindProtoToActivation(test_all_types, descriptor_pool(), + message_factory(), arena(), &activation), + IsOk()); + + EXPECT_THAT(activation.FindVariable("map_int64_int64", descriptor_pool(), + message_factory(), arena()), + IsOkAndHolds(Optional(IsMapValueOfSize(2)))); +} + +TEST_F(BindProtoToActivationTest, BindProtoToActivationMapEmpty) { + TestAllTypes test_all_types; + test_all_types.set_single_int64(123); + Activation activation; + + ASSERT_THAT(BindProtoToActivation(test_all_types, descriptor_pool(), + message_factory(), arena(), &activation), + IsOk()); + + EXPECT_THAT(activation.FindVariable("map_int32_int32", descriptor_pool(), + message_factory(), arena()), + IsOkAndHolds(Optional(IsMapValueOfSize(0)))); +} + +TEST_F(BindProtoToActivationTest, BindProtoToActivationMapComplex) { + TestAllTypes test_all_types; + TestAllTypes::NestedMessage value; + value.set_bb(42); + (*test_all_types.mutable_map_int64_message())[1] = value; + (*test_all_types.mutable_map_int64_message())[2] = value; + + Activation activation; + + ASSERT_THAT(BindProtoToActivation(test_all_types, descriptor_pool(), + message_factory(), arena(), &activation), + IsOk()); + + EXPECT_THAT(activation.FindVariable("map_int64_message", descriptor_pool(), + message_factory(), arena()), + IsOkAndHolds(Optional(IsMapValueOfSize(2)))); +} + +} // namespace +} // namespace cel