diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index 884ff014a..0332474b2 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -48,7 +48,7 @@ inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) { if (JSON_HEDLEY_UNLIKELY(!j.is_null())) { - JSON_THROW(type_error::create(302, concat("type must be null, but is ", j.type_name()), &j)); + throw_type_must_be("null", j); } n = nullptr; } @@ -102,7 +102,7 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) case value_t::binary: case value_t::discarded: default: - JSON_THROW(type_error::create(302, concat("type must be number, but is ", j.type_name()), &j)); + throw_type_must_be("number", j); } } @@ -111,7 +111,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& { if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) { - JSON_THROW(type_error::create(302, concat("type must be boolean, but is ", j.type_name()), &j)); + throw_type_must_be("boolean", j); } b = *j.template get_ptr(); } @@ -121,7 +121,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + throw_type_must_be("string", j); } s = *j.template get_ptr(); } @@ -137,7 +137,7 @@ inline void from_json(const BasicJsonType& j, StringType& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + throw_type_must_be("string", j); } s = *j.template get_ptr(); @@ -183,7 +183,7 @@ inline void from_json(const BasicJsonType& j, std::forward_list& l { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } l.clear(); std::transform(j.rbegin(), j.rend(), @@ -200,7 +200,7 @@ inline void from_json(const BasicJsonType& j, std::valarray& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } l.resize(j.size()); std::transform(j.begin(), j.end(), std::begin(l), @@ -305,7 +305,7 @@ void()) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } from_json_array_impl(j, arr, priority_tag<3> {}); @@ -324,7 +324,7 @@ auto from_json(const BasicJsonType& j, identity_tag> tag) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } return from_json_inplace_array_impl(j, tag, make_index_sequence {}); @@ -335,7 +335,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& { if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) { - JSON_THROW(type_error::create(302, concat("type must be binary, but is ", j.type_name()), &j)); + throw_type_must_be("binary", j); } bin = *j.template get_ptr(); @@ -356,7 +356,7 @@ inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin) } else { - JSON_THROW(type_error::create(302, concat("type must be binary or array, but is ", j.type_name()), &j)); + throw_type_must_be("binary or array", j); } } @@ -377,7 +377,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { - JSON_THROW(type_error::create(302, concat("type must be object, but is ", j.type_name()), &j)); + throw_type_must_be("object", j); } ConstructibleObjectType ret; @@ -432,7 +432,7 @@ inline void from_json(const BasicJsonType& j, ArithmeticType& val) case value_t::binary: case value_t::discarded: default: - JSON_THROW(type_error::create(302, concat("type must be number, but is ", j.type_name()), &j)); + throw_type_must_be("number", j); } } @@ -504,7 +504,7 @@ auto from_json(const BasicJsonType& j, TupleRelated&& t) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } return from_json_tuple_impl(j, std::forward(t), priority_tag<3> {}); @@ -517,14 +517,14 @@ void from_json_pair_array_to_map(const BasicJsonType& j, MapType& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } m.clear(); for (const auto& p : j) { if (JSON_HEDLEY_UNLIKELY(!p.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &p)); + throw_type_must_be("array", p); } m.emplace(p.at(0).template get(), p.at(1).template get()); } @@ -591,7 +591,7 @@ inline void from_json(const BasicJsonType& j, std_fs::path& p) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + throw_type_must_be("string", j); } const auto& s = *j.template get_ptr(); // Checking for C++20 standard or later can be insufficient in case the diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index 8edaee2fe..0e76487a4 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -414,7 +414,7 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map) BasicJsonType key = p.first; if (JSON_HEDLEY_UNLIKELY(!key.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", key.type_name()), &key)); + throw_type_must_be("string", key); } auto& key_string = *key.template get_ptr(); diff --git a/include/nlohmann/detail/exceptions.hpp b/include/nlohmann/detail/exceptions.hpp index 155f4a4e5..f5b7ae101 100644 --- a/include/nlohmann/detail/exceptions.hpp +++ b/include/nlohmann/detail/exceptions.hpp @@ -307,6 +307,20 @@ void templated_json_throw(ExceptionType exception) (void)exception; } +/*! +@brief throws because @a j does not have the type a conversion expects +@param[in] expected the expected type(s), e.g. "array" or "binary or array" +@param[in] j the value with the wrong type +@throw type_error.302 always +*/ +template +JSON_HEDLEY_NO_RETURN inline void throw_type_must_be(const char* expected, const BasicJsonType& j) +{ + static_cast(expected); // unused when JSON_NOEXCEPTION is defined + static_cast(j); + JSON_THROW(type_error::create(302, concat("type must be ", expected, ", but is ", j.type_name()), &j)); +} + } // namespace detail NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 3b0e3b021..154dd39fb 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -12,7 +12,6 @@ #include // ldexp #include // size_t #include // uint8_t, uint16_t, uint32_t, uint64_t, uintmax_t -#include // snprintf #include // memcpy #include // back_inserter #include // numeric_limits @@ -196,8 +195,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(current != char_traits::eof())) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(110, chars_read, - exception_message(concat("expected end of input; last byte: 0x", get_token_string()), "value"), nullptr)); + return last_byte_error(110, concat("expected end of input; last byte: 0x", get_token_string()), "value"); } } @@ -319,8 +317,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(document_size < 0 || static_cast(document_size) != chars_read - document_start)) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, - exception_message(concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(chars_read - document_start), ")"), "document"), nullptr)); + return last_byte_error(112, concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(chars_read - document_start), ")"), "document"); } return true; } @@ -512,9 +509,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(len < 1)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("string length must be at least 1, is ", std::to_string(len)), "string"), nullptr)); + return last_byte_error(112, concat("string length must be at least 1, is ", std::to_string(len)), "string"); } if (JSON_HEDLEY_UNLIKELY(!get_string(len - static_cast(1), result))) @@ -524,10 +519,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(get() != 0x00)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message("BSON string is not null-terminated", - "string"), nullptr)); + return last_byte_error(112, "BSON string is not null-terminated", "string"); } return check_string_utf8(result, "string"); @@ -547,9 +539,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(len < 0)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("byte array length cannot be negative, is ", std::to_string(len)), "binary"), nullptr)); + return last_byte_error(112, concat("byte array length cannot be negative, is ", std::to_string(len)), "binary"); } // All BSON binary values have a subtype @@ -639,9 +629,7 @@ class binary_reader default: // anything else is not supported (yet) { - std::array cr{{}}; - static_cast((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(element_type))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - const std::string cr_str{cr.data()}; + const std::string cr_str = hex_byte(static_cast(element_type)); return sax->parse_error(element_type_parse_position, cr_str, parse_error::create(114, element_type_parse_position, concat("Unsupported BSON record type 0x", cr_str), nullptr)); } @@ -967,9 +955,7 @@ class binary_reader { if (tag_handler == cbor_tag_handler_t::error) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } // ignore and store: the tag value is already in the head, so @@ -989,9 +975,7 @@ class binary_reader { case cbor_tag_handler_t::error: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } case cbor_tag_handler_t::ignore: @@ -1065,9 +1049,7 @@ class binary_reader default: // anything else (0xFF is handled inside the other types) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } } } @@ -1080,10 +1062,7 @@ class binary_reader */ bool cbor_indefinite_string_error(const char* type_name, const char* context) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("indefinite-length ", type_name, - " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", last_token), context), nullptr)); + return last_byte_error(113, concat("indefinite-length ", type_name, " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", get_token_string()), context); } /*! @@ -1160,9 +1139,7 @@ class binary_reader default: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", last_token), "string"), nullptr)); + return last_byte_error(113, concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", get_token_string()), "string"); } } } @@ -1292,9 +1269,7 @@ class binary_reader break; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr)); + return last_byte_error(113, concat("only string keys are supported, but found ", found, "; last byte: 0x", get_token_string()), "object key"); } /*! @@ -1375,9 +1350,7 @@ class binary_reader default: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", last_token), "binary"), nullptr)); + return last_byte_error(113, concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", get_token_string()), "binary"); } } } @@ -2011,9 +1984,7 @@ class binary_reader default: // anything else { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } } } @@ -2094,9 +2065,7 @@ class binary_reader default: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x", last_token), "string"), nullptr)); + return last_byte_error(113, concat("expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x", get_token_string()), "string"); } } } @@ -2188,9 +2157,7 @@ class binary_reader break; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr)); + return last_byte_error(113, concat("only string keys are supported, but found ", found, "; last byte: 0x", get_token_string()), "object key"); } /*! @@ -2499,8 +2466,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(len < 0)) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, - exception_message("string length must not be negative", "string"), nullptr)); + return last_byte_error(113, "string length must not be negative", "string"); } return true; } @@ -2611,7 +2577,7 @@ class binary_reader { message = "expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x" + last_token; } - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(message, "string"), nullptr)); + return last_byte_error(113, message, "string"); } /*! @@ -2696,8 +2662,7 @@ class binary_reader } if (JSON_HEDLEY_UNLIKELY(number < 0)) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, - exception_message("count in an optimized container must be positive", "size"), nullptr)); + return last_byte_error(113, "count in an optimized container must be positive", "size"); } if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(number))) { @@ -2809,7 +2774,7 @@ class binary_reader } if (is_ndarray) // ndarray dimensional vector can only contain integers and cannot embed another array { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message("ndarray dimensional vector is not allowed", "size"), nullptr)); + return last_byte_error(113, "ndarray dimensional vector is not allowed", "size"); } std::vector dim; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim))) @@ -2886,7 +2851,7 @@ class binary_reader { message = "expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x" + last_token; } - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(message, "size"), nullptr)); + return last_byte_error(113, message, "size"); } /*! @@ -2919,9 +2884,7 @@ class binary_reader if (input_format == input_format_t::bjdata && JSON_HEDLEY_UNLIKELY(is_bjd_excluded_optimized_type(result.second))) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("marker 0x", last_token, " is not a permitted optimized array type"), "type"), nullptr)); + return last_byte_error(112, concat("marker 0x", get_token_string(), " is not a permitted optimized array type"), "type"); } if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("type"))) @@ -2936,9 +2899,7 @@ class binary_reader { return false; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr)); + return last_byte_error(112, concat("expected '#' after type information; last byte: 0x", get_token_string()), "size"); } const bool is_error = get_ubjson_size_value(result.first, is_ndarray); @@ -2957,8 +2918,7 @@ class binary_reader const bool is_error = get_ubjson_size_value(result.first, is_ndarray); if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, - exception_message("ndarray requires both type and size", "size"), nullptr)); + return last_byte_error(112, "ndarray requires both type and size", "size"); } return is_error; } @@ -3090,9 +3050,7 @@ class binary_reader } if (JSON_HEDLEY_UNLIKELY(current > 127)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", last_token), "char"), nullptr)); + return last_byte_error(113, concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", get_token_string()), "char"); } string_t s(1, static_cast(current)); return sax->string(s); @@ -3113,8 +3071,7 @@ class binary_reader default: // anything else break; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message("invalid byte: 0x" + last_token, "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } /*! @@ -3138,9 +3095,7 @@ class binary_reader string_t key = "_ArrayType_"; if (JSON_HEDLEY_UNLIKELY(type_name == nullptr)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message("invalid byte: 0x" + last_token, "type"), nullptr)); + return unexpected_byte("invalid byte", "type"); } string_t type = type_name; // sax->string() takes a reference @@ -3223,9 +3178,7 @@ class binary_reader // do not accept ND-array size in objects in BJData if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message("BJData object does not support ND-array size in optimized format", "object"), nullptr)); + return last_byte_error(112, "BJData object does not support ND-array size in optimized format", "object"); } if (size_and_type.first != npos) @@ -3378,20 +3331,6 @@ class binary_reader return 0x80 <= c && c <= 0xBF; } - /*! - @brief report a parse error at the last read byte - - @param[in] detail a detailed error message - @param[in] context further context information - @return false - */ - bool bon8_error(const std::string& detail, const char* context) - { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat(detail, ": 0x", last_token), context), nullptr)); - } - /*! @brief read a BON8 value and everything nested inside it @@ -3607,7 +3546,7 @@ class binary_reader } // 0xFE: end of container where a value is expected - return bon8_error("invalid byte", "value"); + return unexpected_byte("invalid byte", "value"); } /*! @@ -3734,7 +3673,7 @@ class binary_reader current = byte; } - return bon8_error("expected a string; last byte", "key"); + return unexpected_byte("expected a string; last byte", "key"); } /*! @@ -3857,7 +3796,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(!valid_second)) { - return bon8_error("invalid UTF-8 byte", "string"); + return unexpected_byte("invalid UTF-8 byte", "string"); } result.push_back(static_cast(byte)); @@ -3871,7 +3810,7 @@ class binary_reader } if (JSON_HEDLEY_UNLIKELY(!is_bon8_continuation(current))) { - return bon8_error("invalid UTF-8 byte", "string"); + return unexpected_byte("invalid UTF-8 byte", "string"); } result.push_back(static_cast(current)); } @@ -4190,9 +4129,7 @@ class binary_reader if (error_handler == error_handler_t::strict) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message("invalid string: ill-formed UTF-8 byte", context), nullptr)); + return last_byte_error(113, "invalid string: ill-formed UTF-8 byte", context); } result = sanitize_utf8(result, error_handler); @@ -4293,14 +4230,35 @@ class binary_reader return true; } + /*! + @brief reports a parse error at the last read byte + @param[in] id_ the id of the parse_error exception + @param[in] detail a detailed error message + @param[in] context further context information + @return the result of the SAX parser's parse_error() + */ + bool last_byte_error(const int id_, const std::string& detail, const std::string& context) const + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(id_, chars_read, exception_message(detail, context), nullptr)); + } + + /*! + @brief reports the last read byte as unexpected (parse_error.112) + @param[in] detail what is wrong with the byte, e.g. "invalid byte" + @param[in] context further context information + @return the result of the SAX parser's parse_error() + */ + bool unexpected_byte(const char* detail, const std::string& context) const + { + return last_byte_error(112, concat(detail, ": 0x", get_token_string()), context); + } + /*! @return a string representation of the last read byte */ std::string get_token_string() const { - std::array cr{{}}; - static_cast((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(current))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - return std::string{cr.data()}; + return hex_byte(static_cast(current)); } /*! diff --git a/include/nlohmann/detail/input/json_sax.hpp b/include/nlohmann/detail/input/json_sax.hpp index 146944261..d56d88739 100644 --- a/include/nlohmann/detail/input/json_sax.hpp +++ b/include/nlohmann/detail/input/json_sax.hpp @@ -176,6 +176,27 @@ template inline void reserve_array(ArrayType& /*arr*/, std::size_t /*len*/, priority_tag<0> /*unused*/) {} +/*! +@brief reports an object or array whose announced size exceeds max_size() + +Shared by json_sax_dom_parser and json_sax_dom_callback_parser. + +@param[in] sax the SAX parser to report the error to +@param[in] len the number of elements announced by the input, or unknown_size() +@param[in] kind "object" or "array" +@param[in] ref the object or array that was just created +@return whether parsing should continue (false after reporting out_of_range.408) +*/ +template +bool check_container_size(SAX& sax, std::size_t len, const char* kind, BasicJsonType* ref) +{ + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref->max_size())) + { + return sax.parse_error(0, "", out_of_range::create(408, concat("excessive ", kind, " size: ", std::to_string(len)), ref)); + } + return true; +} + #if JSON_DIAGNOSTIC_POSITIONS /*! @brief set the diagnostic positions of a value the DOM SAX parsers just stored @@ -185,6 +206,35 @@ befriends this struct, as the position members are private. */ struct diagnostic_positions { + /*! + @param[in,out] v the object or array whose opening brace or bracket was just read + @param[in] lexer the lexer that read it, or nullptr to leave @a v alone + */ + template + static void set_container_start(BasicJsonType& v, LexerType* lexer) + { + if (lexer) + { + // Lexer has read the first character of the container, so + // subtract 1 from the position to get the correct start position. + v.start_position = lexer->get_position() - 1; + } + } + + /*! + @param[in,out] v the object or array whose closing brace or bracket was just read + @param[in] lexer the lexer that read it, or nullptr to leave @a v alone + */ + template + static void set_container_end(BasicJsonType& v, LexerType* lexer) + { + if (lexer) + { + // Lexer's position is past the closing brace or bracket, so set that as the end position. + v.end_position = lexer->get_position(); + } + } + /*! @param[in,out] v the value that was just parsed @param[in] lexer the lexer that read it, or nullptr to leave @a v alone @@ -349,22 +399,11 @@ class json_sax_dom_parser ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the object here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - // Lexer has read the first character of the object, so - // subtract 1 from the position to get the correct start position. - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) - { - return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); - } - - return true; + return check_container_size(*this, len, "object", ref_stack.back()); } bool key(string_t& val) @@ -383,11 +422,7 @@ class json_sax_dom_parser JSON_ASSERT(ref_stack.back()->is_object()); #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing brace, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -400,17 +435,13 @@ class json_sax_dom_parser ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the array here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + if (JSON_HEDLEY_UNLIKELY(!check_container_size(*this, len, "array", ref_stack.back()))) { - return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + return false; } if (len != detail::unknown_size()) @@ -427,11 +458,7 @@ class json_sax_dom_parser JSON_ASSERT(ref_stack.back()->is_array()); #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing bracket, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -611,21 +638,11 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the object here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - // Lexer has read the first character of the object, so - // subtract 1 from the position to get the correct start position. - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - // check object limit - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) - { - return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); - } + return check_container_size(*this, len, "object", ref_stack.back()); } return true; } @@ -695,11 +712,7 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing brace, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -742,20 +755,13 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the array here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - // Lexer has read the first character of the array, so - // subtract 1 from the position to get the correct start position. - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - // check array limit - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + if (JSON_HEDLEY_UNLIKELY(!check_container_size(*this, len, "array", ref_stack.back()))) { - return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + return false; } if (len != detail::unknown_size()) @@ -779,11 +785,7 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing bracket, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 92f52aedc..96143a8ed 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -166,7 +166,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } reference_tokens.erase(reference_tokens.begin()); @@ -178,7 +178,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } return reference_tokens.front(); @@ -204,7 +204,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } reference_tokens.pop_back(); @@ -216,7 +216,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } return reference_tokens.back(); @@ -244,6 +244,21 @@ class json_pointer } private: + /// @throw out_of_range.405 always + JSON_HEDLEY_NO_RETURN static void throw_no_parent() + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + } + + /// @throw out_of_range.404 always + template + JSON_HEDLEY_NO_RETURN static void throw_unresolved(const string_t& reference_token, BasicJsonContext context) + { + static_cast(reference_token); // unused when JSON_NOEXCEPTION is defined + static_cast(context); + JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), context)); + } + /*! @brief result of @ref parse_array_index @@ -333,7 +348,7 @@ class json_pointer case array_index_status::not_a_number: JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr)); case array_index_status::unresolved: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr)); + throw_unresolved(s, nullptr); case array_index_status::exceeds_size_type: JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); case array_index_status::ok: @@ -349,7 +364,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } json_pointer result = *this; @@ -520,7 +535,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + throw_unresolved(reference_token, ptr); } } @@ -577,7 +592,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + throw_unresolved(reference_token, ptr); } } @@ -639,7 +654,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + throw_unresolved(reference_token, ptr); } } diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 9c89ab0bc..41656d133 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -3266,7 +3266,7 @@ public: { if (!is_binary()) { - JSON_THROW(type_error::create(302, detail::concat("type must be binary, but is ", type_name()), this)); + detail::throw_type_must_be("binary", *this); } return *get_ptr(); @@ -3278,7 +3278,7 @@ public: { if (!is_binary()) { - JSON_THROW(type_error::create(302, detail::concat("type must be binary, but is ", type_name()), this)); + detail::throw_type_must_be("binary", *this); } return *get_ptr(); diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index d0e43e79a..7229587cb 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -5499,6 +5499,20 @@ void templated_json_throw(ExceptionType exception) (void)exception; } +/*! +@brief throws because @a j does not have the type a conversion expects +@param[in] expected the expected type(s), e.g. "array" or "binary or array" +@param[in] j the value with the wrong type +@throw type_error.302 always +*/ +template +JSON_HEDLEY_NO_RETURN inline void throw_type_must_be(const char* expected, const BasicJsonType& j) +{ + static_cast(expected); // unused when JSON_NOEXCEPTION is defined + static_cast(j); + JSON_THROW(type_error::create(302, concat("type must be ", expected, ", but is ", j.type_name()), &j)); +} + } // namespace detail NLOHMANN_JSON_NAMESPACE_END @@ -5591,7 +5605,7 @@ inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) { if (JSON_HEDLEY_UNLIKELY(!j.is_null())) { - JSON_THROW(type_error::create(302, concat("type must be null, but is ", j.type_name()), &j)); + throw_type_must_be("null", j); } n = nullptr; } @@ -5645,7 +5659,7 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) case value_t::binary: case value_t::discarded: default: - JSON_THROW(type_error::create(302, concat("type must be number, but is ", j.type_name()), &j)); + throw_type_must_be("number", j); } } @@ -5654,7 +5668,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& { if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) { - JSON_THROW(type_error::create(302, concat("type must be boolean, but is ", j.type_name()), &j)); + throw_type_must_be("boolean", j); } b = *j.template get_ptr(); } @@ -5664,7 +5678,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + throw_type_must_be("string", j); } s = *j.template get_ptr(); } @@ -5680,7 +5694,7 @@ inline void from_json(const BasicJsonType& j, StringType& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + throw_type_must_be("string", j); } s = *j.template get_ptr(); @@ -5726,7 +5740,7 @@ inline void from_json(const BasicJsonType& j, std::forward_list& l { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } l.clear(); std::transform(j.rbegin(), j.rend(), @@ -5743,7 +5757,7 @@ inline void from_json(const BasicJsonType& j, std::valarray& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } l.resize(j.size()); std::transform(j.begin(), j.end(), std::begin(l), @@ -5848,7 +5862,7 @@ void()) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } from_json_array_impl(j, arr, priority_tag<3> {}); @@ -5867,7 +5881,7 @@ auto from_json(const BasicJsonType& j, identity_tag> tag) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } return from_json_inplace_array_impl(j, tag, make_index_sequence {}); @@ -5878,7 +5892,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& { if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) { - JSON_THROW(type_error::create(302, concat("type must be binary, but is ", j.type_name()), &j)); + throw_type_must_be("binary", j); } bin = *j.template get_ptr(); @@ -5899,7 +5913,7 @@ inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin) } else { - JSON_THROW(type_error::create(302, concat("type must be binary or array, but is ", j.type_name()), &j)); + throw_type_must_be("binary or array", j); } } @@ -5920,7 +5934,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { - JSON_THROW(type_error::create(302, concat("type must be object, but is ", j.type_name()), &j)); + throw_type_must_be("object", j); } ConstructibleObjectType ret; @@ -5975,7 +5989,7 @@ inline void from_json(const BasicJsonType& j, ArithmeticType& val) case value_t::binary: case value_t::discarded: default: - JSON_THROW(type_error::create(302, concat("type must be number, but is ", j.type_name()), &j)); + throw_type_must_be("number", j); } } @@ -6047,7 +6061,7 @@ auto from_json(const BasicJsonType& j, TupleRelated&& t) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } return from_json_tuple_impl(j, std::forward(t), priority_tag<3> {}); @@ -6060,14 +6074,14 @@ void from_json_pair_array_to_map(const BasicJsonType& j, MapType& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } m.clear(); for (const auto& p : j) { if (JSON_HEDLEY_UNLIKELY(!p.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &p)); + throw_type_must_be("array", p); } m.emplace(p.at(0).template get(), p.at(1).template get()); } @@ -6134,7 +6148,7 @@ inline void from_json(const BasicJsonType& j, std_fs::path& p) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + throw_type_must_be("string", j); } const auto& s = *j.template get_ptr(); // Checking for C++20 standard or later can be insufficient in case the @@ -7208,7 +7222,7 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map) BasicJsonType key = p.first; if (JSON_HEDLEY_UNLIKELY(!key.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", key.type_name()), &key)); + throw_type_must_be("string", key); } auto& key_string = *key.template get_ptr(); @@ -7789,7 +7803,6 @@ NLOHMANN_JSON_NAMESPACE_END #include // ldexp #include // size_t #include // uint8_t, uint16_t, uint32_t, uint64_t, uintmax_t -#include // snprintf #include // memcpy #include // back_inserter #include // numeric_limits @@ -12591,6 +12604,27 @@ template inline void reserve_array(ArrayType& /*arr*/, std::size_t /*len*/, priority_tag<0> /*unused*/) {} +/*! +@brief reports an object or array whose announced size exceeds max_size() + +Shared by json_sax_dom_parser and json_sax_dom_callback_parser. + +@param[in] sax the SAX parser to report the error to +@param[in] len the number of elements announced by the input, or unknown_size() +@param[in] kind "object" or "array" +@param[in] ref the object or array that was just created +@return whether parsing should continue (false after reporting out_of_range.408) +*/ +template +bool check_container_size(SAX& sax, std::size_t len, const char* kind, BasicJsonType* ref) +{ + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref->max_size())) + { + return sax.parse_error(0, "", out_of_range::create(408, concat("excessive ", kind, " size: ", std::to_string(len)), ref)); + } + return true; +} + #if JSON_DIAGNOSTIC_POSITIONS /*! @brief set the diagnostic positions of a value the DOM SAX parsers just stored @@ -12600,6 +12634,35 @@ befriends this struct, as the position members are private. */ struct diagnostic_positions { + /*! + @param[in,out] v the object or array whose opening brace or bracket was just read + @param[in] lexer the lexer that read it, or nullptr to leave @a v alone + */ + template + static void set_container_start(BasicJsonType& v, LexerType* lexer) + { + if (lexer) + { + // Lexer has read the first character of the container, so + // subtract 1 from the position to get the correct start position. + v.start_position = lexer->get_position() - 1; + } + } + + /*! + @param[in,out] v the object or array whose closing brace or bracket was just read + @param[in] lexer the lexer that read it, or nullptr to leave @a v alone + */ + template + static void set_container_end(BasicJsonType& v, LexerType* lexer) + { + if (lexer) + { + // Lexer's position is past the closing brace or bracket, so set that as the end position. + v.end_position = lexer->get_position(); + } + } + /*! @param[in,out] v the value that was just parsed @param[in] lexer the lexer that read it, or nullptr to leave @a v alone @@ -12764,22 +12827,11 @@ class json_sax_dom_parser ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the object here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - // Lexer has read the first character of the object, so - // subtract 1 from the position to get the correct start position. - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) - { - return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); - } - - return true; + return check_container_size(*this, len, "object", ref_stack.back()); } bool key(string_t& val) @@ -12798,11 +12850,7 @@ class json_sax_dom_parser JSON_ASSERT(ref_stack.back()->is_object()); #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing brace, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -12815,17 +12863,13 @@ class json_sax_dom_parser ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the array here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + if (JSON_HEDLEY_UNLIKELY(!check_container_size(*this, len, "array", ref_stack.back()))) { - return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + return false; } if (len != detail::unknown_size()) @@ -12842,11 +12886,7 @@ class json_sax_dom_parser JSON_ASSERT(ref_stack.back()->is_array()); #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing bracket, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -13026,21 +13066,11 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the object here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - // Lexer has read the first character of the object, so - // subtract 1 from the position to get the correct start position. - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - // check object limit - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) - { - return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); - } + return check_container_size(*this, len, "object", ref_stack.back()); } return true; } @@ -13110,11 +13140,7 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing brace, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -13157,20 +13183,13 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the array here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - // Lexer has read the first character of the array, so - // subtract 1 from the position to get the correct start position. - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - // check array limit - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + if (JSON_HEDLEY_UNLIKELY(!check_container_size(*this, len, "array", ref_stack.back()))) { - return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + return false; } if (len != detail::unknown_size()) @@ -13194,11 +13213,7 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing bracket, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -13892,8 +13907,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(current != char_traits::eof())) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(110, chars_read, - exception_message(concat("expected end of input; last byte: 0x", get_token_string()), "value"), nullptr)); + return last_byte_error(110, concat("expected end of input; last byte: 0x", get_token_string()), "value"); } } @@ -14015,8 +14029,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(document_size < 0 || static_cast(document_size) != chars_read - document_start)) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, - exception_message(concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(chars_read - document_start), ")"), "document"), nullptr)); + return last_byte_error(112, concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(chars_read - document_start), ")"), "document"); } return true; } @@ -14208,9 +14221,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(len < 1)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("string length must be at least 1, is ", std::to_string(len)), "string"), nullptr)); + return last_byte_error(112, concat("string length must be at least 1, is ", std::to_string(len)), "string"); } if (JSON_HEDLEY_UNLIKELY(!get_string(len - static_cast(1), result))) @@ -14220,10 +14231,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(get() != 0x00)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message("BSON string is not null-terminated", - "string"), nullptr)); + return last_byte_error(112, "BSON string is not null-terminated", "string"); } return check_string_utf8(result, "string"); @@ -14243,9 +14251,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(len < 0)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("byte array length cannot be negative, is ", std::to_string(len)), "binary"), nullptr)); + return last_byte_error(112, concat("byte array length cannot be negative, is ", std::to_string(len)), "binary"); } // All BSON binary values have a subtype @@ -14335,9 +14341,7 @@ class binary_reader default: // anything else is not supported (yet) { - std::array cr{{}}; - static_cast((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(element_type))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - const std::string cr_str{cr.data()}; + const std::string cr_str = hex_byte(static_cast(element_type)); return sax->parse_error(element_type_parse_position, cr_str, parse_error::create(114, element_type_parse_position, concat("Unsupported BSON record type 0x", cr_str), nullptr)); } @@ -14663,9 +14667,7 @@ class binary_reader { if (tag_handler == cbor_tag_handler_t::error) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } // ignore and store: the tag value is already in the head, so @@ -14685,9 +14687,7 @@ class binary_reader { case cbor_tag_handler_t::error: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } case cbor_tag_handler_t::ignore: @@ -14761,9 +14761,7 @@ class binary_reader default: // anything else (0xFF is handled inside the other types) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } } } @@ -14776,10 +14774,7 @@ class binary_reader */ bool cbor_indefinite_string_error(const char* type_name, const char* context) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("indefinite-length ", type_name, - " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", last_token), context), nullptr)); + return last_byte_error(113, concat("indefinite-length ", type_name, " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", get_token_string()), context); } /*! @@ -14856,9 +14851,7 @@ class binary_reader default: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", last_token), "string"), nullptr)); + return last_byte_error(113, concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", get_token_string()), "string"); } } } @@ -14988,9 +14981,7 @@ class binary_reader break; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr)); + return last_byte_error(113, concat("only string keys are supported, but found ", found, "; last byte: 0x", get_token_string()), "object key"); } /*! @@ -15071,9 +15062,7 @@ class binary_reader default: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", last_token), "binary"), nullptr)); + return last_byte_error(113, concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", get_token_string()), "binary"); } } } @@ -15707,9 +15696,7 @@ class binary_reader default: // anything else { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } } } @@ -15790,9 +15777,7 @@ class binary_reader default: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x", last_token), "string"), nullptr)); + return last_byte_error(113, concat("expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x", get_token_string()), "string"); } } } @@ -15884,9 +15869,7 @@ class binary_reader break; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr)); + return last_byte_error(113, concat("only string keys are supported, but found ", found, "; last byte: 0x", get_token_string()), "object key"); } /*! @@ -16195,8 +16178,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(len < 0)) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, - exception_message("string length must not be negative", "string"), nullptr)); + return last_byte_error(113, "string length must not be negative", "string"); } return true; } @@ -16307,7 +16289,7 @@ class binary_reader { message = "expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x" + last_token; } - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(message, "string"), nullptr)); + return last_byte_error(113, message, "string"); } /*! @@ -16392,8 +16374,7 @@ class binary_reader } if (JSON_HEDLEY_UNLIKELY(number < 0)) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, - exception_message("count in an optimized container must be positive", "size"), nullptr)); + return last_byte_error(113, "count in an optimized container must be positive", "size"); } if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(number))) { @@ -16505,7 +16486,7 @@ class binary_reader } if (is_ndarray) // ndarray dimensional vector can only contain integers and cannot embed another array { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message("ndarray dimensional vector is not allowed", "size"), nullptr)); + return last_byte_error(113, "ndarray dimensional vector is not allowed", "size"); } std::vector dim; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim))) @@ -16582,7 +16563,7 @@ class binary_reader { message = "expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x" + last_token; } - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(message, "size"), nullptr)); + return last_byte_error(113, message, "size"); } /*! @@ -16615,9 +16596,7 @@ class binary_reader if (input_format == input_format_t::bjdata && JSON_HEDLEY_UNLIKELY(is_bjd_excluded_optimized_type(result.second))) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("marker 0x", last_token, " is not a permitted optimized array type"), "type"), nullptr)); + return last_byte_error(112, concat("marker 0x", get_token_string(), " is not a permitted optimized array type"), "type"); } if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("type"))) @@ -16632,9 +16611,7 @@ class binary_reader { return false; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr)); + return last_byte_error(112, concat("expected '#' after type information; last byte: 0x", get_token_string()), "size"); } const bool is_error = get_ubjson_size_value(result.first, is_ndarray); @@ -16653,8 +16630,7 @@ class binary_reader const bool is_error = get_ubjson_size_value(result.first, is_ndarray); if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, - exception_message("ndarray requires both type and size", "size"), nullptr)); + return last_byte_error(112, "ndarray requires both type and size", "size"); } return is_error; } @@ -16786,9 +16762,7 @@ class binary_reader } if (JSON_HEDLEY_UNLIKELY(current > 127)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", last_token), "char"), nullptr)); + return last_byte_error(113, concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", get_token_string()), "char"); } string_t s(1, static_cast(current)); return sax->string(s); @@ -16809,8 +16783,7 @@ class binary_reader default: // anything else break; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message("invalid byte: 0x" + last_token, "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } /*! @@ -16834,9 +16807,7 @@ class binary_reader string_t key = "_ArrayType_"; if (JSON_HEDLEY_UNLIKELY(type_name == nullptr)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message("invalid byte: 0x" + last_token, "type"), nullptr)); + return unexpected_byte("invalid byte", "type"); } string_t type = type_name; // sax->string() takes a reference @@ -16919,9 +16890,7 @@ class binary_reader // do not accept ND-array size in objects in BJData if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message("BJData object does not support ND-array size in optimized format", "object"), nullptr)); + return last_byte_error(112, "BJData object does not support ND-array size in optimized format", "object"); } if (size_and_type.first != npos) @@ -17074,20 +17043,6 @@ class binary_reader return 0x80 <= c && c <= 0xBF; } - /*! - @brief report a parse error at the last read byte - - @param[in] detail a detailed error message - @param[in] context further context information - @return false - */ - bool bon8_error(const std::string& detail, const char* context) - { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat(detail, ": 0x", last_token), context), nullptr)); - } - /*! @brief read a BON8 value and everything nested inside it @@ -17303,7 +17258,7 @@ class binary_reader } // 0xFE: end of container where a value is expected - return bon8_error("invalid byte", "value"); + return unexpected_byte("invalid byte", "value"); } /*! @@ -17430,7 +17385,7 @@ class binary_reader current = byte; } - return bon8_error("expected a string; last byte", "key"); + return unexpected_byte("expected a string; last byte", "key"); } /*! @@ -17553,7 +17508,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(!valid_second)) { - return bon8_error("invalid UTF-8 byte", "string"); + return unexpected_byte("invalid UTF-8 byte", "string"); } result.push_back(static_cast(byte)); @@ -17567,7 +17522,7 @@ class binary_reader } if (JSON_HEDLEY_UNLIKELY(!is_bon8_continuation(current))) { - return bon8_error("invalid UTF-8 byte", "string"); + return unexpected_byte("invalid UTF-8 byte", "string"); } result.push_back(static_cast(current)); } @@ -17886,9 +17841,7 @@ class binary_reader if (error_handler == error_handler_t::strict) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message("invalid string: ill-formed UTF-8 byte", context), nullptr)); + return last_byte_error(113, "invalid string: ill-formed UTF-8 byte", context); } result = sanitize_utf8(result, error_handler); @@ -17989,14 +17942,35 @@ class binary_reader return true; } + /*! + @brief reports a parse error at the last read byte + @param[in] id_ the id of the parse_error exception + @param[in] detail a detailed error message + @param[in] context further context information + @return the result of the SAX parser's parse_error() + */ + bool last_byte_error(const int id_, const std::string& detail, const std::string& context) const + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(id_, chars_read, exception_message(detail, context), nullptr)); + } + + /*! + @brief reports the last read byte as unexpected (parse_error.112) + @param[in] detail what is wrong with the byte, e.g. "invalid byte" + @param[in] context further context information + @return the result of the SAX parser's parse_error() + */ + bool unexpected_byte(const char* detail, const std::string& context) const + { + return last_byte_error(112, concat(detail, ": 0x", get_token_string()), context); + } + /*! @return a string representation of the last read byte */ std::string get_token_string() const { - std::array cr{{}}; - static_cast((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(current))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - return std::string{cr.data()}; + return hex_byte(static_cast(current)); } /*! @@ -20051,7 +20025,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } reference_tokens.erase(reference_tokens.begin()); @@ -20063,7 +20037,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } return reference_tokens.front(); @@ -20089,7 +20063,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } reference_tokens.pop_back(); @@ -20101,7 +20075,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } return reference_tokens.back(); @@ -20129,6 +20103,21 @@ class json_pointer } private: + /// @throw out_of_range.405 always + JSON_HEDLEY_NO_RETURN static void throw_no_parent() + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + } + + /// @throw out_of_range.404 always + template + JSON_HEDLEY_NO_RETURN static void throw_unresolved(const string_t& reference_token, BasicJsonContext context) + { + static_cast(reference_token); // unused when JSON_NOEXCEPTION is defined + static_cast(context); + JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), context)); + } + /*! @brief result of @ref parse_array_index @@ -20218,7 +20207,7 @@ class json_pointer case array_index_status::not_a_number: JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr)); case array_index_status::unresolved: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr)); + throw_unresolved(s, nullptr); case array_index_status::exceeds_size_type: JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); case array_index_status::ok: @@ -20234,7 +20223,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } json_pointer result = *this; @@ -20405,7 +20394,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + throw_unresolved(reference_token, ptr); } } @@ -20462,7 +20451,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + throw_unresolved(reference_token, ptr); } } @@ -20524,7 +20513,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + throw_unresolved(reference_token, ptr); } } @@ -30482,7 +30471,7 @@ public: { if (!is_binary()) { - JSON_THROW(type_error::create(302, detail::concat("type must be binary, but is ", type_name()), this)); + detail::throw_type_must_be("binary", *this); } return *get_ptr(); @@ -30494,7 +30483,7 @@ public: { if (!is_binary()) { - JSON_THROW(type_error::create(302, detail::concat("type must be binary, but is ", type_name()), this)); + detail::throw_type_must_be("binary", *this); } return *get_ptr(); diff --git a/tests/src/test_sax.hpp b/tests/src/test_sax.hpp new file mode 100644 index 000000000..0d18e4f7b --- /dev/null +++ b/tests/src/test_sax.hpp @@ -0,0 +1,174 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#pragma once + +#include // size_t +#include // uint8_t (via json::binary_t) +#include // numeric_limits +#include // string, to_string +#include // vector + +#include + +namespace utils +{ +/// a SAX event consumer that records every event it receives as a +/// human-readable string, used by the deserialization tests to check the +/// exact sequence of SAX events a parse run produces +struct SaxEventLogger : public nlohmann::json_sax +{ + using json = nlohmann::json; + + bool null() override + { + events.emplace_back("null()"); + return true; + } + + bool boolean(bool val) override + { + events.emplace_back(val ? "boolean(true)" : "boolean(false)"); + return true; + } + + bool number_integer(json::number_integer_t val) override + { + events.push_back("number_integer(" + std::to_string(val) + ")"); + return true; + } + + bool number_unsigned(json::number_unsigned_t val) override + { + events.push_back("number_unsigned(" + std::to_string(val) + ")"); + return true; + } + + bool number_float(json::number_float_t /*val*/, const std::string& s) override + { + events.push_back("number_float(" + s + ")"); + return true; + } + + bool string(std::string& val) override + { + events.push_back("string(" + val + ")"); + return true; + } + + bool binary(json::binary_t& val) override + { + std::string binary_contents = "binary("; + std::string comma_space; + for (const auto b : val) + { + binary_contents.append(comma_space); + binary_contents.append(std::to_string(static_cast(b))); + comma_space = ", "; + } + binary_contents.append(")"); + events.push_back(binary_contents); + return true; + } + + bool start_object(std::size_t elements) override + { + if (elements == (std::numeric_limits::max)()) + { + events.emplace_back("start_object()"); + } + else + { + events.push_back("start_object(" + std::to_string(elements) + ")"); + } + return true; + } + + bool key(std::string& val) override + { + events.push_back("key(" + val + ")"); + return true; + } + + bool end_object() override + { + events.emplace_back("end_object()"); + return true; + } + + bool start_array(std::size_t elements) override + { + if (elements == (std::numeric_limits::max)()) + { + events.emplace_back("start_array()"); + } + else + { + events.push_back("start_array(" + std::to_string(elements) + ")"); + } + return true; + } + + bool end_array() override + { + events.emplace_back("end_array()"); + return true; + } + + bool parse_error(std::size_t position, const std::string& /*last_token*/, const json::exception& /*ex*/) override + { + errored = true; + events.push_back("parse_error(" + std::to_string(position) + ")"); + return false; + } + + std::vector events {}; // NOLINT(readability-redundant-member-init) + bool errored = false; +}; + +struct SaxEventLoggerExitAfterStartObject : public SaxEventLogger +{ + bool start_object(std::size_t elements) override + { + if (elements == (std::numeric_limits::max)()) + { + events.emplace_back("start_object()"); + } + else + { + events.push_back("start_object(" + std::to_string(elements) + ")"); + } + return false; + } +}; + +struct SaxEventLoggerExitAfterKey : public SaxEventLogger +{ + bool key(std::string& val) override + { + events.push_back("key(" + val + ")"); + return false; + } +}; + +struct SaxEventLoggerExitAfterStartArray : public SaxEventLogger +{ + bool start_array(std::size_t elements) override + { + if (elements == (std::numeric_limits::max)()) + { + events.emplace_back("start_array()"); + } + else + { + events.push_back("start_array(" + std::to_string(elements) + ")"); + } + return false; + } +}; +} // namespace utils diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index 978d94f92..2f6f5989a 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -37,194 +37,15 @@ using nlohmann::json; #include #include +#include "sax_countdown.hpp" +#include "test_sax.hpp" #include "test_utils.hpp" +using utils::SaxCountdown; +using utils::SaxEventLogger; + namespace { -class SaxEventLogger -{ - public: - bool null() - { - events.emplace_back("null()"); - return true; - } - - bool boolean(bool val) - { - events.emplace_back(val ? "boolean(true)" : "boolean(false)"); - return true; - } - - bool number_integer(json::number_integer_t val) - { - events.push_back("number_integer(" + std::to_string(val) + ")"); - return true; - } - - bool number_unsigned(json::number_unsigned_t val) - { - events.push_back("number_unsigned(" + std::to_string(val) + ")"); - return true; - } - - bool number_float(json::number_float_t /*unused*/, const std::string& s) - { - events.push_back("number_float(" + s + ")"); - return true; - } - - bool string(std::string& val) - { - events.push_back("string(" + val + ")"); - return true; - } - - bool binary(json::binary_t& val) - { - std::string binary_contents = "binary("; - std::string comma_space; - for (auto b : val) - { - binary_contents.append(comma_space); - binary_contents.append(std::to_string(static_cast(b))); - comma_space = ", "; - } - binary_contents.append(")"); - events.push_back(binary_contents); - return true; - } - - bool start_object(std::size_t elements) - { - if (elements == (std::numeric_limits::max)()) - { - events.emplace_back("start_object()"); - } - else - { - events.push_back("start_object(" + std::to_string(elements) + ")"); - } - return true; - } - - bool key(std::string& val) - { - events.push_back("key(" + val + ")"); - return true; - } - - bool end_object() - { - events.emplace_back("end_object()"); - return true; - } - - bool start_array(std::size_t elements) - { - if (elements == (std::numeric_limits::max)()) - { - events.emplace_back("start_array()"); - } - else - { - events.push_back("start_array(" + std::to_string(elements) + ")"); - } - return true; - } - - bool end_array() - { - events.emplace_back("end_array()"); - return true; - } - - bool parse_error(std::size_t position, const std::string& /*unused*/, const json::exception& /*unused*/) - { - errored = true; - events.push_back("parse_error(" + std::to_string(position) + ")"); - return false; - } - - std::vector events {}; // NOLINT(readability-redundant-member-init) - bool errored = false; -}; - -class SaxCountdown : public nlohmann::json::json_sax_t -{ - public: - explicit SaxCountdown(const int count) : events_left(count) - {} - - bool null() override - { - return events_left-- > 0; - } - - bool boolean(bool /*val*/) override - { - return events_left-- > 0; - } - - bool number_integer(json::number_integer_t /*val*/) override - { - return events_left-- > 0; - } - - bool number_unsigned(json::number_unsigned_t /*val*/) override - { - return events_left-- > 0; - } - - bool number_float(json::number_float_t /*val*/, const std::string& /*s*/) override - { - return events_left-- > 0; - } - - bool string(std::string& /*val*/) override - { - return events_left-- > 0; - } - - bool binary(json::binary_t& /*val*/) override - { - return events_left-- > 0; - } - - bool start_object(std::size_t /*elements*/) override - { - return events_left-- > 0; - } - - bool key(std::string& /*val*/) override - { - return events_left-- > 0; - } - - bool end_object() override - { - return events_left-- > 0; - } - - bool start_array(std::size_t /*elements*/) override - { - return events_left-- > 0; - } - - bool end_array() override - { - return events_left-- > 0; - } - - bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) override - { - return false; - } - - private: - int events_left = 0; -}; - json parser_helper(const std::string& s); bool accept_helper(const std::string& s); void comments_helper(const std::string& s); diff --git a/tests/src/unit-deserialization.cpp b/tests/src/unit-deserialization.cpp index ca9b63112..300e7708a 100644 --- a/tests/src/unit-deserialization.cpp +++ b/tests/src/unit-deserialization.cpp @@ -36,155 +36,15 @@ using nlohmann::json; #include #include +#include "test_sax.hpp" + +using utils::SaxEventLogger; +using utils::SaxEventLoggerExitAfterKey; +using utils::SaxEventLoggerExitAfterStartArray; +using utils::SaxEventLoggerExitAfterStartObject; + namespace { -struct SaxEventLogger : public nlohmann::json_sax -{ - bool null() override - { - events.emplace_back("null()"); - return true; - } - - bool boolean(bool val) override - { - events.emplace_back(val ? "boolean(true)" : "boolean(false)"); - return true; - } - - bool number_integer(json::number_integer_t val) override - { - events.push_back("number_integer(" + std::to_string(val) + ")"); - return true; - } - - bool number_unsigned(json::number_unsigned_t val) override - { - events.push_back("number_unsigned(" + std::to_string(val) + ")"); - return true; - } - - bool number_float(json::number_float_t /*val*/, const std::string& s) override - { - events.push_back("number_float(" + s + ")"); - return true; - } - - bool string(std::string& val) override - { - events.push_back("string(" + val + ")"); - return true; - } - - bool binary(json::binary_t& val) override - { - std::string binary_contents = "binary("; - std::string comma_space; - for (auto b : val) - { - binary_contents.append(comma_space); - binary_contents.append(std::to_string(static_cast(b))); - comma_space = ", "; - } - binary_contents.append(")"); - events.push_back(binary_contents); - return true; - } - - bool start_object(std::size_t elements) override - { - if (elements == (std::numeric_limits::max)()) - { - events.emplace_back("start_object()"); - } - else - { - events.push_back("start_object(" + std::to_string(elements) + ")"); - } - return true; - } - - bool key(std::string& val) override - { - events.push_back("key(" + val + ")"); - return true; - } - - bool end_object() override - { - events.emplace_back("end_object()"); - return true; - } - - bool start_array(std::size_t elements) override - { - if (elements == (std::numeric_limits::max)()) - { - events.emplace_back("start_array()"); - } - else - { - events.push_back("start_array(" + std::to_string(elements) + ")"); - } - return true; - } - - bool end_array() override - { - events.emplace_back("end_array()"); - return true; - } - - bool parse_error(std::size_t position, const std::string& /*last_token*/, const json::exception& /*ex*/) override - { - events.push_back("parse_error(" + std::to_string(position) + ")"); - return false; - } - - std::vector events {}; // NOLINT(readability-redundant-member-init) -}; - -struct SaxEventLoggerExitAfterStartObject : public SaxEventLogger -{ - bool start_object(std::size_t elements) override - { - if (elements == (std::numeric_limits::max)()) - { - events.emplace_back("start_object()"); - } - else - { - events.push_back("start_object(" + std::to_string(elements) + ")"); - } - return false; - } -}; - -struct SaxEventLoggerExitAfterKey : public SaxEventLogger -{ - bool key(std::string& val) override - { - events.push_back("key(" + val + ")"); - return false; - } -}; - -struct SaxEventLoggerExitAfterStartArray : public SaxEventLogger -{ - bool start_array(std::size_t elements) override - { - if (elements == (std::numeric_limits::max)()) - { - events.emplace_back("start_array()"); - } - else - { - events.push_back("start_array(" + std::to_string(elements) + ")"); - } - return false; - } -}; - template class proxy_iterator {