give all folly exception types default visibility
[folly.git] / folly / experimental / DynamicParser.h
1 /*
2  * Copyright 2017-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  *  Copyright (c) 2015, Facebook, Inc.
18  *  All rights reserved.
19  *
20  *  This source code is licensed under the BSD-style license found in the
21  *  LICENSE file in the root directory of this source tree. An additional grant
22  *  of patent rights can be found in the PATENTS file in the same directory.
23  *
24  */
25 #pragma once
26
27 #include <folly/CPortability.h>
28 #include <folly/ScopeGuard.h>
29 #include <folly/dynamic.h>
30
31 namespace folly {
32
33 /**
34  * DynamicParser provides a tiny DSL for easily, correctly, and losslessly
35  * parsing a folly::dynamic into any other representation.
36  *
37  * To make this concrete, this lets you take a JSON config that potentially
38  * contains user errors, and parse __all__ of its valid parts, while
39  * automatically and __reversibly__ recording any parts that cause errors:
40  *
41  *   {"my values": {
42  *     "an int": "THIS WILL BE RECORDED AS AN ERROR, BUT WE'LL PARSE THE REST",
43  *     "a double": 3.1415,
44  *     "keys & values": {
45  *       "the sky is blue": true,
46  *       "THIS WILL ALSO BE RECORDED AS AN ERROR": "cheese",
47  *       "2+2=5": false,
48  *     }
49  *   }}
50  *
51  * To parse this JSON, you need no exception handling, it is as easy as:
52  *
53  *   folly::dynamic d = ...;  // Input
54  *   int64_t integer;  // Three outputs
55  *   double real;
56  *   std::map<std::string, bool> enabled_widgets;
57  *   DynamicParser p(DynamicParser::OnError::RECORD, &d);
58  *   p.required("my values", [&]() {
59  *     p.optional("an int", [&](int64_t v) { integer = v; });
60  *     p.required("a double", [&](double v) { real = v; });
61  *     p.optional("keys & values", [&]() {
62  *       p.objectItems([&](std::string widget, bool enabled) {
63  *         enabled_widgets.emplace(widget, enabled);
64  *       });
65  *     });
66  *   });
67  *
68  * Your code in the lambdas can throw, and this will be reported just like
69  * missing key and type conversion errors, with precise context on what part
70  * of the folly::dynamic caused the error.  No need to throw:
71  *   std::runtime_error("Value X at key Y caused a flux capacitor overload")
72  * This will do:
73  *   std::runtime_error("Flux capacitor overload")
74  *
75  * == Keys and values are auto-converted to match your callback ==
76  *
77  * DynamicParser's optional(), required(), objectItems(), and
78  * arrayItems() automatically convert the current key and value to match the
79  * signature of the provided callback.  parser.key() and parser.value() can
80  * be used to access the same data without conversion.
81  *
82  * The following types are supported -- you should generally take arguments
83  * by-value, or by-const-reference for dynamics & strings you do not copy.
84  *
85  *   Key: folly::dynamic (no conversion), std::string, int64_t
86  *   Value: folly::dynamic (no conversion), int64_t, bool, double, std::string
87  *
88  * There are 21 supported callback signatures, of three kinds:
89  *
90  *   1: No arguments -- useful if you will just call more parser methods.
91  *
92  *   5: The value alone -- the common case for optional() and required().
93  *        [&](whatever_t value) {}
94  *
95  *   15: Both the key and the value converted according to the rules above:
96  *        [&](whatever_t key, whatever_t) {}
97  *
98  * NB: The key alone should be rarely needed, but these callback styles
99  *     provide it with no conversion overhead, and only minimal verbosity:
100  *       [&](const std::string& k, const folly::dynamic&) {}
101  *       [&]() { auto k = p.key().asString(); }
102  *
103  * == How `releaseErrors()` can make your parse lossless ==
104  *
105  * If you write parsing code by hand, you usually end up with error-handling
106  * resembling that of OnError::THROW -- the first error you hit aborts the
107  * whole parse, and you report it.
108  *
109  * OnError::RECORD offers a more user-friendly alternative for "parse,
110  * serialize, re-parse" pipelines, akin to what web-forms do.  All
111  * exception-causing parts are losslessly recorded in a parallel
112  * folly::dynamic, available via releaseErrors() at the end of the parse.
113  *
114  * Suppose we fail to look up "key1" at the root, and hit a value error in
115  * "key2": {"subkey2": ...}.  The error report will have the form:
116  *
117  *   {"nested": {
118  *     "key_errors": {"key1": "explanatory message"},
119  *     "value": <whole input>,
120  *     "nested": { "key2": { "nested": {
121  *       "subkey2": {"value": <original value>, "error": "message"}
122  *     } } }
123  *   }}
124  *
125  * Errors in array items are handled just the same, but using integer keys.
126  *
127  * The advantage of this approach is that your parsing can throw wherever,
128  * and DynamicParser isolates it, allowing the good parts to parse.
129  *
130  * Put another way, this makes it easy to implement a transformation that
131  * splits a `folly::dynamic` into a "parsed" part (which might be your
132  * struct meant for runtime use), and a matching "errors" part.  As long as
133  * your successful parses are lossless, you can always reconstruct the
134  * original input from the parse output and the recorded "errors".
135  *
136  * == Limitations ==
137  *
138  *  - The input dynamic should be an object or array. wrapError() could be
139  *    exposed to allow parsing single scalars, but this would not be a
140  *    significant usability improvement over try-catch.
141  *
142  *  - Do NOT try to parse the same part of the input dynamic twice. You
143  *    might report multiple value errors, which is currently unsupported.
144  *
145  *  - optional() does not support defaulting. This is unavoidable, since
146  *    DynamicParser does not dictate how you record parsed data.  If your
147  *    parse writes into an output struct, then it ought to be initialized at
148  *    construction time.  If your output is initialized to default values,
149  *    then you need no "default" feature.  If it is not initialized, you are
150  *    in trouble anyway.  Suppose your optional() parse hits an error.  What
151  *    does your output contain?
152  *      - Uninitialized data :(
153  *      - You rely on an optional() feature to fall back to parsing some
154  *        default dynamic.  Sadly, the default hits a parse error.  Now what?
155  *    Since there is no good way to default, DynamicParser leaves it out.
156  *
157  * == Future: un-parsed items ==
158  *
159  * DynamicParser could support erroring on un-parsed items -- the parts of
160  * the folly::dynamic, which were never asked for.  Here is an ok design:
161  *
162  * (i) At the start of parsing any value, the user may call:
163  *   parser.recursivelyForbidUnparsed();
164  *   parser.recursivelyAllowUnparsed();
165  *   parser.locallyForbidUnparsed();
166  *   parser.locallyAllowUnparsed();
167  *
168  * (ii) At the end of the parse, any unparsed items are dumped to "errors".
169  * For example, failing to parse index 1 out of ["v1", "v2", "v3"] yields:
170  *   "nested": {1: {"unparsed": "v2"}}
171  * or perhaps more verbosely:
172  *   "nested": {1: {"error": "unparsed value", "value": "v2"}}
173  *
174  * By default, unparsed items are allowed. Calling a "forbid" function after
175  * some keys have already been parsed is allowed to fail (this permits a
176  * lazy implementation, which has minimal overhead when "forbid" is not
177  * requested).
178  *
179  * == Future: multiple value errors ==
180  *
181  * The present contract is that exactly one value error is reported per
182  * location in the input (multiple key lookup errors are, of course,
183  * supported).  If the need arises, multiple value errors could easily be
184  * supported by replacing the "error" string with an "errors" array.
185  */
186
187 namespace detail {
188 // Why do DynamicParser error messages use folly::dynamic pseudo-JSON?
189 // Firstly, the input dynamic need not correspond to valid JSON.  Secondly,
190 // wrapError() uses integer-keyed objects to report arrary-indexing errors.
191 std::string toPseudoJson(const folly::dynamic& d);
192 } // namespace detail
193
194 /**
195  * With DynamicParser::OnError::THROW, reports the first error.
196  * It is forbidden to call releaseErrors() if you catch this.
197  */
198 struct FOLLY_EXPORT DynamicParserParseError : public std::runtime_error {
199   explicit DynamicParserParseError(folly::dynamic error)
200     : std::runtime_error(folly::to<std::string>(
201         "DynamicParserParseError: ", detail::toPseudoJson(error)
202       )),
203       error_(std::move(error)) {}
204   /**
205    * Structured just like releaseErrors(), but with only 1 error inside:
206    *   {"nested": {"key1": {"nested": {"key2": {"error": "err", "value": 5}}}}}
207    * or:
208    *   {"nested": {"key1": {"key_errors": {"key3": "err"}, "value": 7}}}
209    */
210   const folly::dynamic& error() const { return error_; }
211
212  private:
213   folly::dynamic error_;
214 };
215
216 /**
217  * When DynamicParser is used incorrectly, it will throw this exception
218  * instead of reporting an error via releaseErrors().  It is unsafe to call
219  * any parser methods after catching a LogicError.
220  */
221 struct FOLLY_EXPORT DynamicParserLogicError : public std::logic_error {
222   template <typename... Args>
223   explicit DynamicParserLogicError(Args&&... args)
224     : std::logic_error(folly::to<std::string>(std::forward<Args>(args)...)) {}
225 };
226
227 class DynamicParser {
228  public:
229   enum class OnError {
230     // After parsing, releaseErrors() reports all parse errors.
231     // Throws DynamicParserLogicError on programmer errors.
232     RECORD,
233     // Throws DynamicParserParseError on the first parse error, or
234     // DynamicParserLogicError on programmer errors.
235     THROW,
236   };
237
238   // You MUST NOT destroy `d` before the parser.
239   DynamicParser(OnError on_error, const folly::dynamic* d)
240     : onError_(on_error), stack_(d) {}  // Always access input through stack_
241
242   /**
243    * Once you finished the entire parse, returns a structured description of
244    * all parse errors (see top-of-file docblock).  May ONLY be called once.
245    * May NOT be called if the parse threw any kind of exception.  Returns an
246    * empty object for successful OnError::THROW parsers.
247    */
248   folly::dynamic releaseErrors() { return stack_.releaseErrors(); }
249
250   /**
251    * Error-wraps fn(auto-converted key & value) if d[key] is set. The
252    * top-of-file docblock explains the auto-conversion.
253    */
254   template <typename Fn>
255   void optional(const folly::dynamic& key, Fn);
256
257   // Like optional(), but reports an error if d[key] does not exist.
258   template <typename Fn>
259   void required(const folly::dynamic& key, Fn);
260
261   /**
262    * Iterate over the current object's keys and values. Report each item's
263    * errors under its own key in a matching sub-object of "errors".
264    */
265   template <typename Fn>
266   void objectItems(Fn);
267
268   /**
269    * Like objectItems() -- arrays are treated identically to objects with
270    * integer keys from 0 to size() - 1.
271    */
272   template <typename Fn>
273   void arrayItems(Fn);
274
275   /**
276    * The key currently being parsed (integer if inside an array). Throws if
277    * called outside of a parser callback.
278    */
279   inline const folly::dynamic& key() const { return stack_.key(); }
280   /**
281    * The value currently being parsed (initially, the input dynamic).
282    * Throws if parsing nullptr, or parsing after releaseErrors().
283    */
284   inline const folly::dynamic& value() const { return stack_.value(); }
285
286   /**
287    * By default, DynamicParser's "nested" object coerces all keys to
288    * strings, whether from arrayItems() or from p.optional(some_int, ...),
289    * to allow errors be serialized to JSON.  If you are parsing non-JSON
290    * dynamic objects with non-string keys, this is problematic.  When set to
291    * true, "nested" objects will report integer keys for errors coming from
292    * inside arrays, or the original key type from inside values of objects.
293    */
294   DynamicParser& setAllowNonStringKeyErrors(bool b) {
295     allowNonStringKeyErrors_ = b;
296     return *this;
297   }
298
299  private:
300   /**
301    * If `fn` throws an exception, wrapError() catches it and inserts an
302    * enriched description into stack_.errors_.  If lookup_key is non-null,
303    * reports a key lookup error in "key_errors", otherwise reportse a value
304    * error in "error".
305    *
306    * Not public because that would encourage users to report multiple errors
307    * per input part, which is currently unsupported.  It does not currently
308    * seem like normal user code should need this.
309    */
310   template <typename Fn>
311   void wrapError(const folly::dynamic* lookup_key, Fn);
312
313   void reportError(const folly::dynamic* lookup_k, const std::exception& ex);
314
315   template <typename Fn>
316   void parse(const folly::dynamic& key, const folly::dynamic& value, Fn fn);
317
318   // All of the above business logic obtains the part of the folly::dynamic
319   // it is examining (and the location for reporting errors) via this class,
320   // which lets it correctly handle nesting.
321   struct ParserStack {
322     struct Pop {
323       explicit Pop(ParserStack* sp)
324         : key_(sp->key_), value_(sp->value_), stackPtr_(sp) {}
325       void operator()() noexcept;  // ScopeGuard requires noexcept
326      private:
327       const folly::dynamic* key_;
328       const folly::dynamic* value_;
329       ParserStack* stackPtr_;
330     };
331
332     explicit ParserStack(const folly::dynamic* input)
333       : value_(input),
334         errors_(folly::dynamic::object()),
335         subErrors_({&errors_}) {}
336
337     // Not copiable or movable due to numerous internal pointers
338     ParserStack(const ParserStack&) = delete;
339     ParserStack& operator=(const ParserStack&) = delete;
340     ParserStack(ParserStack&&) = delete;
341     ParserStack& operator=(ParserStack&&) = delete;
342
343     // Lets user code nest parser calls by recording current key+value and
344     // returning an RAII guard to restore the old one.  `noexcept` since it
345     // is used unwrapped.
346     folly::ScopeGuardImpl<Pop> push(
347       const folly::dynamic& k, const folly::dynamic& v
348     ) noexcept;
349
350     // Throws DynamicParserLogicError if used outside of a parsing function.
351     inline const folly::dynamic& key() const;
352     // Throws DynamicParserLogicError if used after releaseErrors().
353     inline const folly::dynamic& value() const;
354
355     // Lazily creates new "nested" sub-objects in errors_.
356     folly::dynamic& errors(bool allow_non_string_keys) noexcept;
357
358     // The user invokes this at most once after the parse is done.
359     folly::dynamic releaseErrors();
360
361     // Invoked on error when using OnError::THROW.
362     [[noreturn]] void throwErrors();
363
364    private:
365     friend struct Pop;
366
367     folly::dynamic releaseErrorsImpl();  // for releaseErrors() & throwErrors()
368
369     // Null outside of a parsing function.
370     const folly::dynamic* key_{nullptr};
371     // Null on errors: when the input was nullptr, or after releaseErrors().
372     const folly::dynamic* value_;
373
374     // An object containing some of these keys:
375     //   "key_errors" -- {"key": "description of error looking up said key"}
376     //   "error" -- why did we fail to parse this value?
377     //   "value" -- a copy of the input causing the error, and
378     //   "nested" -- {"key" or integer for arrays: <another errors_ object>}
379     //
380     // "nested" will contain identically structured objects with keys (array
381     // indices) identifying the origin of the errors.  Of course, "input"
382     // would no longer refer to the whole input, but to a part.
383     folly::dynamic errors_;
384     // We only materialize errors_ sub-objects when needed. This stores keys
385     // for unmaterialized errors, from outermost to innermost.
386     std::vector<const folly::dynamic*> unmaterializedSubErrorKeys_;
387     // Materialized errors, from outermost to innermost
388     std::vector<folly::dynamic*> subErrors_;  // Point into errors_
389   };
390
391   OnError onError_;
392   ParserStack stack_;
393   bool allowNonStringKeyErrors_{false};  // See the setter's docblock.
394 };
395
396 } // namespace folly
397
398 #include <folly/experimental/DynamicParser-inl.h>