Skip to content

Commit 4571e47

Browse files
maskri17copybara-github
authored andcommitted
Updated MessageToJson conversion for FieldMask in CEL C++
PiperOrigin-RevId: 954939460
1 parent 725b335 commit 4571e47

8 files changed

Lines changed: 155 additions & 32 deletions

File tree

common/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,7 @@ cc_test(
886886
"@com_google_absl//absl/time",
887887
"@com_google_absl//absl/types:optional",
888888
"@com_google_cel_spec//proto/cel/expr/conformance/proto3:test_all_types_cc_proto",
889+
"@com_google_protobuf//:field_mask_cc_proto",
889890
"@com_google_protobuf//:protobuf",
890891
"@com_google_protobuf//:struct_cc_proto",
891892
"@com_google_protobuf//:type_cc_proto",

common/values/parsed_message_value.cc

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,11 @@
3434
#include "base/attribute.h"
3535
#include "common/memory.h"
3636
#include "common/value.h"
37+
#include "common/values/values.h"
3738
#include "extensions/protobuf/internal/qualify.h"
38-
#include "internal/empty_descriptors.h"
3939
#include "internal/json.h"
4040
#include "internal/message_equality.h"
4141
#include "internal/status_macros.h"
42-
#include "internal/well_known_types.h"
4342
#include "runtime/runtime_options.h"
4443
#include "google/protobuf/arena.h"
4544
#include "google/protobuf/descriptor.h"
@@ -51,23 +50,13 @@ namespace cel {
5150

5251
namespace {
5352

54-
using ::cel::well_known_types::ValueReflection;
55-
5653
template <typename T>
5754
std::enable_if_t<std::is_base_of_v<google::protobuf::Message, T>,
5855
const google::protobuf::Message* absl_nonnull>
5956
EmptyParsedMessageValue() {
6057
return &T::default_instance();
6158
}
6259

63-
template <typename T>
64-
std::enable_if_t<
65-
std::conjunction_v<std::is_base_of<google::protobuf::MessageLite, T>,
66-
std::negation<std::is_base_of<google::protobuf::Message, T>>>,
67-
const google::protobuf::Message* absl_nonnull>
68-
EmptyParsedMessageValue() {
69-
return internal::GetEmptyDefaultInstance();
70-
}
7160

7261
} // namespace
7362

@@ -114,12 +103,8 @@ absl::Status ParsedMessageValue::ConvertToJson(
114103
ABSL_DCHECK_EQ(json->GetDescriptor()->well_known_type(),
115104
google::protobuf::Descriptor::WELLKNOWNTYPE_VALUE);
116105

117-
ValueReflection value_reflection;
118-
CEL_RETURN_IF_ERROR(value_reflection.Initialize(json->GetDescriptor()));
119-
google::protobuf::Message* json_object = value_reflection.MutableStructValue(json);
120-
121106
return internal::MessageToJson(*value_, descriptor_pool, message_factory,
122-
json_object);
107+
json);
123108
}
124109

125110
absl::Status ParsedMessageValue::ConvertToJsonObject(

common/values/parsed_message_value_test.cc

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414

1515
#include <utility>
1616

17+
#include "google/protobuf/field_mask.pb.h"
1718
#include "google/protobuf/struct.pb.h"
1819
#include "absl/status/status_matchers.h"
19-
#include "absl/strings/cord.h"
2020
#include "absl/strings/string_view.h"
2121
#include "common/memory.h"
2222
#include "common/type.h"
@@ -74,6 +74,19 @@ TEST_F(ParsedMessageValueTest, SerializeTo) {
7474
EXPECT_THAT(std::move(output).Consume(), IsEmpty());
7575
}
7676

77+
TEST_F(ParsedMessageValueTest, ConvertToJsonFieldMask) {
78+
ParsedMessageValue value =
79+
MakeParsedMessage<google::protobuf::FieldMask>(R"pb(paths: "foo.bar"
80+
paths: "baz")pb");
81+
google::protobuf::Message* json =
82+
DynamicParseTextProto<google::protobuf::Value>(R"pb()pb");
83+
ASSERT_THAT(value.ConvertToJson(descriptor_pool(), message_factory(),
84+
cel::to_address(json)),
85+
IsOk());
86+
EXPECT_THAT(*json, EqualsTextProto<google::protobuf::Value>(
87+
R"pb(string_value: "foo.bar,baz")pb"));
88+
}
89+
7790
TEST_F(ParsedMessageValueTest, ConvertToJson) {
7891
MessageValue value = MakeParsedMessage<TestAllTypesProto3>();
7992
auto json = DynamicParseTextProto<google::protobuf::Value>(R"pb()pb");

conformance/BUILD

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,6 @@ _TESTS_TO_SKIP = [
177177
"enums/legacy_proto2/select_big,select_neg",
178178

179179
# Skip until fixed.
180-
"wrappers/field_mask/to_json",
181180
"wrappers/empty/to_json",
182181
"fields/qualified_identifier_resolution/map_value_repeat_key_heterogeneous",
183182
"parse/receiver_function_names",

eval/public/structs/BUILD

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ cc_library(
7171
"@com_google_absl//absl/base:nullability",
7272
"@com_google_absl//absl/functional:overload",
7373
"@com_google_absl//absl/log:absl_check",
74+
"@com_google_absl//absl/log:absl_log",
7475
"@com_google_absl//absl/status",
7576
"@com_google_absl//absl/status:statusor",
7677
"@com_google_absl//absl/strings",
@@ -96,24 +97,22 @@ cc_test(
9697
],
9798
deps = [
9899
":cel_proto_wrap_util",
99-
":protobuf_value_factory",
100100
":trivial_legacy_type_info",
101101
"//eval/public:cel_value",
102-
"//eval/public:message_wrapper",
103102
"//eval/public/containers:container_backed_list_impl",
104103
"//eval/public/containers:container_backed_map_impl",
105104
"//eval/testutil:test_message_cc_proto",
106105
"//internal:proto_time_encoding",
107-
"//internal:status_macros",
108106
"//internal:testing",
109107
"//testutil:util",
110-
"@com_google_absl//absl/base:no_destructor",
111108
"@com_google_absl//absl/status",
112109
"@com_google_absl//absl/strings",
113110
"@com_google_absl//absl/time",
111+
"@com_google_absl//absl/types:span",
114112
"@com_google_protobuf//:any_cc_proto",
115113
"@com_google_protobuf//:duration_cc_proto",
116114
"@com_google_protobuf//:empty_cc_proto",
115+
"@com_google_protobuf//:field_mask_cc_proto",
117116
"@com_google_protobuf//:protobuf",
118117
"@com_google_protobuf//:struct_cc_proto",
119118
"@com_google_protobuf//:wrappers_cc_proto",
@@ -211,13 +210,13 @@ cc_test(
211210
"//eval/public/containers:container_backed_map_impl",
212211
"//eval/testutil:test_message_cc_proto",
213212
"//internal:proto_time_encoding",
214-
"//internal:status_macros",
215213
"//internal:testing",
216214
"//testutil:util",
217215
"@com_google_absl//absl/status",
218216
"@com_google_absl//absl/status:statusor",
219217
"@com_google_absl//absl/strings",
220218
"@com_google_absl//absl/time",
219+
"@com_google_absl//absl/types:span",
221220
"@com_google_protobuf//:any_cc_proto",
222221
"@com_google_protobuf//:duration_cc_proto",
223222
"@com_google_protobuf//:empty_cc_proto",

eval/public/structs/cel_proto_wrap_util.cc

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <cstddef>
1818
#include <cstdint>
1919
#include <limits>
20+
#include <optional>
2021
#include <string>
2122
#include <type_traits>
2223
#include <utility>
@@ -31,6 +32,7 @@
3132
#include "absl/base/optimization.h"
3233
#include "absl/functional/overload.h"
3334
#include "absl/log/absl_check.h"
35+
#include "absl/log/absl_log.h"
3436
#include "absl/status/status.h"
3537
#include "absl/status/statusor.h"
3638
#include "absl/strings/cord.h"
@@ -50,6 +52,7 @@
5052
#include "internal/well_known_types.h"
5153
#include "google/protobuf/arena.h"
5254
#include "google/protobuf/descriptor.h"
55+
#include "google/protobuf/json/json.h"
5356
#include "google/protobuf/message.h"
5457
#include "google/protobuf/message_lite.h"
5558

@@ -79,7 +82,8 @@ using google::protobuf::Descriptor;
7982
using google::protobuf::DescriptorPool;
8083
using google::protobuf::Message;
8184
using google::protobuf::MessageFactory;
82-
85+
using google::protobuf::json::MessageToJsonString;
86+
using google::protobuf::json::PrintOptions;
8387
// kMaxIntJSON is defined as the Number.MAX_SAFE_INTEGER value per EcmaScript 6.
8488
constexpr int64_t kMaxIntJSON = (1ll << 53) - 1;
8589

@@ -98,6 +102,27 @@ static bool IsJSONSafe(uint64_t i) {
98102
return i <= static_cast<uint64_t>(kMaxIntJSON);
99103
}
100104

105+
static std::optional<std::string> GetFieldMaskJsonString(
106+
const google::protobuf::Message& message) {
107+
// TODO(b/540507668): Refactor to pipe descriptor_pool through
108+
// ValueFromValue to use internal::MessageToJson.
109+
PrintOptions json_options;
110+
std::string json_str;
111+
auto status = MessageToJsonString(message, &json_str, json_options);
112+
if (!status.ok()) {
113+
ABSL_LOG(ERROR) << "Failed to convert FieldMask to JSON: " << status;
114+
return std::nullopt;
115+
}
116+
// If JSON marshalling is correct, we know we'll always get a plain
117+
// JSON string value and it shouldn't contain any escapes that we need
118+
// to interpret.
119+
if (json_str.size() >= 2 && json_str.front() == '"' &&
120+
json_str.back() == '"') {
121+
return json_str.substr(1, json_str.size() - 2);
122+
}
123+
return json_str;
124+
}
125+
101126
// Map implementation wrapping google.protobuf.ListValue
102127
class DynamicList : public CelList {
103128
public:
@@ -1079,6 +1104,20 @@ google::protobuf::Message* ValueFromValue(google::protobuf::Message* message, co
10791104
return message;
10801105
}
10811106
} break;
1107+
case CelValue::Type::kMessage: {
1108+
const google::protobuf::Message* message_ptr = value.MessageOrDie();
1109+
if (message_ptr->GetDescriptor()->full_name() ==
1110+
"google.protobuf.FieldMask") {
1111+
std::optional<std::string> fm_str =
1112+
GetFieldMaskJsonString(*message_ptr);
1113+
if (fm_str.has_value()) {
1114+
reflection.SetStringValue(message, *fm_str);
1115+
return message;
1116+
}
1117+
return nullptr;
1118+
}
1119+
return nullptr;
1120+
} break;
10821121
case CelValue::Type::kNullType:
10831122
reflection.SetNullValue(message);
10841123
return message;
@@ -1229,6 +1268,20 @@ bool ValueFromValue(Value* json, const CelValue& value, google::protobuf::Arena*
12291268
return ListFromValue(json->mutable_list_value(), value, arena);
12301269
case CelValue::Type::kMap:
12311270
return StructFromValue(json->mutable_struct_value(), value, arena);
1271+
case CelValue::Type::kMessage: {
1272+
const google::protobuf::Message* message_ptr = value.MessageOrDie();
1273+
if (message_ptr->GetDescriptor()->full_name() ==
1274+
"google.protobuf.FieldMask") {
1275+
std::optional<std::string> fm_str =
1276+
GetFieldMaskJsonString(*message_ptr);
1277+
if (fm_str.has_value()) {
1278+
json->set_string_value(*fm_str);
1279+
return true;
1280+
}
1281+
return false;
1282+
}
1283+
return false;
1284+
}
12321285
case CelValue::Type::kNullType:
12331286
json->set_null_value(protobuf::NULL_VALUE);
12341287
return true;
@@ -1254,7 +1307,7 @@ google::protobuf::Message* AnyFromValue(const google::protobuf::Message* prototy
12541307
case CelValue::Type::kBytes: {
12551308
BytesValue v;
12561309
type_name = v.GetTypeName();
1257-
v.set_value(std::string(value.BytesOrDie().value()));
1310+
v.set_value(value.BytesOrDie().value());
12581311
payload = v.SerializeAsCord();
12591312
} break;
12601313
case CelValue::Type::kDouble: {
@@ -1280,7 +1333,7 @@ google::protobuf::Message* AnyFromValue(const google::protobuf::Message* prototy
12801333
case CelValue::Type::kString: {
12811334
StringValue v;
12821335
type_name = v.GetTypeName();
1283-
v.set_value(std::string(value.StringOrDie().value()));
1336+
v.set_value(value.StringOrDie().value());
12841337
payload = v.SerializeAsCord();
12851338
} break;
12861339
case CelValue::Type::kTimestamp: {

eval/public/structs/cel_proto_wrap_util_test.cc

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "eval/public/structs/cel_proto_wrap_util.h"
1616

1717
#include <cassert>
18+
#include <cstdint>
1819
#include <limits>
1920
#include <memory>
2021
#include <string>
@@ -24,30 +25,32 @@
2425
#include "google/protobuf/any.pb.h"
2526
#include "google/protobuf/duration.pb.h"
2627
#include "google/protobuf/empty.pb.h"
28+
#include "google/protobuf/field_mask.pb.h"
2729
#include "google/protobuf/struct.pb.h"
2830
#include "google/protobuf/wrappers.pb.h"
29-
#include "absl/base/no_destructor.h"
3031
#include "absl/status/status.h"
3132
#include "absl/strings/str_cat.h"
3233
#include "absl/time/time.h"
34+
#include "absl/types/span.h"
3335
#include "eval/public/cel_value.h"
3436
#include "eval/public/containers/container_backed_list_impl.h"
3537
#include "eval/public/containers/container_backed_map_impl.h"
36-
#include "eval/public/message_wrapper.h"
37-
#include "eval/public/structs/protobuf_value_factory.h"
3838
#include "eval/public/structs/trivial_legacy_type_info.h"
3939
#include "eval/testutil/test_message.pb.h"
4040
#include "internal/proto_time_encoding.h"
41-
#include "internal/status_macros.h"
4241
#include "internal/testing.h"
4342
#include "testutil/util.h"
43+
#include "google/protobuf/arena.h"
4444
#include "google/protobuf/dynamic_message.h"
4545
#include "google/protobuf/message.h"
46+
#include "google/protobuf/text_format.h"
4647

4748
namespace google::api::expr::runtime::internal {
4849

4950
namespace {
5051

52+
using ::google::protobuf::FieldMask;
53+
using ::google::protobuf::TextFormat;
5154
using ::testing::Eq;
5255
using ::testing::UnorderedPointwise;
5356

@@ -436,6 +439,74 @@ TEST_F(CelProtoWrapperTest, UnwrapInvalidAny) {
436439
UnwrapMessageToValue(&any, &ProtobufValueFactoryImpl, arena()).IsError());
437440
}
438441

442+
TEST_F(CelProtoWrapperTest, WrapFieldMaskToValue) {
443+
FieldMask field_mask;
444+
ASSERT_TRUE(TextFormat::ParseFromString(R"pb(
445+
paths: "foo.bar" paths: "baz"
446+
)pb",
447+
&field_mask));
448+
CelValue value = ProtobufValueFactoryImpl(&field_mask);
449+
450+
Value expected_message;
451+
ASSERT_TRUE(TextFormat::ParseFromString(R"pb(string_value: "foo.bar,baz")pb",
452+
&expected_message));
453+
454+
ExpectWrappedMessage(value, expected_message);
455+
}
456+
457+
TEST_F(CelProtoWrapperTest, WrapMapWithFieldMaskToAny) {
458+
const std::string kField = "field_mask";
459+
FieldMask field_mask;
460+
ASSERT_TRUE(TextFormat::ParseFromString(R"pb(
461+
paths: "foo.bar" paths: "baz"
462+
)pb",
463+
&field_mask));
464+
CelValue value = ProtobufValueFactoryImpl(&field_mask);
465+
466+
std::vector<std::pair<CelValue, CelValue>> args = {
467+
{CelValue::CreateString(CelValue::StringHolder(&kField)), value}};
468+
ASSERT_OK_AND_ASSIGN(
469+
std::unique_ptr<CelMap> cel_map,
470+
CreateContainerBackedMap(
471+
absl::Span<std::pair<CelValue, CelValue>>(args.data(), args.size())));
472+
CelValue cel_value = CelValue::CreateMap(cel_map.get());
473+
474+
Struct expected_struct;
475+
ASSERT_TRUE(
476+
TextFormat::ParseFromString(R"pb(
477+
fields {
478+
key: "field_mask"
479+
value { string_value: "foo.bar,baz" }
480+
}
481+
)pb",
482+
&expected_struct));
483+
Any expected_message;
484+
ASSERT_TRUE(expected_message.PackFrom(expected_struct));
485+
486+
ExpectWrappedMessage(cel_value, expected_message);
487+
}
488+
489+
TEST_F(CelProtoWrapperTest, WrapListWithFieldMaskToAny) {
490+
FieldMask field_mask;
491+
ASSERT_TRUE(TextFormat::ParseFromString(R"pb(
492+
paths: "foo.bar" paths: "baz"
493+
)pb",
494+
&field_mask));
495+
CelValue value = ProtobufValueFactoryImpl(&field_mask);
496+
497+
std::vector<CelValue> list_entries = {value};
498+
ContainerBackedListImpl cel_list(list_entries);
499+
CelValue list_value = CelValue::CreateList(&cel_list);
500+
501+
ListValue expected_list;
502+
ASSERT_TRUE(TextFormat::ParseFromString(
503+
R"pb(values { string_value: "foo.bar,baz" })pb", &expected_list));
504+
Any expected_message;
505+
ASSERT_TRUE(expected_message.PackFrom(expected_list));
506+
507+
ExpectWrappedMessage(list_value, expected_message);
508+
}
509+
439510
// Test support of google.protobuf.<Type>Value wrappers in CelValue.
440511
TEST_F(CelProtoWrapperTest, UnwrapBoolWrapper) {
441512
bool value = true;

0 commit comments

Comments
 (0)