From bb336da4d9020bb26a6c3e762d74a0c23c9330dc Mon Sep 17 00:00:00 2001 From: sha512sum Date: Sun, 6 Oct 2024 18:56:10 +0000 Subject: [PATCH] Add Stream Errors --- CMakeLists.txt | 5 +- library/include/larra/raw_xml_stream.hpp | 66 ++--------- library/include/larra/serialization.hpp | 143 +++++++++++++++++++++++ library/include/larra/stream_error.hpp | 138 ++++++++++++++++++++++ tests/serialization.cpp | 28 +++++ 5 files changed, 320 insertions(+), 60 deletions(-) create mode 100644 library/include/larra/serialization.hpp create mode 100644 library/include/larra/stream_error.hpp create mode 100644 tests/serialization.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index de8f26c..d37d3c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,7 @@ CPMAddPackage( CPMAddPackage("gh:zeux/pugixml@1.14") CPMAddPackage("gh:fmtlib/fmt#10.2.1") +CPMAddPackage("gh:Neargye/nameof@0.10.4") CPMAddPackage( NAME spdlog @@ -162,12 +163,14 @@ target_include_directories(larra_xmpp PUBLIC if(TARGET Boost::pfr) target_link_libraries(larra_xmpp PUBLIC - Boost::asio Boost::serialization utempl::utempl pugixml::pugixml OpenSSL::SSL + Boost::asio Boost::serialization utempl::utempl pugixml::pugixml + OpenSSL::SSL nameof::nameof OpenSSL::Crypto spdlog xmlplusplus ${LIBXML2_LIBRARIES}) else() find_package(Boost 1.85.0 REQUIRED) target_link_libraries(larra_xmpp PUBLIC utempl::utempl ${Boost_LIBRARIES} pugixml::pugixml OpenSSL::SSL + nameof::nameof OpenSSL::Crypto spdlog xmlplusplus ${LIBXML2_LIBRARIES}) endif() diff --git a/library/include/larra/raw_xml_stream.hpp b/library/include/larra/raw_xml_stream.hpp index 42b9918..3ff16a5 100644 --- a/library/include/larra/raw_xml_stream.hpp +++ b/library/include/larra/raw_xml_stream.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -17,32 +18,6 @@ struct _xmlError; namespace larra::xmpp { -template -concept AsXml = requires(xmlpp::Element* element, const T& obj) { - element << obj; - { T::kDefaultName } -> std::convertible_to; -}; - -template -concept HasDefaultNamespace = requires { - { T::kDefaultNamespace } -> std::convertible_to; -}; - -template -concept HasDefaultPrefix = requires { - { T::kPrefix } -> std::convertible_to; -}; - -template -concept HasAddXmlDecl = requires { - { T::kAddXmlDecl } -> std::convertible_to; -}; - -template -concept HasRemoveEnd = requires { - { T::kRemoveEnd } -> std::convertible_to; -}; - struct XmlGroup : xmlpp::Element { using Element::Element; }; @@ -240,16 +215,16 @@ struct RawXmlStream : Stream { template auto Read(auto& stream) -> boost::asio::awaitable { auto doc = co_await this->Read(stream); - co_return T::Parse(doc->get_root_node()); + co_return Serialization::Parse(doc->get_root_node()); } template auto Read(auto& stream) -> boost::asio::awaitable requires requires(std::unique_ptr ptr) { - { T::Parse(std::move(ptr)) } -> std::same_as; + { Serialization::Parse(std::move(ptr)) } -> std::same_as; } { - co_return T::Parse(co_await this->Read(stream)); + co_return Serialization::Parse(co_await this->Read(stream)); } template @@ -286,36 +261,9 @@ struct RawXmlStream : Stream { template auto Send(const T& xso, auto& stream) const -> boost::asio::awaitable { xmlpp::Document doc; - const std::string empty; - const std::string namespaceStr = [&] -> std::string { - if constexpr(HasDefaultNamespace) { - return T::kDefaultNamespace; - } else { - return empty; - } - }(); - const std::string prefixStr = [&] -> decltype(auto) { - if constexpr(HasDefaultPrefix) { - return T::kPrefix; - } else { - return empty; - } - }(); - const bool bAddXmlDecl = [&] -> bool { - if constexpr(HasAddXmlDecl) { - return T::kAddXmlDecl; - } - return false; - }(); - const bool removeEnd = [&] -> bool { - if constexpr(HasRemoveEnd) { - return T::kRemoveEnd; - } - return false; - }(); - - doc.create_root_node(T::kDefaultName, namespaceStr, prefixStr) << xso; - co_await this->Send(doc, stream, bAddXmlDecl, removeEnd); + using S = Serialization>; + S::Serialize(doc.create_root_node(S::kDefaultName, S::kDefaultNamespace, S::kPrefix), xso); + co_await this->Send(doc, stream, S::kAddXmlDecl, S::kRemoveEnd); } auto Send(const AsXml auto& xso) -> boost::asio::awaitable { diff --git a/library/include/larra/serialization.hpp b/library/include/larra/serialization.hpp new file mode 100644 index 0000000..ae2fc4e --- /dev/null +++ b/library/include/larra/serialization.hpp @@ -0,0 +1,143 @@ +#pragma once +#include +#include + +#include +#include + +namespace larra::xmpp { + +template +concept AsXml = requires(xmlpp::Element* element, const T& obj) { + element << obj; + { T::kDefaultName } -> std::convertible_to; +}; + +template +concept HasDefaultName = requires { + { T::kDefaultName } -> std::convertible_to; +}; + +template +concept HasDefaultNamespace = requires { + { T::kDefaultNamespace } -> std::convertible_to; +}; + +template +concept HasDefaultPrefix = requires { + { T::kPrefix } -> std::convertible_to; +}; + +template +concept HasAddXmlDecl = requires { + { T::kAddXmlDecl } -> std::convertible_to; +}; + +template +concept HasRemoveEnd = requires { + { T::kRemoveEnd } -> std::convertible_to; +}; + +template +concept HasTryParse = requires(xmlpp::Element* element) { + { T::TryParse(element) } -> std::same_as>; +}; + +template +struct SerializationBase { + inline static const std::string kDefaultName = [] { + if constexpr(HasDefaultName) { + return T::kDefaultName; + } else { + return ""; + } + }(); + inline static const std::string kDefaultNamespace = [] { + if constexpr(HasDefaultNamespace) { + return T::kDefaultNamespace; + } else { + return ""; + } + }(); + inline static const std::string kPrefix = [] { + if constexpr(HasDefaultPrefix) { + return T::kPrefix; + } else { + return ""; + } + }(); + static constexpr bool kAddXmlDecl = [] { + if constexpr(HasAddXmlDecl) { + return T::kAddXmlDecl; + } else { + return false; + } + }(); + static constexpr bool kRemoveEnd = [] { + if constexpr(HasRemoveEnd) { + return T::kRemoveEnd; + } else { + return false; + } + }(); +}; + +template +struct Serialization : SerializationBase { + [[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> T { + return T::Parse(element); + } + [[nodiscard]] static constexpr auto TryParse(xmlpp::Element* element) -> std::optional { + if constexpr(HasTryParse) { + return T::TryParse(element); + } else { + try { + return T::Parse(element); + } catch(const std::exception& e) { + SPDLOG_WARN("Failed Parse but no TryParse found: {}", e.what()); + } catch(...) { + SPDLOG_WARN("Failed Parse but no TryParse found"); + } + return std::nullopt; + } + }; + static constexpr auto Serialize(xmlpp::Element* element, const T& object) -> void { + element << object; + } +}; + +template +struct Serialization> : SerializationBase { + [[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> std::optional { + return Serialization::TryParse(element); + } + [[nodiscard]] static constexpr auto TryParse(xmlpp::Element* element) -> std::optional> { + auto response = Serialization::TryParse(element); + return response ? *response : std::nullopt; + } + static constexpr auto Serialize(xmlpp::Element* element, const T& object) -> void { + element << object; + } +}; + +template +struct Serialization> : SerializationBase<> { + [[nodiscard]] static constexpr auto TryParse(xmlpp::Element* element) -> std::optional> { + return utempl::FirstOf(utempl::Tuple{[&] { + return Serialization::TryParse(element); + }...}, + std::optional>{}); + } + [[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> std::variant { + return Serialization::TryParse(element).value(); + } + static constexpr auto Serialize(xmlpp::Element* element, const std::variant& object) -> void { + std::visit( + [&](const auto& object) { + element << object; + }, + object); + } +}; + +} // namespace larra::xmpp diff --git a/library/include/larra/stream_error.hpp b/library/include/larra/stream_error.hpp new file mode 100644 index 0000000..e707608 --- /dev/null +++ b/library/include/larra/stream_error.hpp @@ -0,0 +1,138 @@ +#pragma once +#include + +#include +#include +#include +#include + +namespace larra::xmpp { + +namespace impl { +// std::isupper not declared as constexpr +constexpr auto IsUpper(char ch) -> bool { + return ch >= 'A' && ch <= 'Z'; +} + +constexpr auto ToLower(char ch) -> char { + return (ch >= 'A' && ch <= 'Z') ? static_cast(ch + ('a' - 'A')) : ch; +} + +template +constexpr auto ToKebabCaseName() -> std::string_view { + static constexpr auto rawStr = nameof::nameof_short_type(); + + constexpr auto str = [] { + return rawStr | std::views::transform([](auto ch) { + return impl::IsUpper(ch) ? std::array{'-', impl::ToLower(ch)} : std::array{ch, '\0'}; + }) | + std::views::join | std::views::filter([](char ch) { + return ch != '\0'; + }); + }; + constexpr auto size = std::ranges::fold_left(str(), + std::size_t{}, + [](auto accum, auto) { + return accum + 1; + }) - + 1; + static constexpr auto arr = [&] { + std::array response; // NOLINT + std::ranges::copy(str() | std::views::drop(1), response.begin()); + return response; + }(); + return {arr.data(), size}; +} + +} // namespace impl + +namespace error::stream { + +template +struct BaseError : std::exception { + static constexpr auto kDefaultName = "error"; + static constexpr auto kDefaultNamespace = "stream"; + static inline const auto kKebabCaseName = static_cast(impl::ToKebabCaseName()); + static inline const std::string kErrorContentNamespace = "urn:ietf:params:xml:ns:xmpp-streams"; + static constexpr auto kErrorMessage = [] -> std::string_view { + static constexpr auto str = [] { + return std::array{std::string_view{"Stream Error: "}, nameof::nameof_short_type(), std::string_view{"\0", 1}} | std::views::join; + }; + constexpr auto size = std::ranges::fold_left(str(), std::size_t{}, [](auto accum, auto) { + return accum + 1; + }); + static constexpr auto array = [] { + std::array response; // NOLINT + std::ranges::copy(str(), response.begin()); + return response; + }(); + return {array.data(), array.size() - 1}; + }(); + static constexpr auto TryParse(xmlpp::Element* element) -> std::optional { + return element ? element->get_first_child(kKebabCaseName) ? std::optional{T{}} : std::nullopt : std::nullopt; + } + static constexpr auto Parse(xmlpp::Element* element) -> T { + return TryParse(element).value(); + } + friend constexpr auto operator<<(xmlpp::Element* element, const T& obj) -> void { + auto node = element->add_child_element(kKebabCaseName); + node->set_namespace_declaration(kErrorContentNamespace); + } + [[nodiscard]] constexpr auto what() const noexcept -> const char* override { + return kErrorMessage.data(); + } +}; + +struct BadFormat : BaseError {}; +struct BadNamespacePrefix : BaseError {}; +struct Conflict : BaseError {}; +struct ConnectionTimeout : BaseError {}; +struct HostGone : BaseError {}; +struct HostUnknown : BaseError {}; +struct ImproperAdressing : BaseError {}; +struct InternalServerError : BaseError {}; +struct InvalidForm : BaseError {}; +struct InvalidNamespace : BaseError {}; +struct InvalidXml : BaseError {}; +struct NotAuthorized : BaseError {}; +struct NotWellFormed : BaseError {}; +struct PolicyViolation : BaseError {}; +struct RemoteConnectionFailed : BaseError {}; +struct Reset : BaseError {}; +struct ResourceConstraint : BaseError {}; +struct RestrictedXml : BaseError {}; +struct SeeOtherHost : BaseError {}; +struct SystemShutdown : BaseError {}; +struct UndefinedCondition : BaseError {}; +struct UnsupportedEncoding : BaseError {}; +struct UnsupportedFeature : BaseError {}; +struct UnsupportedStanzaType : BaseError {}; +struct UnsupportedVersion : BaseError {}; + +} // namespace error::stream + +using StreamError = std::variant; + +} // namespace larra::xmpp diff --git a/tests/serialization.cpp b/tests/serialization.cpp new file mode 100644 index 0000000..89e23ac --- /dev/null +++ b/tests/serialization.cpp @@ -0,0 +1,28 @@ +#include + +#include +#include + +namespace larra::xmpp { + +TEST(Parse, Variant) { + xmlpp::Document doc; + auto node = doc.create_root_node("stream:error"); + node->add_child_element("unsupported-stanza-type"); + node->set_namespace_declaration("urn:ietf:params:xml:ns:xmpp-streams"); + auto streamError = Serialization::Parse(node); + EXPECT_TRUE(std::get_if(&streamError)); +} + +TEST(Serialize, Variant) { + using S = Serialization; + StreamError data = error::stream::UnsupportedStanzaType{}; + xmlpp::Document doc; + auto node = doc.create_root_node("stream:error"); + S::Serialize(node, data); + EXPECT_EQ(doc.write_to_string(), + std::string_view{"\n\n"}); +} + +} // namespace larra::xmpp