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