2 * Copyright 2016 Facebook, Inc.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 * Copyright (c) 2015, Facebook, Inc.
18 * All rights reserved.
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.
25 #include <folly/Optional.h>
26 #include <folly/experimental/DynamicParser.h>
27 #include <folly/experimental/TestUtil.h>
28 #include <gtest/gtest.h>
30 using namespace folly;
31 using dynamic = folly::dynamic;
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
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;
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();
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());
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());
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))
66 void checkCoercedAndUncoercedKeys(dynamic good_k, dynamic missing_k) {
67 checkMaybeCoercedKeys(true, good_k, missing_k);
68 checkMaybeCoercedKeys(false, good_k, missing_k);
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);
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);
89 TEST(TestDynamicParser, OnErrorThrowError) {
90 auto d = dynamic::array(dynamic::object("int", "fail"));
91 DynamicParser p(DynamicParser::OnError::THROW, &d);
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();
99 error.at("nested").at("0").at("nested").at("int").at("error");
100 EXPECT_PCRE_MATCH(".*Invalid leading.*", message.getString());
102 "DynamicParserParseError: .*Invalid leading.*", ex.what()
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";
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) {
119 auto d = dynamic::array(
120 dynamic::object("a", 7)("b", 9)("c", 13.3),
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))
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);
147 p.required(1, [&](int64_t k, int64_t v) {
149 EXPECT_EQ(1, p.key().getInt());
150 EXPECT_EQ(5, p.value().getInt());
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);
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());
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());
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());
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());
190 p.optional("not set", [&](bool) { FAIL() << "No key 'not set'"; });
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& v) {
196 EXPECT_EQ(std::string("bools"), k);
197 EXPECT_EQ(k, p.key().getString());
198 EXPECT_EQ(v, p.value());
199 p.arrayItems([&](int64_t k, bool v) {
200 EXPECT_EQ(bools.size(), k);
201 EXPECT_EQ(k, p.key().getInt());
202 EXPECT_EQ(v, p.value().asBool());
207 p.optional(5, [&](int64_t) { FAIL() << "Index 5 does not exist"; });
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);
221 // We can hit multiple key lookup errors, but only one parse error.
222 template <typename Fn>
223 void checkXYKeyErrorsAndParseError(
227 std::string parse_re) {
228 DynamicParser p(DynamicParser::OnError::RECORD, &d);
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))
240 ("value", d)), errors);
243 // Exercise key errors for optional / required, and outer parse errors for
244 // arrayItems / objectItems.
245 TEST(TestDynamicParser, TestKeyAndParseErrors) {
246 checkXYKeyErrorsAndParseError(
248 [&](DynamicParser& p) {
249 p.required("x", [&]() {}); // key
250 p.required("y", [&]() {}); // key
251 p.arrayItems([&]() {}); // parse
253 "Couldn't find key (x|y) .*",
256 checkXYKeyErrorsAndParseError(
258 [&](DynamicParser& p) {
259 p.optional("x", [&]() {}); // key
260 p.optional("y", [&]() {}); // key
261 p.objectItems([&]() {}); // parse
268 // TestKeyAndParseErrors covered required/optional key errors, so only parse
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"));
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);
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);
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"))
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) {
309 dynamic::object("string", dynamic::array("not", "actually")),
310 [&](DynamicParser& p) {
311 p.objectItems([&](const std::string&, const std::string&) {});
314 "TypeError: .* but had type `array'"
317 dynamic::array("this is not a bool"),
318 [&](DynamicParser& p) { p.arrayItems([&](int64_t, bool) {}); },
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"))
331 DynamicParser p(DynamicParser::OnError::RECORD, &d);
332 // Start with a couple of successful nests, building up unmaterialized
334 p.required("x", [&]() {
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");
343 // Key error inside fully materialized errors
344 p.required("also not a key", []() {});
345 throw std::runtime_error("another parse error");
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();
352 auto& base = errors.at("nested").at("x").at("nested").at("0");
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");
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")
369 ("key_errors", dynamic::object("not a key", inner_key_err))
370 ("error", "custom parse error")
371 ("value", dynamic::object("z", "non-object"))
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")))
377 ("k", dynamic::object("error", k_parse_err)("value", false)))
378 ("key_errors", dynamic::object("non-key", root_key_err))
383 TEST(TestDynamicParser, TestRecordThrowOnDoubleParseErrors) {
385 DynamicParser p(DynamicParser::OnError::RECORD, &d);
386 p.arrayItems([&]() {});
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());
395 TEST(TestDynamicParser, TestRecordThrowOnChangingValue) {
397 DynamicParser p(DynamicParser::OnError::RECORD, &d);
398 p.required("x", [&]() {}); // Key error sets "value"
401 p.objectItems([&]() {}); // Will detect the changed value
402 FAIL() << "Should throw on second error with a changing value";
403 } catch (const DynamicParserLogicError& ex) {
405 // Accept 0 or null since folly used to mis-print null as 0.
406 ".*Overwriting value: (0|null) with 5 for error TypeError: .*",
412 TEST(TestDynamicParser, TestThrowOnReleaseWhileParsing) {
413 auto d = dynamic::array(1);
414 DynamicParser p(DynamicParser::OnError::RECORD, &d);
416 p.arrayItems([&]() { p.releaseErrors(); }),
417 DynamicParserLogicError
421 TEST(TestDynamicParser, TestThrowOnReleaseTwice) {
423 DynamicParser p(DynamicParser::OnError::RECORD, &d);
425 EXPECT_THROW(p.releaseErrors(), DynamicParserLogicError);
428 TEST(TestDynamicParser, TestThrowOnNullValue) {
430 DynamicParser p(DynamicParser::OnError::RECORD, &d);
432 EXPECT_THROW(p.value(), DynamicParserLogicError);
435 TEST(TestDynamicParser, TestThrowOnKeyOutsideCallback) {
437 DynamicParser p(DynamicParser::OnError::RECORD, &d);
438 EXPECT_THROW(p.key(), DynamicParserLogicError);