From 06815d274e6dc95e61411e01596d8f0cc8748e99 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 2 Mar 2017 17:55:13 +0100 Subject: [PATCH] :hammer: added user-defined exceptions 104 and 105 These exceptions are thrown in case of parse errors in JSON patch documents. --- src/json.hpp | 21 +++--- src/json.hpp.re2c | 21 +++--- test/src/unit-json_patch.cpp | 120 +++++++++++++++++++++-------------- 3 files changed, 94 insertions(+), 68 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index d4e87e5e..6451d732 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -1107,8 +1107,8 @@ class basic_json json.exception.[parse_error](@ref parse_error).101 | `"parse error at 2: unexpected end of input; expected string literal"` | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @ref parse_error::byte indicates the error position. json.exception.[parse_error](@ref parse_error).102 | `"parse error at 14: missing or wrong low surrogate"` | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. json.exception.[parse_error](@ref parse_error).103 | `"parse error: code points above 0x10FFFF are invalid"` | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. - json.exception.[parse_error](@ref parse_error).104 | "parse error: JSON patch must be an array of objects" | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON documentthat represents an array of objects. - json.exception.[parse_error](@ref parse_error).105 | "parse error: operation must have string member 'op'" | An operation of a JSON Patch document must contain Operation objects MUST have exactly one "op" member, whose value indicates the operation to perform. Its value MUST be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. + json.exception.[parse_error](@ref parse_error).104 | `"parse error: JSON patch must be an array of objects"` | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. + json.exception.[parse_error](@ref parse_error).105 | `"parse error: operation must have string member 'op'"` | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. json.exception.[parse_error](@ref parse_error).106 | "parse error: array index must not begin with '0'" | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number wihtout a leading `0`. json.exception.[parse_error](@ref parse_error).107 | "parse error: JSON pointer must be empty or begin with '/'" | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. json.exception.[parse_error](@ref parse_error).108 | "parse error: escape character '~' must be followed with '0' or '1'" | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. @@ -12713,6 +12713,8 @@ basic_json_parser_74: not found"` @throw invalid_argument if the JSON patch is malformed (e.g., mandatory attributes are missing); example: `"operation add must have member path"` + @throw parse_error.104 if the JSON patch does not consist of an array of + objects @complexity Linear in the size of the JSON value and the length of the JSON patch. As usually only a fraction of the JSON value is affected by @@ -12858,11 +12860,10 @@ basic_json_parser_74: } }; - // type check + // type check: top level value must be an array if (not json_patch.is_array()) { - // a JSON patch must be an array of objects - JSON_THROW(std::invalid_argument("JSON patch must be an array of objects")); + JSON_THROW(parse_error(104, 0, "JSON patch must be an array of objects")); } // iterate and apply the operations @@ -12882,23 +12883,23 @@ basic_json_parser_74: // check if desired value is present if (it == val.m_value.object->end()) { - JSON_THROW(std::invalid_argument(error_msg + " must have member '" + member + "'")); + JSON_THROW(parse_error(105, 0, error_msg + " must have member '" + member + "'")); } // check if result is of type string if (string_type and not it->second.is_string()) { - JSON_THROW(std::invalid_argument(error_msg + " must have string member '" + member + "'")); + JSON_THROW(parse_error(105, 0, error_msg + " must have string member '" + member + "'")); } // no error: return value return it->second; }; - // type check + // type check: every element of the array must be an object if (not val.is_object()) { - JSON_THROW(std::invalid_argument("JSON patch must be an array of objects")); + JSON_THROW(parse_error(104, 0, "JSON patch must be an array of objects")); } // collect mandatory members @@ -12981,7 +12982,7 @@ basic_json_parser_74: { // op must be "add", "remove", "replace", "move", "copy", or // "test" - JSON_THROW(std::invalid_argument("operation value '" + op + "' is invalid")); + JSON_THROW(parse_error(105, 0, "operation value '" + op + "' is invalid")); } } } diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index 1afc025a..3ed26c53 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -1107,8 +1107,8 @@ class basic_json json.exception.[parse_error](@ref parse_error).101 | `"parse error at 2: unexpected end of input; expected string literal"` | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @ref parse_error::byte indicates the error position. json.exception.[parse_error](@ref parse_error).102 | `"parse error at 14: missing or wrong low surrogate"` | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. json.exception.[parse_error](@ref parse_error).103 | `"parse error: code points above 0x10FFFF are invalid"` | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. - json.exception.[parse_error](@ref parse_error).104 | "parse error: JSON patch must be an array of objects" | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON documentthat represents an array of objects. - json.exception.[parse_error](@ref parse_error).105 | "parse error: operation must have string member 'op'" | An operation of a JSON Patch document must contain Operation objects MUST have exactly one "op" member, whose value indicates the operation to perform. Its value MUST be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. + json.exception.[parse_error](@ref parse_error).104 | `"parse error: JSON patch must be an array of objects"` | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. + json.exception.[parse_error](@ref parse_error).105 | `"parse error: operation must have string member 'op'"` | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. json.exception.[parse_error](@ref parse_error).106 | "parse error: array index must not begin with '0'" | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number wihtout a leading `0`. json.exception.[parse_error](@ref parse_error).107 | "parse error: JSON pointer must be empty or begin with '/'" | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. json.exception.[parse_error](@ref parse_error).108 | "parse error: escape character '~' must be followed with '0' or '1'" | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. @@ -11746,6 +11746,8 @@ class basic_json not found"` @throw invalid_argument if the JSON patch is malformed (e.g., mandatory attributes are missing); example: `"operation add must have member path"` + @throw parse_error.104 if the JSON patch does not consist of an array of + objects @complexity Linear in the size of the JSON value and the length of the JSON patch. As usually only a fraction of the JSON value is affected by @@ -11891,11 +11893,10 @@ class basic_json } }; - // type check + // type check: top level value must be an array if (not json_patch.is_array()) { - // a JSON patch must be an array of objects - JSON_THROW(std::invalid_argument("JSON patch must be an array of objects")); + JSON_THROW(parse_error(104, 0, "JSON patch must be an array of objects")); } // iterate and apply the operations @@ -11915,23 +11916,23 @@ class basic_json // check if desired value is present if (it == val.m_value.object->end()) { - JSON_THROW(std::invalid_argument(error_msg + " must have member '" + member + "'")); + JSON_THROW(parse_error(105, 0, error_msg + " must have member '" + member + "'")); } // check if result is of type string if (string_type and not it->second.is_string()) { - JSON_THROW(std::invalid_argument(error_msg + " must have string member '" + member + "'")); + JSON_THROW(parse_error(105, 0, error_msg + " must have string member '" + member + "'")); } // no error: return value return it->second; }; - // type check + // type check: every element of the array must be an object if (not val.is_object()) { - JSON_THROW(std::invalid_argument("JSON patch must be an array of objects")); + JSON_THROW(parse_error(104, 0, "JSON patch must be an array of objects")); } // collect mandatory members @@ -12014,7 +12015,7 @@ class basic_json { // op must be "add", "remove", "replace", "move", "copy", or // "test" - JSON_THROW(std::invalid_argument("operation value '" + op + "' is invalid")); + JSON_THROW(parse_error(105, 0, "operation value '" + op + "' is invalid")); } } } diff --git a/test/src/unit-json_patch.cpp b/test/src/unit-json_patch.cpp index 05ed7502..4dd89b75 100644 --- a/test/src/unit-json_patch.cpp +++ b/test/src/unit-json_patch.cpp @@ -666,40 +666,45 @@ TEST_CASE("JSON patch") { json j; json patch = {{"op", "add"}, {"path", ""}, {"value", 1}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "JSON patch must be an array of objects"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.104] parse error: JSON patch must be an array of objects"); } SECTION("not an array of objects") { json j; json patch = {"op", "add", "path", "", "value", 1}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "JSON patch must be an array of objects"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.104] parse error: JSON patch must be an array of objects"); } SECTION("missing 'op'") { json j; json patch = {{{"foo", "bar"}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation must have member 'op'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation must have member 'op'"); } SECTION("non-string 'op'") { json j; json patch = {{{"op", 1}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation must have string member 'op'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation must have string member 'op'"); } SECTION("invalid operation") { json j; json patch = {{{"op", "foo"}, {"path", ""}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation value 'foo' is invalid"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation value 'foo' is invalid"); } } @@ -709,24 +714,27 @@ TEST_CASE("JSON patch") { json j; json patch = {{{"op", "add"}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation 'add' must have member 'path'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation 'add' must have member 'path'"); } SECTION("non-string 'path'") { json j; json patch = {{{"op", "add"}, {"path", 1}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation 'add' must have string member 'path'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation 'add' must have string member 'path'"); } SECTION("missing 'value'") { json j; json patch = {{{"op", "add"}, {"path", ""}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation 'add' must have member 'value'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation 'add' must have member 'value'"); } SECTION("invalid array index") @@ -744,16 +752,18 @@ TEST_CASE("JSON patch") { json j; json patch = {{{"op", "remove"}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation 'remove' must have member 'path'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation 'remove' must have member 'path'"); } SECTION("non-string 'path'") { json j; json patch = {{{"op", "remove"}, {"path", 1}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation 'remove' must have string member 'path'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation 'remove' must have string member 'path'"); } SECTION("nonexisting target location (array)") @@ -787,24 +797,27 @@ TEST_CASE("JSON patch") { json j; json patch = {{{"op", "replace"}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation 'replace' must have member 'path'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation 'replace' must have member 'path'"); } SECTION("non-string 'path'") { json j; json patch = {{{"op", "replace"}, {"path", 1}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation 'replace' must have string member 'path'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation 'replace' must have string member 'path'"); } SECTION("missing 'value'") { json j; json patch = {{{"op", "replace"}, {"path", ""}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation 'replace' must have member 'value'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation 'replace' must have member 'value'"); } SECTION("nonexisting target location (array)") @@ -830,32 +843,36 @@ TEST_CASE("JSON patch") { json j; json patch = {{{"op", "move"}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation 'move' must have member 'path'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation 'move' must have member 'path'"); } SECTION("non-string 'path'") { json j; json patch = {{{"op", "move"}, {"path", 1}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation 'move' must have string member 'path'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation 'move' must have string member 'path'"); } SECTION("missing 'from'") { json j; json patch = {{{"op", "move"}, {"path", ""}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation 'move' must have member 'from'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation 'move' must have member 'from'"); } SECTION("non-string 'from'") { json j; json patch = {{{"op", "move"}, {"path", ""}, {"from", 1}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation 'move' must have string member 'from'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation 'move' must have string member 'from'"); } SECTION("nonexisting from location (array)") @@ -881,32 +898,36 @@ TEST_CASE("JSON patch") { json j; json patch = {{{"op", "copy"}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation 'copy' must have member 'path'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation 'copy' must have member 'path'"); } SECTION("non-string 'path'") { json j; json patch = {{{"op", "copy"}, {"path", 1}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation 'copy' must have string member 'path'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation 'copy' must have string member 'path'"); } SECTION("missing 'from'") { json j; json patch = {{{"op", "copy"}, {"path", ""}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation 'copy' must have member 'from'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation 'copy' must have member 'from'"); } SECTION("non-string 'from'") { json j; json patch = {{{"op", "copy"}, {"path", ""}, {"from", 1}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation 'copy' must have string member 'from'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation 'copy' must have string member 'from'"); } SECTION("nonexisting from location (array)") @@ -932,24 +953,27 @@ TEST_CASE("JSON patch") { json j; json patch = {{{"op", "test"}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation 'test' must have member 'path'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation 'test' must have member 'path'"); } SECTION("non-string 'path'") { json j; json patch = {{{"op", "test"}, {"path", 1}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation 'test' must have string member 'path'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation 'test' must have string member 'path'"); } SECTION("missing 'value'") { json j; json patch = {{{"op", "test"}, {"path", ""}}}; - CHECK_THROWS_AS(j.patch(patch), std::invalid_argument); - CHECK_THROWS_WITH(j.patch(patch), "operation 'test' must have member 'value'"); + CHECK_THROWS_AS(j.patch(patch), json::parse_error); + CHECK_THROWS_WITH(j.patch(patch), + "[json.exception.parse_error.105] parse error: operation 'test' must have member 'value'"); } } }