Commit 83b07aec authored by Aleksandr Sasha Sergeev's avatar Aleksandr Sasha Sergeev Committed by Facebook GitHub Bot

Add path to problematic element into json print_error

Summary:
To facilitate debugging.

Privacy note: now `what()` message may contain part of JSON.

parser_error `what()` message:

- before the change:
`folly::toJson: JSON object value was a NaN`

- after the change:
`folly::toJson: JSON object value was a NaN when serializing value at "outerKey"->"innerKey"`

Reviewed By: yfeldblum

Differential Revision: D27623818

fbshipit-source-id: 86e61a0fed500eddafbd3e3bb1b9ed363ba8fcae
parent 40233942
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include <algorithm> #include <algorithm>
#include <functional> #include <functional>
#include <iterator> #include <iterator>
#include <sstream>
#include <type_traits> #include <type_traits>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
...@@ -54,21 +55,66 @@ parse_error make_parse_error( ...@@ -54,21 +55,66 @@ parse_error make_parse_error(
} }
struct Printer { struct Printer {
// Context class is allows to restore the path to element that we are about to
// print so that if error happens we can throw meaningful exception.
class Context {
public:
Context(const Context* parent_context, const dynamic& key)
: parent_context_(parent_context), key_(key), is_key_(false) {}
Context(const Context* parent_context, const dynamic& key, bool is_key)
: parent_context_(parent_context), key_(key), is_key_(is_key) {}
// Return location description of a context as a chain of keys
// ex., '"outherKey"->"innerKey"'.
std::string locationDescription() const {
std::vector<std::string> keys;
const Context* ptr = parent_context_;
while (ptr) {
keys.push_back(ptr->getName());
ptr = ptr->parent_context_;
}
keys.push_back(getName());
std::ostringstream stream;
std::reverse_copy(
keys.begin(),
keys.end() - 1,
std::ostream_iterator<std::string>(stream, "->"));
// Add current key.
stream << keys.back();
return stream.str();
}
std::string getName() const {
return Printer::toStringOr(key_, "<unprintable>");
}
std::string typeDescription() const { return is_key_ ? "key" : "value"; }
private:
const Context* const parent_context_;
const dynamic& key_;
bool is_key_;
};
explicit Printer( explicit Printer(
std::string& out, unsigned* indentLevel, serialization_opts const* opts) std::string& out, unsigned* indentLevel, serialization_opts const* opts)
: out_(out), indentLevel_(indentLevel), opts_(*opts) {} : out_(out), indentLevel_(indentLevel), opts_(*opts) {}
void operator()(dynamic const& v) const { void operator()(dynamic const& v, const Context& context) const {
(*this)(v, &context);
}
void operator()(dynamic const& v, const Context* context) const {
switch (v.type()) { switch (v.type()) {
case dynamic::DOUBLE: case dynamic::DOUBLE:
if (!opts_.allow_nan_inf) { if (!opts_.allow_nan_inf) {
if (std::isnan(v.asDouble())) { if (std::isnan(v.asDouble())) {
throw json::print_error( throw json::print_error(
"folly::toJson: JSON object value was a NaN"); "folly::toJson: JSON object value was a NaN when serializing " +
contextDescription(context));
} }
if (std::isinf(v.asDouble())) { if (std::isinf(v.asDouble())) {
throw json::print_error( throw json::print_error(
"folly::toJson: JSON object value was an INF"); "folly::toJson: JSON object value was an INF when serializing " +
contextDescription(context));
} }
} }
toAppend( toAppend(
...@@ -94,10 +140,10 @@ struct Printer { ...@@ -94,10 +140,10 @@ struct Printer {
escapeString(v.asString(), out_, opts_); escapeString(v.asString(), out_, opts_);
break; break;
case dynamic::OBJECT: case dynamic::OBJECT:
printObject(v); printObject(v, context);
break; break;
case dynamic::ARRAY: case dynamic::ARRAY:
printArray(v); printArray(v, context);
break; break;
default: default:
CHECK(0) << "Bad type " << v.type(); CHECK(0) << "Bad type " << v.type();
...@@ -105,28 +151,33 @@ struct Printer { ...@@ -105,28 +151,33 @@ struct Printer {
} }
private: private:
void printKV(const std::pair<const dynamic, dynamic>& p) const { void printKV(
const std::pair<const dynamic, dynamic>& p,
const Context* context) const {
if (!opts_.allow_non_string_keys && !p.first.isString()) { if (!opts_.allow_non_string_keys && !p.first.isString()) {
throw json::print_error( throw json::print_error(
"folly::toJson: JSON object key was not a " "folly::toJson: JSON object key " +
"string"); toStringOr(p.first, "<unprintable key>") +
" was not a string when serializing key at " +
Context(context, p.first, true).locationDescription());
} }
(*this)(p.first); (*this)(p.first, Context(context, p.first, true)); // Key
mapColon(); mapColon();
(*this)(p.second); (*this)(p.second, Context(context, p.first, false)); // Value
} }
template <typename Iterator> template <typename Iterator>
void printKVPairs(Iterator begin, Iterator end) const { void printKVPairs(
printKV(*begin); Iterator begin, Iterator end, const Context* context) const {
printKV(*begin, context);
for (++begin; begin != end; ++begin) { for (++begin; begin != end; ++begin) {
out_ += ','; out_ += ',';
newline(); newline();
printKV(*begin); printKV(*begin, context);
} }
} }
void printObject(dynamic const& o) const { void printObject(dynamic const& o, const Context* context) const {
if (o.empty()) { if (o.empty()) {
out_ += "{}"; out_ += "{}";
return; return;
...@@ -149,16 +200,38 @@ struct Printer { ...@@ -149,16 +200,38 @@ struct Printer {
} else { } else {
sort_keys_by(refs.begin(), refs.end(), std::less<>()); sort_keys_by(refs.begin(), refs.end(), std::less<>());
} }
printKVPairs(refs.cbegin(), refs.cend()); printKVPairs(refs.cbegin(), refs.cend(), context);
} else { } else {
printKVPairs(o.items().begin(), o.items().end()); printKVPairs(o.items().begin(), o.items().end(), context);
} }
outdent(); outdent();
newline(); newline();
out_ += '}'; out_ += '}';
} }
void printArray(dynamic const& a) const { static std::string toStringOr(dynamic const& v, const char* placeholder) {
try {
std::string result;
unsigned indentLevel = 0;
serialization_opts opts;
opts.allow_nan_inf = true;
opts.allow_non_string_keys = true;
Printer printer(result, &indentLevel, &opts);
printer(v, nullptr);
return result;
} catch (...) {
return placeholder;
}
}
static std::string contextDescription(const Context* context) {
if (!context) {
return "<undefined location>";
}
return context->typeDescription() + " at " + context->locationDescription();
}
void printArray(dynamic const& a, const Context* context) const {
if (a.empty()) { if (a.empty()) {
out_ += "[]"; out_ += "[]";
return; return;
...@@ -167,11 +240,11 @@ struct Printer { ...@@ -167,11 +240,11 @@ struct Printer {
out_ += '['; out_ += '[';
indent(); indent();
newline(); newline();
(*this)(a[0]); (*this)(a[0], Context(context, dynamic(0)));
for (auto& val : range(std::next(a.begin()), a.end())) { for (auto it = std::next(a.begin()); it != a.end(); ++it) {
out_ += ','; out_ += ',';
newline(); newline();
(*this)(val); (*this)(*it, Context(context, dynamic(std::distance(a.begin(), it))));
} }
outdent(); outdent();
newline(); newline();
...@@ -659,7 +732,7 @@ std::string serialize(dynamic const& dyn, serialization_opts const& opts) { ...@@ -659,7 +732,7 @@ std::string serialize(dynamic const& dyn, serialization_opts const& opts) {
std::string ret; std::string ret;
unsigned indentLevel = 0; unsigned indentLevel = 0;
Printer p(ret, opts.pretty_formatting ? &indentLevel : nullptr, &opts); Printer p(ret, opts.pretty_formatting ? &indentLevel : nullptr, &opts);
p(dyn); p(dyn, nullptr);
return ret; return ret;
} }
......
...@@ -399,6 +399,49 @@ TEST(Json, Produce) { ...@@ -399,6 +399,49 @@ TEST(Json, Produce) {
EXPECT_EQ("NaN", folly::json::serialize(parseJson("NaN"), opts)); EXPECT_EQ("NaN", folly::json::serialize(parseJson("NaN"), opts));
} }
TEST(Json, PrintExceptionErrorMessages) {
folly::json::serialization_opts opts;
opts.allow_nan_inf = false;
try {
const char* jsonWithInfValue =
"[{}, {}, {\"outerKey\":{\"innerKey\": Infinity}}]";
EXPECT_THROW(
toJson(folly::json::serialize(parseJson(jsonWithInfValue), opts)),
print_error);
toJson(folly::json::serialize(parseJson(jsonWithInfValue), opts));
} catch (const print_error& error) {
EXPECT_EQ(
std::string(
"folly::toJson: JSON object value was an INF when serializing value at 2->\"outerKey\"->\"innerKey\""),
error.what());
}
try {
const char* jsonWithNanValue = "[{\"outerKey\":{\"innerKey\": NaN}}]";
EXPECT_THROW(
toJson(folly::json::serialize(parseJson(jsonWithNanValue), opts)),
print_error);
toJson(folly::json::serialize(parseJson(jsonWithNanValue), opts));
} catch (const print_error& error) {
EXPECT_EQ(
std::string(
"folly::toJson: JSON object value was a NaN when serializing value at 0->\"outerKey\"->\"innerKey\""),
error.what());
}
try {
const dynamic jsonWithNonStringKey(
dynamic::object("abc", "xyz")(42.33, "asd"));
EXPECT_THROW(toJson(jsonWithNonStringKey), print_error);
toJson(jsonWithNonStringKey);
} catch (const print_error& error) {
EXPECT_EQ(
std::string(
"folly::toJson: JSON object key 42.33 was not a string when serializing key at 42.33"),
error.what());
}
}
TEST(Json, JsonEscape) { TEST(Json, JsonEscape) {
folly::json::serialization_opts opts; folly::json::serialization_opts opts;
EXPECT_EQ( EXPECT_EQ(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment