3c18f47ba40906646227b348f30f37fd80f4657d
[folly.git] / folly / experimental / test / DynamicParserTest.cpp
1 /*
2  * Copyright 2016 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 #include <folly/Optional.h>
26 #include <folly/experimental/DynamicParser.h>
27 #include <folly/experimental/TestUtil.h>
28 #include <folly/portability/GTest.h>
29
30 using namespace folly;
31 using dynamic = folly::dynamic;
32
33 // NB Auto-conversions are exercised by all the tests, there's not a great
34 // reason to test all of them explicitly, since any uncaught bugs will fail
35 // at compile-time.
36
37 // See setAllowNonStringKeyErrors() -- most of the tests below presume that
38 // all keys in releaseErrors() are coerced to string.
39 void checkMaybeCoercedKeys(bool coerce, dynamic good_k, dynamic missing_k) {
40   dynamic d = dynamic::object(good_k, 7);
41   DynamicParser p(DynamicParser::OnError::RECORD, &d);
42   p.setAllowNonStringKeyErrors(!coerce);
43   auto coerce_fn = [coerce](dynamic k) -> dynamic {
44     return coerce ? k.asString() : k;
45   };
46
47   // Key and value errors have different code paths, so exercise both.
48   p.required(missing_k, [&]() {});
49   p.required(good_k, [&]() { throw std::runtime_error("failsauce"); });
50   auto errors = p.releaseErrors();
51
52   auto parse_error = errors.at("nested").at(coerce_fn(good_k));
53   EXPECT_EQ(d.at(good_k), parse_error.at("value"));
54   EXPECT_PCRE_MATCH(".*failsauce.*", parse_error.at("error").getString());
55
56   auto key_error = errors.at("key_errors").at(coerce_fn(missing_k));
57   EXPECT_PCRE_MATCH(".*Couldn't find key .* in .*", key_error.getString());
58
59   EXPECT_EQ(dynamic(dynamic::object
60     ("nested", dynamic::object(coerce_fn(good_k), parse_error))
61     ("key_errors", dynamic::object(coerce_fn(missing_k), key_error))
62     ("value", d)
63   ), errors);
64 }
65
66 void checkCoercedAndUncoercedKeys(dynamic good_k, dynamic missing_k) {
67   checkMaybeCoercedKeys(true, good_k, missing_k);
68   checkMaybeCoercedKeys(false, good_k, missing_k);
69 }
70
71 TEST(TestDynamicParser, CoercedAndUncoercedKeys) {
72   // Check that both key errors and value errors are reported via
73   checkCoercedAndUncoercedKeys("a", "b");
74   checkCoercedAndUncoercedKeys(7, 5);
75   checkCoercedAndUncoercedKeys(0.7, 0.5);
76   checkCoercedAndUncoercedKeys(true, false);
77 }
78
79 TEST(TestDynamicParser, OnErrorThrowSuccess) {
80   auto d = dynamic::array(dynamic::object("int", 5));
81   DynamicParser p(DynamicParser::OnError::THROW, &d);
82   folly::Optional<int64_t> i;
83   p.required(0, [&]() { p.optional("int", [&](int64_t v) { i = v; }); });
84   // With THROW, releaseErrors() isn't useful -- it's either empty or throws.
85   EXPECT_EQ(dynamic(dynamic::object()), p.releaseErrors());
86   EXPECT_EQ((int64_t)5, i);
87 }
88
89 TEST(TestDynamicParser, OnErrorThrowError) {
90   auto d = dynamic::array(dynamic::object("int", "fail"));
91   DynamicParser p(DynamicParser::OnError::THROW, &d);
92   try {
93     // Force the exception to bubble up through a couple levels of nesting.
94     p.required(0, [&]() { p.optional("int", [&](int64_t) {}); });
95     FAIL() << "Should have thrown";
96   } catch (const DynamicParserParseError& ex) {
97     auto error = ex.error();
98     const auto& message =
99       error.at("nested").at("0").at("nested").at("int").at("error");
100     EXPECT_PCRE_MATCH(".*Invalid leading.*", message.getString());
101     EXPECT_PCRE_MATCH(
102       "DynamicParserParseError: .*Invalid leading.*", ex.what()
103     );
104     EXPECT_EQ(dynamic(dynamic::object
105       ("nested", dynamic::object
106         ("0", dynamic::object
107           ("nested", dynamic::object
108             ("int", dynamic::object
109               ("error", message)("value", "fail")))))), error);
110     EXPECT_THROW(p.releaseErrors(), DynamicParserLogicError)
111       << "THROW releases the first error eagerly, and throws";
112   }
113 }
114
115 // Errors & exceptions are best tested separately, but squeezing all the
116 // features into one test is good for exercising nesting.
117 TEST(TestDynamicParser, AllParserFeaturesSuccess) {
118   // Input
119   auto d = dynamic::array(
120     dynamic::object("a", 7)("b", 9)("c", 13.3),
121     5,
122     dynamic::array("x", "y", 1, "z"),
123     dynamic::object("int", 7)("false", 0)("true", true)("str", "s"),
124     dynamic::object("bools", dynamic::array(false, true, 0, 1))
125   );
126   // Outputs, in the same order as the inputs.
127   std::map<std::string, double> doubles;
128   folly::Optional<int64_t> outer_int;
129   std::vector<std::string> strings;
130   folly::Optional<int64_t> inner_int;
131   folly::Optional<bool> inner_false;
132   folly::Optional<bool> inner_true;
133   folly::Optional<std::string> inner_str;
134   std::vector<bool> bools;
135   // Parse and verify some invariants
136   DynamicParser p(DynamicParser::OnError::RECORD, &d);
137   EXPECT_EQ(d, p.value());
138   p.required(0, [&](const dynamic& v) {
139     EXPECT_EQ(0, p.key().getInt());
140     EXPECT_EQ(v, p.value());
141     p.objectItems([&](const std::string& k, double v) {
142       EXPECT_EQ(k, p.key().getString());
143       EXPECT_EQ(v, p.value().asDouble());
144       doubles.emplace(k, v);
145     });
146   });
147   p.required(1, [&](int64_t k, int64_t v) {
148     EXPECT_EQ(1, k);
149     EXPECT_EQ(1, p.key().getInt());
150     EXPECT_EQ(5, p.value().getInt());
151     outer_int = v;
152   });
153   p.optional(2, [&](const dynamic& v) {
154     EXPECT_EQ(2, p.key().getInt());
155     EXPECT_EQ(v, p.value());
156     p.arrayItems([&](int64_t k, const std::string& v) {
157       EXPECT_EQ(strings.size(), k);
158       EXPECT_EQ(k, p.key().getInt());
159       EXPECT_EQ(v, p.value().asString());
160       strings.emplace_back(v);
161     });
162   });
163   p.required(3, [&](const dynamic& v) {
164     EXPECT_EQ(3, p.key().getInt());
165     EXPECT_EQ(v, p.value());
166     p.optional("int", [&](const std::string& k, int64_t v) {
167       EXPECT_EQ("int", p.key().getString());
168       EXPECT_EQ(k, p.key().getString());
169       EXPECT_EQ(v, p.value().getInt());
170       inner_int = v;
171     });
172     p.required("false", [&](const std::string& k, bool v) {
173       EXPECT_EQ("false", p.key().getString());
174       EXPECT_EQ(k, p.key().getString());
175       EXPECT_EQ(v, p.value().asBool());
176       inner_false = v;
177     });
178     p.required("true", [&](const std::string& k, bool v) {
179       EXPECT_EQ("true", p.key().getString());
180       EXPECT_EQ(k, p.key().getString());
181       EXPECT_EQ(v, p.value().getBool());
182       inner_true = v;
183     });
184     p.required("str", [&](const std::string& k, const std::string& v) {
185       EXPECT_EQ("str", p.key().getString());
186       EXPECT_EQ(k, p.key().getString());
187       EXPECT_EQ(v, p.value().getString());
188       inner_str = v;
189     });
190     p.optional("not set", [&](bool) { FAIL() << "No key 'not set'"; });
191   });
192   p.required(4, [&](const dynamic& v) {
193     EXPECT_EQ(4, p.key().getInt());
194     EXPECT_EQ(v, p.value());
195     p.optional("bools", [&](const std::string& k, const dynamic& v2) {
196       EXPECT_EQ(std::string("bools"), k);
197       EXPECT_EQ(k, p.key().getString());
198       EXPECT_EQ(v2, p.value());
199       p.arrayItems([&](int64_t k, bool v3) {
200         EXPECT_EQ(bools.size(), k);
201         EXPECT_EQ(k, p.key().getInt());
202         EXPECT_EQ(v3, p.value().asBool());
203         bools.push_back(v3);
204       });
205     });
206   });
207   p.optional(5, [&](int64_t) { FAIL() << "Index 5 does not exist"; });
208   // Confirm the parse
209   EXPECT_EQ(dynamic(dynamic::object()), p.releaseErrors());
210   EXPECT_EQ((decltype(doubles){{"a", 7.}, {"b", 9.}, {"c", 13.3}}), doubles);
211   EXPECT_EQ((int64_t)5, outer_int);
212   EXPECT_EQ((decltype(strings){"x", "y", "1", "z"}), strings);
213   EXPECT_EQ((int64_t)7, inner_int);
214   EXPECT_FALSE(inner_false.value());
215   EXPECT_TRUE(inner_true.value());
216   EXPECT_EQ(std::string("s"), inner_str);
217   EXPECT_EQ(std::string("s"), inner_str);
218   EXPECT_EQ((decltype(bools){false, true, false, true}), bools);
219 }
220
221 // We can hit multiple key lookup errors, but only one parse error.
222 template <typename Fn>
223 void checkXYKeyErrorsAndParseError(
224     const dynamic& d,
225     Fn fn,
226     std::string key_re,
227     std::string parse_re) {
228   DynamicParser p(DynamicParser::OnError::RECORD, &d);
229   fn(p);
230   auto errors = p.releaseErrors();
231   auto x_key_msg = errors.at("key_errors").at("x");
232   EXPECT_PCRE_MATCH(key_re, x_key_msg.getString());
233   auto y_key_msg = errors.at("key_errors").at("y");
234   EXPECT_PCRE_MATCH(key_re, y_key_msg.getString());
235   auto parse_msg = errors.at("error");
236   EXPECT_PCRE_MATCH(parse_re, parse_msg.getString());
237   EXPECT_EQ(dynamic(dynamic::object
238     ("key_errors", dynamic::object("x", x_key_msg)("y", y_key_msg))
239     ("error", parse_msg)
240     ("value", d)), errors);
241 }
242
243 // Exercise key errors for optional / required, and outer parse errors for
244 // arrayItems / objectItems.
245 TEST(TestDynamicParser, TestKeyAndParseErrors) {
246   checkXYKeyErrorsAndParseError(
247     dynamic::object(),
248     [&](DynamicParser& p) {
249       p.required("x", [&]() {});  // key
250       p.required("y", [&]() {});  // key
251       p.arrayItems([&]() {});  // parse
252     },
253     "Couldn't find key (x|y) .*",
254     "^TypeError: .*"
255   );
256   checkXYKeyErrorsAndParseError(
257     dynamic::array(),
258     [&](DynamicParser& p) {
259       p.optional("x", [&]() {});  // key
260       p.optional("y", [&]() {});  // key
261       p.objectItems([&]() {});  // parse
262     },
263     "^TypeError: .*",
264     "^TypeError: .*"
265   );
266 }
267
268 // TestKeyAndParseErrors covered required/optional key errors, so only parse
269 // errors remain.
270 TEST(TestDynamicParser, TestRequiredOptionalParseErrors) {
271   dynamic d = dynamic::object("x", dynamic::array())("y", "z")("z", 1);
272   DynamicParser p(DynamicParser::OnError::RECORD, &d);
273   p.required("x", [&](bool) {});
274   p.required("y", [&](int64_t) {});
275   p.required("z", [&](int64_t) { throw std::runtime_error("CUSTOM"); });
276   auto errors = p.releaseErrors();
277   auto get_expected_error_fn = [&](const dynamic& k, std::string pcre) {
278     auto error = errors.at("nested").at(k);
279     EXPECT_EQ(d.at(k), error.at("value"));
280     EXPECT_PCRE_MATCH(pcre, error.at("error").getString());
281     return dynamic::object("value", d.at(k))("error", error.at("error"));
282   };
283   EXPECT_EQ(dynamic(dynamic::object("nested", dynamic::object
284     ("x", get_expected_error_fn("x", "TypeError: .* but had type `array'"))
285     ("y", get_expected_error_fn("y", ".*Invalid leading character.*"))
286     ("z", get_expected_error_fn("z", "CUSTOM")))), errors);
287 }
288
289 template <typename Fn>
290 void checkItemParseError(
291     // real_k can differ from err_k, which is typically coerced to string
292     dynamic d, Fn fn, dynamic real_k, dynamic err_k, std::string re) {
293   DynamicParser p(DynamicParser::OnError::RECORD, &d);
294   fn(p);
295   auto errors = p.releaseErrors();
296   auto error = errors.at("nested").at(err_k);
297   EXPECT_EQ(d.at(real_k), error.at("value"));
298   EXPECT_PCRE_MATCH(re, error.at("error").getString());
299   EXPECT_EQ(dynamic(dynamic::object("nested", dynamic::object(
300     err_k, dynamic::object("value", d.at(real_k))("error", error.at("error"))
301   ))), errors);
302 }
303
304 // TestKeyAndParseErrors covered outer parse errors for {object,array}Items,
305 // which are the only high-level API cases uncovered by
306 // TestKeyAndParseErrors and TestRequiredOptionalParseErrors.
307 TEST(TestDynamicParser, TestItemParseErrors) {
308   checkItemParseError(
309     dynamic::object("string", dynamic::array("not", "actually")),
310     [&](DynamicParser& p) {
311       p.objectItems([&](const std::string&, const std::string&) {});
312     },
313     "string", "string",
314     "TypeError: .* but had type `array'"
315   );
316   checkItemParseError(
317     dynamic::array("this is not a bool"),
318     [&](DynamicParser& p) { p.arrayItems([&](int64_t, bool) {}); },
319     0, "0",
320     ".*Non-whitespace.*"
321   );
322 }
323
324 // The goal is to exercise the sub-error materialization logic pretty well
325 TEST(TestDynamicParser, TestErrorNesting) {
326   dynamic d = dynamic::object
327     ("x", dynamic::array(
328       dynamic::object("y", dynamic::object("z", "non-object"))
329     ))
330     ("k", false);
331   DynamicParser p(DynamicParser::OnError::RECORD, &d);
332   // Start with a couple of successful nests, building up unmaterialized
333   // error objects.
334   p.required("x", [&]() {
335     p.arrayItems([&]() {
336       p.optional("y", [&]() {
337         // First, a key error
338         p.required("not a key", []() {});
339         // Nest again more to test partially materialized errors.
340         p.objectItems([&]() { p.optional("akey", []() {}); });
341         throw std::runtime_error("custom parse error");
342       });
343       // Key error inside fully materialized errors
344       p.required("also not a key", []() {});
345       throw std::runtime_error("another parse error");
346     });
347   });
348   p.required("non-key", []() {});  // Top-level key error
349   p.optional("k", [&](int64_t, bool) {});  // Non-int key for good measure
350   auto errors = p.releaseErrors();
351
352   auto& base = errors.at("nested").at("x").at("nested").at("0");
353   auto inner_key_err =
354     base.at("nested").at("y").at("key_errors").at("not a key");
355   auto innermost_key_err =
356     base.at("nested").at("y").at("nested").at("z").at("key_errors").at("akey");
357   auto outer_key_err = base.at("key_errors").at("also not a key");
358   auto root_key_err = errors.at("key_errors").at("non-key");
359   auto k_parse_err = errors.at("nested").at("k").at("error");
360
361   EXPECT_EQ(dynamic(dynamic::object
362     ("nested", dynamic::object
363         ("x", dynamic::object("nested", dynamic::object("0", dynamic::object
364           ("nested", dynamic::object("y", dynamic::object
365             ("nested", dynamic::object("z", dynamic::object
366               ("key_errors", dynamic::object("akey", innermost_key_err))
367               ("value", "non-object")
368             ))
369             ("key_errors", dynamic::object("not a key", inner_key_err))
370             ("error", "custom parse error")
371             ("value", dynamic::object("z", "non-object"))
372           ))
373           ("key_errors", dynamic::object("also not a key", outer_key_err))
374           ("error", "another parse error")
375           ("value", dynamic::object("y", dynamic::object("z", "non-object")))
376         )))
377         ("k", dynamic::object("error", k_parse_err)("value", false)))
378     ("key_errors", dynamic::object("non-key", root_key_err))
379     ("value", d)
380   ), errors);
381 }
382
383 TEST(TestDynamicParser, TestRecordThrowOnDoubleParseErrors) {
384   dynamic d = nullptr;
385   DynamicParser p(DynamicParser::OnError::RECORD, &d);
386   p.arrayItems([&]() {});
387   try {
388     p.objectItems([&]() {});
389     FAIL() << "Should throw on double-parsing a value with an error";
390   } catch (const DynamicParserLogicError& ex) {
391     EXPECT_PCRE_MATCH(".*Overwriting error: TypeError: .*", ex.what());
392   }
393 }
394
395 TEST(TestDynamicParser, TestRecordThrowOnChangingValue) {
396   dynamic d = nullptr;
397   DynamicParser p(DynamicParser::OnError::RECORD, &d);
398   p.required("x", [&]() {});  // Key error sets "value"
399   d = 5;
400   try {
401     p.objectItems([&]() {});  // Will detect the changed value
402     FAIL() << "Should throw on second error with a changing value";
403   } catch (const DynamicParserLogicError& ex) {
404     EXPECT_PCRE_MATCH(
405       // Accept 0 or null since folly used to mis-print null as 0.
406       ".*Overwriting value: (0|null) with 5 for error TypeError: .*",
407       ex.what()
408     );
409   }
410 }
411
412 TEST(TestDynamicParser, TestThrowOnReleaseWhileParsing) {
413   auto d = dynamic::array(1);
414   DynamicParser p(DynamicParser::OnError::RECORD, &d);
415   EXPECT_THROW(
416     p.arrayItems([&]() { p.releaseErrors(); }),
417     DynamicParserLogicError
418   );
419 }
420
421 TEST(TestDynamicParser, TestThrowOnReleaseTwice) {
422   dynamic d = nullptr;
423   DynamicParser p(DynamicParser::OnError::RECORD, &d);
424   p.releaseErrors();
425   EXPECT_THROW(p.releaseErrors(), DynamicParserLogicError);
426 }
427
428 TEST(TestDynamicParser, TestThrowOnNullValue) {
429   dynamic d = nullptr;
430   DynamicParser p(DynamicParser::OnError::RECORD, &d);
431   p.releaseErrors();
432   EXPECT_THROW(p.value(), DynamicParserLogicError);
433 }
434
435 TEST(TestDynamicParser, TestThrowOnKeyOutsideCallback) {
436   dynamic d = nullptr;
437   DynamicParser p(DynamicParser::OnError::RECORD, &d);
438   EXPECT_THROW(p.key(), DynamicParserLogicError);
439 }