userver: /data/code/userver/libraries/protobuf/tests/json/any_to_json_test.cpp Source File
Loading...
Searching...
No Matches
any_to_json_test.cpp
1#include <gtest/gtest.h>
2
3#include <memory>
4#include <string>
5
6#include <fmt/format.h>
7#include <google/protobuf/dynamic_message.h>
8
9#include <userver/protobuf/json/convert.hpp>
10#include <userver/utest/assert_macros.hpp>
11
12#include "utils.hpp"
13
14USERVER_NAMESPACE_BEGIN
15
16namespace protobuf::json::tests {
17
18struct AnyToJsonSuccessTestParam {
19 AnyMessageData input = {};
20 std::string expected_json = {};
21 PrintOptions options = {};
22
23 // This variable is used to disable some checks that fail in the native protobuf implementation.
24 bool skip_native_check = false;
25};
26
27struct AnyToJsonFailureTestParam {
28 AnyMessageData input = {};
29 PrintErrorCode expected_errc = {};
30 std::string expected_path = {};
31 PrintOptions options = {};
32
33 // This variable is used to disable some checks that fail in the native protobuf implementation.
34 bool skip_native_check = false;
35};
36
37class AnyToJsonSuccessTest : public ::testing::TestWithParam<AnyToJsonSuccessTestParam> {};
38class AnyToJsonFailureTest : public ::testing::TestWithParam<AnyToJsonFailureTestParam> {};
39
40INSTANTIATE_TEST_SUITE_P(
41 ,
42 AnyToJsonSuccessTest,
43 ::testing::Values(
44 AnyToJsonSuccessTestParam{AnyMessageData{}, R"({})"},
45 AnyToJsonSuccessTestParam{AnyMessageData{RawAnyData{"", ""}}, R"({"field1":{}})"},
46 AnyToJsonSuccessTestParam{
47 AnyMessageData{Int32MessageData{}},
48 R"({
49 "field1": {
50 "@type":"type.googleapis.com/proto_json.messages.Int32Message"
51 }
52 })",
53 {},
54 // Native implementation prohibits empty payload for some reason if not in legacy mode.
55 true
56 },
57 AnyToJsonSuccessTestParam{
58 AnyMessageData{Int32MessageData{}},
59 R"({
60 "field1": {
61 "@type":"type.googleapis.com/proto_json.messages.Int32Message",
62 "field1":0,"field2":0,"field3":0
63 }
64 })",
65 {.always_print_fields_with_no_presence = true},
66 true
67 },
68 AnyToJsonSuccessTestParam{
69 AnyMessageData{Int32MessageData{10, 20, 30}},
70 R"({
71 "field1": {
72 "@type":"type.googleapis.com/proto_json.messages.Int32Message",
73 "field1":10,"field2":20,"field3":30
74 }
75 })"
76 },
77 AnyToJsonSuccessTestParam{
78 AnyMessageData{DurationMessageData{123, 987}},
79 R"({
80 "field1": {
81 "@type":"type.googleapis.com/google.protobuf.Duration",
82 "value":"123.000000987s"
83 }
84 })"
85 },
86 AnyToJsonSuccessTestParam{
87 AnyMessageData{ValueMessageData{kProtoNullValue}},
88 R"({
89 "field1": {
90 "@type":"type.googleapis.com/google.protobuf.Value",
91 "value":null
92 }
93 })"
94 },
95 AnyToJsonSuccessTestParam{
96 AnyMessageData{ValueMessageData{1.5}},
97 R"({
98 "field1": {
99 "@type":"type.googleapis.com/google.protobuf.Value",
100 "value":1.5
101 }
102 })"
103 },
104 AnyToJsonSuccessTestParam{
105 AnyMessageData{ValueMessageData{std::vector<double>{1.5, 1.5}}},
106 R"({
107 "field1": {
108 "@type":"type.googleapis.com/google.protobuf.Value",
109 "value":[1.5,1.5]
110 }
111 })"
112 },
113 AnyToJsonSuccessTestParam{
114 AnyMessageData{ValueMessageData{std::map<std::string, std::string>{{"aaa", "hello"}}}},
115 R"({
116 "field1": {
117 "@type":"type.googleapis.com/google.protobuf.Value",
118 "value":{"aaa":"hello"}
119 }
120 })"
121 },
122 AnyToJsonSuccessTestParam{
123 AnyMessageData{Int32MessageData{100, 200, 300}, true},
124 R"({
125 "field1": {
126 "@type":"type.googleapis.com/google.protobuf.Any",
127 "value": {
128 "@type":"type.googleapis.com/proto_json.messages.Int32Message",
129 "field1":100,"field2":200,"field3":300
130 }
131 }
132 })"
133 },
134 AnyToJsonSuccessTestParam{
135 AnyMessageData{DurationMessageData{-123, -567}, true},
136 R"({
137 "field1": {
138 "@type":"type.googleapis.com/google.protobuf.Any",
139 "value": {
140 "@type":"type.googleapis.com/google.protobuf.Duration",
141 "value":"-123.000000567s"
142 }
143 }
144 })"
145 },
146 AnyToJsonSuccessTestParam{
147 AnyMessageData{ValueMessageData{true}, true},
148 R"({
149 "field1": {
150 "@type":"type.googleapis.com/google.protobuf.Any",
151 "value": {
152 "@type":"type.googleapis.com/google.protobuf.Value",
153 "value":true
154 }
155 }
156 })"
157 },
158 // nonportable_raw_any: serialize Any as {"type_url": "...", "value": "<base64>"} without descriptor lookup
159 AnyToJsonSuccessTestParam{
160 AnyMessageData{Int32MessageData{1, 2, 3}},
161 R"({"field1":{"type_url":"type.googleapis.com/proto_json.messages.Int32Message","value":"CAEQBB0DAAAA"}})",
162 {.preserve_proto_field_names = true, .nonportable_raw_any = true},
163 // Native implementation does not support nonportable_raw_any option.
164 true
165 },
166 AnyToJsonSuccessTestParam{
167 AnyMessageData{Int32MessageData{1, 2, 3}},
168 R"({"field1":{"typeUrl":"type.googleapis.com/proto_json.messages.Int32Message","value":"CAEQBB0DAAAA"}})",
169 {.nonportable_raw_any = true},
170 // Native implementation does not support nonportable_raw_any option.
171 true
172 },
173 AnyToJsonSuccessTestParam{
174 AnyMessageData{RawAnyData{"type.googleapis.com/proto_json.messages.NonExistent", "\x08\x01"}},
175 R"({"field1":{"type_url":"type.googleapis.com/proto_json.messages.NonExistent","value":"CAE="}})",
176 {.preserve_proto_field_names = true, .nonportable_raw_any = true},
177 // Native implementation does not support nonportable_raw_any option.
178 true
179 },
180 AnyToJsonSuccessTestParam{
181 AnyMessageData{RawAnyData{"type.googleapis.com/proto_json.messages.NonExistent", "\x08\x01"}},
182 R"({"field1":{"typeUrl":"type.googleapis.com/proto_json.messages.NonExistent","value":"CAE="}})",
183 {.nonportable_raw_any = true},
184 // Native implementation does not support nonportable_raw_any option.
185 true
186 },
187 AnyToJsonSuccessTestParam{
188 AnyMessageData{RawAnyData{"", ""}},
189 R"({"field1":{"type_url":"","value":""}})",
190 {.preserve_proto_field_names = true, .nonportable_raw_any = true},
191 // Native implementation does not support nonportable_raw_any option.
192 true
193 },
194 AnyToJsonSuccessTestParam{
195 AnyMessageData{RawAnyData{"", ""}},
196 R"({"field1":{"typeUrl":"","value":""}})",
197 {.nonportable_raw_any = true},
198 // Native implementation does not support nonportable_raw_any option.
199 true
200 }
201 )
202);
203
204INSTANTIATE_TEST_SUITE_P(
205 ,
206 AnyToJsonFailureTest,
207 ::testing::Values(
208 AnyToJsonFailureTestParam{AnyMessageData{RawAnyData{"", "\x08\x01"}}, PrintErrorCode::kInvalidValue, "field1"},
209 AnyToJsonFailureTestParam{
210 AnyMessageData{RawAnyData{"oops", ""}},
211 PrintErrorCode::kInvalidValue,
212 "field1",
213 {},
214 // Only in newer protobuf versions this test will succeed.
215 !kIsModernProtoJson
216 },
217 AnyToJsonFailureTestParam{
218 AnyMessageData{RawAnyData{"type.googleapis.com/proto_json.messages.NonExistent", "\x08\x01"}},
219 PrintErrorCode::kInvalidValue,
220 "field1"
221 },
222 // '\x80' is incomplete message.
223 AnyToJsonFailureTestParam{
224 AnyMessageData{RawAnyData{"type.googleapis.com/proto_json.messages.Int32Message", "\x80"}},
225 PrintErrorCode::kInvalidValue,
226 "field1",
227 {},
228 // Native implementation does not fail on invalid binary data and produces strange JSON.
229 true
230 },
231 AnyToJsonFailureTestParam{AnyMessageData{DurationMessageData{-1, 1}}, PrintErrorCode::kInvalidValue, "field1"},
232 AnyToJsonFailureTestParam{
233 AnyMessageData{ValueMessageData{std::vector<double>{std::numeric_limits<double>::infinity()}}},
234 PrintErrorCode::kInvalidValue,
235 "field1.list_value.values[0].number_value",
236 {},
237 // Only in newer protobuf versions this test will succeed.
238 !kIsModernProtoJson
239 }
240 )
241);
242
243TEST_P(AnyToJsonSuccessTest, Test) {
244 const auto& param = GetParam();
245
246 auto input = PrepareTestData(param.input);
247 formats::json::Value json;
248 formats::json::Value expected_json;
249 formats::json::Value sample_json;
250
251 UASSERT_NO_THROW((json = MessageToJson(input, param.options)));
252 UASSERT_NO_THROW((expected_json = PrepareJsonTestData(param.expected_json)));
253
254 if (!param.skip_native_check) {
255 UASSERT_NO_THROW((sample_json = CreateSampleJson(input, param.options)));
256 EXPECT_EQ(json, sample_json);
257 }
258
259 EXPECT_EQ(json, expected_json);
260}
261
262TEST_P(AnyToJsonFailureTest, Test) {
263 const auto& param = GetParam();
264 auto input = PrepareTestData(param.input);
265
266 EXPECT_PRINT_ERROR((void)MessageToJson(input, param.options), param.expected_errc, param.expected_path);
267
268 if (!param.skip_native_check) {
269 UEXPECT_THROW((void)CreateSampleJson(input, param.options), SampleError);
270 }
271}
272
273TEST(AnyToJsonAdditionalTest, InlinedNonNull) {
274 AnyMessageData data{Int32MessageData{1, 2, 3}};
275 auto message = PrepareTestData(data);
276 formats::json::Value json;
277 formats::json::Value sample;
278
279 UASSERT_NO_THROW((json = MessageToJson(message.field1(), {})));
280 UASSERT_NO_THROW((sample = CreateSampleJson(message.field1())));
281 ASSERT_TRUE(json.IsObject());
282 EXPECT_EQ(json, sample);
283 EXPECT_EQ(json["@type"].As<std::string>(), "type.googleapis.com/proto_json.messages.Int32Message");
284 EXPECT_EQ(json["field1"].As<std::int32_t>(), 1);
285 EXPECT_EQ(json["field2"].As<std::int32_t>(), 2);
286 EXPECT_EQ(json["field3"].As<std::int32_t>(), 3);
287}
288
289TEST(AnyToJsonAdditionalTest, InlinedNull) {
290 proto_json::messages::AnyMessage message;
291 formats::json::Value json;
292 formats::json::Value sample;
293
294 UASSERT_NO_THROW((json = MessageToJson(message.field1(), {})));
295 UASSERT_NO_THROW((sample = CreateSampleJson(message.field1())));
296 ASSERT_TRUE(json.IsObject());
297 EXPECT_EQ(json, sample);
298 EXPECT_EQ(json.GetSize(), std::size_t{0});
299}
300
301TEST(AnyToJsonAdditionalTest, DynamicMessage) {
302 using Message = ::google::protobuf::Any;
303 using ProtobufStringType =
304 decltype(std::declval<::google::protobuf::Reflection>()
305 .GetString(std::declval<const ::google::protobuf::Message&>(), nullptr));
306
307 ::google::protobuf::DynamicMessageFactory factory;
308 proto_json::messages::Int32Message payload;
309 ProtobufStringType payload_str;
310
311 payload.set_field1(1001);
312 payload.set_field2(1002);
313 payload.set_field3(1003);
314
315 ASSERT_TRUE(payload.SerializeToString(&payload_str));
316 proto_json::messages::Int32Message payload2;
317
318 {
319 std::unique_ptr<::google::protobuf::Message> message(factory.GetPrototype(Message::descriptor())->New());
320 const auto reflection = message->GetReflection();
321 const auto type_url_desc = message->GetDescriptor()->FindFieldByName("type_url");
322 const auto value_desc = message->GetDescriptor()->FindFieldByName("value");
323
324 reflection->SetString(message.get(), type_url_desc, "type.googleapis.com/proto_json.messages.Int32Message");
325 reflection->SetString(message.get(), value_desc, payload_str);
326
327 formats::json::Value json;
328
329 UASSERT_NO_THROW((json = MessageToJson(*message, {})));
330 ASSERT_TRUE(json.IsObject());
331 ASSERT_EQ(json.GetSize(), std::size_t{4});
332 EXPECT_EQ(json["@type"].As<std::string>(), "type.googleapis.com/proto_json.messages.Int32Message");
333 EXPECT_EQ(json["field1"].As<std::int32_t>(), 1001);
334 EXPECT_EQ(json["field2"].As<std::int32_t>(), 1002);
335 EXPECT_EQ(json["field3"].As<std::int32_t>(), 1003);
336 }
337}
338
339} // namespace protobuf::json::tests
340
341USERVER_NAMESPACE_END