diff --git a/library/include/larra/serialization.hpp b/library/include/larra/serialization.hpp index 31b127a..c4377c2 100644 --- a/library/include/larra/serialization.hpp +++ b/library/include/larra/serialization.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -96,7 +97,8 @@ template struct Serialization : SerializationBase { [[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> T { if(!Serialization::StartCheck(element)) { - throw std::runtime_error("StartCheck failed"); + throw serialization::SerializationError{ + std::format("[{}: {}] serialization error: [ StartCheck failed ]", Serialization::kDefaultName, nameof::nameof_full_type())}; } return T::Parse(element); } diff --git a/library/include/larra/serialization/auto.hpp b/library/include/larra/serialization/auto.hpp new file mode 100644 index 0000000..be4afec --- /dev/null +++ b/library/include/larra/serialization/auto.hpp @@ -0,0 +1,175 @@ +#pragma once +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace larra::xmpp::serialization { + +template +struct Tag {}; + +template +inline constexpr auto kSerializationConfig = std::monostate{}; + +template +inline constexpr auto kDeserializationConfig = kSerializationConfig; + +template +constexpr auto Parse(xmlpp::Element* element, Tag = {}) -> T + requires(!std::same_as)>, std::monostate>); + +template +constexpr auto Parse(xmlpp::Element* element, Tag = {}) -> T; + +struct AttributeConfig {}; + +template +struct MetaInfo { + static constexpr std::size_t kSize = boost::pfr::tuple_size_v; + template + using TupleElement = boost::pfr::tuple_element_t; + + template + static constexpr std::string_view kFieldName = boost::pfr::get_name(); + + template + static constexpr auto Get(Self&& self) -> decltype(auto) { + return boost::pfr::get(std::forward(self)); + } +}; + +template +struct FieldInfo { + using Main = MainT; + using Info = MetaInfo
; + static inline const std::string kName = [] { + if constexpr(requires { Info::template TupleElement::kDefaultName; }) { + return Info::template TupleElement::kDefaultName; + } else { + return static_cast(Info::template kFieldName); + } + }(); +}; + +template +struct Config { + std::optional defaultValue; +}; + +template <> +struct Config { + std::optional defaultValue; +}; + +namespace impl { + +template +struct Config : V { + using V::V; + constexpr auto Base() const -> const V& { + return static_cast(*this); + } + using type = T; +}; + +} // namespace impl + +template +struct ElementConfig { + using type = impl::Config>>; +}; + +template +struct ElementSerializer { + static constexpr auto Parse(xmlpp::Element* element) { + auto node = element->get_first_child(Info::kName); + if(!node) { + throw ElementSerializationError( + std::format("[{}: {}] serialization error: [ Not found ]", Info::kName, nameof::nameof_full_type())); + } + auto elementNode = dynamic_cast(node); + if(!node) { + throw ElementSerializationError( + std::format("[{}: {}] serialization error: [ Invalid node ]", Info::kName, nameof::nameof_full_type())); + } + try { + return ::larra::xmpp::serialization::Parse(elementNode, Tag{}); + } catch(const SerializationError& error) { + throw ElementSerializationError( + std::format("[{}: {}] serialization error: [ {} ]", Info::kName, nameof::nameof_full_type(), error.what())); + } + } +}; + +namespace impl { + +template +consteval auto FindElement(std::string_view field, utempl::TypeList = {}) { + auto fields = boost::pfr::names_as_array(); + return std::ranges::find(fields, field) - fields.begin(); +} + +template +auto ParseField(xmlpp::Element* main) { + using Type = std::decay_t::type; + if constexpr(std::holds_alternative(Config.Base())) { + xmlpp::Attribute* node = main->get_attribute(Info::kName); + if(!node) { + throw AttributeSerializationError(std::format("Attribute [{}] serialization error", Info::kName)); + } + return node->get_value(); + } else { + return ElementSerializer::Parse(main); + } +} + +} // namespace impl + +template +struct SerializationConfig { + decltype([] { + return [](auto... is) -> utempl::Tuple>::type...> { + std::unreachable(); + } | utempl::kSeq>; + }()) tuple{}; + template // NOLINTNEXTLINE + consteval auto With(this Self&& self, ElementConfig>::type config) -> SerializationConfig { + auto tuple = std::forward_like(self.tuple); + Get(tuple) = std::move(config); + return {std::move(tuple)}; + } + template + constexpr auto With(this Self&& self, ElementConfig(Name), T>>::type config) + -> SerializationConfig { + return std::forward(self).template With(Name)>(std::move(config)); + } +}; + +template +constexpr auto Parse(xmlpp::Element* element, Tag) -> T { + return Serialization::Parse(element); +} +template +constexpr auto Parse(xmlpp::Element* element, Tag) -> T + 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 { + return T{impl::ParseField<*((*configs).second), FieldInfo>(element)...}; + } catch(const SerializationError& error) { + throw ElementSerializationError(std::format("[{}] serialization error: [ {} ]", nameof::nameof_full_type(), error.what())); + } + }); +} + +} // namespace larra::xmpp::serialization diff --git a/library/include/larra/serialization/error.hpp b/library/include/larra/serialization/error.hpp new file mode 100644 index 0000000..a3594d5 --- /dev/null +++ b/library/include/larra/serialization/error.hpp @@ -0,0 +1,18 @@ +#pragma once +#include + +namespace larra::xmpp::serialization { + +struct SerializationError : std::runtime_error { + using std::runtime_error::runtime_error; +}; + +struct AttributeSerializationError : SerializationError { + using SerializationError::SerializationError; +}; + +struct ElementSerializationError : SerializationError { + using SerializationError::SerializationError; +}; + +} // namespace larra::xmpp::serialization diff --git a/tests/serialization.cpp b/tests/serialization.cpp index 89e23ac..a51b17f 100644 --- a/tests/serialization.cpp +++ b/tests/serialization.cpp @@ -1,8 +1,12 @@ #include #include +#include +#include #include +using namespace std::literals; + namespace larra::xmpp { TEST(Parse, Variant) { @@ -21,8 +25,83 @@ TEST(Serialize, Variant) { auto node = doc.create_root_node("stream:error"); S::Serialize(node, data); EXPECT_EQ(doc.write_to_string(), - std::string_view{"\n\n"}); + "\n\n"sv); +} + +namespace tests::serialization { + +struct SomeStruct { + static constexpr auto kDefaultName = "some"; + std::string value; + [[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct; +}; + +struct SomeStruct2 { + static constexpr auto kDefaultName = "some2"; + SomeStruct value; + [[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct2; +}; + +struct SomeStruct3 { + static constexpr auto kDefaultName = "some3"; + int value; + [[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct3; +}; + +struct SomeStruct4 { + static constexpr auto kDefaultName = "some4"; + SomeStruct3 value; + [[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct4; +}; + +} // namespace tests::serialization + +namespace serialization { + +template <> +constexpr auto kSerializationConfig = SerializationConfig{}; + +template <> +constexpr auto kSerializationConfig = + SerializationConfig{}.With<"value">(serialization::Config{}); + +template <> +constexpr auto kSerializationConfig = + SerializationConfig{}.With<"value">(serialization::Config{}); + +} // namespace serialization + +auto tests::serialization::SomeStruct::Parse(xmlpp::Element* element) -> SomeStruct { + return ::larra::xmpp::serialization::Parse(element); +} + +auto tests::serialization::SomeStruct2::Parse(xmlpp::Element* element) -> SomeStruct2 { + return ::larra::xmpp::serialization::Parse(element); +} + +auto tests::serialization::SomeStruct3::Parse(xmlpp::Element*) -> SomeStruct3 { + return {.value = 42}; // NOLINT +} + +auto tests::serialization::SomeStruct4::Parse(xmlpp::Element* element) -> SomeStruct4 { + return ::larra::xmpp::serialization::Parse(element); +} + +TEST(Parse, Auto) { + xmlpp::Document doc; + auto node = doc.create_root_node("some2"); + node = node->add_child_element("some"); + node->set_attribute("value", "Hello"); + auto a = Serialization::Parse(node); + EXPECT_EQ(a.value, "Hello"sv); + auto b = Serialization::Parse(doc.get_root_node()); + EXPECT_EQ(b.value.value, "Hello"sv); + EXPECT_THROW(std::ignore = tests::serialization::SomeStruct2::Parse(node), serialization::SerializationError); + auto node2 = node->add_child_element("some4"); + node2->add_child_element("some3"); + auto c = Serialization::Parse(node2); + EXPECT_EQ(c.value.value, 42); } } // namespace larra::xmpp