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/experimental/DynamicParser.h>
27 #include <folly/Optional.h>
32 folly::dynamic& insertAtKey(
33 folly::dynamic* d, bool allow_non_string_keys, const folly::dynamic& key) {
36 // folly::dynamic allows non-null scalars for keys.
37 } else if (key.isNumber() || key.isBool()) {
38 return allow_non_string_keys ? (*d)[key] : (*d)[key.asString()];
40 // One cause might be oddness like p.optional(dynamic::array(...), ...);
41 throw DynamicParserLogicError(
42 "Unsupported key type ", key.typeName(), " of ", detail::toPseudoJson(key)
45 } // anonymous namespace
47 void DynamicParser::reportError(
48 const folly::dynamic* lookup_k,
49 const std::exception& ex) {
50 // If descendants of this item, or other keys on it, already reported an
51 // error, the error object would already exist.
52 auto& e = stack_.errors(allowNonStringKeyErrors_);
54 // Save the original, unparseable value of the item causing the error.
56 // value() can throw here, but if it does, it is due to programmer error,
57 // so we don't want to report it as a parse error anyway.
58 if (auto* e_val_ptr = e.get_ptr("value")) {
59 // Failing to access distinct keys on the same value can generate
60 // multiple errors, but the value should remain the same.
61 if (*e_val_ptr != value()) {
62 throw DynamicParserLogicError(
63 "Overwriting value: ", detail::toPseudoJson(*e_val_ptr), " with ",
64 detail::toPseudoJson(value()), " for error ", ex.what()
68 // The e["value"].isNull() trick cannot be used because value().type()
69 // *can* be folly::dynamic::Type::NULLT, so we must hash again.
73 // Differentiate between "parsing value" and "looking up key" errors.
74 auto& e_msg = [&]() -> folly::dynamic& {
75 if (lookup_k == nullptr) { // {object,array}Items, or post-key-lookup
78 // Multiple key lookups can report errors on the same collection.
79 auto& key_errors = e["key_errors"];
80 if (key_errors.isNull()) {
81 // Treat arrays as integer-keyed objects.
82 key_errors = folly::dynamic::object();
84 return insertAtKey(&key_errors, allowNonStringKeyErrors_, *lookup_k);
86 if (!e_msg.isNull()) {
87 throw DynamicParserLogicError(
88 "Overwriting error: ", detail::toPseudoJson(e_msg), " with: ",
96 break; // Continue parsing
98 stack_.throwErrors(); // Package releaseErrors() into an exception.
99 LOG(FATAL) << "Not reached"; // silence lint false positive
101 LOG(FATAL) << "Bad onError_: " << static_cast<int>(onError_);
105 void DynamicParser::ParserStack::Pop::operator()() noexcept {
106 stackPtr_->key_ = key_;
107 stackPtr_->value_ = value_;
108 if (stackPtr_->unmaterializedSubErrorKeys_.empty()) {
109 // There should be the current error, and the root.
110 CHECK_GE(stackPtr_->subErrors_.size(), 2u)
111 << "Internal bug: out of suberrors";
112 stackPtr_->subErrors_.pop_back();
114 // Errors were never materialized for this subtree, so errors_ only has
115 // ancestors of the item being processed.
116 stackPtr_->unmaterializedSubErrorKeys_.pop_back();
117 CHECK(!stackPtr_->subErrors_.empty()) << "Internal bug: out of suberrors";
121 folly::ScopeGuardImpl<DynamicParser::ParserStack::Pop>
122 DynamicParser::ParserStack::push(
123 const folly::dynamic& k,
124 const folly::dynamic& v) noexcept {
125 // Save the previous state of the parser.
126 folly::ScopeGuardImpl<DynamicParser::ParserStack::Pop> guard(
127 DynamicParser::ParserStack::Pop(this)
131 // We create errors_ sub-objects lazily to keep the result small.
132 unmaterializedSubErrorKeys_.emplace_back(key_);
136 // `noexcept` because if the materialization loop threw, we'd end up with
137 // more suberrors than we started with.
138 folly::dynamic& DynamicParser::ParserStack::errors(
139 bool allow_non_string_keys) noexcept {
140 // Materialize the lazy "key + parent's type" error objects we'll need.
141 CHECK(!subErrors_.empty()) << "Internal bug: out of suberrors";
142 for (const auto& suberror_key : unmaterializedSubErrorKeys_) {
143 auto& nested = (*subErrors_.back())["nested"];
144 if (nested.isNull()) {
145 nested = folly::dynamic::object();
147 // Find, or insert a dummy entry for the current key
149 insertAtKey(&nested, allow_non_string_keys, *suberror_key);
150 if (my_errors.isNull()) {
151 my_errors = folly::dynamic::object();
153 subErrors_.emplace_back(&my_errors);
155 unmaterializedSubErrorKeys_.clear();
156 return *subErrors_.back();
159 folly::dynamic DynamicParser::ParserStack::releaseErrors() {
161 key_ || unmaterializedSubErrorKeys_.size() != 0 || subErrors_.size() != 1
163 throw DynamicParserLogicError(
164 "Do not releaseErrors() while parsing: ", key_ != nullptr, " / ",
165 unmaterializedSubErrorKeys_.size(), " / ", subErrors_.size()
168 return releaseErrorsImpl();
171 [[noreturn]] void DynamicParser::ParserStack::throwErrors() {
172 throw DynamicParserParseError(releaseErrorsImpl());
175 folly::dynamic DynamicParser::ParserStack::releaseErrorsImpl() {
176 if (errors_.isNull()) {
177 throw DynamicParserLogicError("Do not releaseErrors() twice");
179 auto errors = std::move(errors_);
180 errors_ = nullptr; // Prevent a second release.
181 value_ = nullptr; // Break attempts to parse again.
186 std::string toPseudoJson(const folly::dynamic& d) {
187 std::stringstream ss;
191 } // namespace detail