Skip to content

Commit fcad392

Browse files
maskri17copybara-github
authored andcommitted
Updated MessageToJson conversion for FieldMask in CEL C++
PiperOrigin-RevId: 956173251
1 parent 6f716a5 commit fcad392

8 files changed

Lines changed: 282 additions & 32 deletions

File tree

common/BUILD

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

common/values/parsed_message_value.cc

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
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"
3839
#include "internal/empty_descriptors.h"
3940
#include "internal/json.h"
@@ -51,8 +52,6 @@ namespace cel {
5152

5253
namespace {
5354

54-
using ::cel::well_known_types::ValueReflection;
55-
5655
template <typename T>
5756
std::enable_if_t<std::is_base_of_v<google::protobuf::Message, T>,
5857
const google::protobuf::Message* absl_nonnull>
@@ -114,12 +113,8 @@ absl::Status ParsedMessageValue::ConvertToJson(
114113
ABSL_DCHECK_EQ(json->GetDescriptor()->well_known_type(),
115114
google::protobuf::Descriptor::WELLKNOWNTYPE_VALUE);
116115

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-
121116
return internal::MessageToJson(*value_, descriptor_pool, message_factory,
122-
json_object);
117+
json);
123118
}
124119

125120
absl::Status ParsedMessageValue::ConvertToJsonObject(

common/values/parsed_message_value_test.cc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
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"
1920
#include "absl/strings/cord.h"
@@ -84,6 +85,19 @@ TEST_F(ParsedMessageValueTest, ConvertToJson) {
8485
R"pb(struct_value: {})pb"));
8586
}
8687

88+
TEST_F(ParsedMessageValueTest, ConvertToJsonFieldMask) {
89+
ParsedMessageValue value =
90+
MakeParsedMessage<google::protobuf::FieldMask>(R"pb(paths: "foo.bar"
91+
paths: "baz")pb");
92+
google::protobuf::Message* json =
93+
DynamicParseTextProto<google::protobuf::Value>(R"pb()pb");
94+
ASSERT_THAT(value.ConvertToJson(descriptor_pool(), message_factory(),
95+
cel::to_address(json)),
96+
IsOk());
97+
EXPECT_THAT(*json, EqualsTextProto<google::protobuf::Value>(
98+
R"pb(string_value: "foo.bar,baz")pb"));
99+
}
100+
87101
TEST_F(ParsedMessageValueTest, Equal) {
88102
MessageValue value = MakeParsedMessage<TestAllTypesProto3>();
89103
EXPECT_THAT(

conformance/BUILD

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,6 @@ _TESTS_TO_SKIP = [
175175
"namespace/namespace/self_eval_container_lookup,self_eval_container_lookup_unchecked",
176176

177177
# Skip until fixed.
178-
"wrappers/field_mask/to_json",
179-
"wrappers/empty/to_json",
180178
"fields/qualified_identifier_resolution/map_value_repeat_key_heterogeneous",
181179

182180
# Future features for CEL 1.0

eval/public/structs/BUILD

Lines changed: 4 additions & 0 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",
@@ -111,9 +112,11 @@ cc_test(
111112
"@com_google_absl//absl/status",
112113
"@com_google_absl//absl/strings",
113114
"@com_google_absl//absl/time",
115+
"@com_google_absl//absl/types:span",
114116
"@com_google_protobuf//:any_cc_proto",
115117
"@com_google_protobuf//:duration_cc_proto",
116118
"@com_google_protobuf//:empty_cc_proto",
119+
"@com_google_protobuf//:field_mask_cc_proto",
117120
"@com_google_protobuf//:protobuf",
118121
"@com_google_protobuf//:struct_cc_proto",
119122
"@com_google_protobuf//:wrappers_cc_proto",
@@ -218,6 +221,7 @@ cc_test(
218221
"@com_google_absl//absl/status:statusor",
219222
"@com_google_absl//absl/strings",
220223
"@com_google_absl//absl/time",
224+
"@com_google_absl//absl/types:span",
221225
"@com_google_protobuf//:any_cc_proto",
222226
"@com_google_protobuf//:duration_cc_proto",
223227
"@com_google_protobuf//:empty_cc_proto",

eval/public/structs/cel_proto_wrap_util.cc

Lines changed: 69 additions & 1 deletion
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,6 +82,8 @@ using google::protobuf::Descriptor;
7982
using google::protobuf::DescriptorPool;
8083
using google::protobuf::Message;
8184
using google::protobuf::MessageFactory;
85+
using google::protobuf::json::MessageToJsonString;
86+
using google::protobuf::json::PrintOptions;
8287

8388
// kMaxIntJSON is defined as the Number.MAX_SAFE_INTEGER value per EcmaScript 6.
8489
constexpr int64_t kMaxIntJSON = (1ll << 53) - 1;
@@ -98,6 +103,35 @@ static bool IsJSONSafe(uint64_t i) {
98103
return i <= static_cast<uint64_t>(kMaxIntJSON);
99104
}
100105

106+
static bool IsEmptyProto(const google::protobuf::Descriptor* descriptor) {
107+
return descriptor->full_name() == "google.protobuf.Empty";
108+
}
109+
110+
static bool IsFieldMaskProto(const google::protobuf::Descriptor* descriptor) {
111+
return descriptor->full_name() == "google.protobuf.FieldMask";
112+
}
113+
114+
static std::optional<std::string> GetFieldMaskJsonString(
115+
const google::protobuf::Message& message) {
116+
// TODO(b/540507668): Refactor to pipe descriptor_pool through
117+
// ValueFromValue to use internal::MessageToJson.
118+
PrintOptions json_options;
119+
std::string json_str;
120+
auto status = MessageToJsonString(message, &json_str, json_options);
121+
if (!status.ok()) {
122+
ABSL_LOG(ERROR) << "Failed to convert FieldMask to JSON: " << status;
123+
return std::nullopt;
124+
}
125+
// If JSON marshalling is correct, we know we'll always get a plain
126+
// JSON string value and it shouldn't contain any escapes that we need
127+
// to interpret.
128+
if (json_str.size() >= 2 && json_str.front() == '"' &&
129+
json_str.back() == '"') {
130+
return json_str.substr(1, json_str.size() - 2);
131+
}
132+
return json_str;
133+
}
134+
101135
// Map implementation wrapping google.protobuf.ListValue
102136
class DynamicList : public CelList {
103137
public:
@@ -1079,6 +1113,23 @@ google::protobuf::Message* ValueFromValue(google::protobuf::Message* message, co
10791113
return message;
10801114
}
10811115
} break;
1116+
case CelValue::Type::kMessage: {
1117+
const google::protobuf::Message* message_ptr = value.MessageOrDie();
1118+
if (IsEmptyProto(message_ptr->GetDescriptor())) {
1119+
reflection.MutableStructValue(message);
1120+
return message;
1121+
}
1122+
if (IsFieldMaskProto(message_ptr->GetDescriptor())) {
1123+
std::optional<std::string> fm_str =
1124+
GetFieldMaskJsonString(*message_ptr);
1125+
if (fm_str.has_value()) {
1126+
reflection.SetStringValue(message, *fm_str);
1127+
return message;
1128+
}
1129+
return nullptr;
1130+
}
1131+
return nullptr;
1132+
} break;
10821133
case CelValue::Type::kNullType:
10831134
reflection.SetNullValue(message);
10841135
return message;
@@ -1229,6 +1280,23 @@ bool ValueFromValue(Value* json, const CelValue& value, google::protobuf::Arena*
12291280
return ListFromValue(json->mutable_list_value(), value, arena);
12301281
case CelValue::Type::kMap:
12311282
return StructFromValue(json->mutable_struct_value(), value, arena);
1283+
case CelValue::Type::kMessage: {
1284+
const google::protobuf::Message* message_ptr = value.MessageOrDie();
1285+
if (IsEmptyProto(message_ptr->GetDescriptor())) {
1286+
json->mutable_struct_value();
1287+
return true;
1288+
}
1289+
if (IsFieldMaskProto(message_ptr->GetDescriptor())) {
1290+
std::optional<std::string> fm_str =
1291+
GetFieldMaskJsonString(*message_ptr);
1292+
if (fm_str.has_value()) {
1293+
json->set_string_value(*fm_str);
1294+
return true;
1295+
}
1296+
return false;
1297+
}
1298+
return false;
1299+
}
12321300
case CelValue::Type::kNullType:
12331301
json->set_null_value(protobuf::NULL_VALUE);
12341302
return true;
@@ -1254,7 +1322,7 @@ google::protobuf::Message* AnyFromValue(const google::protobuf::Message* prototy
12541322
case CelValue::Type::kBytes: {
12551323
BytesValue v;
12561324
type_name = v.GetTypeName();
1257-
v.set_value(std::string(value.BytesOrDie().value()));
1325+
v.set_value(value.BytesOrDie().value());
12581326
payload = v.SerializeAsCord();
12591327
} break;
12601328
case CelValue::Type::kDouble: {

0 commit comments

Comments
 (0)