Add missing uint32 type to folly::ProgramOptions::gFlagAdders
[folly.git] / folly / experimental / DynamicParser.cpp
1 /*
2  * Copyright 2017 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
27 #include <folly/Optional.h>
28
29 namespace folly {
30
31 namespace {
32 folly::dynamic& insertAtKey(
33     folly::dynamic* d, bool allow_non_string_keys, const folly::dynamic& key) {
34   if (key.isString()) {
35     return (*d)[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()];
39   }
40   // One cause might be oddness like p.optional(dynamic::array(...), ...);
41   throw DynamicParserLogicError(
42     "Unsupported key type ", key.typeName(), " of ", detail::toPseudoJson(key)
43   );
44 }
45 }  // anonymous namespace
46
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_);
53
54   // Save the original, unparseable value of the item causing the error.
55   //
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()
65       );
66     }
67   } else {
68     // The e["value"].isNull() trick cannot be used because value().type()
69     // *can* be folly::dynamic::Type::NULLT, so we must hash again.
70     e["value"] = value();
71   }
72
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
76       return e["error"];
77     }
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();
83     }
84     return insertAtKey(&key_errors, allowNonStringKeyErrors_, *lookup_k);
85   }();
86   if (!e_msg.isNull()) {
87     throw DynamicParserLogicError(
88       "Overwriting error: ", detail::toPseudoJson(e_msg), " with: ",
89       ex.what()
90     );
91   }
92   e_msg = ex.what();
93
94   switch (onError_) {
95     case OnError::RECORD:
96       break;  // Continue parsing
97     case OnError::THROW:
98       stack_.throwErrors();  // Package releaseErrors() into an exception.
99     default:
100       LOG(FATAL) << "Bad onError_: " << static_cast<int>(onError_);
101   }
102 }
103
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();
112   } else {
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";
117   }
118 }
119
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)
127   );
128   key_ = &k;
129   value_ = &v;
130   // We create errors_ sub-objects lazily to keep the result small.
131   unmaterializedSubErrorKeys_.emplace_back(key_);
132   return guard;
133 }
134
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();
145     }
146     // Find, or insert a dummy entry for the current key
147     auto& my_errors =
148       insertAtKey(&nested, allow_non_string_keys, *suberror_key);
149     if (my_errors.isNull()) {
150       my_errors = folly::dynamic::object();
151     }
152     subErrors_.emplace_back(&my_errors);
153   }
154   unmaterializedSubErrorKeys_.clear();
155   return *subErrors_.back();
156 }
157
158 folly::dynamic DynamicParser::ParserStack::releaseErrors() {
159   if (
160     key_ || unmaterializedSubErrorKeys_.size() != 0 || subErrors_.size() != 1
161   ) {
162     throw DynamicParserLogicError(
163       "Do not releaseErrors() while parsing: ", key_ != nullptr, " / ",
164       unmaterializedSubErrorKeys_.size(), " / ", subErrors_.size()
165     );
166   }
167   return releaseErrorsImpl();
168 }
169
170 [[noreturn]] void DynamicParser::ParserStack::throwErrors() {
171   throw DynamicParserParseError(releaseErrorsImpl());
172 }
173
174 folly::dynamic DynamicParser::ParserStack::releaseErrorsImpl() {
175   if (errors_.isNull()) {
176     throw DynamicParserLogicError("Do not releaseErrors() twice");
177   }
178   auto errors = std::move(errors_);
179   errors_ = nullptr;  // Prevent a second release.
180   value_ = nullptr;  // Break attempts to parse again.
181   return errors;
182 }
183
184 namespace detail {
185 std::string toPseudoJson(const folly::dynamic& d) {
186   std::stringstream ss;
187   ss << d;
188   return ss.str();
189 }
190 }  // namespace detail
191
192 }  // namespace folly