1 // This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
2 // the main distribution directory for license terms and copyright or visit
3 // https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
4
5 #define CAF_SUITE json_writer
6
7 #include "caf/json_writer.hpp"
8
9 #include "core-test.hpp"
10
11 using namespace caf;
12
13 using namespace std::literals::string_literals;
14
15 namespace {
16
17 struct fixture {
18 template <class T>
19 expected<std::string>
to_json_string__anon9543816a0111::fixture20 to_json_string(T&& x, size_t indentation,
21 bool skip_empty_fields
22 = json_writer::skip_empty_fields_default) {
23 json_writer writer;
24 writer.indentation(indentation);
25 writer.skip_empty_fields(skip_empty_fields);
26 if (writer.apply(std::forward<T>(x))) {
27 auto buf = writer.str();
28 return {std::string{buf.begin(), buf.end()}};
29 } else {
30 MESSAGE("partial JSON output: " << writer.str());
31 return {writer.get_error()};
32 }
33 }
34 };
35
36 } // namespace
37
CAF_TEST_FIXTURE_SCOPE(json_writer_tests,fixture)38 CAF_TEST_FIXTURE_SCOPE(json_writer_tests, fixture)
39
40 SCENARIO("the JSON writer converts builtin types to strings") {
41 GIVEN("an integer") {
42 auto x = 42;
43 WHEN("converting it to JSON with any indentation factor") {
44 THEN("the JSON output is the number") {
45 CHECK_EQ(to_json_string(x, 0), "42"s);
46 CHECK_EQ(to_json_string(x, 2), "42"s);
47 }
48 }
49 }
50 GIVEN("a string") {
51 std::string x = R"_(hello "world"!)_";
52 WHEN("converting it to JSON with any indentation factor") {
53 THEN("the JSON output is the escaped string") {
54 std::string out = R"_("hello \"world\"!")_";
55 CHECK_EQ(to_json_string(x, 0), out);
56 CHECK_EQ(to_json_string(x, 2), out);
57 }
58 }
59 }
60 GIVEN("a list") {
61 auto x = std::vector<int>{1, 2, 3};
62 WHEN("converting it to JSON with indentation factor 0") {
63 THEN("the JSON output is a single line") {
64 std::string out = "[1, 2, 3]";
65 CHECK_EQ(to_json_string(x, 0), out);
66 }
67 }
68 WHEN("converting it to JSON with indentation factor 2") {
69 THEN("the JSON output uses multiple lines") {
70 std::string out = R"_([
71 1,
72 2,
73 3
74 ])_";
75 CHECK_EQ(to_json_string(x, 2), out);
76 }
77 }
78 }
79 GIVEN("a dictionary") {
80 std::map<std::string, std::string> x;
81 x.emplace("a", "A");
82 x.emplace("b", "B");
83 x.emplace("c", "C");
84 WHEN("converting it to JSON with indentation factor 0") {
85 THEN("the JSON output is a single line") {
86 CHECK_EQ(to_json_string(x, 0), R"_({"a": "A", "b": "B", "c": "C"})_"s);
87 }
88 }
89 WHEN("converting it to JSON with indentation factor 2") {
90 THEN("the JSON output uses multiple lines") {
91 std::string out = R"_({
92 "a": "A",
93 "b": "B",
94 "c": "C"
95 })_";
96 CHECK_EQ(to_json_string(x, 2), out);
97 }
98 }
99 }
100 GIVEN("a message") {
101 auto x = make_message(put_atom_v, "foo", 42);
102 WHEN("converting it to JSON with indentation factor 0") {
103 THEN("the JSON output is a single line") {
104 std::string out = R"_([{"@type": "caf::put_atom"}, "foo", 42])_";
105 CHECK_EQ(to_json_string(x, 0), out);
106 }
107 }
108 WHEN("converting it to JSON with indentation factor 2") {
109 THEN("the JSON output uses multiple lines") {
110 std::string out = R"_([
111 {
112 "@type": "caf::put_atom"
113 },
114 "foo",
115 42
116 ])_";
117 CHECK_EQ(to_json_string(x, 2), out);
118 }
119 }
120 }
121 }
122
123 SCENARIO("the JSON writer converts simple structs to strings") {
124 GIVEN("a dummy_struct object") {
125 dummy_struct x{10, "foo"};
126 WHEN("converting it to JSON with indentation factor 0") {
127 THEN("the JSON output is a single line") {
128 std::string out = R"_({"@type": "dummy_struct", "a": 10, "b": "foo"})_";
129 CHECK_EQ(to_json_string(x, 0), out);
130 }
131 }
132 WHEN("converting it to JSON with indentation factor 2") {
133 THEN("the JSON output uses multiple lines") {
134 std::string out = R"_({
135 "@type": "dummy_struct",
136 "a": 10,
137 "b": "foo"
138 })_";
139 CHECK_EQ(to_json_string(x, 2), out);
140 }
141 }
142 }
143 }
144
145 SCENARIO("the JSON writer converts nested structs to strings") {
146 GIVEN("a rectangle object") {
147 auto x = rectangle{{100, 200}, {10, 20}};
148 WHEN("converting it to JSON with indentation factor 0") {
149 THEN("the JSON output is a single line") {
150 std::string out = R"({"@type": "rectangle", )"
151 R"("top-left": {"x": 100, "y": 200}, )"
152 R"("bottom-right": {"x": 10, "y": 20}})";
153 CHECK_EQ(to_json_string(x, 0), out);
154 }
155 }
156 WHEN("converting it to JSON with indentation factor 2") {
157 THEN("the JSON output uses multiple lines") {
158 std::string out = R"_({
159 "@type": "rectangle",
160 "top-left": {
161 "x": 100,
162 "y": 200
163 },
164 "bottom-right": {
165 "x": 10,
166 "y": 20
167 }
168 })_";
169 CHECK_EQ(to_json_string(x, 2), out);
170 }
171 }
172 }
173 }
174
175 SCENARIO("the JSON writer converts structs with member dictionaries") {
176 GIVEN("a phone_book object") {
177 phone_book x;
178 x.city = "Model City";
179 x.entries["Bob"] = 555'6837;
180 x.entries["Jon"] = 555'9347;
181 WHEN("converting it to JSON with indentation factor 0") {
182 THEN("the JSON output is a single line") {
183 std::string out = R"({"@type": "phone_book",)"
184 R"( "city": "Model City",)"
185 R"( "entries": )"
186 R"({"Bob": 5556837,)"
187 R"( "Jon": 5559347}})";
188 CHECK_EQ(to_json_string(x, 0), out);
189 }
190 }
191 WHEN("converting it to JSON with indentation factor 2") {
192 THEN("the JSON output uses multiple lines") {
193 std::string out = R"({
194 "@type": "phone_book",
195 "city": "Model City",
196 "entries": {
197 "Bob": 5556837,
198 "Jon": 5559347
199 }
200 })";
201 CHECK_EQ(to_json_string(x, 2), out);
202 }
203 }
204 }
205 }
206
207 SCENARIO("the JSON writer omits or nulls missing values") {
208 GIVEN("a dummy_user object without nickname") {
209 dummy_user user;
210 user.name = "Bjarne";
211 WHEN("converting it to JSON with skip_empty_fields = true (default)") {
212 THEN("the JSON output omits the field 'nickname'") {
213 std::string out = R"({"@type": "dummy_user", "name": "Bjarne"})";
214 CHECK_EQ(to_json_string(user, 0), out);
215 }
216 }
217 WHEN("converting it to JSON with skip_empty_fields = false") {
218 THEN("the JSON output includes the field 'nickname' with a null value") {
219 std::string out
220 = R"({"@type": "dummy_user", "name": "Bjarne", "nickname": null})";
221 CHECK_EQ(to_json_string(user, 0, false), out);
222 }
223 }
224 }
225 }
226
227 SCENARIO("the JSON writer annotates variant fields") {
228 GIVEN("a widget object with rectangle shape") {
229 widget x;
230 x.color = "red";
231 x.shape = rectangle{{10, 10}, {20, 20}};
232 WHEN("converting it to JSON with indentation factor 0") {
233 THEN("the JSON is a single line containing '@shape-type = rectangle'") {
234 std::string out = R"({"@type": "widget", )"
235 R"("color": "red", )"
236 R"("@shape-type": "rectangle", )"
237 R"("shape": )"
238 R"({"top-left": {"x": 10, "y": 10}, )"
239 R"("bottom-right": {"x": 20, "y": 20}}})";
240 CHECK_EQ(to_json_string(x, 0), out);
241 }
242 }
243 WHEN("converting it to JSON with indentation factor 2") {
244 THEN("the JSON is multiple lines containing '@shape-type = rectangle'") {
245 std::string out = R"({
246 "@type": "widget",
247 "color": "red",
248 "@shape-type": "rectangle",
249 "shape": {
250 "top-left": {
251 "x": 10,
252 "y": 10
253 },
254 "bottom-right": {
255 "x": 20,
256 "y": 20
257 }
258 }
259 })";
260 CHECK_EQ(to_json_string(x, 2), out);
261 }
262 }
263 }
264 GIVEN("a widget object with circle shape") {
265 widget x;
266 x.color = "red";
267 x.shape = circle{{15, 15}, 5};
268 WHEN("converting it to JSON with indentation factor 0") {
269 THEN("the JSON is a single line containing '@shape-type = circle'") {
270 std::string out = R"({"@type": "widget", )"
271 R"("color": "red", )"
272 R"("@shape-type": "circle", )"
273 R"("shape": )"
274 R"({"center": {"x": 15, "y": 15}, )"
275 R"("radius": 5}})";
276 CHECK_EQ(to_json_string(x, 0), out);
277 }
278 }
279 WHEN("converting it to JSON with indentation factor 2") {
280 THEN("the JSON is multiple lines containing '@shape-type = circle'") {
281 std::string out = R"({
282 "@type": "widget",
283 "color": "red",
284 "@shape-type": "circle",
285 "shape": {
286 "center": {
287 "x": 15,
288 "y": 15
289 },
290 "radius": 5
291 }
292 })";
293 CHECK_EQ(to_json_string(x, 2), out);
294 }
295 }
296 }
297 }
298
299 CAF_TEST_FIXTURE_SCOPE_END()
300