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 )
159);
160
161INSTANTIATE_TEST_SUITE_P(
162 ,
163 AnyToJsonFailureTest,
164 ::testing::Values(
165 AnyToJsonFailureTestParam{AnyMessageData{RawAnyData{"", "\x08\x01"}}, PrintErrorCode::kInvalidValue, "field1"},
166 AnyToJsonFailureTestParam{
167 AnyMessageData{RawAnyData{"oops", ""}},
168 PrintErrorCode::kInvalidValue,
169 "field1",
170 {},
171 // Only in newer protobuf versions this test will succeed.
172 !kIsModernProtoJson
173 },
174 AnyToJsonFailureTestParam{
175 AnyMessageData{RawAnyData{"type.googleapis.com/proto_json.messages.NonExistent", "\x08\x01"}},
176 PrintErrorCode::kInvalidValue,
177 "field1"
178 },
179 // '\x80' is incomplete message.
180 AnyToJsonFailureTestParam{
181 AnyMessageData{RawAnyData{"type.googleapis.com/proto_json.messages.Int32Message", "\x80"}},
182 PrintErrorCode::kInvalidValue,
183 "field1",
184 {},
185 // Native implementation does not fail on invalid binary data and produces strange JSON.
186 true
187 },
188 AnyToJsonFailureTestParam{AnyMessageData{DurationMessageData{-1, 1}}, PrintErrorCode::kInvalidValue, "field1"},
189 AnyToJsonFailureTestParam{
190 AnyMessageData{ValueMessageData{std::vector<double>{std::numeric_limits<double>::infinity()}}},
191 PrintErrorCode::kInvalidValue,
192 "field1.list_value.values[0].number_value",
193 {},
194 // Only in newer protobuf versions this test will succeed.
195 !kIsModernProtoJson
196 }
197 )
198);
199
200TEST_P(AnyToJsonSuccessTest, Test) {
201 const auto& param = GetParam();
202
203 auto input = PrepareTestData(param.input);
204 formats::json::Value json;
205 formats::json::Value expected_json;
206 formats::json::Value sample_json;
207
208 UASSERT_NO_THROW((json = MessageToJson(input, param.options)));
209 UASSERT_NO_THROW((expected_json = PrepareJsonTestData(param.expected_json)));
210
211 if (!param.skip_native_check) {
212 UASSERT_NO_THROW((sample_json = CreateSampleJson(input, param.options)));
213 EXPECT_EQ(json, sample_json);
214 }
215
216 EXPECT_EQ(json, expected_json);
217}
218
219TEST_P(AnyToJsonFailureTest, Test) {
220 const auto& param = GetParam();
221 auto input = PrepareTestData(param.input);
222
223 EXPECT_PRINT_ERROR((void)MessageToJson(input, param.options), param.expected_errc, param.expected_path);
224
225 if (!param.skip_native_check) {
226 UEXPECT_THROW((void)CreateSampleJson(input, param.options), SampleError);
227 }
228}
229
230TEST(AnyToJsonAdditionalTest, InlinedNonNull) {
231 AnyMessageData data{Int32MessageData{1, 2, 3}};
232 auto message = PrepareTestData(data);
233 formats::json::Value json;
234 formats::json::Value sample;
235
236 UASSERT_NO_THROW((json = MessageToJson(message.field1(), {})));
237 UASSERT_NO_THROW((sample = CreateSampleJson(message.field1())));
238 ASSERT_TRUE(json.IsObject());
239 EXPECT_EQ(json, sample);
240 EXPECT_EQ(json["@type"].As<std::string>(), "type.googleapis.com/proto_json.messages.Int32Message");
241 EXPECT_EQ(json["field1"].As<std::int32_t>(), 1);
242 EXPECT_EQ(json["field2"].As<std::int32_t>(), 2);
243 EXPECT_EQ(json["field3"].As<std::int32_t>(), 3);
244}
245
246TEST(AnyToJsonAdditionalTest, InlinedNull) {
247 proto_json::messages::AnyMessage message;
248 formats::json::Value json;
249 formats::json::Value sample;
250
251 UASSERT_NO_THROW((json = MessageToJson(message.field1(), {})));
252 UASSERT_NO_THROW((sample = CreateSampleJson(message.field1())));
253 ASSERT_TRUE(json.IsObject());
254 EXPECT_EQ(json, sample);
255 EXPECT_EQ(json.GetSize(), std::size_t{0});
256}
257
258TEST(AnyToJsonAdditionalTest, DynamicMessage) {
259 using Message = ::google::protobuf::Any;
260 using ProtobufStringType =
261 decltype(std::declval<::google::protobuf::Reflection>()
262 .GetString(std::declval<const ::google::protobuf::Message&>(), nullptr));
263
264 ::google::protobuf::DynamicMessageFactory factory;
265 proto_json::messages::Int32Message payload;
266 ProtobufStringType payload_str;
267
268 payload.set_field1(1001);
269 payload.set_field2(1002);
270 payload.set_field3(1003);
271
272 ASSERT_TRUE(payload.SerializeToString(&payload_str));
273 proto_json::messages::Int32Message payload2;
274
275 {
276 std::unique_ptr<::google::protobuf::Message> message(factory.GetPrototype(Message::descriptor())->New());
277 const auto reflection = message->GetReflection();
278 const auto type_url_desc = message->GetDescriptor()->FindFieldByName("type_url");
279 const auto value_desc = message->GetDescriptor()->FindFieldByName("value");
280
281 reflection->SetString(message.get(), type_url_desc, "type.googleapis.com/proto_json.messages.Int32Message");
282 reflection->SetString(message.get(), value_desc, payload_str);
283
284 formats::json::Value json;
285
286 UASSERT_NO_THROW((json = MessageToJson(*message, {})));
287 ASSERT_TRUE(json.IsObject());
288 ASSERT_EQ(json.GetSize(), std::size_t{4});
289 EXPECT_EQ(json["@type"].As<std::string>(), "type.googleapis.com/proto_json.messages.Int32Message");
290 EXPECT_EQ(json["field1"].As<std::int32_t>(), 1001);
291 EXPECT_EQ(json["field2"].As<std::int32_t>(), 1002);
292 EXPECT_EQ(json["field3"].As<std::int32_t>(), 1003);
293 }
294}
295
296} // namespace protobuf::json::tests
297
298USERVER_NAMESPACE_END