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