Skip to content

Commit 76d416b

Browse files
committed
issue4561 - use diagnostic positions for exceptions
1 parent 05f8bb5 commit 76d416b

9 files changed

+127
-9
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#include <iostream>
2+
3+
#define JSON_DIAGNOSTIC_POSITIONS 1
4+
#include <nlohmann/json.hpp>
5+
6+
using json = nlohmann::json;
7+
8+
/* Demonstration of type error exception with diagnostic postions support enabled */
9+
int main()
10+
{
11+
//Invalid json string - housenumber type must be int instead of string
12+
std::string json_invalid_string = R"(
13+
{
14+
"address": {
15+
"street": "Fake Street",
16+
"housenumber": "1"
17+
}
18+
}
19+
)";
20+
json j = json::parse(json_invalid_string);
21+
try
22+
{
23+
int housenumber = j["address"]["housenumber"];
24+
}
25+
catch (const json::exception& e)
26+
{
27+
std::cout << e.what() << '\n';
28+
}
29+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[json.exception.type_error.302] (bytes 92-95) type must be number, but is string
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#include <iostream>
2+
3+
#define JSON_DIAGNOSTICS 1
4+
#define JSON_DIAGNOSTIC_POSITIONS 1
5+
#include <nlohmann/json.hpp>
6+
7+
using json = nlohmann::json;
8+
9+
/* Demonstration of type error exception with diagnostic postions support enabled */
10+
int main()
11+
{
12+
//Invalid json string - housenumber type must be int instead of string
13+
std::string json_invalid_string = R"(
14+
{
15+
"address": {
16+
"street": "Fake Street",
17+
"housenumber": "1"
18+
}
19+
}
20+
)";
21+
json j = json::parse(json_invalid_string);
22+
try
23+
{
24+
int housenumber = j["address"]["housenumber"];
25+
}
26+
catch (const json::exception& e)
27+
{
28+
std::cout << e.what() << '\n';
29+
}
30+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[json.exception.type_error.302] (/address/housenumber) (bytes 92-95) type must be number, but is string

include/nlohmann/detail/exceptions.hpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,16 +131,30 @@ class exception : public std::exception
131131
{
132132
return concat(a, '/', detail::escape(b));
133133
});
134-
return concat('(', str, ") ");
134+
135+
return concat('(', str, ") ") + get_byte_positions(leaf_element);
135136
#else
136-
static_cast<void>(leaf_element);
137-
return "";
137+
return get_byte_positions(leaf_element);
138138
#endif
139139
}
140140

141141
private:
142142
/// an exception object as storage for error messages
143143
std::runtime_error m;
144+
template<typename BasicJsonType>
145+
static std::string get_byte_positions(const BasicJsonType* leaf_element)
146+
{
147+
#if JSON_DIAGNOSTIC_POSITIONS
148+
if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos))
149+
{
150+
return concat('(', "bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") ");
151+
}
152+
return "";
153+
#else
154+
static_cast<void>(leaf_element);
155+
return "";
156+
#endif
157+
}
144158
};
145159

146160
/// @brief exception indicating a parse error

single_include/nlohmann/json.hpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4534,16 +4534,30 @@ class exception : public std::exception
45344534
{
45354535
return concat(a, '/', detail::escape(b));
45364536
});
4537-
return concat('(', str, ") ");
4537+
4538+
return concat('(', str, ") ") + get_byte_positions(leaf_element);
45384539
#else
4539-
static_cast<void>(leaf_element);
4540-
return "";
4540+
return get_byte_positions(leaf_element);
45414541
#endif
45424542
}
45434543

45444544
private:
45454545
/// an exception object as storage for error messages
45464546
std::runtime_error m;
4547+
template<typename BasicJsonType>
4548+
static std::string get_byte_positions(const BasicJsonType* leaf_element)
4549+
{
4550+
#if JSON_DIAGNOSTIC_POSITIONS
4551+
if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos))
4552+
{
4553+
return concat('(', "bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") ");
4554+
}
4555+
return "";
4556+
#else
4557+
static_cast<void>(leaf_element);
4558+
return "";
4559+
#endif
4560+
}
45474561
};
45484562

45494563
/// @brief exception indicating a parse error

tests/src/unit-json_patch.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,20 @@ TEST_CASE("JSON patch")
6060
json const doc2 = R"({ "q": { "bar": 2 } })"_json;
6161

6262
// because "a" does not exist.
63+
#if JSON_DIAGNOSTIC_POSITIONS
64+
CHECK_THROWS_WITH_AS(doc2.patch(patch1), "[json.exception.out_of_range.403] (bytes 0-21) key 'a' not found", json::out_of_range&);
65+
#else
6366
CHECK_THROWS_WITH_AS(doc2.patch(patch1), "[json.exception.out_of_range.403] key 'a' not found", json::out_of_range&);
67+
#endif
6468

6569
json const doc3 = R"({ "a": {} })"_json;
6670
json const patch2 = R"([{ "op": "add", "path": "/a/b/c", "value": 1 }])"_json;
6771

6872
// should cause an error because "b" does not exist in doc3
6973
#if JSON_DIAGNOSTICS
7074
CHECK_THROWS_WITH_AS(doc3.patch(patch2), "[json.exception.out_of_range.403] (/a) key 'b' not found", json::out_of_range&);
75+
#elif JSON_DIAGNOSTIC_POSITIONS
76+
CHECK_THROWS_WITH_AS(doc3.patch(patch2), "[json.exception.out_of_range.403] (bytes 7-9) key 'b' not found", json::out_of_range&);
7177
#else
7278
CHECK_THROWS_WITH_AS(doc3.patch(patch2), "[json.exception.out_of_range.403] key 'b' not found", json::out_of_range&);
7379
#endif
@@ -333,6 +339,8 @@ TEST_CASE("JSON patch")
333339
CHECK_THROWS_AS(doc.patch(patch), json::other_error&);
334340
#if JSON_DIAGNOSTICS
335341
CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (/0) unsuccessful: " + patch[0].dump());
342+
#elif JSON_DIAGNOSTIC_POSITIONS
343+
CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (bytes 47-95) unsuccessful: " + patch[0].dump());
336344
#else
337345
CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] unsuccessful: " + patch[0].dump());
338346
#endif
@@ -417,8 +425,11 @@ TEST_CASE("JSON patch")
417425
// applied), because the "add" operation's target location that
418426
// references neither the root of the document, nor a member of
419427
// an existing object, nor a member of an existing array.
420-
428+
#if JSON_DIAGNOSTIC_POSITIONS
429+
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.403] (bytes 21-37) key 'baz' not found", json::out_of_range&);
430+
#else
421431
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.403] key 'baz' not found", json::out_of_range&);
432+
#endif
422433
}
423434

424435
// A.13. Invalid JSON Patch Document
@@ -476,6 +487,8 @@ TEST_CASE("JSON patch")
476487
CHECK_THROWS_AS(doc.patch(patch), json::other_error&);
477488
#if JSON_DIAGNOSTICS
478489
CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (/0) unsuccessful: " + patch[0].dump());
490+
#elif JSON_DIAGNOSTIC_POSITIONS
491+
CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (bytes 47-92) unsuccessful: " + patch[0].dump());
479492
#else
480493
CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] unsuccessful: " + patch[0].dump());
481494
#endif
@@ -1205,6 +1218,8 @@ TEST_CASE("JSON patch")
12051218
CHECK_THROWS_AS(doc.patch(patch), json::other_error&);
12061219
#if JSON_DIAGNOSTICS
12071220
CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (/0) unsuccessful: " + patch[0].dump());
1221+
#elif JSON_DIAGNOSTIC_POSITIONS
1222+
CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (bytes 47-117) unsuccessful: " + patch[0].dump());
12081223
#else
12091224
CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] unsuccessful: " + patch[0].dump());
12101225
#endif

tests/src/unit-json_pointer.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,11 +203,15 @@ TEST_CASE("JSON pointers")
203203
// escaped access
204204
CHECK(j[json::json_pointer("/a~1b")] == j["a/b"]);
205205
CHECK(j[json::json_pointer("/m~0n")] == j["m~n"]);
206-
206+
#if JSON_DIAGNOSTIC_POSITIONS
207+
// unescaped access
208+
CHECK_THROWS_WITH_AS(j.at(json::json_pointer("/a/b")),
209+
"[json.exception.out_of_range.403] (bytes 13-297) key 'a' not found", json::out_of_range&);
210+
#else
207211
// unescaped access
208212
CHECK_THROWS_WITH_AS(j.at(json::json_pointer("/a/b")),
209213
"[json.exception.out_of_range.403] key 'a' not found", json::out_of_range&);
210-
214+
#endif
211215
// unresolved access
212216
const json j_primitive = 1;
213217
CHECK_THROWS_WITH_AS(j_primitive["/foo"_json_pointer],

tests/src/unit-regression1.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1400,14 +1400,24 @@ TEST_CASE("regression tests 1")
14001400
auto p1 = R"([{"op": "move",
14011401
"from": "/one/two/three",
14021402
"path": "/a/b/c"}])"_json;
1403+
#if JSON_DIAGNOSTIC_POSITIONS
1404+
CHECK_THROWS_WITH_AS(model.patch(p1),
1405+
"[json.exception.out_of_range.403] (bytes 0-158) key 'a' not found", json::out_of_range&);
1406+
#else
14031407
CHECK_THROWS_WITH_AS(model.patch(p1),
14041408
"[json.exception.out_of_range.403] key 'a' not found", json::out_of_range&);
1409+
#endif
14051410

14061411
auto p2 = R"([{"op": "copy",
14071412
"from": "/one/two/three",
14081413
"path": "/a/b/c"}])"_json;
1414+
#if JSON_DIAGNOSTIC_POSITIONS
1415+
CHECK_THROWS_WITH_AS(model.patch(p2),
1416+
"[json.exception.out_of_range.403] (bytes 0-158) key 'a' not found", json::out_of_range&);
1417+
#else
14091418
CHECK_THROWS_WITH_AS(model.patch(p2),
14101419
"[json.exception.out_of_range.403] key 'a' not found", json::out_of_range&);
1420+
#endif
14111421
}
14121422

14131423
SECTION("issue #961 - incorrect parsing of indefinite length CBOR strings")

0 commit comments

Comments
 (0)