diff --git a/library/include/larra/client/client.hpp b/library/include/larra/client/client.hpp index 8629c05..9fbdd3f 100644 --- a/library/include/larra/client/client.hpp +++ b/library/include/larra/client/client.hpp @@ -84,14 +84,12 @@ struct Client { auto set_bind_response = co_await connection.template Read>(); std::visit(utempl::Overloaded( - [](auto error) { - throw "Error response on IQ: Set::Bind: ''"; // TODO(unknown): Add exact error parsing - }, [&](::iq::ResultBind r) { jid.resource = std::move(r.payload.jid->resource); SPDLOG_INFO("Allocated resource: {}", jid.resource); - }), - set_bind_response); + }, + IqErrThrowVisitor{"Error response on IQ: Set::Bind"}), + std::move(set_bind_response)); co_return; } @@ -101,14 +99,12 @@ struct Client { const auto get_roster_response = co_await connection.template Read>(); std::visit(utempl::Overloaded( - [](auto error) { - throw "Error response on IQ: Get::Roster: ''"; // TODO(unknown): Add exact error parsing - }, [&](::iq::ResultRoster r) { roster = std::move(r.payload); SPDLOG_INFO("New roster: {}", ToString(roster)); - }), - get_roster_response); + }, + IqErrThrowVisitor{"Error response on IQ: Get::Roster"}), + std::move(get_roster_response)); co_return; } diff --git a/library/include/larra/iq.hpp b/library/include/larra/iq.hpp index 0ed886b..13cd081 100644 --- a/library/include/larra/iq.hpp +++ b/library/include/larra/iq.hpp @@ -1,9 +1,12 @@ #pragma once +#include + #include #include -#include +#include #include #include +#include #include namespace larra::xmpp { @@ -115,7 +118,41 @@ using Error = BaseImplWithPayload; } // namespace iq +using IqError = iq::Error; + template -using Iq = std::variant, iq::Set, iq::Result, iq::Error>; +using Iq = std::variant, iq::Set, iq::Result, IqError>; + +struct IqErrThrowVisitor { + constexpr IqErrThrowVisitor(std::string_view baseErrorMsg) : baseErrorMsg(baseErrorMsg) { + } + + template + void operator()(const iq::Get&) { + static constexpr std::string_view getErrorMsg = ": 'Get' is an invalid type for IQ result. Expected 'Result' or 'Error'"; + + auto finalErrorMsg = std::string(baseErrorMsg).append(getErrorMsg); + SPDLOG_ERROR(finalErrorMsg); + throw std::runtime_error{finalErrorMsg}; + } + template + void operator()(const iq::Set&) { + static constexpr std::string_view getErrorMsg = ": 'Set' is an invalid type for IQ result. Expected 'Result' or 'Error'"; + + auto finalErrorMsg = std::string(baseErrorMsg).append(getErrorMsg); + SPDLOG_ERROR(finalErrorMsg); + throw std::runtime_error{finalErrorMsg}; + } + void operator()(IqError err) { + SPDLOG_ERROR(baseErrorMsg); + std::visit( + [](auto exception) { + throw exception; + }, + std::move(err.payload)); + } + + std::string_view baseErrorMsg; +}; } // namespace larra::xmpp diff --git a/library/include/larra/stanza_error.hpp b/library/include/larra/stanza_error.hpp new file mode 100644 index 0000000..9931ac1 --- /dev/null +++ b/library/include/larra/stanza_error.hpp @@ -0,0 +1,148 @@ +#pragma once +#include + +#include +#include +#include + +namespace larra::xmpp::stanza::error { + +struct StanzaBaseError : std::exception {}; + +// DO NOT MOVE TO ANOTHER NAMESPACE(where no heirs). VIA friend A FUNCTION IS ADDED THAT VIA ADL WILL BE SEARCHED FOR HEIRS +// C++20 modules very unstable in clangd :( +template +struct StanzaErrorImpl : StanzaBaseError { + static constexpr auto kDefaultName = "error"; + static constexpr auto kDefaultNamespace = "urn:ietf:params:xml:ns:xmpp-stanzas"; + static inline const auto kKebabCaseName = static_cast(utils::ToKebabCaseName()); + + static constexpr auto kErrorMessage = [] -> std::string_view { + static constexpr auto name = nameof::nameof_short_type(); + static constexpr auto str = [] { + return std::array{std::string_view{"Stanza IQ Error: "}, std::string_view{name}, std::string_view{"\0", 1}} | std::views::join; + }; + static constexpr auto array = str() | std::ranges::to>>(); + return {array.data(), array.size() - 1}; + }(); + + std::optional by{}; + std::string type; + + // TODO(unknown): Add "optional text children" support for stanza error. Check "XML Stanzas" -> "Syntax" for more details + static constexpr auto TryParse(xmlpp::Element* element) -> std::optional { + if(not element) { + return std::nullopt; + } + + auto by = element->get_attribute("by"); + auto type = element->get_attribute("type"); + if(not type) { + return std::nullopt; + } + + auto node = element->get_first_child(kKebabCaseName); + if(not node) { + return std::nullopt; + } + + T obj; + obj.type = type->get_value(); + if(by) { + obj.by = std::optional{by->get_value()}; + } + return obj; + } + static constexpr auto Parse(xmlpp::Element* element) -> T { + return TryParse(element).value(); + } + friend constexpr auto operator<<(xmlpp::Element* element, const T& obj) -> void { + element->set_attribute("type", obj.type); + if(obj.by) { + element->set_attribute("by", *obj.by); + } + + auto node = element->add_child_element(kKebabCaseName); + node->set_namespace_declaration(kDefaultNamespace); + } + constexpr auto operator==(const StanzaErrorImpl&) const -> bool { + return true; + }; + [[nodiscard]] constexpr auto what() const noexcept -> const char* override { + return kErrorMessage.data(); + } +}; + +// Helper class to prevent parsing response stream into an expected return type if its name is an 'error' +struct UnknownStanzaError : StanzaBaseError { + static constexpr auto kDefaultName = "stream:error"; + static constexpr std::string_view kErrorMessage = "Unknown XMPP stream error"; + + static constexpr auto TryParse(xmlpp::Element* element) { + return std::optional{UnknownStanzaError{}}; + } + static constexpr auto Parse(xmlpp::Element* element) { + return TryParse(element).value(); + } + friend constexpr auto operator<<(xmlpp::Element* element, const UnknownStanzaError& obj) -> void { + throw std::runtime_error{std::format("'{}' must never be written into the real stream!", kErrorMessage)}; + } + constexpr auto operator==(const UnknownStanzaError&) const -> bool { + return true; + }; + [[nodiscard]] constexpr auto what() const noexcept -> const char* override { + return kErrorMessage.data(); + } +}; + +struct BadRequest : StanzaErrorImpl {}; +struct Conflict : StanzaErrorImpl {}; +struct FeatureNotImplemented : StanzaErrorImpl {}; +struct Forbidden : StanzaErrorImpl {}; +struct Gone : StanzaErrorImpl {}; +struct InternalServerError : StanzaErrorImpl {}; +struct ItemNotFound : StanzaErrorImpl {}; +struct JidMalformed : StanzaErrorImpl {}; +struct NotAcceptable : StanzaErrorImpl {}; +struct NotAllowed : StanzaErrorImpl {}; +struct NotAuthorized : StanzaErrorImpl {}; +struct PolicyViolation : StanzaErrorImpl {}; +struct RecipientUnavailable : StanzaErrorImpl {}; +struct Redirect : StanzaErrorImpl {}; +struct RegistrationRequired : StanzaErrorImpl {}; +struct RemoteServerNotFound : StanzaErrorImpl {}; +struct RemoteServerTimeout : StanzaErrorImpl {}; +struct ResourceConstraint : StanzaErrorImpl {}; +struct ServiceUnavailable : StanzaErrorImpl {}; +struct SubscriptionRequired : StanzaErrorImpl {}; +struct UndefinedCondition : StanzaErrorImpl {}; +struct UnexpectedRequest : StanzaErrorImpl {}; + +using StanzaError = std::variant; + +static_assert(std::is_same_v - 1, StanzaError>, UnknownStanzaError>, + "'UnknownStanzaError' must be at the end of 'StanzaError' variant"); + +} // namespace larra::xmpp::stanza::error diff --git a/library/include/larra/stream_error.hpp b/library/include/larra/stream_error.hpp index 648b2a4..21d2058 100644 --- a/library/include/larra/stream_error.hpp +++ b/library/include/larra/stream_error.hpp @@ -52,7 +52,7 @@ struct UnknownXmppError : BaseError { return TryParse(element).value(); } friend constexpr auto operator<<(xmlpp::Element* element, const UnknownXmppError& obj) -> void { - throw std::format("'{}' must never be written into the real stream!", kErrorMessage); + throw std::runtime_error{std::format("'{}' must never be written into the real stream!", kErrorMessage)}; } [[nodiscard]] constexpr auto what() const noexcept -> const char* override { return kErrorMessage.data(); @@ -112,7 +112,8 @@ using StreamError = std::variant; -static_assert(!std::is_same_v - 1, StreamError>, StreamError>, - "'UnknownXmppError' must be at the end of 'StreamError' variant"); +static_assert( + std::is_same_v - 1, StreamError>, error::stream::UnknownXmppError>, + "'UnknownXmppError' must be at the end of 'StreamError' variant"); } // namespace larra::xmpp diff --git a/library/src/bind.cpp b/library/src/bind.cpp index b82f72b..ede05d0 100644 --- a/library/src/bind.cpp +++ b/library/src/bind.cpp @@ -2,7 +2,7 @@ namespace larra::xmpp::iq { auto operator<<(xmlpp::Element* element, const Bind& bind) -> void { - element->set_attribute("xmlns", Bind::kDefaultNamespace); + element->set_namespace_declaration(Bind::kDefaultNamespace); if(bind.jid) { auto* jid_el = element->add_child_element("jid"); diff --git a/library/src/roster.cpp b/library/src/roster.cpp index 7b01dcc..b06164b 100644 --- a/library/src/roster.cpp +++ b/library/src/roster.cpp @@ -21,7 +21,7 @@ auto RosterItem::Parse(xmlpp::Element* element) -> RosterItem { } auto operator<<(xmlpp::Element* element, const Roster& self) -> void { - element->set_attribute("xmlns", Roster::kDefaultNamespace); + element->set_namespace_declaration(Roster::kDefaultNamespace); S::Serialize(element, self); } auto Roster::Parse(xmlpp::Element* element) -> Roster {