DynamicParserTest.cpp 16 KB

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