Skip to content

Commit

Permalink
issue4561 - use diagnostic positions for exceptions
Browse files Browse the repository at this point in the history
Signed-off-by: Harinath Nampally <harinath922@gmail.com>
  • Loading branch information
hnampally committed Jan 7, 2025
1 parent 05f8bb5 commit 3070676
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 9 deletions.
29 changes: 29 additions & 0 deletions docs/examples/diagnostic_positions_exception.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include <iostream>

#define JSON_DIAGNOSTIC_POSITIONS 1
#include <nlohmann/json.hpp>

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';
}
}
1 change: 1 addition & 0 deletions docs/examples/diagnostic_positions_exception.output
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[json.exception.type_error.302] (bytes 92-95) type must be number, but is string
30 changes: 30 additions & 0 deletions docs/examples/diagnostics_extended_positions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include <iostream>

#define JSON_DIAGNOSTICS 1
#define JSON_DIAGNOSTIC_POSITIONS 1
#include <nlohmann/json.hpp>

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';
}
}
1 change: 1 addition & 0 deletions docs/examples/diagnostics_extended_positions.output
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[json.exception.type_error.302] (/address/housenumber) (bytes 92-95) type must be number, but is string
20 changes: 17 additions & 3 deletions include/nlohmann/detail/exceptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>(leaf_element);
return "";
return get_byte_positions(leaf_element);
#endif
}

private:
/// an exception object as storage for error messages
std::runtime_error m;
template<typename BasicJsonType>
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<void>(leaf_element);
return "";
#endif
}
};

/// @brief exception indicating a parse error
Expand Down
20 changes: 17 additions & 3 deletions single_include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>(leaf_element);
return "";
return get_byte_positions(leaf_element);
#endif
}

private:
/// an exception object as storage for error messages
std::runtime_error m;
template<typename BasicJsonType>
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<void>(leaf_element);
return "";
#endif
}
};

/// @brief exception indicating a parse error
Expand Down
17 changes: 16 additions & 1 deletion tests/src/unit-json_patch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,20 @@ 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;

// 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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions tests/src/unit-json_pointer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
10 changes: 10 additions & 0 deletions tests/src/unit-regression1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down

0 comments on commit 3070676

Please sign in to comment.