From 625d01f2ce9295a9b16de86d9161afb491f3fc43 Mon Sep 17 00:00:00 2001 From: David Corbeil Date: Fri, 11 Jul 2025 16:02:16 -0400 Subject: [PATCH] cbor supports std::optional; fixes #135 Signed-off-by: David Corbeil --- include/rfl/cbor/Parser.hpp | 35 ---------------- include/rfl/cbor/Writer.hpp | 8 ++-- src/rfl/cbor/Writer.cpp | 16 ++++---- tests/cbor/test_error_messages.cpp | 6 ++- tests/cbor/test_integers.cpp | 12 +++--- tests/cbor/test_no_optionals.cpp | 28 +++++++++++++ tests/cbor/test_optional_fields.cpp | 62 +++++++++++++++++++++++++++++ 7 files changed, 114 insertions(+), 53 deletions(-) create mode 100644 tests/cbor/test_no_optionals.cpp create mode 100644 tests/cbor/test_optional_fields.cpp diff --git a/include/rfl/cbor/Parser.hpp b/include/rfl/cbor/Parser.hpp index 46d26f3e..594a236b 100644 --- a/include/rfl/cbor/Parser.hpp +++ b/include/rfl/cbor/Parser.hpp @@ -6,41 +6,6 @@ #include "Reader.hpp" #include "Writer.hpp" -namespace rfl::parsing { - -/// CBOR requires us to explicitly set the number of fields in advance. -/// Because of that, we require all of the fields and then set them to nullptr, -/// if necessary. -template - requires AreReaderAndWriter> -struct Parser, - ProcessorsType> - : public NamedTupleParser< - cbor::Reader, cbor::Writer, - /*_ignore_empty_containers=*/false, - /*_all_required=*/true, - /*_no_field_names=*/ProcessorsType::no_field_names_, ProcessorsType, - FieldTypes...> {}; - -template - requires AreReaderAndWriter> -struct Parser, ProcessorsType> - : public TupleParser> {}; - -template - requires AreReaderAndWriter> -struct Parser, ProcessorsType> - : public TupleParser> {}; - -} // namespace rfl::parsing - namespace rfl::cbor { template diff --git a/include/rfl/cbor/Writer.hpp b/include/rfl/cbor/Writer.hpp index 1003bcda..f31d2d61 100644 --- a/include/rfl/cbor/Writer.hpp +++ b/include/rfl/cbor/Writer.hpp @@ -41,7 +41,7 @@ class Writer { OutputArrayType array_as_root(const size_t _size) const noexcept; - OutputObjectType object_as_root(const size_t _size) const noexcept; + OutputObjectType object_as_root(const size_t) const noexcept; OutputVarType null_as_root() const noexcept; @@ -57,11 +57,11 @@ class Writer { const size_t _size, OutputObjectType* _parent) const noexcept; - OutputObjectType add_object_to_array(const size_t _size, + OutputObjectType add_object_to_array(const size_t, OutputArrayType* _parent) const noexcept; OutputObjectType add_object_to_object( - const std::string_view& _name, const size_t _size, + const std::string_view& _name, const size_t, OutputObjectType* _parent) const noexcept; template @@ -90,7 +90,7 @@ class Writer { private: OutputArrayType new_array(const size_t _size) const noexcept; - OutputObjectType new_object(const size_t _size) const noexcept; + OutputObjectType new_object() const noexcept; template OutputVarType new_value(const T& _var) const noexcept { diff --git a/src/rfl/cbor/Writer.cpp b/src/rfl/cbor/Writer.cpp index 074cdafd..8d5dfb18 100644 --- a/src/rfl/cbor/Writer.cpp +++ b/src/rfl/cbor/Writer.cpp @@ -12,8 +12,8 @@ Writer::OutputArrayType Writer::array_as_root( } Writer::OutputObjectType Writer::object_as_root( - const size_t _size) const noexcept { - return new_object(_size); + const size_t) const noexcept { + return new_object(); } Writer::OutputVarType Writer::null_as_root() const noexcept { @@ -34,15 +34,15 @@ Writer::OutputArrayType Writer::add_array_to_object( } Writer::OutputObjectType Writer::add_object_to_array( - const size_t _size, OutputArrayType* _parent) const noexcept { - return new_object(_size); + const size_t, OutputArrayType* _parent) const noexcept { + return new_object(); } Writer::OutputObjectType Writer::add_object_to_object( - const std::string_view& _name, const size_t _size, + const std::string_view& _name, const size_t, OutputObjectType* _parent) const noexcept { encoder_->key(_name); - return new_object(_size); + return new_object(); } Writer::OutputVarType Writer::add_null_to_array( @@ -71,8 +71,8 @@ Writer::OutputArrayType Writer::new_array(const size_t _size) const noexcept { return OutputArrayType{}; } -Writer::OutputObjectType Writer::new_object(const size_t _size) const noexcept { - encoder_->begin_object(_size); +Writer::OutputObjectType Writer::new_object() const noexcept { + encoder_->begin_object(); return OutputObjectType{}; } diff --git a/tests/cbor/test_error_messages.cpp b/tests/cbor/test_error_messages.cpp index 42f1e31f..a38cbcb2 100644 --- a/tests/cbor/test_error_messages.cpp +++ b/tests/cbor/test_error_messages.cpp @@ -47,7 +47,11 @@ TEST(cbor, test_decode_error_without_exception) { }); // A proposal: A generic prefix, followed by the underlying library's error output - const std::string expected = R"(Could not parse CBOR: An unknown type was found in the stream at position 1)"; + const std::string expected = R"(Found 4 errors: +1) Field named 'firstName' not found. +2) Field named 'lastName' not found. +3) Field named 'birthday' not found. +4) Field named 'children' not found.)"; EXPECT_EQ(result.error().what(), expected); EXPECT_TRUE(!result.has_value() && true); diff --git a/tests/cbor/test_integers.cpp b/tests/cbor/test_integers.cpp index 29e0637e..f35f6611 100644 --- a/tests/cbor/test_integers.cpp +++ b/tests/cbor/test_integers.cpp @@ -18,19 +18,21 @@ TEST(cbor, test_integers_signedness) { std::vector unsigned_buffer = rfl::cbor::write(Unsigned{BIG_INT}); std::vector unsigned_expected = { - 0xA1, 0x63, 0x75, 0x36, 0x34, - 0x1B, // Per RFC 8949, Initial byte '0x1B' indicates "unsigned integer (eight-byte uint64_t follows)" + 0xBF, 0x63, 0x75, 0x36, 0x34, + 0x1B, // Per RFC 8949, Initial byte '0x1B' indicates "unsigned integer (eight-byte uint64_t follows)" // See: https://www.rfc-editor.org/rfc/rfc8949.html#section-appendix.b - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF }; EXPECT_EQ(std::vector(unsigned_expected.begin(), unsigned_expected.end()), unsigned_buffer); std::vector signed_buffer = rfl::cbor::write(Signed{static_cast(BIG_INT)}); std::vector signed_expected = { - 0xA1, 0x63, 0x69, 0x36, 0x34, - 0x20 // Per RFC 8949, Initial byte '0x20' indicates "negative integer -1-0x00..-1-0x17 (-1..-24)" + 0xBF, 0x63, 0x69, 0x36, 0x34, + 0x20, // Per RFC 8949, Initial byte '0x20' indicates "negative integer -1-0x00..-1-0x17 (-1..-24)" // See: https://www.rfc-editor.org/rfc/rfc8949.html#section-appendix.b + 0xFF }; EXPECT_EQ(std::vector(signed_expected.begin(), signed_expected.end()), signed_buffer); diff --git a/tests/cbor/test_no_optionals.cpp b/tests/cbor/test_no_optionals.cpp new file mode 100644 index 00000000..552cb916 --- /dev/null +++ b/tests/cbor/test_no_optionals.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_no_optionals { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + rfl::Rename<"children", std::optional>> children; +}; + +struct OptionalPerson { + std::optional opt_person; +}; + +struct Empty{}; + +TEST(cbor, test_no_optionals) { + auto empty_struct_cbor = rfl::cbor::write(Empty{}); + auto no_person_cbor = rfl::cbor::write(OptionalPerson{}); + + EXPECT_NE(empty_struct_cbor.size(), no_person_cbor.size()); +} +} // namespace test_no_optionals diff --git a/tests/cbor/test_optional_fields.cpp b/tests/cbor/test_optional_fields.cpp new file mode 100644 index 00000000..e26bf309 --- /dev/null +++ b/tests/cbor/test_optional_fields.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_optional_fields { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + rfl::Rename<"children", std::optional>> children; + + bool operator==(const Person& other) const { + return first_name.get() == other.first_name.get() && + last_name.get() == other.last_name.get() && + children.get() == other.children.get(); + } +}; + +struct OptionalPerson { + std::optional opt_person; +}; + +struct Empty{}; + +TEST(cbor, test_optional_no_fields) { + auto empty_struct_cbor = rfl::cbor::write(Empty{}); + auto no_person_cbor = rfl::cbor::write(OptionalPerson{}); + + EXPECT_EQ(empty_struct_cbor.size(), no_person_cbor.size()); +} + +TEST(cbor, test_optional_fields) { + const auto bart = Person{.first_name = "Bart"}; + + const auto lisa = Person{.first_name = "Lisa"}; + + const auto maggie = Person{.first_name = "Maggie"}; + + const auto homer = + Person{.first_name = "Homer", + .children = std::vector({bart, lisa, maggie})}; + + const OptionalPerson homer_optional = { + .opt_person = homer + }; + + const auto homer_optional_cbor = rfl::cbor::write(homer_optional); + const auto res = rfl::cbor::read(homer_optional_cbor); + + EXPECT_TRUE(res && true) << "Test failed on read. Error: " + << res.error().what(); + + const auto actual_homer = res.value(); + + EXPECT_TRUE(actual_homer.opt_person.has_value()); + EXPECT_EQ(*actual_homer.opt_person, homer); +} +} // namespace test_optional_fields