diff --git a/library/include/larra/serialization/auto.hpp b/library/include/larra/serialization/auto.hpp index 8667957..ac74ae5 100644 --- a/library/include/larra/serialization/auto.hpp +++ b/library/include/larra/serialization/auto.hpp @@ -27,6 +27,13 @@ constexpr auto Parse(xmlpp::Element* element, Tag = {}) -> T template constexpr auto Parse(xmlpp::Element* element, Tag = {}) -> T; +template +constexpr auto Serialize(xmlpp::Element* node, const T& element) -> void + requires(!std::same_as)>, std::monostate>); + +template +constexpr auto Serialize(xmlpp::Element* node, const T& element) -> void; + struct AttributeConfig {}; template @@ -111,10 +118,23 @@ struct ElementSerializer { } try { return ::larra::xmpp::serialization::Parse(elementNode, Tag{}); - } catch(const ParsingError& error) { + } catch(const std::exception& error) { 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) { + throw ElementSerializaionError( + std::format("[{}: {}] serialization error: [ node creation failed ]", Info::kName, nameof::nameof_full_type())); + } + try { + ::larra::xmpp::serialization::Serialize(created, element); + } catch(const std::exception& err) { + throw ElementSerializaionError( + std::format("[{}: {}] serialization error: [ {} ]", Info::kName, nameof::nameof_full_type(), err.what())); + } + } }; namespace impl { @@ -143,6 +163,27 @@ 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())); + } + } else { + ElementSerializer::Serialize(main, obj); + } +} + } // namespace impl template @@ -173,7 +214,7 @@ template constexpr auto Parse(xmlpp::Element* element, Tag) -> T requires(!std::same_as)>, std::monostate>) { - static constexpr SerializationConfig config = kSerializationConfig; + static constexpr SerializationConfig config = kDeserializationConfig; constexpr auto tuple = utempl::Map(config.tuple, [](auto& ref) { return &ref; }); @@ -186,4 +227,27 @@ constexpr auto Parse(xmlpp::Element* element, Tag) -> T }); } +template +constexpr auto Serialize(xmlpp::Element* node, const T& element) -> void + requires(!std::same_as)>, std::monostate>) +{ + static constexpr SerializationConfig config = kSerializationConfig; + constexpr auto tuple = utempl::Map(config.tuple, [](auto& ref) { + return &ref; + }); + + return utempl::Unpack(utempl::PackConstexprWrapper(), [&](auto... configs) { + try { + (impl::SerializeField<*((*configs).second), FieldInfo>(node, boost::pfr::get<(*configs).first>(element)), ...); + } catch(const ParsingError& error) { + throw ElementParsingError(std::format("[{}] parsing error: [ {} ]", nameof::nameof_full_type(), error.what())); + } + }); +} + +template +constexpr auto Serialize(xmlpp::Element* node, const T& element) -> void { + Serialization::Serialize(node, element); +} + } // namespace larra::xmpp::serialization diff --git a/library/include/larra/serialization/error.hpp b/library/include/larra/serialization/error.hpp index 83ea97e..851ed08 100644 --- a/library/include/larra/serialization/error.hpp +++ b/library/include/larra/serialization/error.hpp @@ -15,4 +15,16 @@ struct ElementParsingError : ParsingError { using ParsingError::ParsingError; }; +struct SerializationError : std::runtime_error { + using std::runtime_error::runtime_error; +}; + +struct AttributeSerializationError : SerializationError { + using SerializationError::SerializationError; +}; + +struct ElementSerializaionError : SerializationError { + using SerializationError::SerializationError; +}; + } // namespace larra::xmpp::serialization diff --git a/tests/serialization.cpp b/tests/serialization.cpp index 0e95574..3e724a9 100644 --- a/tests/serialization.cpp +++ b/tests/serialization.cpp @@ -36,12 +36,14 @@ struct SomeStruct { static constexpr auto kDefaultName = "some"; std::string value; [[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct; + friend auto operator<<(xmlpp::Element* element, const SomeStruct& self); }; struct SomeStruct2 { static constexpr auto kDefaultName = "some2"; SomeStruct value; [[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct2; + friend auto operator<<(xmlpp::Element* element, const SomeStruct2& self); }; struct SomeStruct3 { @@ -60,6 +62,7 @@ struct SomeStruct5 { static constexpr auto kDefaultName = "some5"; BareJid value; [[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct5; + friend auto operator<<(xmlpp::Element* element, const SomeStruct5& self); }; } // namespace tests::serialization @@ -80,26 +83,42 @@ constexpr auto kSerializationConfig = Seriali } // namespace serialization -auto tests::serialization::SomeStruct::Parse(xmlpp::Element* element) -> SomeStruct { +namespace tests::serialization { + +auto SomeStruct::Parse(xmlpp::Element* element) -> SomeStruct { return ::larra::xmpp::serialization::Parse(element); } -auto tests::serialization::SomeStruct2::Parse(xmlpp::Element* element) -> SomeStruct2 { +auto SomeStruct2::Parse(xmlpp::Element* element) -> SomeStruct2 { return ::larra::xmpp::serialization::Parse(element); } -auto tests::serialization::SomeStruct3::Parse(xmlpp::Element*) -> SomeStruct3 { +auto SomeStruct3::Parse(xmlpp::Element*) -> SomeStruct3 { return {.value = 42}; // NOLINT } -auto tests::serialization::SomeStruct4::Parse(xmlpp::Element* element) -> SomeStruct4 { +auto SomeStruct4::Parse(xmlpp::Element* element) -> SomeStruct4 { return ::larra::xmpp::serialization::Parse(element); } -auto tests::serialization::SomeStruct5::Parse(xmlpp::Element* element) -> SomeStruct5 { +auto SomeStruct5::Parse(xmlpp::Element* element) -> SomeStruct5 { return ::larra::xmpp::serialization::Parse(element); } +auto operator<<(xmlpp::Element* element, const SomeStruct& self) { + ::larra::xmpp::serialization::Serialize(element, self); +} + +auto operator<<(xmlpp::Element* element, const SomeStruct2& self) { + ::larra::xmpp::serialization::Serialize(element, self); +} + +auto operator<<(xmlpp::Element* element, const SomeStruct5& self) { + ::larra::xmpp::serialization::Serialize(element, self); +} + +} // namespace tests::serialization + TEST(AutoParse, Basic) { xmlpp::Document doc; auto node = doc.create_root_node("some2"); @@ -125,4 +144,18 @@ TEST(AutoParse, Attribute) { EXPECT_EQ(a.value.username, "user"sv); } +TEST(AutoSerialize, Basic) { + xmlpp::Document doc; + auto node = doc.create_root_node("some2"); + node << tests::serialization::SomeStruct2{.value = {.value = "testData"}}; + EXPECT_EQ(doc.write_to_string(), "\n\n"); +} + +TEST(AutoSerialize, Attribute) { + xmlpp::Document doc; + auto node = doc.create_root_node("some5"); + node << tests::serialization::SomeStruct5{.value = {.username = "user", .server = "server.i2p"}}; + EXPECT_EQ(doc.write_to_string(), "\n\n"); +} + } // namespace larra::xmpp