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