userver: /data/code/userver/libraries/protobuf/tests/json/any_from_json_test.cpp Source File
Loading...
Searching...
No Matches
any_from_json_test.cpp
1#include <gtest/gtest.h>
2
3#include <memory>
4#include <ostream>
5#include <string>
6
7#include <fmt/format.h>
8#include <google/protobuf/dynamic_message.h>
9
10#include <userver/formats/json/serialize.hpp>
11#include <userver/protobuf/json/convert.hpp>
12#include <userver/utest/assert_macros.hpp>
13
14#include "utils.hpp"
15
16USERVER_NAMESPACE_BEGIN
17
18namespace protobuf::json::tests {
19
20struct AnyFromJsonSuccessTestParam {
21 std::string input = {};
22 AnyMessageData expected_message = {};
23 ParseOptions options = {};
24
25 // This variable is used to disable some checks that fail in the native protobuf implementation.
26 bool skip_native_check = false;
27};
28
29struct AnyFromJsonFailureTestParam {
30 std::string input = {};
31 ParseErrorCode expected_errc = {};
32 std::string expected_path = {};
33 ParseOptions options = {};
34};
35
36void PrintTo(const AnyFromJsonSuccessTestParam& param, std::ostream* os) {
37 *os << fmt::format("{{ input = '{}' }}", param.input);
38}
39
40void PrintTo(const AnyFromJsonFailureTestParam& param, std::ostream* os) {
41 *os << fmt::format("{{ input = '{}' }}", param.input);
42}
43
44class AnyFromJsonSuccessTest : public ::testing::TestWithParam<AnyFromJsonSuccessTestParam> {};
45class AnyFromJsonFailureTest : public ::testing::TestWithParam<AnyFromJsonFailureTestParam> {};
46
47INSTANTIATE_TEST_SUITE_P(
48 ,
49 AnyFromJsonSuccessTest,
50 ::testing::Values(
51 AnyFromJsonSuccessTestParam{R"({})", AnyMessageData{}},
52 AnyFromJsonSuccessTestParam{R"({"field1":null})", AnyMessageData{}},
53 AnyFromJsonSuccessTestParam{R"({"field1":{}})", AnyMessageData{RawAnyData{"", ""}}},
54 AnyFromJsonSuccessTestParam{
55 R"({
56 "field1": {
57 "@type":"type.googleapis.com/proto_json.messages.Int32Message"
58 }
59 })",
60 AnyMessageData{Int32MessageData{}}
61 },
62 AnyFromJsonSuccessTestParam{
63 R"({
64 "field1": {
65 "@type":"type.googleapis.com/proto_json.messages.Int32Message",
66 "field1":0,"field2":0,"field3":0
67 }
68 })",
69 AnyMessageData{Int32MessageData{0, 0, 0}}
70 },
71 AnyFromJsonSuccessTestParam{
72 R"({
73 "field1": {
74 "@type":"type.googleapis.com/proto_json.messages.Int32Message",
75 "field1":null,"field2":null,"field3":1
76 }
77 })",
78 AnyMessageData{Int32MessageData{0, 0, 1}}
79 },
80 AnyFromJsonSuccessTestParam{
81 R"({
82 "field1": {
83 "@type":"type.googleapis.com/proto_json.messages.Int32Message",
84 "field1":10,"field2":20,"field3":30
85 }
86 })",
87 AnyMessageData{Int32MessageData{10, 20, 30}}
88 },
89 AnyFromJsonSuccessTestParam{
90 R"({
91 "field1": {
92 "@type":"type.googleapis.com/proto_json.messages.Int32Message",
93 "field1":10,"field2":20,"field3":30,"unknown_field":true
94 }
95 })",
96 AnyMessageData{Int32MessageData{10, 20, 30}},
97 {.ignore_unknown_fields = true}
98 },
99 AnyFromJsonSuccessTestParam{
100 R"({
101 "field1": {
102 "@type":"type.googleapis.com/google.protobuf.Duration",
103 "value":"123.000000987s"
104 }
105 })",
106 AnyMessageData{DurationMessageData{123, 987}}
107 },
108 AnyFromJsonSuccessTestParam{
109 R"({
110 "field1": {
111 "@type":"type.googleapis.com/google.protobuf.Duration",
112 "value":"123.000000987s",
113 "unknown_field":true
114 }
115 })",
116 AnyMessageData{DurationMessageData{123, 987}},
117 {.ignore_unknown_fields = true},
118 // We want 'ignore_unknown_options' to work in all cases.
119 true
120 },
121 AnyFromJsonSuccessTestParam{
122 R"({
123 "field1": {
124 "@type":"type.googleapis.com/google.protobuf.Duration"
125 }
126 })",
127 AnyMessageData{DurationMessageData{0, 0}}
128 },
129 AnyFromJsonSuccessTestParam{
130 R"({
131 "field1": {
132 "@type":"type.googleapis.com/google.protobuf.Duration",
133 "value":null
134 }
135 })",
136 AnyMessageData{DurationMessageData{0, 0}}
137 },
138 AnyFromJsonSuccessTestParam{
139 R"({
140 "field1": {
141 "@type":"type.googleapis.com/google.protobuf.Value",
142 "value":null
143 }
144 })",
145 AnyMessageData{ValueMessageData{kProtoNullValue}}
146 },
147 AnyFromJsonSuccessTestParam{
148 R"({
149 "field1": {
150 "@type":"type.googleapis.com/google.protobuf.Value",
151 "value":"hello"
152 }
153 })",
154 AnyMessageData{ValueMessageData{"hello"}}
155 },
156 AnyFromJsonSuccessTestParam{
157 R"({
158 "field1": {
159 "@type":"type.googleapis.com/google.protobuf.Value",
160 "value":[1.5,1.5]
161 }
162 })",
163 AnyMessageData{ValueMessageData{std::vector<double>{1.5, 1.5}}}
164 },
165 AnyFromJsonSuccessTestParam{
166 R"({
167 "field1": {
168 "@type":"type.googleapis.com/google.protobuf.Value",
169 "value":{"aaa":"hello","bbb":"world"}
170 }
171 })",
172 AnyMessageData{ValueMessageData{std::map<std::string, std::string>{{"aaa", "hello"}, {"bbb", "world"}}}}
173 },
174 // nonportable_raw_any: parse Any from {"type_url": "...", "value": "<base64>"} without descriptor lookup
175 AnyFromJsonSuccessTestParam{
176 R"({"field1":{"type_url":"type.googleapis.com/proto_json.messages.Int32Message","value":"CAoQFBge"}})",
177 AnyMessageData{
178 RawAnyData{"type.googleapis.com/proto_json.messages.Int32Message", "\x08\x0a\x10\x14\x18\x1e"}
179 },
180 {.nonportable_raw_any = true},
181 // Native implementation does not support nonportable_raw_any option.
182 true
183 },
184 AnyFromJsonSuccessTestParam{
185 R"({"field1":{"typeUrl":"type.googleapis.com/proto_json.messages.Int32Message","value":"CAoQFBge"}})",
186 AnyMessageData{
187 RawAnyData{"type.googleapis.com/proto_json.messages.Int32Message", "\x08\x0a\x10\x14\x18\x1e"}
188 },
189 {.nonportable_raw_any = true},
190 // Native implementation does not support nonportable_raw_any option.
191 true
192 },
193 AnyFromJsonSuccessTestParam{
194 R"({"field1":{"type_url":"type.googleapis.com/proto_json.messages.NonExistent","value":"CAE="}})",
195 AnyMessageData{RawAnyData{"type.googleapis.com/proto_json.messages.NonExistent", "\x08\x01"}},
196 {.nonportable_raw_any = true},
197 // Native implementation does not support nonportable_raw_any option.
198 true
199 },
200 AnyFromJsonSuccessTestParam{
201 R"({"field1":{"type_url":"","value":""}})",
202 AnyMessageData{RawAnyData{"", ""}},
203 {.nonportable_raw_any = true},
204 // Native implementation does not support nonportable_raw_any option.
205 true
206 }
207 )
208);
209
210INSTANTIATE_TEST_SUITE_P(
211 ,
212 AnyFromJsonFailureTest,
213 ::testing::Values(
214 AnyFromJsonFailureTestParam{R"({"field1":[]})", ParseErrorCode::kInvalidType, "field1"},
215 AnyFromJsonFailureTestParam{R"({"field1":true})", ParseErrorCode::kInvalidType, "field1"},
216 AnyFromJsonFailureTestParam{R"({"field1":1})", ParseErrorCode::kInvalidType, "field1"},
217 AnyFromJsonFailureTestParam{R"({"field1":"hello"})", ParseErrorCode::kInvalidType, "field1"},
218 AnyFromJsonFailureTestParam{
219 R"({
220 "field1": {
221 "@type":"oops"
222 }
223 })",
225 "field1"
226 },
227 AnyFromJsonFailureTestParam{
228 R"({
229 "field1": {
230 "@type":"type.googleapis.com/proto_json.messages.NonExistent"
231 }
232 })",
234 "field1"
235 },
236 AnyFromJsonFailureTestParam{
237 R"({
238 "field1": {
239 "@type":true
240 }
241 })",
243 "field1"
244 },
245 AnyFromJsonFailureTestParam{
246 R"({
247 "field1": {
248 "value":"123.000000987s"
249 }
250 })",
252 "field1"
253 },
254 AnyFromJsonFailureTestParam{
255 R"({
256 "field1": {
257 "field1":1
258 }
259 })",
261 "field1"
262 },
263 AnyFromJsonFailureTestParam{
264 R"({
265 "field1": {
266 "@type":"type.googleapis.com/proto_json.messages.Int32Message",
267 "field1":10,"field2":20,"field3":30,"unknown_field":true
268 }
269 })",
271 "field1.unknown_field",
272 {.ignore_unknown_fields = false}
273 },
274 AnyFromJsonFailureTestParam{
275 R"({
276 "field1": {
277 "@type":"type.googleapis.com/google.protobuf.Duration",
278 "value":"123.000000987s",
279 "unknown_field":true
280 }
281 })",
283 "field1.unknown_field",
284 {.ignore_unknown_fields = false}
285 },
286 // nonportable_raw_any: missing 'value' field
287 AnyFromJsonFailureTestParam{
288 R"({"field1":{"type_url":"type.googleapis.com/some.Type"}})",
290 "field1",
291 {.nonportable_raw_any = true}
292 },
293 // nonportable_raw_any: missing 'type_url' field
294 AnyFromJsonFailureTestParam{
295 R"({"field1":{"value":""}})",
297 "field1",
298 {.nonportable_raw_any = true}
299 },
300 // nonportable_raw_any: non-string 'type_url'
301 AnyFromJsonFailureTestParam{
302 R"({"field1":{"type_url":123,"value":""}})",
304 "field1",
305 {.nonportable_raw_any = true}
306 }
307 )
308);
309
310TEST_P(AnyFromJsonSuccessTest, Test) {
311 const auto& param = GetParam();
312
313 proto_json::messages::AnyMessage message;
314 proto_json::messages::AnyMessage expected_message;
315 proto_json::messages::AnyMessage sample_message;
316 formats::json::Value input = PrepareJsonTestData(param.input);
317 expected_message = PrepareTestData(param.expected_message);
318
319 message.mutable_field1()->set_type_url("dump");
320
321 UASSERT_NO_THROW((message = JsonToMessage<proto_json::messages::AnyMessage>(input, param.options)));
322
323 if (!param.skip_native_check) {
324 UASSERT_NO_THROW(InitSampleMessage(param.input, sample_message, param.options));
325 CheckMessageEqual(message, sample_message);
326 }
327
328 CheckMessageEqual(message, expected_message);
329}
330
331TEST_P(AnyFromJsonFailureTest, Test) {
332 const auto& param = GetParam();
333
334 proto_json::messages::AnyMessage sample_message;
335 formats::json::Value input = PrepareJsonTestData(param.input);
336
338 (void)JsonToMessage<proto_json::messages::AnyMessage>(input, param.options),
339 param.expected_errc,
340 param.expected_path
341 );
342 UEXPECT_THROW(InitSampleMessage(param.input, sample_message, param.options), SampleError);
343}
344
345TEST(AnyFromJsonAdditionalTest, InlinedNonNull) {
346 using Message = ::google::protobuf::Any;
347
348 const char* json_str =
349 R"({
350 "@type":"type.googleapis.com/proto_json.messages.Int32Message",
351 "field1":1,"field2":2,"field3":3
352 })";
353 const auto json = formats::json::FromString(json_str);
354 Message message;
355 Message sample;
356 proto_json::messages::Int32Message payload;
357
358 message.set_type_url("dump");
359
360 UASSERT_NO_THROW((message = JsonToMessage<Message>(json)));
361 UASSERT_NO_THROW(InitSampleMessage(json_str, sample));
362 ASSERT_TRUE(message.Is<proto_json::messages::Int32Message>());
363 ASSERT_TRUE(message.UnpackTo(&payload));
364
365 EXPECT_EQ(payload.field1(), 1);
366 EXPECT_EQ(payload.field2(), 2);
367 EXPECT_EQ(payload.field3(), 3);
368 CheckMessageEqual(message, sample);
369}
370
371TEST(AnyFromJsonAdditionalTest, InlinedNull) {
372 using Message = ::google::protobuf::Any;
373
374 {
375 const auto json = formats::json::FromString("null");
376 Message message;
377
378 EXPECT_PARSE_ERROR((message = JsonToMessage<Message>(json)), ParseErrorCode::kInvalidType, "/");
379 UEXPECT_THROW(InitSampleMessage("null", message), SampleError);
380 }
381
382 {
383 const auto json = formats::json::FromString("{}");
384 Message message;
385 Message sample;
386
387 message.set_type_url("dump");
388
389 UASSERT_NO_THROW((message = JsonToMessage<Message>(json)));
390 UASSERT_NO_THROW(InitSampleMessage("{}", sample));
391
392 EXPECT_TRUE(message.type_url().empty());
393 EXPECT_TRUE(message.value().empty());
394 CheckMessageEqual(message, sample);
395 }
396}
397
398TEST(AnyFromJsonAdditionalTest, DynamicMessage) {
399 using Message = ::google::protobuf::Any;
400
401 const char* json_str =
402 R"({
403 "@type":"type.googleapis.com/proto_json.messages.Int32Message",
404 "field1":1,"field2":2,"field3":3
405 })";
406 const auto json = formats::json::FromString(json_str);
407 Message message;
408 proto_json::messages::Int32Message payload;
409 ::google::protobuf::DynamicMessageFactory factory;
410
411 {
412 std::unique_ptr<::google::protobuf::Message> message(factory.GetPrototype(Message::descriptor())->New());
413
414 UASSERT_NO_THROW(JsonToMessage(json, *message));
415
416 const auto reflection = message->GetReflection();
417 const auto type_url_desc = message->GetDescriptor()->FindFieldByName("type_url");
418 const auto value_desc = message->GetDescriptor()->FindFieldByName("value");
419
420 ASSERT_EQ(
421 reflection->GetString(*message, type_url_desc),
422 "type.googleapis.com/proto_json.messages.Int32Message"
423 );
424 ASSERT_TRUE(payload.ParseFromString(reflection->GetString(*message, value_desc)));
425 EXPECT_EQ(payload.field1(), 1);
426 EXPECT_EQ(payload.field2(), 2);
427 EXPECT_EQ(payload.field3(), 3);
428 }
429}
430
431} // namespace protobuf::json::tests
432
433USERVER_NAMESPACE_END