1/*
2 * Copyright 2012-present Facebook, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17// @author Nicholas Ormrod <njormrod@fb.com>
18
19#include <folly/DynamicConverter.h>
20
21#include <folly/FBVector.h>
22#include <folly/portability/GTest.h>
23
24#include <algorithm>
25#include <map>
26#include <vector>
27
28using namespace folly;
29using namespace folly::dynamicconverter_detail;
30
31TEST(DynamicConverter, template_metaprogramming) {
32 struct A {};
33
34 bool c1f = is_container<int>::value;
35 bool c2f = is_container<std::pair<int, int>>::value;
36 bool c3f = is_container<A>::value;
37 bool c4f = class_is_container<A>::value;
38
39 bool c1t = is_container<std::vector<int>>::value;
40 bool c2t = is_container<std::set<int>>::value;
41 bool c3t = is_container<std::map<int, int>>::value;
42 bool c4t = class_is_container<std::vector<A>>::value;
43
44 EXPECT_EQ(c1f, false);
45 EXPECT_EQ(c2f, false);
46 EXPECT_EQ(c3f, false);
47 EXPECT_EQ(c4f, false);
48 EXPECT_EQ(c1t, true);
49 EXPECT_EQ(c2t, true);
50 EXPECT_EQ(c3t, true);
51 EXPECT_EQ(c4t, true);
52
53 bool m1f = is_map<int>::value;
54 bool m2f = is_map<std::set<int>>::value;
55
56 bool m1t = is_map<std::map<int, int>>::value;
57
58 EXPECT_EQ(m1f, false);
59 EXPECT_EQ(m2f, false);
60 EXPECT_EQ(m1t, true);
61
62 bool r1f = is_range<int>::value;
63
64 bool r1t = is_range<std::set<int>>::value;
65 bool r2t = is_range<std::vector<int>>::value;
66
67 EXPECT_EQ(r1f, false);
68 EXPECT_EQ(r1t, true);
69 EXPECT_EQ(r2t, true);
70}
71
72TEST(DynamicConverter, arithmetic_types) {
73 dynamic d1 = 12;
74 auto i1 = convertTo<int>(d1);
75 EXPECT_EQ(i1, 12);
76
77 dynamic d2 = 123456789012345;
78 auto i2 = convertTo<int64_t>(d2);
79 EXPECT_EQ(i2, 123456789012345);
80
81 dynamic d4 = 3.141;
82 auto i4 = convertTo<float>(d4);
83 EXPECT_EQ((int)(i4 * 100), 314);
84
85 dynamic d5 = true;
86 auto i5 = convertTo<bool>(d5);
87 EXPECT_EQ(i5, true);
88
89 dynamic d6 = 15;
90 const auto i6 = convertTo<const int>(d6);
91 EXPECT_EQ(i6, 15);
92
93 dynamic d7 = "87";
94 auto i7 = convertTo<int>(d7);
95 EXPECT_EQ(i7, 87);
96
97 dynamic d8 = "false";
98 auto i8 = convertTo<bool>(d8);
99 EXPECT_EQ(i8, false);
100}
101
102TEST(DynamicConverter, enums) {
103 enum enum1 { foo = 1, bar = 2 };
104
105 dynamic d1 = 1;
106 auto i1 = convertTo<enum1>(d1);
107 EXPECT_EQ(i1, foo);
108
109 dynamic d2 = 2;
110 auto i2 = convertTo<enum1>(d2);
111 EXPECT_EQ(i2, bar);
112
113 enum class enum2 { FOO = 1, BAR = 2 };
114
115 dynamic d3 = 1;
116 auto i3 = convertTo<enum2>(d3);
117 EXPECT_EQ(i3, enum2::FOO);
118
119 dynamic d4 = 2;
120 auto i4 = convertTo<enum2>(d4);
121 EXPECT_EQ(i4, enum2::BAR);
122}
123
124TEST(DynamicConverter, simple_builtins) {
125 dynamic d1 = "Haskell";
126 auto i1 = convertTo<folly::fbstring>(d1);
127 EXPECT_EQ(i1, "Haskell");
128
129 dynamic d2 = 13;
130 auto i2 = convertTo<std::string>(d2);
131 EXPECT_EQ(i2, "13");
132
133 dynamic d3 = dynamic::array(12, "Scala");
134 auto i3 = convertTo<std::pair<int, std::string>>(d3);
135 EXPECT_EQ(i3.first, 12);
136 EXPECT_EQ(i3.second, "Scala");
137
138 dynamic d4 = dynamic::object("C", "C++");
139 auto i4 = convertTo<std::pair<std::string, folly::fbstring>>(d4);
140 EXPECT_EQ(i4.first, "C");
141 EXPECT_EQ(i4.second, "C++");
142}
143
144TEST(DynamicConverter, simple_fbvector) {
145 dynamic d1 = dynamic::array(1, 2, 3);
146 auto i1 = convertTo<folly::fbvector<int>>(d1);
147 decltype(i1) i1b = {1, 2, 3};
148 EXPECT_EQ(i1, i1b);
149}
150
151TEST(DynamicConverter, simple_container) {
152 dynamic d1 = dynamic::array(1, 2, 3);
153 auto i1 = convertTo<std::vector<int>>(d1);
154 decltype(i1) i1b = {1, 2, 3};
155 EXPECT_EQ(i1, i1b);
156
157 dynamic d2 = dynamic::array(1, 3, 5, 2, 4);
158 auto i2 = convertTo<std::set<int>>(d2);
159 decltype(i2) i2b = {1, 2, 3, 5, 4};
160 EXPECT_EQ(i2, i2b);
161}
162
163TEST(DynamicConverter, simple_map) {
164 dynamic d1 = dynamic::object(1, "one")(2, "two");
165 auto i1 = convertTo<std::map<int, std::string>>(d1);
166 decltype(i1) i1b = {{1, "one"}, {2, "two"}};
167 EXPECT_EQ(i1, i1b);
168
169 dynamic d2 =
170 dynamic::array(dynamic::array(3, "three"), dynamic::array(4, "four"));
171 auto i2 = convertTo<std::unordered_map<int, std::string>>(d2);
172 decltype(i2) i2b = {{3, "three"}, {4, "four"}};
173 EXPECT_EQ(i2, i2b);
174}
175
176TEST(DynamicConverter, map_keyed_by_string) {
177 dynamic d1 = dynamic::object("1", "one")("2", "two");
178 auto i1 = convertTo<std::map<std::string, std::string>>(d1);
179 decltype(i1) i1b = {{"1", "one"}, {"2", "two"}};
180 EXPECT_EQ(i1, i1b);
181
182 dynamic d2 =
183 dynamic::array(dynamic::array("3", "three"), dynamic::array("4", "four"));
184 auto i2 = convertTo<std::unordered_map<std::string, std::string>>(d2);
185 decltype(i2) i2b = {{"3", "three"}, {"4", "four"}};
186 EXPECT_EQ(i2, i2b);
187}
188
189TEST(DynamicConverter, map_to_vector_of_pairs) {
190 dynamic d1 = dynamic::object("1", "one")("2", "two");
191 auto i1 = convertTo<std::vector<std::pair<std::string, std::string>>>(d1);
192 std::sort(i1.begin(), i1.end());
193 decltype(i1) i1b = {{"1", "one"}, {"2", "two"}};
194 EXPECT_EQ(i1, i1b);
195}
196
197TEST(DynamicConverter, nested_containers) {
198 dynamic d1 =
199 dynamic::array(dynamic::array(1), dynamic::array(), dynamic::array(2, 3));
200 auto i1 = convertTo<folly::fbvector<std::vector<uint8_t>>>(d1);
201 decltype(i1) i1b = {{1}, {}, {2, 3}};
202 EXPECT_EQ(i1, i1b);
203
204 dynamic h2a = dynamic::array("3", ".", "1", "4");
205 dynamic h2b = dynamic::array("2", ".", "7", "2");
206 dynamic d2 = dynamic::object(3.14, h2a)(2.72, h2b);
207 auto i2 = convertTo<std::map<double, std::vector<folly::fbstring>>>(d2);
208 decltype(i2) i2b = {
209 {3.14, {"3", ".", "1", "4"}},
210 {2.72, {"2", ".", "7", "2"}},
211 };
212 EXPECT_EQ(i2, i2b);
213}
214
215struct A {
216 int i;
217 bool operator==(const A& o) const {
218 return i == o.i;
219 }
220};
221namespace folly {
222template <>
223struct DynamicConverter<A> {
224 static A convert(const dynamic& d) {
225 return {convertTo<int>(d["i"])};
226 }
227};
228} // namespace folly
229TEST(DynamicConverter, custom_class) {
230 dynamic d1 = dynamic::object("i", 17);
231 auto i1 = convertTo<A>(d1);
232 EXPECT_EQ(i1.i, 17);
233
234 dynamic d2 =
235 dynamic::array(dynamic::object("i", 18), dynamic::object("i", 19));
236 auto i2 = convertTo<std::vector<A>>(d2);
237 decltype(i2) i2b = {{18}, {19}};
238 EXPECT_EQ(i2, i2b);
239}
240
241TEST(DynamicConverter, crazy) {
242 // we are going to create a vector<unordered_map<bool, T>>
243 // we will construct some of the maps from dynamic objects,
244 // some from a vector of KV pairs.
245 // T will be vector<set<string>>
246
247 std::set<std::string> s1 = {"a", "e", "i", "o", "u"};
248 std::set<std::string> s2 = {"2", "3", "5", "7"};
249 std::set<std::string> s3 = {"Hello", "World"};
250
251 std::vector<std::set<std::string>> v1 = {};
252 std::vector<std::set<std::string>> v2 = {s1, s2};
253 std::vector<std::set<std::string>> v3 = {s3};
254
255 std::unordered_map<bool, std::vector<std::set<std::string>>> m1 = {
256 {true, v1}, {false, v2}};
257 std::unordered_map<bool, std::vector<std::set<std::string>>> m2 = {
258 {true, v3}};
259
260 std::vector<std::unordered_map<bool, std::vector<std::set<std::string>>>> f1 =
261 {m1, m2};
262
263 dynamic ds1 = dynamic::array("a", "e", "i", "o", "u");
264 dynamic ds2 = dynamic::array("2", "3", "5", "7");
265 dynamic ds3 = dynamic::array("Hello", "World");
266
267 dynamic dv1 = dynamic::array;
268 dynamic dv2 = dynamic::array(ds1, ds2);
269 dynamic dv3(dynamic::array(ds3));
270
271 dynamic dm1 = dynamic::object(true, dv1)(false, dv2);
272 dynamic dm2 = dynamic::array(dynamic::array(true, dv3));
273
274 dynamic df1 = dynamic::array(dm1, dm2);
275
276 auto i = convertTo<std::vector<
277 std::unordered_map<bool, std::vector<std::set<std::string>>>>>(
278 df1); // yes, that is 5 close-chevrons
279
280 EXPECT_EQ(f1, i);
281}
282
283TEST(DynamicConverter, consts) {
284 dynamic d1 = 7.5;
285 auto i1 = convertTo<const double>(d1);
286 EXPECT_EQ(7.5, i1);
287
288 dynamic d2 = "Hello";
289 auto i2 = convertTo<const std::string>(d2);
290 decltype(i2) i2b = "Hello";
291 EXPECT_EQ(i2b, i2);
292
293 dynamic d3 = true;
294 auto i3 = convertTo<const bool>(d3);
295 EXPECT_TRUE(i3);
296
297 dynamic d4 = "true";
298 auto i4 = convertTo<const bool>(d4);
299 EXPECT_TRUE(i4);
300
301 dynamic d5 = dynamic::array(1, 2);
302 auto i5 = convertTo<const std::pair<const int, const int>>(d5);
303 decltype(i5) i5b = {1, 2};
304 EXPECT_EQ(i5b, i5);
305}
306
307struct Token {
308 int kind_;
309 fbstring lexeme_;
310
311 explicit Token(int kind, const fbstring& lexeme)
312 : kind_(kind), lexeme_(lexeme) {}
313};
314
315namespace folly {
316template <>
317struct DynamicConverter<Token> {
318 static Token convert(const dynamic& d) {
319 int k = convertTo<int>(d["KIND"]);
320 fbstring lex = convertTo<fbstring>(d["LEXEME"]);
321 return Token(k, lex);
322 }
323};
324} // namespace folly
325
326TEST(DynamicConverter, example) {
327 dynamic d1 = dynamic::object("KIND", 2)("LEXEME", "a token");
328 auto i1 = convertTo<Token>(d1);
329 EXPECT_EQ(i1.kind_, 2);
330 EXPECT_EQ(i1.lexeme_, "a token");
331}
332
333TEST(DynamicConverter, construct) {
334 using std::map;
335 using std::pair;
336 using std::string;
337 using std::vector;
338 {
339 vector<int> c{1, 2, 3};
340 dynamic d = dynamic::array(1, 2, 3);
341 EXPECT_EQ(d, toDynamic(c));
342 }
343
344 {
345 vector<float> c{1.0f, 2.0f, 4.0f};
346 dynamic d = dynamic::array(1.0, 2.0, 4.0);
347 EXPECT_EQ(d, toDynamic(c));
348 }
349
350 {
351 map<int, int> c{{2, 4}, {3, 9}};
352 dynamic d = dynamic::object(2, 4)(3, 9);
353 EXPECT_EQ(d, toDynamic(c));
354 }
355
356 {
357 map<string, string> c{{"a", "b"}};
358 dynamic d = dynamic::object("a", "b");
359 EXPECT_EQ(d, toDynamic(c));
360 }
361
362 {
363 map<string, pair<string, int>> c{{"a", {"b", 3}}};
364 dynamic d = dynamic::object("a", dynamic::array("b", 3));
365 EXPECT_EQ(d, toDynamic(c));
366 }
367
368 {
369 map<string, pair<string, int>> c{{"a", {"b", 3}}};
370 dynamic d = dynamic::object("a", dynamic::array("b", 3));
371 EXPECT_EQ(d, toDynamic(c));
372 }
373
374 {
375 vector<int> vi{2, 3, 4, 5};
376 auto c = std::make_pair(
377 range(vi.begin(), vi.begin() + 3),
378 range(vi.begin() + 1, vi.begin() + 4));
379 dynamic d =
380 dynamic::array(dynamic::array(2, 3, 4), dynamic::array(3, 4, 5));
381 EXPECT_EQ(d, toDynamic(c));
382 }
383
384 {
385 vector<bool> vb{true, false};
386 dynamic d = dynamic::array(true, false);
387 EXPECT_EQ(d, toDynamic(vb));
388 }
389}
390
391TEST(DynamicConverter, errors) {
392 const auto int32Over =
393 static_cast<int64_t>(std::numeric_limits<int32_t>().max()) + 1;
394 const auto floatOver =
395 static_cast<double>(std::numeric_limits<float>().max()) * 2;
396
397 dynamic d1 = int32Over;
398 EXPECT_THROW(convertTo<int32_t>(d1), std::range_error);
399
400 dynamic d2 = floatOver;
401 EXPECT_THROW(convertTo<float>(d2), std::range_error);
402}
403
404TEST(DynamicConverter, partial_dynamics) {
405 std::vector<dynamic> c{
406 dynamic::array(2, 3, 4),
407 dynamic::array(3, 4, 5),
408 };
409 dynamic d = dynamic::array(dynamic::array(2, 3, 4), dynamic::array(3, 4, 5));
410 EXPECT_EQ(d, toDynamic(c));
411
412 std::unordered_map<std::string, dynamic> m{{"one", 1}, {"two", 2}};
413 dynamic md = dynamic::object("one", 1)("two", 2);
414 EXPECT_EQ(md, toDynamic(m));
415}
416
417TEST(DynamicConverter, asan_exception_case_umap) {
418 EXPECT_THROW(
419 (convertTo<std::unordered_map<int, int>>(dynamic::array(1))), TypeError);
420}
421
422TEST(DynamicConverter, asan_exception_case_uset) {
423 EXPECT_THROW(
424 (convertTo<std::unordered_set<int>>(
425 dynamic::array(1, dynamic::array(), 3))),
426 TypeError);
427}
428
429static int constructB = 0;
430static int destroyB = 0;
431static int ticker = 0;
432struct B {
433 struct BException : std::exception {};
434
435 /* implicit */ B(int x) : x_(x) {
436 if (ticker-- == 0) {
437 throw BException();
438 }
439 constructB++;
440 }
441 B(const B& o) : x_(o.x_) {
442 constructB++;
443 }
444 ~B() {
445 destroyB++;
446 }
447 int x_;
448};
449namespace folly {
450template <>
451struct DynamicConverter<B> {
452 static B convert(const dynamic& d) {
453 return B(convertTo<int>(d));
454 }
455};
456} // namespace folly
457
458TEST(DynamicConverter, double_destroy) {
459 dynamic d = dynamic::array(1, 3, 5, 7, 9, 11, 13, 15, 17);
460 ticker = 3;
461
462 EXPECT_THROW(convertTo<std::vector<B>>(d), B::BException);
463 EXPECT_EQ(constructB, destroyB);
464}
465
466TEST(DynamicConverter, simple_vector_bool) {
467 std::vector<bool> bools{true, false};
468 auto d = toDynamic(bools);
469 auto actual = convertTo<decltype(bools)>(d);
470 EXPECT_EQ(bools, actual);
471}
472