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