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