diff --git a/docs/examples/diagnostic_positions_exception.cpp b/docs/examples/diagnostic_positions_exception.cpp new file mode 100644 index 0000000000..66a9301f76 --- /dev/null +++ b/docs/examples/diagnostic_positions_exception.cpp @@ -0,0 +1,29 @@ +#include + +#define JSON_DIAGNOSTIC_POSITIONS 1 +#include + +using json = nlohmann::json; + +/* Demonstration of type error exception with diagnostic postions support enabled */ +int main() +{ + //Invalid json string - housenumber type must be int instead of string + std::string json_invalid_string = R"( + { + "address": { + "street": "Fake Street", + "housenumber": "1" + } + } + )"; + json j = json::parse(json_invalid_string); + try + { + int housenumber = j["address"]["housenumber"]; + } + catch (const json::exception& e) + { + std::cout << e.what() << '\n'; + } +} diff --git a/docs/examples/diagnostic_positions_exception.output b/docs/examples/diagnostic_positions_exception.output new file mode 100644 index 0000000000..564deb34d3 --- /dev/null +++ b/docs/examples/diagnostic_positions_exception.output @@ -0,0 +1 @@ +[json.exception.type_error.302] (bytes 92-95) type must be number, but is string diff --git a/docs/examples/diagnostics_extended_positions.cpp b/docs/examples/diagnostics_extended_positions.cpp new file mode 100644 index 0000000000..027dfd373b --- /dev/null +++ b/docs/examples/diagnostics_extended_positions.cpp @@ -0,0 +1,30 @@ +#include + +#define JSON_DIAGNOSTICS 1 +#define JSON_DIAGNOSTIC_POSITIONS 1 +#include + +using json = nlohmann::json; + +/* Demonstration of type error exception with diagnostic postions support enabled */ +int main() +{ + //Invalid json string - housenumber type must be int instead of string + std::string json_invalid_string = R"( + { + "address": { + "street": "Fake Street", + "housenumber": "1" + } + } + )"; + json j = json::parse(json_invalid_string); + try + { + int housenumber = j["address"]["housenumber"]; + } + catch (const json::exception& e) + { + std::cout << e.what() << '\n'; + } +} \ No newline at end of file diff --git a/docs/examples/diagnostics_extended_positions.output b/docs/examples/diagnostics_extended_positions.output new file mode 100644 index 0000000000..35096d9467 --- /dev/null +++ b/docs/examples/diagnostics_extended_positions.output @@ -0,0 +1 @@ +[json.exception.type_error.302] (/address/housenumber) (bytes 92-95) type must be number, but is string diff --git a/include/nlohmann/detail/exceptions.hpp b/include/nlohmann/detail/exceptions.hpp index 1fabeb16d3..82541fb82a 100644 --- a/include/nlohmann/detail/exceptions.hpp +++ b/include/nlohmann/detail/exceptions.hpp @@ -131,16 +131,30 @@ class exception : public std::exception { return concat(a, '/', detail::escape(b)); }); - return concat('(', str, ") "); + + return concat('(', str, ") ") + get_byte_positions(leaf_element); #else - static_cast(leaf_element); - return ""; + return get_byte_positions(leaf_element); #endif } private: /// an exception object as storage for error messages std::runtime_error m; + template + static std::string get_byte_positions(const BasicJsonType* leaf_element) + { +#if JSON_DIAGNOSTIC_POSITIONS + if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos)) + { + return concat('(', "bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") "); + } + return ""; +#else + static_cast(leaf_element); + return ""; +#endif + } }; /// @brief exception indicating a parse error diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 9314724f6e..19d3818ba4 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -4534,16 +4534,30 @@ class exception : public std::exception { return concat(a, '/', detail::escape(b)); }); - return concat('(', str, ") "); + + return concat('(', str, ") ") + get_byte_positions(leaf_element); #else - static_cast(leaf_element); - return ""; + return get_byte_positions(leaf_element); #endif } private: /// an exception object as storage for error messages std::runtime_error m; + template + static std::string get_byte_positions(const BasicJsonType* leaf_element) + { +#if JSON_DIAGNOSTIC_POSITIONS + if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos)) + { + return concat('(', "bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") "); + } + return ""; +#else + static_cast(leaf_element); + return ""; +#endif + } }; /// @brief exception indicating a parse error diff --git a/tests/src/unit-json_patch.cpp b/tests/src/unit-json_patch.cpp index da0cf73101..18f25c7fea 100644 --- a/tests/src/unit-json_patch.cpp +++ b/tests/src/unit-json_patch.cpp @@ -60,7 +60,11 @@ TEST_CASE("JSON patch") json const doc2 = R"({ "q": { "bar": 2 } })"_json; // because "a" does not exist. +#if JSON_DIAGNOSTIC_POSITIONS + CHECK_THROWS_WITH_AS(doc2.patch(patch1), "[json.exception.out_of_range.403] (bytes 0-21) key 'a' not found", json::out_of_range&); +#else CHECK_THROWS_WITH_AS(doc2.patch(patch1), "[json.exception.out_of_range.403] key 'a' not found", json::out_of_range&); +#endif json const doc3 = R"({ "a": {} })"_json; json const patch2 = R"([{ "op": "add", "path": "/a/b/c", "value": 1 }])"_json; @@ -68,6 +72,8 @@ TEST_CASE("JSON patch") // should cause an error because "b" does not exist in doc3 #if JSON_DIAGNOSTICS CHECK_THROWS_WITH_AS(doc3.patch(patch2), "[json.exception.out_of_range.403] (/a) key 'b' not found", json::out_of_range&); +#elif JSON_DIAGNOSTIC_POSITIONS + CHECK_THROWS_WITH_AS(doc3.patch(patch2), "[json.exception.out_of_range.403] (bytes 7-9) key 'b' not found", json::out_of_range&); #else CHECK_THROWS_WITH_AS(doc3.patch(patch2), "[json.exception.out_of_range.403] key 'b' not found", json::out_of_range&); #endif @@ -333,6 +339,8 @@ TEST_CASE("JSON patch") CHECK_THROWS_AS(doc.patch(patch), json::other_error&); #if JSON_DIAGNOSTICS CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (/0) unsuccessful: " + patch[0].dump()); +#elif JSON_DIAGNOSTIC_POSITIONS + CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (bytes 47-95) unsuccessful: " + patch[0].dump()); #else CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] unsuccessful: " + patch[0].dump()); #endif @@ -417,8 +425,11 @@ TEST_CASE("JSON patch") // applied), because the "add" operation's target location that // references neither the root of the document, nor a member of // an existing object, nor a member of an existing array. - +#if JSON_DIAGNOSTIC_POSITIONS + CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.403] (bytes 21-37) key 'baz' not found", json::out_of_range&); +#else CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.403] key 'baz' not found", json::out_of_range&); +#endif } // A.13. Invalid JSON Patch Document @@ -476,6 +487,8 @@ TEST_CASE("JSON patch") CHECK_THROWS_AS(doc.patch(patch), json::other_error&); #if JSON_DIAGNOSTICS CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (/0) unsuccessful: " + patch[0].dump()); +#elif JSON_DIAGNOSTIC_POSITIONS + CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (bytes 47-92) unsuccessful: " + patch[0].dump()); #else CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] unsuccessful: " + patch[0].dump()); #endif @@ -1205,6 +1218,8 @@ TEST_CASE("JSON patch") CHECK_THROWS_AS(doc.patch(patch), json::other_error&); #if JSON_DIAGNOSTICS CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (/0) unsuccessful: " + patch[0].dump()); +#elif JSON_DIAGNOSTIC_POSITIONS + CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (bytes 47-117) unsuccessful: " + patch[0].dump()); #else CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] unsuccessful: " + patch[0].dump()); #endif diff --git a/tests/src/unit-json_pointer.cpp b/tests/src/unit-json_pointer.cpp index beb24cc9e7..bc4226b709 100644 --- a/tests/src/unit-json_pointer.cpp +++ b/tests/src/unit-json_pointer.cpp @@ -203,11 +203,15 @@ TEST_CASE("JSON pointers") // escaped access CHECK(j[json::json_pointer("/a~1b")] == j["a/b"]); CHECK(j[json::json_pointer("/m~0n")] == j["m~n"]); - +#if JSON_DIAGNOSTIC_POSITIONS + // unescaped access + CHECK_THROWS_WITH_AS(j.at(json::json_pointer("/a/b")), + "[json.exception.out_of_range.403] (bytes 13-297) key 'a' not found", json::out_of_range&); +#else // unescaped access CHECK_THROWS_WITH_AS(j.at(json::json_pointer("/a/b")), "[json.exception.out_of_range.403] key 'a' not found", json::out_of_range&); - +#endif // unresolved access const json j_primitive = 1; CHECK_THROWS_WITH_AS(j_primitive["/foo"_json_pointer], diff --git a/tests/src/unit-regression1.cpp b/tests/src/unit-regression1.cpp index ecb970d2d9..7043e9f0fc 100644 --- a/tests/src/unit-regression1.cpp +++ b/tests/src/unit-regression1.cpp @@ -1400,14 +1400,24 @@ TEST_CASE("regression tests 1") auto p1 = R"([{"op": "move", "from": "/one/two/three", "path": "/a/b/c"}])"_json; +#if JSON_DIAGNOSTIC_POSITIONS + CHECK_THROWS_WITH_AS(model.patch(p1), + "[json.exception.out_of_range.403] (bytes 0-158) key 'a' not found", json::out_of_range&); +#else CHECK_THROWS_WITH_AS(model.patch(p1), "[json.exception.out_of_range.403] key 'a' not found", json::out_of_range&); +#endif auto p2 = R"([{"op": "copy", "from": "/one/two/three", "path": "/a/b/c"}])"_json; +#if JSON_DIAGNOSTIC_POSITIONS + CHECK_THROWS_WITH_AS(model.patch(p2), + "[json.exception.out_of_range.403] (bytes 0-158) key 'a' not found", json::out_of_range&); +#else CHECK_THROWS_WITH_AS(model.patch(p2), "[json.exception.out_of_range.403] key 'a' not found", json::out_of_range&); +#endif } SECTION("issue #961 - incorrect parsing of indefinite length CBOR strings")