diff --git a/core/include/join/variant.hpp b/core/include/join/variant.hpp index 8cade182..a77c6dcc 100644 --- a/core/include/join/variant.hpp +++ b/core/include/join/variant.hpp @@ -716,7 +716,7 @@ namespace join alignas (Ts...) unsigned char _data[std::max ({sizeof (Ts)...})]; /// index of the alternative that is currently held by the variant. - uint32_t _which = 0; + uint16_t _which = 0; }; /** diff --git a/data/include/join/json.hpp b/data/include/join/json.hpp index 89424582..27c5fc1c 100644 --- a/data/include/join/json.hpp +++ b/data/include/join/json.hpp @@ -41,10 +41,12 @@ #include #include #include +#include #include #include #include #include +#include // C. #include @@ -430,6 +432,19 @@ namespace join return 0; } + /** + * @brief set binary value. + * @param value binary value to set. + * @return -1. + */ + int setBinary ([[maybe_unused]] std::span value, + [[maybe_unused]] std::optional subtype = std::nullopt) + { + join::lastError = make_error_code (SaxErrc::InvalidValue); + reset (); + return -1; + } + /** * @brief start array. * @param size array size (ignored). @@ -656,6 +671,31 @@ namespace join return setKey (std::string_view (key)); } + /** + * @brief set binary key. + * @param key object key. + * @param subtype binary subtype. + * @return -1. + */ + int setKey ([[maybe_unused]] std::span key, + [[maybe_unused]] std::optional subtype = std::nullopt) + { + join::lastError = make_error_code (SaxErrc::InvalidKey); + reset (); + return -1; + } + + /** + * @brief start an array or object key. + * @return -1. + */ + int startKey () + { + join::lastError = make_error_code (SaxErrc::InvalidKey); + reset (); + return -1; + } + /** * @brief stop object. * @return 0 on success, -1 otherwise. diff --git a/data/include/join/pack.hpp b/data/include/join/pack.hpp index af7fc87b..a4e75261 100644 --- a/data/include/join/pack.hpp +++ b/data/include/join/pack.hpp @@ -36,8 +36,10 @@ // C++. #include +#include #include #include +#include // C. #include @@ -338,6 +340,78 @@ namespace join return 0; } + /** + * @brief set binary value. + * @param value binary value to set. + * @param subtype binary subtype. + * @return 0 on success, -1 otherwise. + */ + int setBinary (std::span value, std::optional subtype = std::nullopt) + { + if (subtype) + { + switch (value.size ()) + { + case 1: + append (0xd4); + break; + + case 2: + append (0xd5); + break; + + case 4: + append (0xd6); + break; + + case 8: + append (0xd7); + break; + + case 16: + append (0xd8); + break; + + default: + if (value.size () < 256) + { + append (0xc7); + pack (static_cast (value.size ())); + } + else if (value.size () < 65536) + { + append (0xc8); + pack (static_cast (value.size ())); + } + else + { + append (0xc9); + pack (static_cast (value.size ())); + } + break; + } + pack (*subtype); + } + else if (value.size () < 256) + { + append (0xc4); + pack (static_cast (value.size ())); + } + else if (value.size () < 65536) + { + append (0xc5); + pack (static_cast (value.size ())); + } + else + { + append (0xc6); + pack (static_cast (value.size ())); + } + append (reinterpret_cast (value.data ()), static_cast (value.size ())); + endValue (); + return 0; + } + /** * @brief start array. * @param size array size. @@ -554,6 +628,38 @@ namespace join return setKey (std::string_view (key)); } + /** + * @brief set binary key. + * @param key object key. + * @param subtype binary subtype. + * @return 0 on success, -1 otherwise. + */ + int setKey (std::span key, std::optional subtype = std::nullopt) + { + if (JOIN_UNLIKELY (_depth == 0)) + { + join::lastError = make_error_code (SaxErrc::InvalidParent); + reset (); + return -1; + } + return setBinary (key, subtype); + } + + /** + * @brief start an array or object key. + * @return 0 on success, -1 otherwise. + */ + int startKey () + { + if (JOIN_UNLIKELY (_depth == 0)) + { + join::lastError = make_error_code (SaxErrc::InvalidParent); + reset (); + return -1; + } + return 0; + } + /** * @brief stop object. * @return 0 on success, -1 otherwise. @@ -1068,7 +1174,7 @@ namespace join while (len) { - if (JOIN_UNLIKELY (readKey (document, handler) == -1)) + if (JOIN_UNLIKELY (readKey (document, handler, depth + 1) == -1)) { return -1; } @@ -1088,10 +1194,11 @@ namespace join * @brief parse a key of any type. * @param document document to parse. * @param handler handler receiving the document. + * @param depth number of enclosing containers. * @return 0 on success, -1 otherwise. */ template - int readKey (ProbeType& document, Handler& handler) + int readKey (ProbeType& document, Handler& handler, size_t depth) { int ch = document.peek (); if (JOIN_UNLIKELY (ch == std::char_traits::eof ())) @@ -1118,10 +1225,22 @@ namespace join { return readString (document, handler, true); } + else if (isBin (head)) + { + return readBin (document, handler, true); + } else if (isNumber (head)) { return readNumber (document, handler, true); } + else if (isArray (head) || isObject (head)) + { + if (JOIN_UNLIKELY (handler.startKey () == -1)) + { + return -1; + } + return readValue (document, handler, depth); + } else { join::lastError = make_error_code (SaxErrc::InvalidValue); @@ -1197,12 +1316,14 @@ namespace join * @brief parse binary data. * @param document document to parse. * @param handler handler receiving the document. + * @param isKey indicate whether the element to parse is a key or not. * @return 0 on success, -1 otherwise. */ template - int readBin (ProbeType& document, Handler& handler) + int readBin (ProbeType& document, Handler& handler, bool isKey = false) { uint32_t len = 0; + std::optional subtype; if (document.getIf (0xc6)) { @@ -1225,6 +1346,70 @@ namespace join return -1; } } + else + { + if (document.getIf (0xd4)) + { + len = 1; + } + else if (document.getIf (0xd5)) + { + len = 2; + } + else if (document.getIf (0xd6)) + { + len = 4; + } + else if (document.getIf (0xd7)) + { + len = 8; + } + else if (document.getIf (0xd8)) + { + len = 16; + } + else if (document.getIf (0xc9)) + { + if (JOIN_UNLIKELY (unpack (document, len) == -1)) + { + return -1; + } + } + else if (document.getIf (0xc8)) + { + if (JOIN_UNLIKELY (unpack (document, len) == -1)) + { + return -1; + } + } + else if (document.getIf (0xc7)) + { + if (JOIN_UNLIKELY (unpack (document, len) == -1)) + { + return -1; + } + } + + int8_t type = 0; + if (JOIN_UNLIKELY (unpack (document, type) == -1)) + { + return -1; + } + subtype = type; + } + + if constexpr (std::is_same_v) + { + if (JOIN_UNLIKELY (static_cast (document.end () - document.tell ()) < len)) + { + join::lastError = make_error_code (SaxErrc::Truncated); + return -1; + } + + std::span output (reinterpret_cast (document.tell ()), len); + document.seek (document.tell () + len); + return isKey ? handler.setKey (output, subtype) : handler.setBinary (output, subtype); + } thread_local std::string cache; std::string output = std::move (cache); @@ -1234,7 +1419,8 @@ namespace join return -1; } - int result = handler.setString (output); + std::span bytes (reinterpret_cast (output.data ()), output.size ()); + int result = isKey ? handler.setKey (bytes, subtype) : handler.setBinary (bytes, subtype); cache = std::move (output); return result; } @@ -1512,7 +1698,7 @@ namespace join */ constexpr bool isBin (uint8_t c) { - return (c == 0xc4) || (c == 0xc5) || (c == 0xc6); + return ((c >= 0xc4) && (c <= 0xc9)) || ((c >= 0xd4) && (c <= 0xd8)); } /** diff --git a/data/include/join/sax.hpp b/data/include/join/sax.hpp index d80b63e0..59977e31 100644 --- a/data/include/join/sax.hpp +++ b/data/include/join/sax.hpp @@ -31,6 +31,8 @@ // C++. #include +#include +#include // C. #include @@ -125,6 +127,18 @@ namespace join return 0; } + /** + * @brief set binary value. + * @param value binary value to set. + * @param subtype binary subtype. + * @return 0. + */ + int setBinary ([[maybe_unused]] std::span value, + [[maybe_unused]] std::optional subtype = std::nullopt) noexcept + { + return 0; + } + /** * @brief start array. * @param size array size, absent when called by the JSON reader. @@ -255,6 +269,27 @@ namespace join return 0; } + /** + * @brief set binary key. + * @param key object key. + * @param subtype binary subtype. + * @return 0. + */ + int setKey ([[maybe_unused]] std::span key, + [[maybe_unused]] std::optional subtype = std::nullopt) noexcept + { + return 0; + } + + /** + * @brief start an array or object key. + * @return 0. + */ + int startKey () noexcept + { + return 0; + } + /** * @brief stop object. * @return 0. diff --git a/data/include/join/value.hpp b/data/include/join/value.hpp index d32672bc..975eb0a9 100644 --- a/data/include/join/value.hpp +++ b/data/include/join/value.hpp @@ -39,11 +39,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include // C. @@ -64,6 +66,32 @@ namespace join /// string. using String = View; + /** + * @brief binary. + */ + struct Binary + { + /** + * @brief compare binaries. + * @param other binary to compare to. + * @return true if equal. + */ + constexpr bool operator== (const Binary& other) const = default; + + /** + * @brief three-way compare binaries. + * @param other binary to compare to. + * @return ordering of this binary relative to other. + */ + constexpr std::partial_ordering operator<=> (const Binary& other) const = default; + + /// binary data. + View data; + + /// binary subtype. + std::optional subtype; + }; + /** * @brief member. */ @@ -123,8 +151,9 @@ namespace join Unsigned64Value = 5, /**< index of the 64 bits unsigned integer alternative. */ RealValue = 6, /**< index of the real alternative. */ StringValue = 7, /**< index of the string alternative. */ - ArrayValue = 8, /**< index of the array alternative. */ - ObjectValue = 9, /**< index of the object alternative. */ + BinaryValue = 8, /**< index of the binary alternative. */ + ArrayValue = 9, /**< index of the array alternative. */ + ObjectValue = 10, /**< index of the object alternative. */ }; /** @@ -331,6 +360,29 @@ namespace join { } + /** + * @brief constructs a binary value. + * @param value binary value. + * @param subtype binary subtype. + */ + BasicValue (std::span value, std::optional subtype = std::nullopt) + : BasicValue (nullptr) + { + _data.template set (copyBinary (value, subtype)); + } + + /** + * @brief constructs a binary value using an allocator owned by the caller. + * @param value binary value. + * @param subtype binary subtype. + * @param allocator allocator providing the memory of the value. + */ + BasicValue (std::span value, std::optional subtype, Allocator& allocator) + : BasicValue (allocator) + { + _data.template set (copyBinary (value, subtype)); + } + /** * @brief constructs an array value. * @param value array value. @@ -545,6 +597,24 @@ namespace join return *this = std::string_view (value); } + /** + * @brief assign a binary value. + * @param value binary value. + * @return a reference of the current object. + */ + Value& operator= (std::span value) + { + if (_owner || (_allocator == nullptr)) + { + return *this = BasicValue (value); + } + + _data.template set (copyBinary (value, std::nullopt)); + _capacity = 0; + + return *this; + } + /** * @brief assign an array value. * @param value array value. @@ -1403,6 +1473,36 @@ namespace join return getString (); } + /** + * @brief check if the variable held by value is a binary value. + * @return true if the variable held by value is a binary value, false otherwise. + */ + constexpr bool isBinary () const + { + return _data.index () == BinaryValue; + } + + /** + * @brief get variable held by value as a binary value. + * @return variable held by value as a binary value. + * @throw std::bad_cast. + */ + constexpr std::span getBinary () const + { + const Binary& value = _data.template get (); + return std::span (value.data.data (), value.data.size ()); + } + + /** + * @brief get the subtype of the binary value held by value. + * @return subtype of the binary value, std::nullopt if none. + * @throw std::bad_cast. + */ + constexpr std::optional getSubtype () const + { + return _data.template get ().subtype; + } + /** * @brief check if the variable held by value is an array. * @return true if the variable held by value is an array, false otherwise. @@ -2587,6 +2687,18 @@ namespace join return pushBack (Value (value, *allocator ())); } + /** + * @brief appends a binary value at the end of the nested container. + * @param value the element to append. + * @param subtype binary subtype. + * @return a reference to the pushed object. + * @throw std::bad_cast. + */ + Value& pushBack (std::span value, std::optional subtype = std::nullopt) + { + return pushBack (Value (value, subtype, *allocator ())); + } + /** * @brief appends an array value at the end of the nested container. * @param value the element to append. @@ -3095,7 +3207,8 @@ namespace join private: /// variant type holding the value. - using Data = Variant; + using Data = + Variant; // friendship with value builder. friend class ValueBuilder; @@ -3167,6 +3280,19 @@ namespace join return String (data, value.size ()); } + /** + * @brief copy binary data into the allocator. + * @param value binary data to copy. + * @param subtype binary subtype. + * @return copied binary data. + */ + Binary copyBinary (std::span value, std::optional subtype) + { + uint8_t* data = allocate (value.size ()); + std::copy_n (value.data (), value.size (), data); + return Binary{View (data, value.size ()), subtype}; + } + /** * @brief move values of the open containers of a builder into the allocator. * @param values values to move. @@ -3246,6 +3372,10 @@ namespace join _data.template set (copyString (other.getString ())); break; + case BinaryValue: + _data.template set (copyBinary (other.getBinary (), other.getSubtype ())); + break; + case ArrayValue: _data.template set (copyArray (other.getArray ())); break; @@ -3399,6 +3529,9 @@ namespace join case Value::StringValue: return handler.setString (value.getString ()); + case Value::BinaryValue: + return handler.setBinary (value.getBinary (), value.getSubtype ()); + case Value::ArrayValue: return setArray (value.getArray (), handler); @@ -3485,9 +3618,17 @@ namespace join result = handler.setKey (member.first.getDouble ()); break; + case Value::BinaryValue: + result = handler.setKey (member.first.getBinary (), member.first.getSubtype ()); + break; + case Value::ArrayValue: case Value::ObjectValue: - join::lastError = make_error_code (SaxErrc::InvalidKey); + result = handler.startKey (); + if (result == 0) + { + result = setValue (member.first, handler); + } break; default: diff --git a/data/include/join/value_builder.hpp b/data/include/join/value_builder.hpp index ae4e82e4..0ce3589e 100644 --- a/data/include/join/value_builder.hpp +++ b/data/include/join/value_builder.hpp @@ -32,8 +32,10 @@ // C++. #include +#include #include #include +#include // C. #include @@ -181,6 +183,17 @@ namespace join return emplaceValue (_root.copyString (value)); } + /** + * @brief set binary value. + * @param value binary value to set. + * @param subtype binary subtype. + * @return 0 on success, -1 otherwise. + */ + int setBinary (std::span value, std::optional subtype = std::nullopt) + { + return emplaceValue (_root.copyBinary (value, subtype)); + } + /** * @brief start array. * @param size array size (ignored). @@ -410,6 +423,40 @@ namespace join return setKey (std::string_view (key)); } + /** + * @brief set binary key. + * @param key object key to set. + * @param subtype binary subtype. + * @return 0 on success, -1 otherwise. + */ + int setKey (std::span key, std::optional subtype = std::nullopt) + { + if (JOIN_UNLIKELY (_depth == 0)) + { + join::lastError = make_error_code (SaxErrc::InvalidParent); + return -1; + } + + _stack.emplace_back (_root.copyBinary (key, subtype)); + + return 0; + } + + /** + * @brief start an array or object key. + * @return 0 on success, -1 otherwise. + */ + int startKey () + { + if (JOIN_UNLIKELY (_depth == 0)) + { + join::lastError = make_error_code (SaxErrc::InvalidParent); + return -1; + } + + return 0; + } + /** * @brief stop object. * @return 0 on success, -1 otherwise. diff --git a/data/tests/json_writer_test.cpp b/data/tests/json_writer_test.cpp index 10a95273..94278993 100644 --- a/data/tests/json_writer_test.cpp +++ b/data/tests/json_writer_test.cpp @@ -31,6 +31,7 @@ // C++. #include #include +#include using join::JsonWriter; using join::Value; @@ -234,6 +235,21 @@ TEST (JsonWriter, setString) EXPECT_EQ (stream.str (), "\"\\\"\\\\\\b\\f\\n\\r\\t\\u0019\""); } +/** + * @brief Test setBinary method. + */ +TEST (JsonWriter, setBinary) +{ + std::stringstream stream; + JsonWriter jsonWriter (stream); + const uint8_t data[] = {0x01}; + + EXPECT_EQ (jsonWriter.setBinary (std::span (data, sizeof (data))), -1); + EXPECT_EQ (join::lastError, SaxErrc::InvalidValue); + EXPECT_EQ (jsonWriter.setBinary (std::span (data, sizeof (data)), -1), -1); + EXPECT_EQ (join::lastError, SaxErrc::InvalidValue); +} + /** * @brief Test startArray method. */ @@ -360,6 +376,8 @@ TEST (JsonWriter, setKey) EXPECT_EQ (join::lastError, SaxErrc::InvalidKey); EXPECT_EQ (invalid (6.5), -1); EXPECT_EQ (join::lastError, SaxErrc::InvalidKey); + EXPECT_EQ (invalid (std::span ()), -1); + EXPECT_EQ (join::lastError, SaxErrc::InvalidKey); EXPECT_EQ (stream.str (), ""); jsonWriter.reset (); @@ -384,6 +402,19 @@ TEST (JsonWriter, setKey) EXPECT_EQ (stream.str (), "{\"\\\"\\\\\\b\\f\\n\\r\\t\\u0019\":"); } +/** + * @brief Test startKey method. + */ +TEST (JsonWriter, startKey) +{ + std::stringstream stream; + JsonWriter jsonWriter (stream); + + EXPECT_EQ (jsonWriter.startObject (), 0); + EXPECT_EQ (jsonWriter.startKey (), -1); + EXPECT_EQ (join::lastError, SaxErrc::InvalidKey); +} + /** * @brief Test stopObject method. */ diff --git a/data/tests/pack_reader_test.cpp b/data/tests/pack_reader_test.cpp index bf21495f..a7f7bc57 100644 --- a/data/tests/pack_reader_test.cpp +++ b/data/tests/pack_reader_test.cpp @@ -30,10 +30,13 @@ #include // C++. +#include #include +#include #include #include #include +#include // C. #include @@ -774,6 +777,11 @@ TEST (PackReader, fail) stream.str (std::string ({'\xd7'})); ASSERT_NE (PackReader ().deserialize (stream, value), 0); + stream.clear (); + stream.str (std::string ({'\x91', '\xc1'})); + ASSERT_NE (PackReader ().deserialize (stream, value), 0); + ASSERT_EQ (join::lastError, SaxErrc::InvalidValue); + stream.clear (); stream.str (std::string ({'\x92', '\x01'})); ASSERT_NE (PackReader ().deserialize (stream, value), 0); @@ -1349,6 +1357,8 @@ TEST (PackReader, str) */ TEST (PackReader, bin) { + const uint8_t hello[] = {0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x0a, 0x57, 0x6f, 0x72, 0x6c, 0x64}; + const uint8_t bytes[] = {0x00, 0xff}; std::stringstream stream; Value value; @@ -1357,8 +1367,8 @@ TEST (PackReader, bin) ASSERT_EQ (PackReader ().deserialize (stream, value), 0) << join::lastError.message (); ASSERT_TRUE (value.isArray ()); ASSERT_FALSE (value.empty ()); - ASSERT_TRUE (value[0].isString ()); - EXPECT_EQ (value[0].getString (), ""); + ASSERT_TRUE (value[0].isBinary ()); + EXPECT_TRUE (value[0].getBinary ().empty ()); stream.clear (); stream.str (std::string ( @@ -1366,8 +1376,8 @@ TEST (PackReader, bin) ASSERT_EQ (PackReader ().deserialize (stream, value), 0) << join::lastError.message (); ASSERT_TRUE (value.isArray ()); ASSERT_FALSE (value.empty ()); - ASSERT_TRUE (value[0].isString ()); - EXPECT_EQ (value[0].getString (), "Hello"); + ASSERT_TRUE (value[0].isBinary ()); + EXPECT_TRUE (std::ranges::equal (value[0].getBinary (), std::span (hello, 5))); stream.clear (); stream.str (std::string ({'\xdd', '\x00', '\x00', '\x00', '\x01', '\xc6', '\x00', '\x00', '\x00', '\x0b', '\x48', @@ -1375,67 +1385,71 @@ TEST (PackReader, bin) ASSERT_EQ (PackReader ().deserialize (stream, value), 0) << join::lastError.message (); ASSERT_TRUE (value.isArray ()); ASSERT_FALSE (value.empty ()); - ASSERT_TRUE (value[0].isString ()); - EXPECT_EQ (value[0].getString (), "Hello\nWorld"); + ASSERT_TRUE (value[0].isBinary ()); + EXPECT_TRUE (std::ranges::equal (value[0].getBinary (), std::span (hello, sizeof (hello)))); - stream.clear (); - stream.str (std::string ({'\xdd', '\x00', '\x00', '\x00', '\x01', '\xc4', '\x0b', '\x48', '\x65', '\x6c', '\x6c', - '\x6f', '\x00', '\x57', '\x6f', '\x72', '\x6c', '\x64'})); - ASSERT_EQ (PackReader ().deserialize (stream, value), 0) << join::lastError.message (); + ASSERT_EQ (PackReader ().deserialize (std::string ({'\x91', '\xc4', '\x02', '\x00', '\xff'}), value), 0) + << join::lastError.message (); ASSERT_TRUE (value.isArray ()); ASSERT_FALSE (value.empty ()); - ASSERT_TRUE (value[0].isString ()); - EXPECT_EQ (value[0].getString (), std::string_view ("Hello\0World", 11)); + ASSERT_TRUE (value[0].isBinary ()); + EXPECT_TRUE (std::ranges::equal (value[0].getBinary (), std::span (bytes, sizeof (bytes)))); - stream.clear (); - stream.str (std::string ({'\xdd', '\x00', '\x00', '\x00', '\x01', '\xc5', '\x00', '\x08', '\x22', '\x5c', '\x2f', - '\x08', '\x0c', '\x0a', '\x0d', '\x09'})); - ASSERT_EQ (PackReader ().deserialize (stream, value), 0) << join::lastError.message (); - ASSERT_TRUE (value.isArray ()); - ASSERT_FALSE (value.empty ()); - ASSERT_TRUE (value[0].isString ()); - EXPECT_EQ (value[0].getString (), "\"\\/\b\f\n\r\t"); + ASSERT_EQ (PackReader ().deserialize (std::string ({'\x91', '\xc4', '\x05', '\x00', '\xff'}), value), -1); + EXPECT_EQ (join::lastError, SaxErrc::Truncated); +} - stream.clear (); - stream.str (std::string ({'\xdd', '\x00', '\x00', '\x00', '\x01', '\xc6', '\x00', '\x00', '\x00', '\x01', '\x24'})); - ASSERT_EQ (PackReader ().deserialize (stream, value), 0) << join::lastError.message (); - ASSERT_TRUE (value.isArray ()); - ASSERT_FALSE (value.empty ()); - ASSERT_TRUE (value[0].isString ()); - EXPECT_EQ (value[0].getString (), "\x24"); +/** + * @brief Test MessagePack parse ext. + */ +TEST (PackReader, ext) +{ + const std::vector data (65536, 0x78); + const uint8_t bytes[] = {0x00, 0xff}; + Value value; - stream.clear (); - stream.str (std::string ({'\xdd', '\x00', '\x00', '\x00', '\x01', '\xc4', '\x02', '\xc2', '\xa2'})); - ASSERT_EQ (PackReader ().deserialize (stream, value), 0) << join::lastError.message (); - ASSERT_TRUE (value.isArray ()); - ASSERT_FALSE (value.empty ()); - ASSERT_TRUE (value[0].isString ()); - EXPECT_EQ (value[0].getString (), "\xC2\xA2"); + auto ext = [&] (const std::string& head, size_t size) { + std::stringstream stream (head + std::string (size, 'x')); + if (PackReader ().deserialize (stream, value) == -1) + { + return false; + } + return value.isBinary () && (value.getSubtype () == -1) && + std::ranges::equal (value.getBinary (), std::span (data.data (), size)); + }; + + EXPECT_TRUE (ext (std::string ({'\xd4', '\xff'}), 1)) << join::lastError.message (); + EXPECT_TRUE (ext (std::string ({'\xd5', '\xff'}), 2)) << join::lastError.message (); + EXPECT_TRUE (ext (std::string ({'\xd6', '\xff'}), 4)) << join::lastError.message (); + EXPECT_TRUE (ext (std::string ({'\xd7', '\xff'}), 8)) << join::lastError.message (); + EXPECT_TRUE (ext (std::string ({'\xd8', '\xff'}), 16)) << join::lastError.message (); + EXPECT_TRUE (ext (std::string ({'\xc7', '\x00', '\xff'}), 0)) << join::lastError.message (); + EXPECT_TRUE (ext (std::string ({'\xc7', '\xff', '\xff'}), 255)) << join::lastError.message (); + EXPECT_TRUE (ext (std::string ({'\xc8', '\xff', '\xff', '\xff'}), 65535)) << join::lastError.message (); + EXPECT_TRUE (ext (std::string ({'\xc9', '\x00', '\x01', '\x00', '\x00', '\xff'}), 65536)) + << join::lastError.message (); - stream.clear (); - stream.str (std::string ({'\xdd', '\x00', '\x00', '\x00', '\x01', '\xc5', '\x00', '\x03', '\xe2', '\x82', '\xac'})); - ASSERT_EQ (PackReader ().deserialize (stream, value), 0) << join::lastError.message (); + ASSERT_EQ (PackReader ().deserialize (std::string ({'\x91', '\xd5', '\x01', '\x00', '\xff'}), value), 0) + << join::lastError.message (); ASSERT_TRUE (value.isArray ()); - ASSERT_FALSE (value.empty ()); - ASSERT_TRUE (value[0].isString ()); - EXPECT_EQ (value[0].getString (), "\xE2\x82\xAC"); + ASSERT_TRUE (value[0].isBinary ()); + EXPECT_EQ (value[0].getSubtype (), 1); + EXPECT_TRUE (std::ranges::equal (value[0].getBinary (), std::span (bytes, sizeof (bytes)))); - stream.clear (); - stream.str (std::string ({'\xdd', '\x00', '\x00', '\x00', '\x01', '\xc6', '\x00', '\x00', '\x00', '\x04', '\xf0', - '\x9d', '\x84', '\x9e'})); - ASSERT_EQ (PackReader ().deserialize (stream, value), 0) << join::lastError.message (); - ASSERT_TRUE (value.isArray ()); - ASSERT_FALSE (value.empty ()); - ASSERT_TRUE (value[0].isString ()); - EXPECT_EQ (value[0].getString (), "\xF0\x9D\x84\x9E"); + ASSERT_EQ (PackReader ().deserialize (std::string ({'\x91', '\xd5', '\x01', '\x00'}), value), -1); + EXPECT_EQ (join::lastError, SaxErrc::Truncated); - Nested nested; - ASSERT_EQ ( - PackReader ().deserialize (std::string ({'\x91', '\xc4', '\x05', '\x91', '\xc4', '\x02', 'a', 'b'}), nested), 0) - << join::lastError.message (); - ASSERT_EQ (nested.strings.size (), 2); - EXPECT_EQ (nested.strings[0], "ab"); - EXPECT_EQ (nested.strings[1], std::string ({'\x91', '\xc4', '\x02', 'a', 'b'})); + ASSERT_EQ (PackReader ().deserialize (std::string ({'\x91', '\xd5'}), value), -1); + EXPECT_EQ (join::lastError, SaxErrc::Truncated); + + ASSERT_EQ (PackReader ().deserialize (std::string ({'\x91', '\xc7'}), value), -1); + EXPECT_EQ (join::lastError, SaxErrc::Truncated); + + ASSERT_EQ (PackReader ().deserialize (std::string ({'\x91', '\xc8', '\x00'}), value), -1); + EXPECT_EQ (join::lastError, SaxErrc::Truncated); + + ASSERT_EQ (PackReader ().deserialize (std::string ({'\x91', '\xc9', '\x00'}), value), -1); + EXPECT_EQ (join::lastError, SaxErrc::Truncated); } /** @@ -1443,6 +1457,7 @@ TEST (PackReader, bin) */ TEST (PackReader, key) { + const uint8_t bytes[] = {0x00, 0xff}; std::stringstream stream; Value value; @@ -1506,16 +1521,43 @@ TEST (PackReader, key) ASSERT_TRUE (value.isObject ()); ASSERT_TRUE (value.contains (1.0)); + stream.clear (); + stream.str (std::string ({'\x81', '\xc4', '\x02', '\x00', '\xff', '\x01'})); + ASSERT_EQ (PackReader ().deserialize (stream, value), 0) << join::lastError.message (); + ASSERT_TRUE (value.isObject ()); + ASSERT_TRUE (value.contains (Value (std::span (bytes, sizeof (bytes))))); + + ASSERT_EQ (PackReader ().deserialize (std::string ({'\x81', '\xd5', '\xff', '\x00', '\xff', '\x01'}), value), 0) + << join::lastError.message (); + ASSERT_TRUE (value.isObject ()); + ASSERT_TRUE (value.contains (Value (std::span (bytes, sizeof (bytes)), -1))); + + Value arrayKey; + arrayKey.pushBack (1); stream.clear (); stream.str (std::string ({'\x81', '\x91', '\x01', '\x01'})); - ASSERT_NE (PackReader ().deserialize (stream, value), 0); - ASSERT_EQ (join::lastError, SaxErrc::InvalidValue); + ASSERT_EQ (PackReader ().deserialize (stream, value), 0) << join::lastError.message (); + ASSERT_TRUE (value.isObject ()); + ASSERT_TRUE (value.contains (arrayKey)); + Value objectKey; + objectKey["a"] = 1; stream.clear (); stream.str (std::string ({'\x81', '\x81', '\xa1', '\x61', '\x01', '\x01'})); + ASSERT_EQ (PackReader ().deserialize (stream, value), 0) << join::lastError.message (); + ASSERT_TRUE (value.isObject ()); + ASSERT_TRUE (value.contains (objectKey)); + + stream.clear (); + stream.str (std::string ({'\x81', '\xc1', '\x01'})); ASSERT_NE (PackReader ().deserialize (stream, value), 0); ASSERT_EQ (join::lastError, SaxErrc::InvalidValue); + std::stringstream out; + JsonWriter jsonWriter (out); + ASSERT_EQ (PackReader ().deserialize (std::string ({'\x81', '\x91', '\x01', '\x01'}), jsonWriter), -1); + ASSERT_EQ (join::lastError, SaxErrc::InvalidKey); + stream.clear (); stream.str (std::string ({'\x81', '\xcc'})); ASSERT_NE (PackReader ().deserialize (stream, value), 0); diff --git a/data/tests/pack_writer_test.cpp b/data/tests/pack_writer_test.cpp index 48b2b47d..4350231d 100644 --- a/data/tests/pack_writer_test.cpp +++ b/data/tests/pack_writer_test.cpp @@ -31,6 +31,8 @@ // C++. #include +#include +#include using join::PackWriter; using join::Value; @@ -378,6 +380,51 @@ TEST (PackWriter, setString) EXPECT_EQ (stream.str ().compare (0, 6, std::string ({'\xDB', '\x00', '\x01', '\x00', '\x00', '\x78'})), 0); } +/** + * @brief Test setBinary method. + */ +TEST (PackWriter, setBinary) +{ + std::stringstream stream; + PackWriter packWriter (stream); + const std::vector data (65536, 0x78); + + stream.str (""); + EXPECT_EQ (packWriter.setBinary (std::span (data.data (), 255)), 0); + EXPECT_EQ (stream.str ().compare (0, 4, std::string ({'\xC4', '\xFF', '\x78', '\x78'})), 0); + + stream.str (""); + EXPECT_EQ (packWriter.setBinary (std::span (data.data (), 256)), 0); + EXPECT_EQ (stream.str ().compare (0, 5, std::string ({'\xC5', '\x01', '\x00', '\x78', '\x78'})), 0); + + stream.str (""); + EXPECT_EQ (packWriter.setBinary (std::span (data.data (), 65535)), 0); + EXPECT_EQ (stream.str ().compare (0, 5, std::string ({'\xC5', '\xFF', '\xFF', '\x78', '\x78'})), 0); + + stream.str (""); + EXPECT_EQ (packWriter.setBinary (std::span (data.data (), 65536)), 0); + EXPECT_EQ (stream.str ().compare (0, 7, std::string ({'\xC6', '\x00', '\x01', '\x00', '\x00', '\x78', '\x78'})), 0); + + auto ext = [&] (size_t size) { + stream.str (""); + packWriter.setBinary (std::span (data.data (), size), -1); + packWriter.flush (); + return stream.str ().substr (0, stream.str ().size () - size); + }; + + EXPECT_EQ (ext (1), std::string ({'\xD4', '\xFF'})); + EXPECT_EQ (ext (2), std::string ({'\xD5', '\xFF'})); + EXPECT_EQ (ext (4), std::string ({'\xD6', '\xFF'})); + EXPECT_EQ (ext (8), std::string ({'\xD7', '\xFF'})); + EXPECT_EQ (ext (16), std::string ({'\xD8', '\xFF'})); + EXPECT_EQ (ext (0), std::string ({'\xC7', '\x00', '\xFF'})); + EXPECT_EQ (ext (3), std::string ({'\xC7', '\x03', '\xFF'})); + EXPECT_EQ (ext (255), std::string ({'\xC7', '\xFF', '\xFF'})); + EXPECT_EQ (ext (256), std::string ({'\xC8', '\x01', '\x00', '\xFF'})); + EXPECT_EQ (ext (65535), std::string ({'\xC8', '\xFF', '\xFF', '\xFF'})); + EXPECT_EQ (ext (65536), std::string ({'\xC9', '\x00', '\x01', '\x00', '\x00', '\xFF'})); +} + /** * @brief Test startArray method. */ @@ -484,11 +531,11 @@ TEST (PackWriter, setKey) std::stringstream stream; PackWriter packWriter (stream); - auto key = [&] (auto value) { + auto key = [&] (auto... value) { stream.str (""); packWriter.reset (); packWriter.startObject (1); - int result = packWriter.setKey (value); + int result = packWriter.setKey (value...); packWriter.flush (); return result; }; @@ -511,6 +558,8 @@ TEST (PackWriter, setKey) EXPECT_EQ (join::lastError, SaxErrc::InvalidParent); EXPECT_EQ (packWriter.setKey ("eight"), -1); EXPECT_EQ (join::lastError, SaxErrc::InvalidParent); + EXPECT_EQ (packWriter.setKey (std::span ()), -1); + EXPECT_EQ (join::lastError, SaxErrc::InvalidParent); EXPECT_EQ (key (nullptr), 0); EXPECT_EQ (stream.str ().substr (1), std::string ({'\xC0'})); @@ -566,6 +615,28 @@ TEST (PackWriter, setKey) EXPECT_EQ (key ("a"), 0); EXPECT_EQ (stream.str ().substr (1), std::string ({'\xA1', 'a'})); + + const uint8_t bytes[] = {0x00, 0xff}; + EXPECT_EQ (key (std::span (bytes, sizeof (bytes))), 0); + EXPECT_EQ (stream.str ().substr (1), std::string ({'\xC4', '\x02', '\x00', '\xFF'})); + + EXPECT_EQ (key (std::span (bytes, sizeof (bytes)), -1), 0); + EXPECT_EQ (stream.str ().substr (1), std::string ({'\xD5', '\xFF', '\x00', '\xFF'})); +} + +/** + * @brief Test startKey method. + */ +TEST (PackWriter, startKey) +{ + std::stringstream stream; + PackWriter packWriter (stream); + + EXPECT_EQ (packWriter.startKey (), -1); + EXPECT_EQ (join::lastError, SaxErrc::InvalidParent); + + EXPECT_EQ (packWriter.startObject (1), 0); + EXPECT_EQ (packWriter.startKey (), 0); } /** @@ -655,12 +726,12 @@ TEST (PackWriter, document) value = Object (); value.insert (key, 1); stream.str (""); - EXPECT_EQ (value.serialize (packWriter), -1); - EXPECT_EQ (join::lastError, SaxErrc::InvalidKey); - EXPECT_EQ (stream.str (), ""); + EXPECT_EQ (value.serialize (packWriter), 0) << join::lastError.message (); + EXPECT_EQ (stream.str (), std::string ({'\x81', '\x91', '\x01', '\x01'})); value = nullptr; value.pushBack (7); + stream.str (""); EXPECT_EQ (value.serialize (packWriter), 0) << join::lastError.message (); EXPECT_EQ (stream.str (), std::string ({'\x91', '\x07'})); } diff --git a/data/tests/sax_handler_test.cpp b/data/tests/sax_handler_test.cpp index 8c570a8e..73bbc021 100644 --- a/data/tests/sax_handler_test.cpp +++ b/data/tests/sax_handler_test.cpp @@ -32,6 +32,7 @@ // C++. #include #include +#include using join::Value; using join::Object; @@ -133,6 +134,16 @@ TEST (SaxHandler, setString) EXPECT_EQ (counter.strings, 2U); } +/** + * @brief Test setBinary method. + */ +TEST (SaxHandler, setBinary) +{ + SaxHandler handler; + EXPECT_EQ (handler.setBinary (std::span ()), 0); + EXPECT_EQ (handler.setBinary (std::span (), -1), 0); +} + /** * @brief Test startArray method. */ @@ -190,6 +201,17 @@ TEST (SaxHandler, setKey) EXPECT_EQ (handler.setKey (6.5), 0); EXPECT_EQ (handler.setKey (std::string_view ("seven")), 0); EXPECT_EQ (handler.setKey ("eight"), 0); + EXPECT_EQ (handler.setKey (std::span ()), 0); + EXPECT_EQ (handler.setKey (std::span (), -1), 0); +} + +/** + * @brief Test startKey method. + */ +TEST (SaxHandler, startKey) +{ + SaxHandler handler; + EXPECT_EQ (handler.startKey (), 0); } /** diff --git a/data/tests/value_builder_test.cpp b/data/tests/value_builder_test.cpp index ff4179aa..403013db 100644 --- a/data/tests/value_builder_test.cpp +++ b/data/tests/value_builder_test.cpp @@ -33,9 +33,12 @@ // C++. #include +#include +#include #include #include #include +#include using join::Array; using join::Object; @@ -251,6 +254,31 @@ TEST (ValueBuilder, setString) ASSERT_EQ (value[1], "b"); } +/** + * @brief Test setBinary method. + */ +TEST (ValueBuilder, setBinary) +{ + const uint8_t data[] = {0x01, 0x02}; + Value value; + ValueBuilder builder (value); + ASSERT_EQ (builder.setBinary (std::span (data, sizeof (data))), 0); + ASSERT_TRUE (value.isBinary ()); + ASSERT_TRUE (std::ranges::equal (value.getBinary (), std::span (data, sizeof (data)))); + ASSERT_NE (value.getBinary ().data (), data); + + ASSERT_EQ (builder.startArray (), 0); + ASSERT_EQ (builder.setBinary (std::span (data, 1)), 0); + ASSERT_EQ (builder.stopArray (), 0); + ASSERT_EQ (value.size (), 1U); + ASSERT_TRUE (std::ranges::equal (value[0].getBinary (), std::span (data, 1))); + + ASSERT_EQ (builder.setBinary (std::span (data, sizeof (data)), -1), 0); + ASSERT_TRUE (value.isBinary ()); + ASSERT_TRUE (std::ranges::equal (value.getBinary (), std::span (data, sizeof (data)))); + ASSERT_EQ (value.getSubtype (), -1); +} + /** * @brief Test startArray method. */ @@ -273,6 +301,18 @@ TEST (ValueBuilder, startArray) ASSERT_EQ (value.size (), 1U); ASSERT_TRUE (value[0].isArray ()); ASSERT_TRUE (value[0].empty ()); + + builder.reset (); + ASSERT_EQ (builder.startObject (), 0); + ASSERT_EQ (builder.startArray (), 0); + ASSERT_EQ (builder.setInt (1), 0); + ASSERT_EQ (builder.stopArray (), 0); + ASSERT_EQ (builder.setInt (2), 0); + ASSERT_EQ (builder.stopObject (), 0); + ASSERT_EQ (value.size (), 1U); + ASSERT_TRUE (value.getObject ()[0].first.isArray ()); + ASSERT_EQ (value.getObject ()[0].first.at (0), 1); + ASSERT_EQ (value.getObject ()[0].second, 2); } /** @@ -378,7 +418,10 @@ TEST (ValueBuilder, setKey) ASSERT_EQ (join::lastError, SaxErrc::InvalidParent); ASSERT_EQ (builder.setKey (6.5), -1); ASSERT_EQ (join::lastError, SaxErrc::InvalidParent); + ASSERT_EQ (builder.setKey (std::span ()), -1); + ASSERT_EQ (join::lastError, SaxErrc::InvalidParent); + const uint8_t data[] = {0x01, 0x02}; ASSERT_EQ (builder.startObject (), 0); ASSERT_EQ (builder.setKey (nullptr), 0); ASSERT_EQ (builder.setInt (0), 0); @@ -398,8 +441,12 @@ TEST (ValueBuilder, setKey) ASSERT_EQ (builder.setInt (7), 0); ASSERT_EQ (builder.setKey ("eight"), 0); ASSERT_EQ (builder.setInt (8), 0); + ASSERT_EQ (builder.setKey (std::span (data, sizeof (data))), 0); + ASSERT_EQ (builder.setInt (9), 0); + ASSERT_EQ (builder.setKey (std::span (data, sizeof (data)), -1), 0); + ASSERT_EQ (builder.setInt (10), 0); ASSERT_EQ (builder.stopObject (), 0); - ASSERT_EQ (value.size (), 9U); + ASSERT_EQ (value.size (), 11U); ASSERT_TRUE (value.getObject ()[0].first.isNull ()); ASSERT_TRUE (value.getObject ()[1].first.isBool ()); ASSERT_TRUE (value.getObject ()[2].first.isInt ()); @@ -409,6 +456,22 @@ TEST (ValueBuilder, setKey) ASSERT_TRUE (value.getObject ()[6].first.isDouble ()); ASSERT_EQ (value["seven"], 7); ASSERT_EQ (value["eight"], 8); + ASSERT_EQ (value[Value (std::span (data, sizeof (data)))], 9); + ASSERT_EQ (value[Value (std::span (data, sizeof (data)), -1)], 10); +} + +/** + * @brief Test startKey method. + */ +TEST (ValueBuilder, startKey) +{ + Value value; + ValueBuilder builder (value); + ASSERT_EQ (builder.startKey (), -1); + ASSERT_EQ (join::lastError, SaxErrc::InvalidParent); + + ASSERT_EQ (builder.startObject (), 0); + ASSERT_EQ (builder.startKey (), 0); } /** diff --git a/data/tests/value_test.cpp b/data/tests/value_test.cpp index 3a0db4f5..c531c361 100644 --- a/data/tests/value_test.cpp +++ b/data/tests/value_test.cpp @@ -33,10 +33,13 @@ // C++. #include +#include #include +#include #include #include #include +#include using join::Array; using join::Member; @@ -224,6 +227,17 @@ TEST (Value, create) ASSERT_TRUE (viewStringValue.isString ()); ASSERT_EQ (viewStringValue.getString (), "quu"); + const uint8_t bytes[] = {0x00, 0x01, 0xff}; + Value binaryValue (std::span (bytes, sizeof (bytes))); + ASSERT_TRUE (binaryValue.isBinary ()); + ASSERT_TRUE (std::ranges::equal (binaryValue.getBinary (), std::span (bytes, sizeof (bytes)))); + ASSERT_EQ (binaryValue.getSubtype (), std::nullopt); + + Value extensionValue (std::span (bytes, sizeof (bytes)), -1); + ASSERT_TRUE (extensionValue.isBinary ()); + ASSERT_TRUE (std::ranges::equal (extensionValue.getBinary (), std::span (bytes, sizeof (bytes)))); + ASSERT_EQ (extensionValue.getSubtype (), -1); + Value arrayValue (array.getArray ()); ASSERT_TRUE (arrayValue.isArray ()); ASSERT_EQ (arrayValue.size (), 1U); @@ -327,6 +341,18 @@ TEST (Value, copy) Value stringValue (value); ASSERT_TRUE (stringValue.isString ()); + const uint8_t bytes[] = {0x00, 0x01, 0xff}; + value = std::span (bytes, sizeof (bytes)); + Value binaryValue (value); + ASSERT_TRUE (binaryValue.isBinary ()); + ASSERT_TRUE (std::ranges::equal (binaryValue.getBinary (), std::span (bytes, sizeof (bytes)))); + + value = Value (std::span (bytes, sizeof (bytes)), -1); + Value extensionValue (value); + ASSERT_TRUE (extensionValue.isBinary ()); + ASSERT_TRUE (std::ranges::equal (extensionValue.getBinary (), std::span (bytes, sizeof (bytes)))); + ASSERT_EQ (extensionValue.getSubtype (), -1); + value = array.getArray (); Value arrayValue (value); ASSERT_TRUE (arrayValue.isArray ()); @@ -2070,6 +2096,113 @@ TEST (Value, isString) ASSERT_FALSE (value.isString ()); } +/** + * @brief Test isBinary method. + */ +TEST (Value, isBinary) +{ + Value array; + array.pushBack (1); + + Value object; + object["i"] = 1; + + const uint8_t bytes[] = {0x00, 0x01, 0xff}; + + Value value; + ASSERT_FALSE (value.isBinary ()); + + value = nullptr; + ASSERT_FALSE (value.isBinary ()); + + value = true; + ASSERT_FALSE (value.isBinary ()); + + value = false; + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::min (); + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::max (); + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::min (); + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::max (); + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::min (); + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::max (); + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::min (); + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::max (); + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::min (); + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::max (); + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::min (); + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::max (); + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::min (); + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::max (); + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::min (); + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::max (); + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::min (); + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::max (); + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::min (); + ASSERT_FALSE (value.isBinary ()); + + value = std::numeric_limits::max (); + ASSERT_FALSE (value.isBinary ()); + + value = ""; + ASSERT_FALSE (value.isBinary ()); + + value = "foo"; + ASSERT_FALSE (value.isBinary ()); + + value = "02:42:64:2f:6a:d0"; + ASSERT_FALSE (value.isBinary ()); + + value = "127.0.0.1"; + ASSERT_FALSE (value.isBinary ()); + + value = std::span (bytes, sizeof (bytes)); + ASSERT_TRUE (value.isBinary ()); + + value = array.getArray (); + ASSERT_FALSE (value.isBinary ()); + + value = object.getObject (); + ASSERT_FALSE (value.isBinary ()); +} + /** * @brief Test isArray method. */ @@ -3964,6 +4097,223 @@ TEST (Value, getString) ASSERT_THROW ((tmp = static_cast (value)), std::bad_cast); } +/** + * @brief Test getBinary method. + */ +TEST (Value, getBinary) +{ + Value array; + array.pushBack (1); + + Value object; + object["i"] = 1; + + const uint8_t bytes[] = {0x00, 0x01, 0xff}; + + Value value; + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = nullptr; + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = true; + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = false; + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = ""; + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = "foo"; + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = "02:42:64:2f:6a:d0"; + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = "127.0.0.1"; + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = std::span (bytes, sizeof (bytes)); + ASSERT_TRUE (std::ranges::equal (value.getBinary (), std::span (bytes, sizeof (bytes)))); + + value = array.getArray (); + ASSERT_THROW (value.getBinary (), std::bad_cast); + + value = object.getObject (); + ASSERT_THROW (value.getBinary (), std::bad_cast); +} + +/** + * @brief Test getSubtype method. + */ +TEST (Value, getSubtype) +{ + Value array; + array.pushBack (1); + + Value object; + object["i"] = 1; + + const uint8_t bytes[] = {0x00, 0x01, 0xff}; + + Value value; + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = nullptr; + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = true; + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = false; + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::min (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::numeric_limits::max (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = ""; + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = "foo"; + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = "02:42:64:2f:6a:d0"; + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = "127.0.0.1"; + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = std::span (bytes, sizeof (bytes)); + ASSERT_EQ (value.getSubtype (), std::nullopt); + + value = Value (std::span (bytes, sizeof (bytes)), -1); + ASSERT_EQ (value.getSubtype (), -1); + + value = array.getArray (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); + + value = object.getObject (); + ASSERT_THROW (value.getSubtype (), std::bad_cast); +} + /** * @brief Test getArray method. */ @@ -4490,6 +4840,7 @@ TEST (Value, subscript) ASSERT_EQ (value[uint32_t (1)], 1); ASSERT_EQ (value[int64_t (2)], 2); ASSERT_EQ (value[uint64_t (3)], 3); + ASSERT_EQ (value[Value (uint64_t (3))], 3); ASSERT_THROW (value[nullptr], std::bad_cast); ASSERT_THROW (value[true], std::bad_cast); ASSERT_THROW (value[1.0], std::bad_cast); @@ -4522,6 +4873,11 @@ TEST (Value, assign) value["string"] = "foobar"; ASSERT_TRUE (value["string"].isString ()); + const uint8_t bytes[] = {0x00, 0x01, 0xff}; + value["binary"] = std::span (bytes, sizeof (bytes)); + ASSERT_TRUE (value["binary"].isBinary ()); + ASSERT_TRUE (std::ranges::equal (value["binary"].getBinary (), std::span (bytes, sizeof (bytes)))); + value["array"] = Array (); ASSERT_TRUE (value["array"].isArray ()); @@ -5324,6 +5680,13 @@ TEST (Value, pushBack) value = object.getObject (); ASSERT_THROW (value.pushBack (1), std::bad_cast); + + const uint8_t bytes[] = {0x00, 0x01, 0xff}; + value = Array (); + ASSERT_TRUE (value.pushBack (std::span (bytes, sizeof (bytes))).isBinary ()); + ASSERT_TRUE (std::ranges::equal (value[0].getBinary (), std::span (bytes, sizeof (bytes)))); + ASSERT_EQ (value.pushBack (std::span (bytes, sizeof (bytes)), -1).getSubtype (), -1); + ASSERT_TRUE (std::ranges::equal (value[1].getBinary (), std::span (bytes, sizeof (bytes)))); } /** @@ -5599,6 +5962,8 @@ TEST (Value, contains) ASSERT_FALSE (value.contains (int64_t (-4))); ASSERT_FALSE (value.contains (uint64_t (5))); ASSERT_FALSE (value.contains (6.5)); + ASSERT_TRUE (value.contains (Value ("seven"))); + ASSERT_FALSE (value.contains (Value ("eight"))); value = nullptr; value.pushBack (0); @@ -5613,6 +5978,8 @@ TEST (Value, contains) ASSERT_FALSE (value.contains (int64_t (-1))); ASSERT_TRUE (value.contains (uint64_t (1))); ASSERT_FALSE (value.contains (uint64_t (2))); + ASSERT_TRUE (value.contains (Value (1))); + ASSERT_FALSE (value.contains (Value (2))); ASSERT_THROW (value.contains (nullptr), std::bad_cast); ASSERT_THROW (value.contains (true), std::bad_cast); ASSERT_THROW (value.contains (1.0), std::bad_cast); @@ -5724,6 +6091,9 @@ TEST (Value, serialize) Value array; array.pushBack (1); + Value object; + object["i"] = 1; + Value keys; keys[nullptr] = 0; keys[true] = 1; @@ -5761,6 +6131,25 @@ TEST (Value, serialize) ASSERT_EQ (value.serialize (out, 4), 0) << join::lastError.message (); EXPECT_EQ (out.str (), indented.str ()); + const uint8_t bytes[] = {0x00, 0xff}; + Value binary; + binary.pushBack (std::span (bytes, sizeof (bytes))); + out.str (""); + ASSERT_EQ (binary.serialize (out), 0) << join::lastError.message (); + EXPECT_EQ (out.str (), std::string ({'\x91', '\xc4', '\x02', '\x00', '\xff'})); + + Value extension; + extension.pushBack (std::span (bytes, sizeof (bytes)), -1); + out.str (""); + ASSERT_EQ (extension.serialize (out), 0) << join::lastError.message (); + EXPECT_EQ (out.str (), std::string ({'\x91', '\xd5', '\xff', '\x00', '\xff'})); + + Value binaryKey; + binaryKey[Value (std::span (bytes, sizeof (bytes)), -1)] = 1; + out.str (""); + ASSERT_EQ (binaryKey.serialize (out), 0) << join::lastError.message (); + EXPECT_EQ (out.str (), std::string ({'\x81', '\xd5', '\xff', '\x00', '\xff', '\x01'})); + out.str (""); PackWriter packWriter (out); ASSERT_EQ (value.serialize (packWriter), 0) << join::lastError.message (); @@ -5796,9 +6185,18 @@ TEST (Value, serialize) value = Object (); value.insert (array, 1); out.str (""); - ASSERT_EQ (value.serialize (packWriter), -1); + ASSERT_EQ (value.serialize (packWriter), 0) << join::lastError.message (); + EXPECT_EQ (out.str (), std::string ({'\x81', '\x91', '\x01', '\x01'})); + + value = Object (); + value.insert (object, 1); + out.str (""); + ASSERT_EQ (value.serialize (packWriter), 0) << join::lastError.message (); + EXPECT_EQ (out.str (), std::string ({'\x81', '\x81', '\xa1', '\x69', '\x01', '\x01'})); + + out.str (""); + ASSERT_EQ (value.serialize (out), -1); ASSERT_EQ (join::lastError, SaxErrc::InvalidKey); - ASSERT_EQ (out.str (), ""); value = nullptr; value.pushBack (7); @@ -6237,6 +6635,15 @@ TEST (Value, equal) ASSERT_FALSE ("foo" == value); ASSERT_FALSE (array.getArray () == value); ASSERT_TRUE (object.getObject () == value); + + const uint8_t bytes[] = {0x00, 0x01, 0xff}; + value = std::span (bytes, sizeof (bytes)); + ASSERT_TRUE (value == Value (std::span (bytes, sizeof (bytes)))); + ASSERT_FALSE (value == Value (std::span (bytes, sizeof (bytes)), 0)); + + value = Value (std::span (bytes, sizeof (bytes)), 0); + ASSERT_TRUE (value == Value (std::span (bytes, sizeof (bytes)), 0)); + ASSERT_FALSE (value == Value (std::span (bytes, sizeof (bytes)), -1)); } /** @@ -6914,6 +7321,12 @@ TEST (Value, lower) ASSERT_TRUE ("foo" < value); ASSERT_TRUE (array.getArray () < value); ASSERT_FALSE (object.getObject () < value); + + const uint8_t bytes[] = {0x00, 0x01, 0xff}; + value = Value (std::span (bytes, sizeof (bytes)), 0); + ASSERT_TRUE (Value (std::span (bytes, sizeof (bytes))) < value); + ASSERT_TRUE (Value (std::span (bytes, sizeof (bytes)), -1) < value); + ASSERT_FALSE (Value (std::span (bytes, sizeof (bytes)), 1) < value); } /**