Unverified Commit 176d8e26 authored by Niels Lohmann's avatar Niels Lohmann Committed by GitHub

Merge pull request #2562 from nlohmann/diagnostics

Better diagnostics
parents bb90e34d 56a6dec0
...@@ -34,6 +34,7 @@ option(JSON_BuildTests "Build the unit tests when BUILD_TESTING is enabled." ${M ...@@ -34,6 +34,7 @@ option(JSON_BuildTests "Build the unit tests when BUILD_TESTING is enabled." ${M
option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT}) option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT})
option(JSON_MultipleHeaders "Use non-amalgamated version of the library." OFF) option(JSON_MultipleHeaders "Use non-amalgamated version of the library." OFF)
option(JSON_ImplicitConversions "Enable implicit conversions." ON) option(JSON_ImplicitConversions "Enable implicit conversions." ON)
option(JSON_Diagnostics "Enable better diagnostic messages." OFF)
## ##
## CONFIGURATION ## CONFIGURATION
...@@ -63,6 +64,10 @@ if (NOT JSON_ImplicitConversions) ...@@ -63,6 +64,10 @@ if (NOT JSON_ImplicitConversions)
message(STATUS "Implicit conversions are disabled") message(STATUS "Implicit conversions are disabled")
endif() endif()
if (JSON_Diagnostics)
message(STATUS "Diagnostics enabled")
endif()
## ##
## TARGET ## TARGET
## create target and add include path ## create target and add include path
...@@ -79,6 +84,7 @@ target_compile_definitions( ...@@ -79,6 +84,7 @@ target_compile_definitions(
${NLOHMANN_JSON_TARGET_NAME} ${NLOHMANN_JSON_TARGET_NAME}
INTERFACE INTERFACE
JSON_USE_IMPLICIT_CONVERSIONS=$<BOOL:${JSON_ImplicitConversions}> JSON_USE_IMPLICIT_CONVERSIONS=$<BOOL:${JSON_ImplicitConversions}>
JSON_DIAGNOSTICS=$<BOOL:${JSON_Diagnostics}>
) )
target_include_directories( target_include_directories(
......
#include <iostream>
# define JSON_DIAGNOSTICS 1
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
json j;
j["address"]["street"] = "Fake Street";
j["address"]["housenumber"] = "12";
try
{
int housenumber = j["address"]["housenumber"];
}
catch (json::exception& e)
{
std::cout << e.what() << '\n';
}
}
<a target="_blank" href="https://wandbox.org/permlink/pbz3ULoJ4maRnV8N"><b>online</b></a>
\ No newline at end of file
[json.exception.type_error.302] (/address/housenumber) type must be number, but is string
#include <iostream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
json j;
j["address"]["street"] = "Fake Street";
j["address"]["housenumber"] = "12";
try
{
int housenumber = j["address"]["housenumber"];
}
catch (json::exception& e)
{
std::cout << e.what() << '\n';
}
}
<a target="_blank" href="https://wandbox.org/permlink/fWfQhHzG03P6PAcC"><b>online</b></a>
\ No newline at end of file
[json.exception.type_error.302] type must be number, but is string
...@@ -12,6 +12,14 @@ This macro overrides `#!cpp catch` calls inside the library. The argument is the ...@@ -12,6 +12,14 @@ This macro overrides `#!cpp catch` calls inside the library. The argument is the
See [Switch off exceptions](../home/exceptions.md#switch-off-exceptions) for an example. See [Switch off exceptions](../home/exceptions.md#switch-off-exceptions) for an example.
## `JSON_DIAGNOSTICS`
This macro enables extended diagnostics for exception messages. Possible values are `1` to enable or `0` to disable (default).
When enabled, exception messages contain a [JSON Pointer](json_pointer.md) to the JSON value that triggered the exception, see [Extended diagnostic messages](../home/exceptions.md#extended-diagnostic-messages) for an example. Note that enabling this macro increases the size of every JSON value by one pointer and adds some runtime overhead.
The diagnostics messages can also be controlled with the CMake option `JSON_Diagnostics` (`OFF` by default) which sets `JSON_DIAGNOSTICS` accordingly.
## `JSON_NOEXCEPTION` ## `JSON_NOEXCEPTION`
Exceptions can be switched off by defining the symbol `JSON_NOEXCEPTION`. Exceptions can be switched off by defining the symbol `JSON_NOEXCEPTION`.
...@@ -56,6 +64,8 @@ When defined to `0`, implicit conversions are switched off. By default, implicit ...@@ -56,6 +64,8 @@ When defined to `0`, implicit conversions are switched off. By default, implicit
auto s = j.get<std::string>(); auto s = j.get<std::string>();
``` ```
Implicit conversions can also be controlled with the CMake option `JSON_ImplicitConversions` (`ON` by default) which sets `JSON_USE_IMPLICIT_CONVERSIONS` accordingly.
## `NLOHMANN_DEFINE_TYPE_INTRUSIVE(type, member...)` ## `NLOHMANN_DEFINE_TYPE_INTRUSIVE(type, member...)`
This macro can be used to simplify the serialization/deserialization of types if (1) want to use a JSON object as serialization and (2) want to use the member variable names as object keys in that object. This macro can be used to simplify the serialization/deserialization of types if (1) want to use a JSON object as serialization and (2) want to use the member variable names as object keys in that object.
......
...@@ -50,6 +50,43 @@ Note that `JSON_THROW_USER` should leave the current scope (e.g., by throwing or ...@@ -50,6 +50,43 @@ Note that `JSON_THROW_USER` should leave the current scope (e.g., by throwing or
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
``` ```
### Extended diagnostic messages
Exceptions in the library are thrown in the local context of the JSON value they are detected. This makes detailed diagnostics messages, and hence debugging, difficult.
??? example
```cpp
--8<-- "examples/diagnostics_standard.cpp"
```
Output:
```
--8<-- "examples/diagnostics_standard.output"
```
This exception can be hard to debug if storing the value `#!c "12"` and accessing it is further apart.
To create better diagnostics messages, each JSON value needs a pointer to its parent value such that a global context (i.e., a path from the root value to the value that lead to the exception) can be created. That global context is provided as [JSON Pointer](../features/json_pointer.md).
As this global context comes at the price of storing one additional pointer per JSON value and runtime overhead to maintain the parent relation, extended diagnostics are disabled by default. They can, however, be enabled by defining the preprocessor symbol [`JSON_DIAGNOSTICS`](../features/macros.md#json_diagnostics) to `1` before including `json.hpp`.
??? example
```cpp
--8<-- "examples/diagnostics_extended.cpp"
```
Output:
```
--8<-- "examples/diagnostics_extended.output"
```
Now the exception message contains a JSON Pointer `/address/housenumber` that indicates which value has the wrong type.
## Parse errors ## Parse errors
This exception is thrown by the library when a parse error occurs. Parse errors This exception is thrown by the library when a parse error occurs. Parse errors
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
The class is licensed under the [MIT License](https://opensource.org/licenses/MIT): The class is licensed under the [MIT License](https://opensource.org/licenses/MIT):
Copyright &copy; 2013-2020 [Niels Lohmann](https://nlohmann.me) Copyright &copy; 2013-2021 [Niels Lohmann](https://nlohmann.me)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
......
...@@ -27,7 +27,7 @@ void from_json(const BasicJsonType& j, typename std::nullptr_t& n) ...@@ -27,7 +27,7 @@ void from_json(const BasicJsonType& j, typename std::nullptr_t& n)
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_null())) if (JSON_HEDLEY_UNLIKELY(!j.is_null()))
{ {
JSON_THROW(type_error::create(302, "type must be null, but is " + std::string(j.type_name()))); JSON_THROW(type_error::create(302, "type must be null, but is " + std::string(j.type_name()), j));
} }
n = nullptr; n = nullptr;
} }
...@@ -58,7 +58,7 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) ...@@ -58,7 +58,7 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
} }
default: default:
JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()), j));
} }
} }
...@@ -67,7 +67,7 @@ void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) ...@@ -67,7 +67,7 @@ void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b)
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) if (JSON_HEDLEY_UNLIKELY(!j.is_boolean()))
{ {
JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()))); JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()), j));
} }
b = *j.template get_ptr<const typename BasicJsonType::boolean_t*>(); b = *j.template get_ptr<const typename BasicJsonType::boolean_t*>();
} }
...@@ -77,7 +77,7 @@ void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) ...@@ -77,7 +77,7 @@ void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s)
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_string())) if (JSON_HEDLEY_UNLIKELY(!j.is_string()))
{ {
JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()), j));
} }
s = *j.template get_ptr<const typename BasicJsonType::string_t*>(); s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
} }
...@@ -93,7 +93,7 @@ void from_json(const BasicJsonType& j, ConstructibleStringType& s) ...@@ -93,7 +93,7 @@ void from_json(const BasicJsonType& j, ConstructibleStringType& s)
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_string())) if (JSON_HEDLEY_UNLIKELY(!j.is_string()))
{ {
JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()), j));
} }
s = *j.template get_ptr<const typename BasicJsonType::string_t*>(); s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
...@@ -133,7 +133,7 @@ void from_json(const BasicJsonType& j, std::forward_list<T, Allocator>& l) ...@@ -133,7 +133,7 @@ void from_json(const BasicJsonType& j, std::forward_list<T, Allocator>& l)
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_array())) if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{ {
JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j));
} }
l.clear(); l.clear();
std::transform(j.rbegin(), j.rend(), std::transform(j.rbegin(), j.rend(),
...@@ -150,7 +150,7 @@ void from_json(const BasicJsonType& j, std::valarray<T>& l) ...@@ -150,7 +150,7 @@ void from_json(const BasicJsonType& j, std::valarray<T>& l)
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_array())) if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{ {
JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j));
} }
l.resize(j.size()); l.resize(j.size());
std::transform(j.begin(), j.end(), std::begin(l), std::transform(j.begin(), j.end(), std::begin(l),
...@@ -241,8 +241,7 @@ void()) ...@@ -241,8 +241,7 @@ void())
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_array())) if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{ {
JSON_THROW(type_error::create(302, "type must be array, but is " + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j));
std::string(j.type_name())));
} }
from_json_array_impl(j, arr, priority_tag<3> {}); from_json_array_impl(j, arr, priority_tag<3> {});
...@@ -253,7 +252,7 @@ void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) ...@@ -253,7 +252,7 @@ void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin)
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) if (JSON_HEDLEY_UNLIKELY(!j.is_binary()))
{ {
JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(j.type_name()))); JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(j.type_name()), j));
} }
bin = *j.template get_ptr<const typename BasicJsonType::binary_t*>(); bin = *j.template get_ptr<const typename BasicJsonType::binary_t*>();
...@@ -265,7 +264,7 @@ void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) ...@@ -265,7 +264,7 @@ void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_object())) if (JSON_HEDLEY_UNLIKELY(!j.is_object()))
{ {
JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()))); JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()), j));
} }
ConstructibleObjectType ret; ConstructibleObjectType ret;
...@@ -319,7 +318,7 @@ void from_json(const BasicJsonType& j, ArithmeticType& val) ...@@ -319,7 +318,7 @@ void from_json(const BasicJsonType& j, ArithmeticType& val)
} }
default: default:
JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()), j));
} }
} }
...@@ -348,14 +347,14 @@ void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>& ...@@ -348,14 +347,14 @@ void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>&
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_array())) if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{ {
JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j));
} }
m.clear(); m.clear();
for (const auto& p : j) for (const auto& p : j)
{ {
if (JSON_HEDLEY_UNLIKELY(!p.is_array())) if (JSON_HEDLEY_UNLIKELY(!p.is_array()))
{ {
JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()))); JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()), j));
} }
m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>()); m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
} }
...@@ -368,14 +367,14 @@ void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyE ...@@ -368,14 +367,14 @@ void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyE
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_array())) if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{ {
JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j));
} }
m.clear(); m.clear();
for (const auto& p : j) for (const auto& p : j)
{ {
if (JSON_HEDLEY_UNLIKELY(!p.is_array())) if (JSON_HEDLEY_UNLIKELY(!p.is_array()))
{ {
JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()))); JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()), j));
} }
m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>()); m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
} }
......
...@@ -132,6 +132,7 @@ struct external_constructor<value_t::array> ...@@ -132,6 +132,7 @@ struct external_constructor<value_t::array>
{ {
j.m_type = value_t::array; j.m_type = value_t::array;
j.m_value = arr; j.m_value = arr;
j.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
...@@ -140,6 +141,7 @@ struct external_constructor<value_t::array> ...@@ -140,6 +141,7 @@ struct external_constructor<value_t::array>
{ {
j.m_type = value_t::array; j.m_type = value_t::array;
j.m_value = std::move(arr); j.m_value = std::move(arr);
j.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
...@@ -152,6 +154,7 @@ struct external_constructor<value_t::array> ...@@ -152,6 +154,7 @@ struct external_constructor<value_t::array>
using std::end; using std::end;
j.m_type = value_t::array; j.m_type = value_t::array;
j.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr)); j.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
j.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
...@@ -164,6 +167,7 @@ struct external_constructor<value_t::array> ...@@ -164,6 +167,7 @@ struct external_constructor<value_t::array>
for (const bool x : arr) for (const bool x : arr)
{ {
j.m_value.array->push_back(x); j.m_value.array->push_back(x);
j.set_parent(j.m_value.array->back());
} }
j.assert_invariant(); j.assert_invariant();
} }
...@@ -179,6 +183,7 @@ struct external_constructor<value_t::array> ...@@ -179,6 +183,7 @@ struct external_constructor<value_t::array>
{ {
std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin());
} }
j.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
}; };
...@@ -191,6 +196,7 @@ struct external_constructor<value_t::object> ...@@ -191,6 +196,7 @@ struct external_constructor<value_t::object>
{ {
j.m_type = value_t::object; j.m_type = value_t::object;
j.m_value = obj; j.m_value = obj;
j.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
...@@ -199,6 +205,7 @@ struct external_constructor<value_t::object> ...@@ -199,6 +205,7 @@ struct external_constructor<value_t::object>
{ {
j.m_type = value_t::object; j.m_type = value_t::object;
j.m_value = std::move(obj); j.m_value = std::move(obj);
j.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
...@@ -211,6 +218,7 @@ struct external_constructor<value_t::object> ...@@ -211,6 +218,7 @@ struct external_constructor<value_t::object>
j.m_type = value_t::object; j.m_type = value_t::object;
j.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj)); j.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
j.set_parents();
j.assert_invariant(); j.assert_invariant();
} }
}; };
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include <stdexcept> // runtime_error #include <stdexcept> // runtime_error
#include <string> // to_string #include <string> // to_string
#include <nlohmann/detail/value_t.hpp>
#include <nlohmann/detail/string_escape.hpp>
#include <nlohmann/detail/input/position_t.hpp> #include <nlohmann/detail/input/position_t.hpp>
#include <nlohmann/detail/macro_scope.hpp> #include <nlohmann/detail/macro_scope.hpp>
...@@ -65,6 +67,61 @@ class exception : public std::exception ...@@ -65,6 +67,61 @@ class exception : public std::exception
return "[json.exception." + ename + "." + std::to_string(id_) + "] "; return "[json.exception." + ename + "." + std::to_string(id_) + "] ";
} }
template<typename BasicJsonType>
static std::string diagnostics(const BasicJsonType& leaf_element)
{
#if JSON_DIAGNOSTICS
std::vector<std::string> tokens;
for (const auto* current = &leaf_element; current->m_parent != nullptr; current = current->m_parent)
{
switch (current->m_parent->type())
{
case value_t::array:
{
for (std::size_t i = 0; i < current->m_parent->m_value.array->size(); ++i)
{
if (&current->m_parent->m_value.array->operator[](i) == current)
{
tokens.emplace_back(std::to_string(i));
break;
}
}
break;
}
case value_t::object:
{
for (const auto& element : *current->m_parent->m_value.object)
{
if (&element.second == current)
{
tokens.emplace_back(element.first.c_str());
break;
}
}
break;
}
default: // LCOV_EXCL_LINE
break; // LCOV_EXCL_LINE
}
}
if (tokens.empty())
{
return "";
}
return "(" + std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
[](const std::string & a, const std::string & b)
{
return a + "/" + detail::escape(b);
}) + ") ";
#else
return "";
#endif
}
private: private:
/// an exception object as storage for error messages /// an exception object as storage for error messages
std::runtime_error m; std::runtime_error m;
...@@ -127,18 +184,20 @@ class parse_error : public exception ...@@ -127,18 +184,20 @@ class parse_error : public exception
@param[in] what_arg the explanatory string @param[in] what_arg the explanatory string
@return parse_error object @return parse_error object
*/ */
static parse_error create(int id_, const position_t& pos, const std::string& what_arg) template<typename BasicJsonType>
static parse_error create(int id_, const position_t& pos, const std::string& what_arg, const BasicJsonType& context)
{ {
std::string w = exception::name("parse_error", id_) + "parse error" + std::string w = exception::name("parse_error", id_) + "parse error" +
position_string(pos) + ": " + what_arg; position_string(pos) + ": " + exception::diagnostics(context) + what_arg;
return parse_error(id_, pos.chars_read_total, w.c_str()); return parse_error(id_, pos.chars_read_total, w.c_str());
} }
static parse_error create(int id_, std::size_t byte_, const std::string& what_arg) template<typename BasicJsonType>
static parse_error create(int id_, std::size_t byte_, const std::string& what_arg, const BasicJsonType& context)
{ {
std::string w = exception::name("parse_error", id_) + "parse error" + std::string w = exception::name("parse_error", id_) + "parse error" +
(byte_ != 0 ? (" at byte " + std::to_string(byte_)) : "") + (byte_ != 0 ? (" at byte " + std::to_string(byte_)) : "") +
": " + what_arg; ": " + exception::diagnostics(context) + what_arg;
return parse_error(id_, byte_, w.c_str()); return parse_error(id_, byte_, w.c_str());
} }
...@@ -204,9 +263,10 @@ caught.,invalid_iterator} ...@@ -204,9 +263,10 @@ caught.,invalid_iterator}
class invalid_iterator : public exception class invalid_iterator : public exception
{ {
public: public:
static invalid_iterator create(int id_, const std::string& what_arg) template<typename BasicJsonType>
static invalid_iterator create(int id_, const std::string& what_arg, const BasicJsonType& context)
{ {
std::string w = exception::name("invalid_iterator", id_) + what_arg; std::string w = exception::name("invalid_iterator", id_) + exception::diagnostics(context) + what_arg;
return invalid_iterator(id_, w.c_str()); return invalid_iterator(id_, w.c_str());
} }
...@@ -258,9 +318,10 @@ caught.,type_error} ...@@ -258,9 +318,10 @@ caught.,type_error}
class type_error : public exception class type_error : public exception
{ {
public: public:
static type_error create(int id_, const std::string& what_arg) template<typename BasicJsonType>
static type_error create(int id_, const std::string& what_arg, const BasicJsonType& context)
{ {
std::string w = exception::name("type_error", id_) + what_arg; std::string w = exception::name("type_error", id_) + exception::diagnostics(context) + what_arg;
return type_error(id_, w.c_str()); return type_error(id_, w.c_str());
} }
...@@ -305,9 +366,10 @@ caught.,out_of_range} ...@@ -305,9 +366,10 @@ caught.,out_of_range}
class out_of_range : public exception class out_of_range : public exception
{ {
public: public:
static out_of_range create(int id_, const std::string& what_arg) template<typename BasicJsonType>
static out_of_range create(int id_, const std::string& what_arg, const BasicJsonType& context)
{ {
std::string w = exception::name("out_of_range", id_) + what_arg; std::string w = exception::name("out_of_range", id_) + exception::diagnostics(context) + what_arg;
return out_of_range(id_, w.c_str()); return out_of_range(id_, w.c_str());
} }
...@@ -343,9 +405,10 @@ caught.,other_error} ...@@ -343,9 +405,10 @@ caught.,other_error}
class other_error : public exception class other_error : public exception
{ {
public: public:
static other_error create(int id_, const std::string& what_arg) template<typename BasicJsonType>
static other_error create(int id_, const std::string& what_arg, const BasicJsonType& context)
{ {
std::string w = exception::name("other_error", id_) + what_arg; std::string w = exception::name("other_error", id_) + exception::diagnostics(context) + what_arg;
return other_error(id_, w.c_str()); return other_error(id_, w.c_str());
} }
......
...@@ -219,8 +219,7 @@ class json_sax_dom_parser ...@@ -219,8 +219,7 @@ class json_sax_dom_parser
if (JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) if (JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size()))
{ {
JSON_THROW(out_of_range::create(408, JSON_THROW(out_of_range::create(408, "excessive object size: " + std::to_string(len), *ref_stack.back()));
"excessive object size: " + std::to_string(len)));
} }
return true; return true;
...@@ -235,6 +234,7 @@ class json_sax_dom_parser ...@@ -235,6 +234,7 @@ class json_sax_dom_parser
bool end_object() bool end_object()
{ {
ref_stack.back()->set_parents();
ref_stack.pop_back(); ref_stack.pop_back();
return true; return true;
} }
...@@ -245,8 +245,7 @@ class json_sax_dom_parser ...@@ -245,8 +245,7 @@ class json_sax_dom_parser
if (JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) if (JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size()))
{ {
JSON_THROW(out_of_range::create(408, JSON_THROW(out_of_range::create(408, "excessive array size: " + std::to_string(len), *ref_stack.back()));
"excessive array size: " + std::to_string(len)));
} }
return true; return true;
...@@ -254,6 +253,7 @@ class json_sax_dom_parser ...@@ -254,6 +253,7 @@ class json_sax_dom_parser
bool end_array() bool end_array()
{ {
ref_stack.back()->set_parents();
ref_stack.pop_back(); ref_stack.pop_back();
return true; return true;
} }
...@@ -400,7 +400,7 @@ class json_sax_dom_callback_parser ...@@ -400,7 +400,7 @@ class json_sax_dom_callback_parser
// check object limit // check object limit
if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size()))
{ {
JSON_THROW(out_of_range::create(408, "excessive object size: " + std::to_string(len))); JSON_THROW(out_of_range::create(408, "excessive object size: " + std::to_string(len), *ref_stack.back()));
} }
return true; return true;
...@@ -425,11 +425,18 @@ class json_sax_dom_callback_parser ...@@ -425,11 +425,18 @@ class json_sax_dom_callback_parser
bool end_object() bool end_object()
{ {
if (ref_stack.back() && !callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back())) if (ref_stack.back())
{
if (!callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back()))
{ {
// discard object // discard object
*ref_stack.back() = discarded; *ref_stack.back() = discarded;
} }
else
{
ref_stack.back()->set_parents();
}
}
JSON_ASSERT(!ref_stack.empty()); JSON_ASSERT(!ref_stack.empty());
JSON_ASSERT(!keep_stack.empty()); JSON_ASSERT(!keep_stack.empty());
...@@ -463,7 +470,7 @@ class json_sax_dom_callback_parser ...@@ -463,7 +470,7 @@ class json_sax_dom_callback_parser
// check array limit // check array limit
if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size()))
{ {
JSON_THROW(out_of_range::create(408, "excessive array size: " + std::to_string(len))); JSON_THROW(out_of_range::create(408, "excessive array size: " + std::to_string(len), *ref_stack.back()));
} }
return true; return true;
...@@ -476,7 +483,11 @@ class json_sax_dom_callback_parser ...@@ -476,7 +483,11 @@ class json_sax_dom_callback_parser
if (ref_stack.back()) if (ref_stack.back())
{ {
keep = callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back()); keep = callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back());
if (!keep) if (keep)
{
ref_stack.back()->set_parents();
}
else
{ {
// discard array // discard array
*ref_stack.back() = discarded; *ref_stack.back() = discarded;
...@@ -574,7 +585,7 @@ class json_sax_dom_callback_parser ...@@ -574,7 +585,7 @@ class json_sax_dom_callback_parser
// array // array
if (ref_stack.back()->is_array()) if (ref_stack.back()->is_array())
{ {
ref_stack.back()->m_value.array->push_back(std::move(value)); ref_stack.back()->m_value.array->emplace_back(std::move(value));
return {true, &(ref_stack.back()->m_value.array->back())}; return {true, &(ref_stack.back()->m_value.array->back())};
} }
......
...@@ -88,7 +88,6 @@ class parser ...@@ -88,7 +88,6 @@ class parser
{ {
json_sax_dom_callback_parser<BasicJsonType> sdp(result, callback, allow_exceptions); json_sax_dom_callback_parser<BasicJsonType> sdp(result, callback, allow_exceptions);
sax_parse_internal(&sdp); sax_parse_internal(&sdp);
result.assert_invariant();
// in strict mode, input must be completely read // in strict mode, input must be completely read
if (strict && (get_token() != token_type::end_of_input)) if (strict && (get_token() != token_type::end_of_input))
...@@ -96,7 +95,7 @@ class parser ...@@ -96,7 +95,7 @@ class parser
sdp.parse_error(m_lexer.get_position(), sdp.parse_error(m_lexer.get_position(),
m_lexer.get_token_string(), m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(), parse_error::create(101, m_lexer.get_position(),
exception_message(token_type::end_of_input, "value"))); exception_message(token_type::end_of_input, "value"), BasicJsonType()));
} }
// in case of an error, return discarded value // in case of an error, return discarded value
...@@ -117,15 +116,13 @@ class parser ...@@ -117,15 +116,13 @@ class parser
{ {
json_sax_dom_parser<BasicJsonType> sdp(result, allow_exceptions); json_sax_dom_parser<BasicJsonType> sdp(result, allow_exceptions);
sax_parse_internal(&sdp); sax_parse_internal(&sdp);
result.assert_invariant();
// in strict mode, input must be completely read // in strict mode, input must be completely read
if (strict && (get_token() != token_type::end_of_input)) if (strict && (get_token() != token_type::end_of_input))
{ {
sdp.parse_error(m_lexer.get_position(), sdp.parse_error(m_lexer.get_position(),
m_lexer.get_token_string(), m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), BasicJsonType()));
exception_message(token_type::end_of_input, "value")));
} }
// in case of an error, return discarded value // in case of an error, return discarded value
...@@ -135,6 +132,8 @@ class parser ...@@ -135,6 +132,8 @@ class parser
return; return;
} }
} }
result.assert_invariant();
} }
/*! /*!
...@@ -161,8 +160,7 @@ class parser ...@@ -161,8 +160,7 @@ class parser
{ {
return sax->parse_error(m_lexer.get_position(), return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(), m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), BasicJsonType()));
exception_message(token_type::end_of_input, "value")));
} }
return result; return result;
...@@ -208,8 +206,7 @@ class parser ...@@ -208,8 +206,7 @@ class parser
{ {
return sax->parse_error(m_lexer.get_position(), return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(), m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), BasicJsonType()));
exception_message(token_type::value_string, "object key")));
} }
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
{ {
...@@ -221,8 +218,7 @@ class parser ...@@ -221,8 +218,7 @@ class parser
{ {
return sax->parse_error(m_lexer.get_position(), return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(), m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), BasicJsonType()));
exception_message(token_type::name_separator, "object separator")));
} }
// remember we are now inside an object // remember we are now inside an object
...@@ -265,7 +261,7 @@ class parser ...@@ -265,7 +261,7 @@ class parser
{ {
return sax->parse_error(m_lexer.get_position(), return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(), m_lexer.get_token_string(),
out_of_range::create(406, "number overflow parsing '" + m_lexer.get_token_string() + "'")); out_of_range::create(406, "number overflow parsing '" + m_lexer.get_token_string() + "'", BasicJsonType()));
} }
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string()))) if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string())))
...@@ -335,16 +331,14 @@ class parser ...@@ -335,16 +331,14 @@ class parser
// using "uninitialized" to avoid "expected" message // using "uninitialized" to avoid "expected" message
return sax->parse_error(m_lexer.get_position(), return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(), m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), BasicJsonType()));
exception_message(token_type::uninitialized, "value")));
} }
default: // the last token was unexpected default: // the last token was unexpected
{ {
return sax->parse_error(m_lexer.get_position(), return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(), m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), BasicJsonType()));
exception_message(token_type::literal_or_value, "value")));
} }
} }
} }
...@@ -390,8 +384,7 @@ class parser ...@@ -390,8 +384,7 @@ class parser
return sax->parse_error(m_lexer.get_position(), return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(), m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, "array"), BasicJsonType()));
exception_message(token_type::end_array, "array")));
} }
// states.back() is false -> object // states.back() is false -> object
...@@ -404,8 +397,7 @@ class parser ...@@ -404,8 +397,7 @@ class parser
{ {
return sax->parse_error(m_lexer.get_position(), return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(), m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), BasicJsonType()));
exception_message(token_type::value_string, "object key")));
} }
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
...@@ -418,8 +410,7 @@ class parser ...@@ -418,8 +410,7 @@ class parser
{ {
return sax->parse_error(m_lexer.get_position(), return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(), m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), BasicJsonType()));
exception_message(token_type::name_separator, "object separator")));
} }
// parse values // parse values
...@@ -447,8 +438,7 @@ class parser ...@@ -447,8 +438,7 @@ class parser
return sax->parse_error(m_lexer.get_position(), return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(), m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, "object"), BasicJsonType()));
exception_message(token_type::end_object, "object")));
} }
} }
......
...@@ -257,7 +257,7 @@ class iter_impl ...@@ -257,7 +257,7 @@ class iter_impl
} }
case value_t::null: case value_t::null:
JSON_THROW(invalid_iterator::create(214, "cannot get value")); JSON_THROW(invalid_iterator::create(214, "cannot get value", *m_object));
default: default:
{ {
...@@ -266,7 +266,7 @@ class iter_impl ...@@ -266,7 +266,7 @@ class iter_impl
return *m_object; return *m_object;
} }
JSON_THROW(invalid_iterator::create(214, "cannot get value")); JSON_THROW(invalid_iterator::create(214, "cannot get value", *m_object));
} }
} }
} }
...@@ -300,7 +300,7 @@ class iter_impl ...@@ -300,7 +300,7 @@ class iter_impl
return m_object; return m_object;
} }
JSON_THROW(invalid_iterator::create(214, "cannot get value")); JSON_THROW(invalid_iterator::create(214, "cannot get value", *m_object));
} }
} }
} }
...@@ -401,7 +401,7 @@ class iter_impl ...@@ -401,7 +401,7 @@ class iter_impl
// if objects are not the same, the comparison is undefined // if objects are not the same, the comparison is undefined
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
{ {
JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", *m_object));
} }
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
...@@ -438,7 +438,7 @@ class iter_impl ...@@ -438,7 +438,7 @@ class iter_impl
// if objects are not the same, the comparison is undefined // if objects are not the same, the comparison is undefined
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
{ {
JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", *m_object));
} }
JSON_ASSERT(m_object != nullptr); JSON_ASSERT(m_object != nullptr);
...@@ -446,7 +446,7 @@ class iter_impl ...@@ -446,7 +446,7 @@ class iter_impl
switch (m_object->m_type) switch (m_object->m_type)
{ {
case value_t::object: case value_t::object:
JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators")); JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators", *m_object));
case value_t::array: case value_t::array:
return (m_it.array_iterator < other.m_it.array_iterator); return (m_it.array_iterator < other.m_it.array_iterator);
...@@ -494,7 +494,7 @@ class iter_impl ...@@ -494,7 +494,7 @@ class iter_impl
switch (m_object->m_type) switch (m_object->m_type)
{ {
case value_t::object: case value_t::object:
JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", *m_object));
case value_t::array: case value_t::array:
{ {
...@@ -565,7 +565,7 @@ class iter_impl ...@@ -565,7 +565,7 @@ class iter_impl
switch (m_object->m_type) switch (m_object->m_type)
{ {
case value_t::object: case value_t::object:
JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", *m_object));
case value_t::array: case value_t::array:
return m_it.array_iterator - other.m_it.array_iterator; return m_it.array_iterator - other.m_it.array_iterator;
...@@ -586,13 +586,13 @@ class iter_impl ...@@ -586,13 +586,13 @@ class iter_impl
switch (m_object->m_type) switch (m_object->m_type)
{ {
case value_t::object: case value_t::object:
JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators")); JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators", *m_object));
case value_t::array: case value_t::array:
return *std::next(m_it.array_iterator, n); return *std::next(m_it.array_iterator, n);
case value_t::null: case value_t::null:
JSON_THROW(invalid_iterator::create(214, "cannot get value")); JSON_THROW(invalid_iterator::create(214, "cannot get value", *m_object));
default: default:
{ {
...@@ -601,7 +601,7 @@ class iter_impl ...@@ -601,7 +601,7 @@ class iter_impl
return *m_object; return *m_object;
} }
JSON_THROW(invalid_iterator::create(214, "cannot get value")); JSON_THROW(invalid_iterator::create(214, "cannot get value", *m_object));
} }
} }
} }
...@@ -619,7 +619,7 @@ class iter_impl ...@@ -619,7 +619,7 @@ class iter_impl
return m_it.object_iterator->first; return m_it.object_iterator->first;
} }
JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators")); JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators", *m_object));
} }
/*! /*!
......
This diff is collapsed.
...@@ -57,7 +57,7 @@ class binary_writer ...@@ -57,7 +57,7 @@ class binary_writer
default: default:
{ {
JSON_THROW(type_error::create(317, "to serialize to BSON, top-level type must be object, but is " + std::string(j.type_name()))); JSON_THROW(type_error::create(317, "to serialize to BSON, top-level type must be object, but is " + std::string(j.type_name()), j));;
} }
} }
} }
...@@ -901,13 +901,12 @@ class binary_writer ...@@ -901,13 +901,12 @@ class binary_writer
@return The size of a BSON document entry header, including the id marker @return The size of a BSON document entry header, including the id marker
and the entry name size (and its null-terminator). and the entry name size (and its null-terminator).
*/ */
static std::size_t calc_bson_entry_header_size(const string_t& name) static std::size_t calc_bson_entry_header_size(const string_t& name, const BasicJsonType& j)
{ {
const auto it = name.find(static_cast<typename string_t::value_type>(0)); const auto it = name.find(static_cast<typename string_t::value_type>(0));
if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos))
{ {
JSON_THROW(out_of_range::create(409, JSON_THROW(out_of_range::create(409, "BSON key cannot contain code point U+0000 (at byte " + std::to_string(it) + ")", j));
"BSON key cannot contain code point U+0000 (at byte " + std::to_string(it) + ")"));
} }
return /*id*/ 1ul + name.size() + /*zero-terminator*/1u; return /*id*/ 1ul + name.size() + /*zero-terminator*/1u;
...@@ -1017,21 +1016,21 @@ class binary_writer ...@@ -1017,21 +1016,21 @@ class binary_writer
@brief Writes a BSON element with key @a name and unsigned @a value @brief Writes a BSON element with key @a name and unsigned @a value
*/ */
void write_bson_unsigned(const string_t& name, void write_bson_unsigned(const string_t& name,
const std::uint64_t value) const BasicJsonType& j)
{ {
if (value <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)())) if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
{ {
write_bson_entry_header(name, 0x10 /* int32 */); write_bson_entry_header(name, 0x10 /* int32 */);
write_number<std::int32_t, true>(static_cast<std::int32_t>(value)); write_number<std::int32_t, true>(static_cast<std::int32_t>(j.m_value.number_unsigned));
} }
else if (value <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)())) else if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
{ {
write_bson_entry_header(name, 0x12 /* int64 */); write_bson_entry_header(name, 0x12 /* int64 */);
write_number<std::int64_t, true>(static_cast<std::int64_t>(value)); write_number<std::int64_t, true>(static_cast<std::int64_t>(j.m_value.number_unsigned));
} }
else else
{ {
JSON_THROW(out_of_range::create(407, "integer number " + std::to_string(value) + " cannot be represented by BSON as it does not fit int64")); JSON_THROW(out_of_range::create(407, "integer number " + std::to_string(j.m_value.number_unsigned) + " cannot be represented by BSON as it does not fit int64", j));
} }
} }
...@@ -1108,7 +1107,7 @@ class binary_writer ...@@ -1108,7 +1107,7 @@ class binary_writer
static std::size_t calc_bson_element_size(const string_t& name, static std::size_t calc_bson_element_size(const string_t& name,
const BasicJsonType& j) const BasicJsonType& j)
{ {
const auto header_size = calc_bson_entry_header_size(name); const auto header_size = calc_bson_entry_header_size(name, j);
switch (j.type()) switch (j.type())
{ {
case value_t::object: case value_t::object:
...@@ -1177,7 +1176,7 @@ class binary_writer ...@@ -1177,7 +1176,7 @@ class binary_writer
return write_bson_integer(name, j.m_value.number_integer); return write_bson_integer(name, j.m_value.number_integer);
case value_t::number_unsigned: case value_t::number_unsigned:
return write_bson_unsigned(name, j.m_value.number_unsigned); return write_bson_unsigned(name, j);
case value_t::string: case value_t::string:
return write_bson_string(name, *j.m_value.string); return write_bson_string(name, *j.m_value.string);
......
...@@ -499,7 +499,7 @@ class serializer ...@@ -499,7 +499,7 @@ class serializer
{ {
std::string sn(3, '\0'); std::string sn(3, '\0');
(std::snprintf)(&sn[0], sn.size(), "%.2X", byte); (std::snprintf)(&sn[0], sn.size(), "%.2X", byte);
JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn)); JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn, BasicJsonType()));
} }
case error_handler_t::ignore: case error_handler_t::ignore:
...@@ -593,7 +593,7 @@ class serializer ...@@ -593,7 +593,7 @@ class serializer
{ {
std::string sn(3, '\0'); std::string sn(3, '\0');
(std::snprintf)(&sn[0], sn.size(), "%.2X", static_cast<std::uint8_t>(s.back())); (std::snprintf)(&sn[0], sn.size(), "%.2X", static_cast<std::uint8_t>(s.back()));
JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn)); JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn, BasicJsonType()));
} }
case error_handler_t::ignore: case error_handler_t::ignore:
......
#pragma once
#include <string>
#include <nlohmann/detail/macro_scope.hpp>
namespace nlohmann
{
namespace detail
{
/*!
@brief replace all occurrences of a substring by another string
@param[in,out] s the string to manipulate; changed so that all
occurrences of @a f are replaced with @a t
@param[in] f the substring to replace with @a t
@param[in] t the string to replace @a f
@pre The search string @a f must not be empty. **This precondition is
enforced with an assertion.**
@since version 2.0.0
*/
inline void replace_substring(std::string& s, const std::string& f,
const std::string& t)
{
JSON_ASSERT(!f.empty());
for (auto pos = s.find(f); // find first occurrence of f
pos != std::string::npos; // make sure f was found
s.replace(pos, f.size(), t), // replace with t, and
pos = s.find(f, pos + t.size())) // find next occurrence of f
{}
}
/*!
* @brief string escaping as described in RFC 6901 (Sect. 4)
* @param[in] s string to escape
* @return escaped string
*
* Note the order of escaping "~" to "~0" and "/" to "~1" is important.
*/
inline std::string escape(std::string s)
{
replace_substring(s, "~", "~0");
replace_substring(s, "/", "~1");
return s;
}
/*!
* @brief string unescaping as described in RFC 6901 (Sect. 4)
* @param[in] s string to unescape
* @return unescaped string
*
* Note the order of escaping "~1" to "/" and "~0" to "~" is important.
*/
static void unescape(std::string& s)
{
replace_substring(s, "~1", "/");
replace_substring(s, "~0", "~");
}
} // namespace detail
} // namespace nlohmann
...@@ -32,7 +32,7 @@ number_float), because the library distinguishes these three types for numbers: ...@@ -32,7 +32,7 @@ number_float), because the library distinguishes these three types for numbers:
@ref basic_json::number_float_t is used for floating-point numbers or to @ref basic_json::number_float_t is used for floating-point numbers or to
approximate integers which do not fit in the limits of their respective type. approximate integers which do not fit in the limits of their respective type.
@sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON @sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON
value with the default value for a given type value with the default value for a given type
@since version 1.0.0 @since version 1.0.0
......
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -110,6 +110,7 @@ set(files ...@@ -110,6 +110,7 @@ set(files
src/unit-convenience.cpp src/unit-convenience.cpp
src/unit-conversions.cpp src/unit-conversions.cpp
src/unit-deserialization.cpp src/unit-deserialization.cpp
src/unit-diagnostics.cpp
src/unit-element_access1.cpp src/unit-element_access1.cpp
src/unit-element_access2.cpp src/unit-element_access2.cpp
src/unit-hash.cpp src/unit-hash.cpp
......
...@@ -101,7 +101,11 @@ TEST_CASE("BSON") ...@@ -101,7 +101,11 @@ TEST_CASE("BSON")
{ std::string("en\0try", 6), true } { std::string("en\0try", 6), true }
}; };
CHECK_THROWS_AS(json::to_bson(j), json::out_of_range&); CHECK_THROWS_AS(json::to_bson(j), json::out_of_range&);
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH(json::to_bson(j), "[json.exception.out_of_range.409] (/en) BSON key cannot contain code point U+0000 (at byte 2)");
#else
CHECK_THROWS_WITH(json::to_bson(j), "[json.exception.out_of_range.409] BSON key cannot contain code point U+0000 (at byte 2)"); CHECK_THROWS_WITH(json::to_bson(j), "[json.exception.out_of_range.409] BSON key cannot contain code point U+0000 (at byte 2)");
#endif
} }
SECTION("string length must be at least 1") SECTION("string length must be at least 1")
...@@ -1235,7 +1239,11 @@ TEST_CASE("BSON numerical data") ...@@ -1235,7 +1239,11 @@ TEST_CASE("BSON numerical data")
}; };
CHECK_THROWS_AS(json::to_bson(j), json::out_of_range&); CHECK_THROWS_AS(json::to_bson(j), json::out_of_range&);
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_STD_STR(json::to_bson(j), "[json.exception.out_of_range.407] (/entry) integer number " + std::to_string(i) + " cannot be represented by BSON as it does not fit int64");
#else
CHECK_THROWS_WITH_STD_STR(json::to_bson(j), "[json.exception.out_of_range.407] integer number " + std::to_string(i) + " cannot be represented by BSON as it does not fit int64"); CHECK_THROWS_WITH_STD_STR(json::to_bson(j), "[json.exception.out_of_range.407] integer number " + std::to_string(i) + " cannot be represented by BSON as it does not fit int64");
#endif
} }
} }
......
/*
__ _____ _____ _____
__| | __| | | | JSON for Modern C++ (test suite)
| | |__ | | | | | | version 3.9.1
|_____|_____|_____|_|___| https://github.com/nlohmann/json
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
SPDX-License-Identifier: MIT
Copyright (c) 2013-2019 Niels Lohmann <http://nlohmann.me>.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "doctest_compatibility.h"
#ifdef JSON_DIAGNOSTICS
#undef JSON_DIAGNOSTICS
#endif
#define JSON_DIAGNOSTICS 1
#include <nlohmann/json.hpp>
using nlohmann::json;
TEST_CASE("Better diagnostics")
{
SECTION("empty JSON Pointer")
{
json j = 1;
std::string s;
CHECK_THROWS_WITH_AS(s = j.get<std::string>(), "[json.exception.type_error.302] type must be string, but is number", json::type_error);
}
SECTION("invalid type")
{
json j;
j["a"]["b"]["c"] = 1;
std::string s;
CHECK_THROWS_WITH_AS(s = j["a"]["b"]["c"].get<std::string>(), "[json.exception.type_error.302] (/a/b/c) type must be string, but is number", json::type_error);
}
SECTION("missing key")
{
json j;
j["object"]["object"] = true;
CHECK_THROWS_WITH_AS(j["object"].at("not_found"), "[json.exception.out_of_range.403] (/object) key 'not_found' not found", json::out_of_range);
}
SECTION("array index out of range")
{
json j;
j["array"][4] = true;
CHECK_THROWS_WITH_AS(j["array"].at(5), "[json.exception.out_of_range.401] (/array) array index 5 is out of range", json::out_of_range);
}
SECTION("array index at wrong type")
{
json j;
j["array"][4] = true;
CHECK_THROWS_WITH_AS(j["array"][4][5], "[json.exception.type_error.305] (/array/4) cannot use operator[] with a numeric argument with boolean", json::type_error);
}
SECTION("wrong iterator")
{
json j;
j["array"] = json::array();
CHECK_THROWS_WITH_AS(j["array"].erase(j.begin()), "[json.exception.invalid_iterator.202] (/array) iterator does not fit current value", json::invalid_iterator);
}
SECTION("JSON Pointer escaping")
{
json j;
j["a/b"]["m~n"] = 1;
std::string s;
CHECK_THROWS_WITH_AS(s = j["a/b"]["m~n"].get<std::string>(), "[json.exception.type_error.302] (/a~1b/m~0n) type must be string, but is number", json::type_error);
}
SECTION("Parse error")
{
CHECK_THROWS_WITH_AS(json::parse(""), "[json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", json::parse_error);
}
SECTION("Regression test for https://github.com/nlohmann/json/pull/2562#pullrequestreview-574858448")
{
CHECK_THROWS_WITH_AS(json({"0", "0"})[1].get<int>(), "[json.exception.type_error.302] (/1) type must be number, but is string", json::type_error);
CHECK_THROWS_WITH_AS(json({"0", "1"})[1].get<int>(), "[json.exception.type_error.302] (/1) type must be number, but is string", json::type_error);
}
SECTION("Regression test for https://github.com/nlohmann/json/pull/2562/files/380a613f2b5d32425021129cd1f371ddcfd54ddf#r563259793")
{
json j;
j["/foo"] = {1, 2, 3};
CHECK_THROWS_WITH_AS(j.unflatten(), "[json.exception.type_error.315] (/~1foo) values in object must be primitive", json::type_error);
}
}
This diff is collapsed.
This diff is collapsed.
...@@ -496,8 +496,11 @@ TEST_CASE("JSON pointers") ...@@ -496,8 +496,11 @@ TEST_CASE("JSON pointers")
// error for nonprimitve values // error for nonprimitve values
CHECK_THROWS_AS(json({{"/1", {1, 2, 3}}}).unflatten(), json::type_error&); CHECK_THROWS_AS(json({{"/1", {1, 2, 3}}}).unflatten(), json::type_error&);
CHECK_THROWS_WITH(json({{"/1", {1, 2, 3}}}).unflatten(), #if JSON_DIAGNOSTICS
"[json.exception.type_error.315] values in object must be primitive"); CHECK_THROWS_WITH(json({{"/1", {1, 2, 3}}}).unflatten(), "[json.exception.type_error.315] (/~11) values in object must be primitive");
#else
CHECK_THROWS_WITH(json({{"/1", {1, 2, 3}}}).unflatten(), "[json.exception.type_error.315] values in object must be primitive");
#endif
// error for conflicting values // error for conflicting values
json j_error = {{"", 42}, {"/foo", 17}}; json j_error = {{"", 42}, {"/foo", 17}};
......
...@@ -394,7 +394,11 @@ TEST_CASE("regression tests 1") ...@@ -394,7 +394,11 @@ TEST_CASE("regression tests 1")
// improve coverage // improve coverage
o["int"] = 1; o["int"] = 1;
CHECK_THROWS_AS(s2 = o["int"], json::type_error); CHECK_THROWS_AS(s2 = o["int"], json::type_error);
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH(s2 = o["int"], "[json.exception.type_error.302] (/int) type must be string, but is number");
#else
CHECK_THROWS_WITH(s2 = o["int"], "[json.exception.type_error.302] type must be string, but is number"); CHECK_THROWS_WITH(s2 = o["int"], "[json.exception.type_error.302] type must be string, but is number");
#endif
} }
#endif #endif
......
...@@ -1181,8 +1181,8 @@ TEST_CASE("Unicode" * doctest::skip()) ...@@ -1181,8 +1181,8 @@ TEST_CASE("Unicode" * doctest::skip())
CHECK_NOTHROW(json::json_pointer("/" + ptr)); CHECK_NOTHROW(json::json_pointer("/" + ptr));
// check escape/unescape roundtrip // check escape/unescape roundtrip
auto escaped = json::json_pointer::escape(ptr); auto escaped = nlohmann::detail::escape(ptr);
json::json_pointer::unescape(escaped); nlohmann::detail::unescape(escaped);
CHECK(escaped == ptr); CHECK(escaped == ptr);
} }
} }
......
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