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