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 WriteOptions 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 WriteErrorCode expected_errc = {};
30 std::string expected_path = {};
31 WriteOptions 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 true // native implementation prohibits empty payload for some reason if not in legacy mode
55 },
56 AnyToJsonSuccessTestParam{
57 AnyMessageData{Int32MessageData{}},
58 R"({
59 "field1": {
60 "@type":"type.googleapis.com/proto_json.messages.Int32Message",
61 "field1":0,"field2":0,"field3":0
62 }
63 })",
64 {.always_print_fields_with_no_presence = true},
65 true
66 },
67 AnyToJsonSuccessTestParam{
68 AnyMessageData{Int32MessageData{10, 20, 30}},
69 R"({
70 "field1": {
71 "@type":"type.googleapis.com/proto_json.messages.Int32Message",
72 "field1":10,"field2":20,"field3":30
73 }
74 })"
75 },
76 AnyToJsonSuccessTestParam{
77 AnyMessageData{DurationMessageData{123, 987}},
78 R"({
79 "field1": {
80 "@type":"type.googleapis.com/google.protobuf.Duration",
81 "value":"123.000000987s"
82 }
83 })"
84 },
85 AnyToJsonSuccessTestParam{
86 AnyMessageData{ValueMessageData{kProtoNullValue}},
87 R"({
88 "field1": {
89 "@type":"type.googleapis.com/google.protobuf.Value",
90 "value":null
91 }
92 })"
93 },
94 AnyToJsonSuccessTestParam{
95 AnyMessageData{ValueMessageData{1.5}},
96 R"({
97 "field1": {
98 "@type":"type.googleapis.com/google.protobuf.Value",
99 "value":1.5
100 }
101 })"
102 },
103 AnyToJsonSuccessTestParam{
104 AnyMessageData{ValueMessageData{std::vector<double>{1.5, 1.5}}},
105 R"({
106 "field1": {
107 "@type":"type.googleapis.com/google.protobuf.Value",
108 "value":[1.5,1.5]
109 }
110 })"
111 },
112 AnyToJsonSuccessTestParam{
113 AnyMessageData{ValueMessageData{std::map<std::string, std::string>{{"aaa", "hello"}}}},
114 R"({
115 "field1": {
116 "@type":"type.googleapis.com/google.protobuf.Value",
117 "value":{"aaa":"hello"}
118 }
119 })"
120 },
121 AnyToJsonSuccessTestParam{
122 AnyMessageData{Int32MessageData{100, 200, 300}, true},
123 R"({
124 "field1": {
125 "@type":"type.googleapis.com/google.protobuf.Any",
126 "value": {
127 "@type":"type.googleapis.com/proto_json.messages.Int32Message",
128 "field1":100,"field2":200,"field3":300
129 }
130 }
131 })"
132 },
133 AnyToJsonSuccessTestParam{
134 AnyMessageData{DurationMessageData{-123, -567}, true},
135 R"({
136 "field1": {
137 "@type":"type.googleapis.com/google.protobuf.Any",
138 "value": {
139 "@type":"type.googleapis.com/google.protobuf.Duration",
140 "value":"-123.000000567s"
141 }
142 }
143 })"
144 },
145 AnyToJsonSuccessTestParam{
146 AnyMessageData{ValueMessageData{true}, true},
147 R"({
148 "field1": {
149 "@type":"type.googleapis.com/google.protobuf.Any",
150 "value": {
151 "@type":"type.googleapis.com/google.protobuf.Value",
152 "value":true
153 }
154 }
155 })"
156 }
157 )
158);
159
160INSTANTIATE_TEST_SUITE_P(
161 ,
162 AnyToJsonFailureTest,
163 ::testing::Values(
164 AnyToJsonFailureTestParam{AnyMessageData{RawAnyData{"", "\x08\x01"}}, WriteErrorCode::kInvalidValue, "field1"},
165 AnyToJsonFailureTestParam{AnyMessageData{RawAnyData{"oops", ""}}, WriteErrorCode::kInvalidValue, "field1"},
166 AnyToJsonFailureTestParam{
167 AnyMessageData{RawAnyData{"type.googleapis.com/proto_json.messages.NonExistent", "\x08\x01"}},
168 WriteErrorCode::kInvalidValue,
169 "field1"
170 },
171 // '\x80' is incomplete message
172 AnyToJsonFailureTestParam{
173 AnyMessageData{RawAnyData{"type.googleapis.com/proto_json.messages.Int32Message", "\x80"}},
174 WriteErrorCode::kInvalidValue,
175 "field1",
176 {},
177 true // native implementation does not fail on invalid binary data and produces strange JSON
178 },
179 AnyToJsonFailureTestParam{AnyMessageData{DurationMessageData{1, -1}}, WriteErrorCode::kInvalidValue, "field1"},
180 AnyToJsonFailureTestParam{
181 AnyMessageData{ValueMessageData{std::vector<double>{std::numeric_limits<double>::infinity()}}},
182 WriteErrorCode::kInvalidValue,
183 "field1.list_value.values[0].number_value"
184 }
185 )
186);
187
188TEST_P(AnyToJsonSuccessTest, Test) {
189 const auto& param = GetParam();
190
191 auto input = PrepareTestData(param.input);
192 formats::json::Value json, expected_json, sample_json;
193
194 UASSERT_NO_THROW((json = MessageToJson(input, param.options)));
195 UASSERT_NO_THROW((expected_json = PrepareJsonTestData(param.expected_json)));
196
197 if (!param.skip_native_check) {
198 UASSERT_NO_THROW((sample_json = CreateSampleJson(input, param.options)));
199 EXPECT_EQ(json, sample_json);
200 }
201
202 EXPECT_EQ(json, expected_json);
203}
204
205TEST_P(AnyToJsonFailureTest, Test) {
206 const auto& param = GetParam();
207 auto input = PrepareTestData(param.input);
208
209 EXPECT_WRITE_ERROR((void)MessageToJson(input, param.options), param.expected_errc, param.expected_path);
210
211 if (!param.skip_native_check) {
212 UEXPECT_THROW((void)CreateSampleJson(input, param.options), SampleError);
213 }
214}
215
216TEST(AnyToJsonAdditionalTest, InlinedNonNull) {
217 AnyMessageData data{Int32MessageData{1, 2, 3}};
218 auto message = PrepareTestData(data);
219 formats::json::Value json, sample;
220
221 UASSERT_NO_THROW((json = MessageToJson(message.field1())));
222 UASSERT_NO_THROW((sample = CreateSampleJson(message.field1())));
223 ASSERT_TRUE(json.IsObject());
224 EXPECT_EQ(json, sample);
225 EXPECT_EQ(json["@type"].As<std::string>(), "type.googleapis.com/proto_json.messages.Int32Message");
226 EXPECT_EQ(json["field1"].As<std::int32_t>(), 1);
227 EXPECT_EQ(json["field2"].As<std::int32_t>(), 2);
228 EXPECT_EQ(json["field3"].As<std::int32_t>(), 3);
229}
230
231TEST(AnyToJsonAdditionalTest, InlinedNull) {
232 proto_json::messages::AnyMessage message;
233 formats::json::Value json, sample;
234
235 UASSERT_NO_THROW((json = MessageToJson(message.field1())));
236 UASSERT_NO_THROW((sample = CreateSampleJson(message.field1())));
237 ASSERT_TRUE(json.IsObject());
238 EXPECT_EQ(json, sample);
239 EXPECT_EQ(json.GetSize(), std::size_t{0});
240}
241
242TEST(AnyToJsonAdditionalTest, DynamicMessage) {
243 using Message = ::google::protobuf::Any;
244 using ProtobufStringType =
245 decltype(std::declval<::google::protobuf::Reflection>()
246 .GetString(std::declval<const ::google::protobuf::Message&>(), nullptr));
247
248 ::google::protobuf::DynamicMessageFactory factory;
249 proto_json::messages::Int32Message payload;
250 ProtobufStringType payload_str;
251
252 payload.set_field1(1001);
253 payload.set_field2(1002);
254 payload.set_field3(1003);
255
256 ASSERT_TRUE(payload.SerializeToString(&payload_str));
257 proto_json::messages::Int32Message payload2;
258
259 {
260 std::unique_ptr<::google::protobuf::Message> message(factory.GetPrototype(Message::descriptor())->New());
261 const auto reflection = message->GetReflection();
262 const auto type_url_desc = message->GetDescriptor()->FindFieldByName("type_url");
263 const auto value_desc = message->GetDescriptor()->FindFieldByName("value");
264
265 reflection->SetString(message.get(), type_url_desc, "type.googleapis.com/proto_json.messages.Int32Message");
266 reflection->SetString(message.get(), value_desc, payload_str);
267
268 formats::json::Value json;
269
270 UASSERT_NO_THROW((json = MessageToJson(*message)));
271 ASSERT_TRUE(json.IsObject());
272 ASSERT_EQ(json.GetSize(), std::size_t{4});
273 EXPECT_EQ(json["@type"].As<std::string>(), "type.googleapis.com/proto_json.messages.Int32Message");
274 EXPECT_EQ(json["field1"].As<std::int32_t>(), 1001);
275 EXPECT_EQ(json["field2"].As<std::int32_t>(), 1002);
276 EXPECT_EQ(json["field3"].As<std::int32_t>(), 1003);
277 }
278}
279
280} // namespace protobuf::json::tests
281
282USERVER_NAMESPACE_END