From 810ebbfa86666bafc802cd1d2232b54d579735e5 Mon Sep 17 00:00:00 2001 From: sha512sum Date: Tue, 12 Nov 2024 14:36:18 +0000 Subject: [PATCH] Add Serialization and Deserialization generation for std::optional --- library/include/larra/serialization/auto.hpp | 114 ++++++++++++++----- tests/serialization.cpp | 25 ++++ 2 files changed, 110 insertions(+), 29 deletions(-) diff --git a/library/include/larra/serialization/auto.hpp b/library/include/larra/serialization/auto.hpp index 83dc3ab..b469dae 100644 --- a/library/include/larra/serialization/auto.hpp +++ b/library/include/larra/serialization/auto.hpp @@ -28,6 +28,11 @@ constexpr auto Parse(xmlpp::Element* element, Tag = {}) -> T template constexpr auto Parse(xmlpp::Element* element, Tag = {}) -> T; +template +constexpr auto TryParse(xmlpp::Element* element, Tag = {}) -> std::optional { + return Serialization>::Parse(element); +} + template constexpr auto Serialize(xmlpp::Element* node, const T& element) -> void requires(!std::same_as)>, std::monostate>); @@ -64,9 +69,7 @@ struct FieldInfo { }; template -struct Config { - std::optional defaultValue; -}; +struct Config {}; // GCC workaround: operator== @@ -87,9 +90,11 @@ constexpr auto operator==(const Config&, const Config&) -> bool { return false; } -template <> -struct Config { - std::optional defaultValue; +template +struct Config> { + std::optional defaultValue = std::nullopt; + bool strict = true; + std::optional> main = std::nullopt; }; namespace impl { @@ -131,7 +136,7 @@ struct ElementSerializer { throw ElementParsingError(std::format("[{}: {}] parsing error: [ Not found ]", Info::kName, nameof::nameof_full_type())); } auto elementNode = dynamic_cast(node); - if(!node) { + if(!elementNode) { throw ElementParsingError(std::format("[{}: {}] parsing error: [ Invalid node ]", Info::kName, nameof::nameof_full_type())); } try { @@ -140,6 +145,7 @@ struct ElementSerializer { throw ElementParsingError(std::format("[{}: {}] parsing error: [ {} ]", Info::kName, nameof::nameof_full_type(), error.what())); } } + static constexpr auto Serialize(xmlpp::Element* node, const T& element) { auto created = node->add_child_element(Info::kName); if(!node) { @@ -155,8 +161,40 @@ struct ElementSerializer { } }; +template +struct ElementSerializer, Config, Info> { + static constexpr auto Parse(xmlpp::Element* element) -> std::optional { + return [&] { + auto node = element->get_first_child(Info::kName); + if(!node) { + return std::nullopt; + } + auto elementNode = dynamic_cast(node); + if(!elementNode) { + return std::nullopt; + } + if constexpr(Config.strict) { + return ElementSerializer::Parse(element); + } else { + return ElementSerializer::TryParse(element); + } + }() + .or_else([] { + return Config.defaultValue; + }); + } + static constexpr auto Serialize(xmlpp::Element* node, const std::optional& element) { + if(element) { + ElementSerializer::Serialize(node, *element); + } else if(Config.defaultValue) { + Serialize(node, Config.defaultValue); + } + } +}; + template struct ElementSerializer, Config, Info> { + // TODO(sha512sum): Add Config and main options instead use Serialization> static constexpr auto Parse(xmlpp::Element* element) { try { return Serialization>::Parse(element); @@ -174,6 +212,44 @@ struct ElementSerializer, Config, Info> { } } }; + +template +struct AttributeSerializer { + static constexpr auto Parse(xmlpp::Element* element) -> T { + auto node = element->get_attribute(Info::kName); + if(!node) { + throw AttributeParsingError(std::format("Attribute [{}: {}] parsing error", Info::kName, nameof::nameof_full_type())); + } + if constexpr(requires(std::string_view view) { T::Parse(view); }) { + return T::Parse(node->get_value()); + } else { + return node->get_value(); + } + } + static constexpr auto Serialize(xmlpp::Element* element, const T& obj) { + if constexpr(requires { + { ToString(obj) } -> std::convertible_to; + }) { + element->set_attribute(Info::kName, ToString(obj)); + } else { + element->set_attribute(Info::kName, obj); + } + } +}; + +template +struct AttributeSerializer, Info> { + static constexpr auto Parse(xmlpp::Element* element) -> std::optional { + auto node = element->get_attribute(Info::kName); + return node ? std::optional{AttributeSerializer::Parse(element)} : std::nullopt; + } + static constexpr auto Serialize(xmlpp::Element* element, const std::optional& obj) { + if(obj) { + AttributeSerializer::Serialize(element, *obj); + } + } +}; + namespace impl { template @@ -186,15 +262,7 @@ template auto ParseField(xmlpp::Element* main) -> std::decay_t::type { using Type = std::decay_t::type; if constexpr(std::holds_alternative(Config.Base())) { - xmlpp::Attribute* node = main->get_attribute(Info::kName); - if(!node) { - throw AttributeParsingError(std::format("Attribute [{}: {}] parsing error", Info::kName, nameof::nameof_full_type())); - } - if constexpr(requires(std::string_view view) { Type::Parse(view); }) { - return Type::Parse(node->get_value()); - } else { - return node->get_value(); - } + return AttributeSerializer::Parse(main); } else { return ElementSerializer::Parse(main); } @@ -203,19 +271,7 @@ auto ParseField(xmlpp::Element* main) -> std::decay_t::type { template auto SerializeField(xmlpp::Element* main, const T& obj) { if constexpr(std::holds_alternative(Config.Base())) { - auto node = main->set_attribute(Info::kName, [&] -> decltype(auto) { - if constexpr(requires { - { ToString(obj) } -> std::convertible_to; - }) { - return ToString(obj); - } else { - return obj; - } - }()); - if(!node) { - throw AttributeSerializationError( - std::format("[{}: {}] parsing error: [ node creation failed ]", Info::kName, nameof::nameof_full_type())); - } + AttributeSerializer::Serialize(main, obj); } else { ElementSerializer::Serialize(main, obj); } diff --git a/tests/serialization.cpp b/tests/serialization.cpp index 7221a49..abe41b3 100644 --- a/tests/serialization.cpp +++ b/tests/serialization.cpp @@ -72,6 +72,12 @@ struct SomeStruct6 { [[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct6; }; +struct SomeStruct7 { + static constexpr auto kDefaultName = "some7"; + std::optional value; + [[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct7; +}; + } // namespace tests::serialization namespace serialization { @@ -91,6 +97,10 @@ constexpr auto kSerializationConfig = Seriali template <> constexpr auto kSerializationConfig = SerializationConfig{}.With<"some">({Config>{}}); + +template <> +constexpr auto kSerializationConfig = SerializationConfig{}; + } // namespace serialization namespace tests::serialization { @@ -119,6 +129,10 @@ auto SomeStruct6::Parse(xmlpp::Element* element) -> SomeStruct6 { return ::larra::xmpp::serialization::Parse(element); } +auto SomeStruct7::Parse(xmlpp::Element* element) -> SomeStruct7 { + return ::larra::xmpp::serialization::Parse(element); +} + auto operator<<(xmlpp::Element* element, const SomeStruct& self) { ::larra::xmpp::serialization::Serialize(element, self); } @@ -171,6 +185,17 @@ TEST(AutoParse, Vector) { }) | std::ranges::to>()); } +TEST(AutoParse, Optional) { + xmlpp::Document doc; + auto node = doc.create_root_node("some7"); + auto value = Serialization::Parse(node).value; + EXPECT_EQ(value, std::nullopt); + node->set_attribute("value", "user@server.i2p/resource"); + auto value2 = Serialization::Parse(node).value; + FullJid expectData{.username = "user", .server = "server.i2p", .resource = "resource"}; + EXPECT_EQ(value2, expectData); +} + TEST(AutoSerialize, Basic) { xmlpp::Document doc; auto node = doc.create_root_node("some2");