2 * Copyright 2017 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)
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.
100 LOG(FATAL) << "Bad onError_: " << static_cast<int>(onError_);
104 void DynamicParser::ParserStack::Pop::operator()() noexcept {
105 stackPtr_->key_ = key_;
106 stackPtr_->value_ = value_;
107 if (stackPtr_->unmaterializedSubErrorKeys_.empty()) {
108 // There should be the current error, and the root.
109 CHECK_GE(stackPtr_->subErrors_.size(), 2u)
110 << "Internal bug: out of suberrors";
111 stackPtr_->subErrors_.pop_back();
113 // Errors were never materialized for this subtree, so errors_ only has
114 // ancestors of the item being processed.
115 stackPtr_->unmaterializedSubErrorKeys_.pop_back();
116 CHECK(!stackPtr_->subErrors_.empty()) << "Internal bug: out of suberrors";
120 folly::ScopeGuardImpl<DynamicParser::ParserStack::Pop>
121 DynamicParser::ParserStack::push(
122 const folly::dynamic& k,
123 const folly::dynamic& v) noexcept {
124 // Save the previous state of the parser.
125 folly::ScopeGuardImpl<DynamicParser::ParserStack::Pop> guard(
126 DynamicParser::ParserStack::Pop(this)
130 // We create errors_ sub-objects lazily to keep the result small.
131 unmaterializedSubErrorKeys_.emplace_back(key_);
135 // `noexcept` because if the materialization loop threw, we'd end up with
136 // more suberrors than we started with.
137 folly::dynamic& DynamicParser::ParserStack::errors(
138 bool allow_non_string_keys) noexcept {
139 // Materialize the lazy "key + parent's type" error objects we'll need.
140 CHECK(!subErrors_.empty()) << "Internal bug: out of suberrors";
141 for (const auto& suberror_key : unmaterializedSubErrorKeys_) {
142 auto& nested = (*subErrors_.back())["nested"];
143 if (nested.isNull()) {
144 nested = folly::dynamic::object();
146 // Find, or insert a dummy entry for the current key
148 insertAtKey(&nested, allow_non_string_keys, *suberror_key);
149 if (my_errors.isNull()) {
150 my_errors = folly::dynamic::object();
152 subErrors_.emplace_back(&my_errors);
154 unmaterializedSubErrorKeys_.clear();
155 return *subErrors_.back();
158 folly::dynamic DynamicParser::ParserStack::releaseErrors() {
160 key_ || unmaterializedSubErrorKeys_.size() != 0 || subErrors_.size() != 1
162 throw DynamicParserLogicError(
163 "Do not releaseErrors() while parsing: ", key_ != nullptr, " / ",
164 unmaterializedSubErrorKeys_.size(), " / ", subErrors_.size()
167 return releaseErrorsImpl();
170 [[noreturn]] void DynamicParser::ParserStack::throwErrors() {
171 throw DynamicParserParseError(releaseErrorsImpl());
174 folly::dynamic DynamicParser::ParserStack::releaseErrorsImpl() {
175 if (errors_.isNull()) {
176 throw DynamicParserLogicError("Do not releaseErrors() twice");
178 auto errors = std::move(errors_);
179 errors_ = nullptr; // Prevent a second release.
180 value_ = nullptr; // Break attempts to parse again.
185 std::string toPseudoJson(const folly::dynamic& d) {
186 std::stringstream ss;
190 } // namespace detail