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 #pragma once
20 
21 #include <iterator>
22 #include <type_traits>
23 
24 #include <boost/iterator/iterator_adaptor.hpp>
25 #include <boost/mpl/has_xxx.hpp>
26 
27 #include <folly/Likely.h>
28 #include <folly/Optional.h>
29 #include <folly/Traits.h>
30 #include <folly/Utility.h>
31 #include <folly/dynamic.h>
32 #include <folly/lang/Exception.h>
33 
34 namespace folly {
35 template <typename T>
36 T convertTo(const dynamic&);
37 template <typename T>
38 dynamic toDynamic(const T&);
39 } // namespace folly
40 
41 /**
42  * convertTo returns a well-typed representation of the input dynamic.
43  *
44  * Example:
45  *
46  *   dynamic d = dynamic::array(
47  *       dynamic::array(1, 2, 3),
48  *       dynamic::array(4, 5)); // a vector of vector of int
49  *   auto vvi = convertTo<fbvector<fbvector<int>>>(d);
50  *
51  * See docs/DynamicConverter.md for supported types and customization
52  */
53 
54 namespace folly {
55 
56 ///////////////////////////////////////////////////////////////////////////////
57 // traits
58 
59 namespace dynamicconverter_detail {
60 
61 BOOST_MPL_HAS_XXX_TRAIT_DEF(value_type)
62 BOOST_MPL_HAS_XXX_TRAIT_DEF(iterator)
63 BOOST_MPL_HAS_XXX_TRAIT_DEF(mapped_type)
64 BOOST_MPL_HAS_XXX_TRAIT_DEF(key_type)
65 
66 template <typename T>
67 struct iterator_class_is_container {
68   typedef std::reverse_iterator<typename T::iterator> some_iterator;
69   enum {
70     value = has_value_type<T>::value &&
71         std::is_constructible<T, some_iterator, some_iterator>::value
72   };
73 };
74 
75 template <typename T>
76 using class_is_container =
77     Conjunction<has_iterator<T>, iterator_class_is_container<T>>;
78 
79 template <typename T>
80 using is_range = StrictConjunction<has_value_type<T>, has_iterator<T>>;
81 
82 template <typename T>
83 using is_container = StrictConjunction<std::is_class<T>, class_is_container<T>>;
84 
85 template <typename T>
86 using is_map = StrictConjunction<is_range<T>, has_mapped_type<T>>;
87 
88 template <typename T>
89 using is_associative = StrictConjunction<is_range<T>, has_key_type<T>>;
90 
91 } // namespace dynamicconverter_detail
92 
93 ///////////////////////////////////////////////////////////////////////////////
94 // custom iterators
95 
96 /**
97  * We have iterators that dereference to dynamics, but need iterators
98  * that dereference to typename T.
99  *
100  * Implementation details:
101  *   1. We cache the value of the dereference operator. This is necessary
102  *      because boost::iterator_adaptor requires *it to return a
103  *      reference.
104  *   2. For const reasons, we cannot call operator= to refresh the
105  *      cache: we must call the destructor then placement new.
106  */
107 
108 namespace dynamicconverter_detail {
109 
110 template <typename T>
111 struct Dereferencer {
derefToCacheDereferencer112   static inline void derefToCache(
113       Optional<T>* /* mem */, const dynamic::const_item_iterator& /* it */) {
114     throw_exception<TypeError>("array", dynamic::Type::OBJECT);
115   }
116 
derefToCacheDereferencer117   static inline void derefToCache(
118       Optional<T>* mem, const dynamic::const_iterator& it) {
119     mem->emplace(convertTo<T>(*it));
120   }
121 };
122 
123 template <typename F, typename S>
124 struct Dereferencer<std::pair<F, S>> {
125   static inline void derefToCache(
126       Optional<std::pair<F, S>>* mem, const dynamic::const_item_iterator& it) {
127     mem->emplace(convertTo<F>(it->first), convertTo<S>(it->second));
128   }
129 
130   // Intentional duplication of the code in Dereferencer
131   template <typename T>
132   static inline void derefToCache(
133       Optional<T>* mem, const dynamic::const_iterator& it) {
134     mem->emplace(convertTo<T>(*it));
135   }
136 };
137 
138 template <typename T, typename It>
139 class Transformer
140     : public boost::
141           iterator_adaptor<Transformer<T, It>, It, typename T::value_type> {
142   friend class boost::iterator_core_access;
143 
144   typedef typename T::value_type ttype;
145 
146   mutable Optional<ttype> cache_;
147 
148   void increment() {
149     ++this->base_reference();
150     cache_ = none;
151   }
152 
153   ttype& dereference() const {
154     if (!cache_) {
155       Dereferencer<ttype>::derefToCache(&cache_, this->base_reference());
156     }
157     return cache_.value();
158   }
159 
160  public:
161   explicit Transformer(const It& it) : Transformer::iterator_adaptor_(it) {}
162 };
163 
164 // conversion factory
165 template <typename T, typename It>
166 inline std::move_iterator<Transformer<T, It>> conversionIterator(const It& it) {
167   return std::make_move_iterator(Transformer<T, It>(it));
168 }
169 
170 } // namespace dynamicconverter_detail
171 
172 ///////////////////////////////////////////////////////////////////////////////
173 // DynamicConverter specializations
174 
175 /**
176  * Each specialization of DynamicConverter has the function
177  *     'static T convert(const dynamic&);'
178  */
179 
180 // default - intentionally unimplemented
181 template <typename T, typename Enable = void>
182 struct DynamicConverter;
183 
184 // boolean
185 template <>
186 struct DynamicConverter<bool> {
187   static bool convert(const dynamic& d) { return d.asBool(); }
188 };
189 
190 // integrals
191 template <typename T>
192 struct DynamicConverter<
193     T,
194     typename std::enable_if<
195         std::is_integral<T>::value && !std::is_same<T, bool>::value>::type> {
196   static T convert(const dynamic& d) { return folly::to<T>(d.asInt()); }
197 };
198 
199 // enums
200 template <typename T>
201 struct DynamicConverter<
202     T,
203     typename std::enable_if<std::is_enum<T>::value>::type> {
204   static T convert(const dynamic& d) {
205     using type = typename std::underlying_type<T>::type;
206     return static_cast<T>(DynamicConverter<type>::convert(d));
207   }
208 };
209 
210 // floating point
211 template <typename T>
212 struct DynamicConverter<
213     T,
214     typename std::enable_if<std::is_floating_point<T>::value>::type> {
215   static T convert(const dynamic& d) { return folly::to<T>(d.asDouble()); }
216 };
217 
218 // fbstring
219 template <>
220 struct DynamicConverter<folly::fbstring> {
221   static folly::fbstring convert(const dynamic& d) { return d.asString(); }
222 };
223 
224 // std::string
225 template <>
226 struct DynamicConverter<std::string> {
227   static std::string convert(const dynamic& d) { return d.asString(); }
228 };
229 
230 // std::pair
231 template <typename F, typename S>
232 struct DynamicConverter<std::pair<F, S>> {
233   static std::pair<F, S> convert(const dynamic& d) {
234     if (d.isArray() && d.size() == 2) {
235       return std::make_pair(convertTo<F>(d[0]), convertTo<S>(d[1]));
236     } else if (d.isObject() && d.size() == 1) {
237       auto it = d.items().begin();
238       return std::make_pair(convertTo<F>(it->first), convertTo<S>(it->second));
239     } else {
240       throw_exception<TypeError>("array (size 2) or object (size 1)", d.type());
241     }
242   }
243 };
244 
245 // non-associative containers
246 template <typename C>
247 struct DynamicConverter<
248     C,
249     typename std::enable_if<
250         dynamicconverter_detail::is_container<C>::value &&
251         !dynamicconverter_detail::is_associative<C>::value>::type> {
252   static C convert(const dynamic& d) {
253     if (d.isArray()) {
254       return C(
255           dynamicconverter_detail::conversionIterator<C>(d.begin()),
256           dynamicconverter_detail::conversionIterator<C>(d.end()));
257     } else if (d.isObject()) {
258       return C(
259           dynamicconverter_detail::conversionIterator<C>(d.items().begin()),
260           dynamicconverter_detail::conversionIterator<C>(d.items().end()));
261     } else {
262       throw_exception<TypeError>("object or array", d.type());
263     }
264   }
265 };
266 
267 // associative containers
268 template <typename C>
269 struct DynamicConverter<
270     C,
271     typename std::enable_if<
272         dynamicconverter_detail::is_container<C>::value &&
273         dynamicconverter_detail::is_associative<C>::value>::type> {
274   static C convert(const dynamic& d) {
275     C ret; // avoid direct initialization due to unordered_map's constructor
276            // causing memory corruption if the iterator throws an exception
277     if (d.isArray()) {
278       ret.insert(
279           dynamicconverter_detail::conversionIterator<C>(d.begin()),
280           dynamicconverter_detail::conversionIterator<C>(d.end()));
281     } else if (d.isObject()) {
282       ret.insert(
283           dynamicconverter_detail::conversionIterator<C>(d.items().begin()),
284           dynamicconverter_detail::conversionIterator<C>(d.items().end()));
285     } else {
286       throw_exception<TypeError>("object or array", d.type());
287     }
288     return ret;
289   }
290 };
291 
292 ///////////////////////////////////////////////////////////////////////////////
293 // DynamicConstructor specializations
294 
295 /**
296  * Each specialization of DynamicConstructor has the function
297  *     'static dynamic construct(const C&);'
298  */
299 
300 // default
301 template <typename C, typename Enable = void>
302 struct DynamicConstructor {
303   static dynamic construct(const C& x) { return dynamic(x); }
304 };
305 
306 // identity
307 template <typename C>
308 struct DynamicConstructor<
309     C,
310     typename std::enable_if<std::is_same<C, dynamic>::value>::type> {
311   static dynamic construct(const C& x) { return x; }
312 };
313 
314 // enums
315 template <typename C>
316 struct DynamicConstructor<
317     C,
318     typename std::enable_if<std::is_enum<C>::value>::type> {
319   static dynamic construct(const C& x) { return dynamic(to_underlying(x)); }
320 };
321 
322 // maps
323 template <typename C>
324 struct DynamicConstructor<
325     C,
326     typename std::enable_if<
327         !std::is_same<C, dynamic>::value &&
328         dynamicconverter_detail::is_map<C>::value>::type> {
329   static dynamic construct(const C& x) {
330     dynamic d = dynamic::object;
331     for (const auto& pair : x) {
332       d.insert(toDynamic(pair.first), toDynamic(pair.second));
333     }
334     return d;
335   }
336 };
337 
338 // other ranges
339 template <typename C>
340 struct DynamicConstructor<
341     C,
342     typename std::enable_if<
343         !std::is_same<C, dynamic>::value &&
344         !dynamicconverter_detail::is_map<C>::value &&
345         !std::is_constructible<StringPiece, const C&>::value &&
346         dynamicconverter_detail::is_range<C>::value>::type> {
347   static dynamic construct(const C& x) {
348     dynamic d = dynamic::array;
349     for (const auto& item : x) {
350       d.push_back(toDynamic(item));
351     }
352     return d;
353   }
354 };
355 
356 // pair
357 template <typename A, typename B>
358 struct DynamicConstructor<std::pair<A, B>, void> {
359   static dynamic construct(const std::pair<A, B>& x) {
360     dynamic d = dynamic::array;
361     d.push_back(toDynamic(x.first));
362     d.push_back(toDynamic(x.second));
363     return d;
364   }
365 };
366 
367 // vector<bool>
368 template <>
369 struct DynamicConstructor<std::vector<bool>, void> {
370   static dynamic construct(const std::vector<bool>& x) {
371     dynamic d = dynamic::array;
372     // Intentionally specifying the type as bool here.
373     // std::vector<bool>'s iterators return a proxy which is a prvalue
374     // and hence cannot bind to an lvalue reference such as auto&
375     for (bool item : x) {
376       d.push_back(toDynamic(item));
377     }
378     return d;
379   }
380 };
381 
382 ///////////////////////////////////////////////////////////////////////////////
383 // implementation
384 
385 template <typename T>
386 T convertTo(const dynamic& d) {
387   return DynamicConverter<typename std::remove_cv<T>::type>::convert(d);
388 }
389 
390 template <typename T>
391 dynamic toDynamic(const T& x) {
392   return DynamicConstructor<typename std::remove_cv<T>::type>::construct(x);
393 }
394 
395 } // namespace folly
396