Added std::variant and Visitor for all IQ Stanza errors
This commit is contained in:
parent
54e9556478
commit
b565a09c1a
6 changed files with 199 additions and 17 deletions
|
@ -84,14 +84,12 @@ struct Client {
|
||||||
|
|
||||||
auto set_bind_response = co_await connection.template Read<Iq<::iq::Bind>>();
|
auto set_bind_response = co_await connection.template Read<Iq<::iq::Bind>>();
|
||||||
std::visit(utempl::Overloaded(
|
std::visit(utempl::Overloaded(
|
||||||
[](auto error) {
|
|
||||||
throw "Error response on IQ: Set::Bind: ''"; // TODO(unknown): Add exact error parsing
|
|
||||||
},
|
|
||||||
[&](::iq::ResultBind r) {
|
[&](::iq::ResultBind r) {
|
||||||
jid.resource = std::move(r.payload.jid->resource);
|
jid.resource = std::move(r.payload.jid->resource);
|
||||||
SPDLOG_INFO("Allocated resource: {}", 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;
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,14 +99,12 @@ struct Client {
|
||||||
|
|
||||||
const auto get_roster_response = co_await connection.template Read<Iq<::iq::Roster>>();
|
const auto get_roster_response = co_await connection.template Read<Iq<::iq::Roster>>();
|
||||||
std::visit(utempl::Overloaded(
|
std::visit(utempl::Overloaded(
|
||||||
[](auto error) {
|
|
||||||
throw "Error response on IQ: Get::Roster: ''"; // TODO(unknown): Add exact error parsing
|
|
||||||
},
|
|
||||||
[&](::iq::ResultRoster r) {
|
[&](::iq::ResultRoster r) {
|
||||||
roster = std::move(r.payload);
|
roster = std::move(r.payload);
|
||||||
SPDLOG_INFO("New roster: {}", ToString(roster));
|
SPDLOG_INFO("New roster: {}", ToString(roster));
|
||||||
}),
|
},
|
||||||
get_roster_response);
|
IqErrThrowVisitor{"Error response on IQ: Get::Roster"}),
|
||||||
|
std::move(get_roster_response));
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
#include <larra/jid.hpp>
|
#include <larra/jid.hpp>
|
||||||
#include <larra/serialization.hpp>
|
#include <larra/serialization.hpp>
|
||||||
#include <larra/stream_error.hpp>
|
#include <larra/stanza_error.hpp>
|
||||||
#include <larra/utils.hpp>
|
#include <larra/utils.hpp>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace larra::xmpp {
|
namespace larra::xmpp {
|
||||||
|
@ -115,7 +118,41 @@ using Error = BaseImplWithPayload<kErrorName, Payload>;
|
||||||
|
|
||||||
} // namespace iq
|
} // namespace iq
|
||||||
|
|
||||||
|
using IqError = iq::Error<stanza::error::StanzaError>;
|
||||||
|
|
||||||
template <typename Payload>
|
template <typename Payload>
|
||||||
using Iq = std::variant<iq::Get<Payload>, iq::Set<Payload>, iq::Result<Payload>, iq::Error<Payload>>;
|
using Iq = std::variant<iq::Get<Payload>, iq::Set<Payload>, iq::Result<Payload>, IqError>;
|
||||||
|
|
||||||
|
struct IqErrThrowVisitor {
|
||||||
|
constexpr IqErrThrowVisitor(std::string_view baseErrorMsg) : baseErrorMsg(baseErrorMsg) {
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Payload>
|
||||||
|
void operator()(const iq::Get<Payload>&) {
|
||||||
|
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 <typename Payload>
|
||||||
|
void operator()(const iq::Set<Payload>&) {
|
||||||
|
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
|
} // namespace larra::xmpp
|
||||||
|
|
148
library/include/larra/stanza_error.hpp
Normal file
148
library/include/larra/stanza_error.hpp
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
#pragma once
|
||||||
|
#include <libxml++/libxml++.h>
|
||||||
|
|
||||||
|
#include <larra/utils.hpp>
|
||||||
|
#include <optional>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
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 <typename T>
|
||||||
|
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<std::string>(utils::ToKebabCaseName<T>());
|
||||||
|
|
||||||
|
static constexpr auto kErrorMessage = [] -> std::string_view {
|
||||||
|
static constexpr auto name = nameof::nameof_short_type<T>();
|
||||||
|
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<utils::RangeToWrapper<std::array<char, std::ranges::distance(str())>>>();
|
||||||
|
return {array.data(), array.size() - 1};
|
||||||
|
}();
|
||||||
|
|
||||||
|
std::optional<std::string> 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<T> {
|
||||||
|
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<T>&) 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<BadRequest> {};
|
||||||
|
struct Conflict : StanzaErrorImpl<Conflict> {};
|
||||||
|
struct FeatureNotImplemented : StanzaErrorImpl<FeatureNotImplemented> {};
|
||||||
|
struct Forbidden : StanzaErrorImpl<Forbidden> {};
|
||||||
|
struct Gone : StanzaErrorImpl<Gone> {};
|
||||||
|
struct InternalServerError : StanzaErrorImpl<InternalServerError> {};
|
||||||
|
struct ItemNotFound : StanzaErrorImpl<ItemNotFound> {};
|
||||||
|
struct JidMalformed : StanzaErrorImpl<JidMalformed> {};
|
||||||
|
struct NotAcceptable : StanzaErrorImpl<NotAcceptable> {};
|
||||||
|
struct NotAllowed : StanzaErrorImpl<NotAllowed> {};
|
||||||
|
struct NotAuthorized : StanzaErrorImpl<NotAuthorized> {};
|
||||||
|
struct PolicyViolation : StanzaErrorImpl<PolicyViolation> {};
|
||||||
|
struct RecipientUnavailable : StanzaErrorImpl<RecipientUnavailable> {};
|
||||||
|
struct Redirect : StanzaErrorImpl<Redirect> {};
|
||||||
|
struct RegistrationRequired : StanzaErrorImpl<RegistrationRequired> {};
|
||||||
|
struct RemoteServerNotFound : StanzaErrorImpl<RemoteServerNotFound> {};
|
||||||
|
struct RemoteServerTimeout : StanzaErrorImpl<RemoteServerTimeout> {};
|
||||||
|
struct ResourceConstraint : StanzaErrorImpl<ResourceConstraint> {};
|
||||||
|
struct ServiceUnavailable : StanzaErrorImpl<ServiceUnavailable> {};
|
||||||
|
struct SubscriptionRequired : StanzaErrorImpl<SubscriptionRequired> {};
|
||||||
|
struct UndefinedCondition : StanzaErrorImpl<UndefinedCondition> {};
|
||||||
|
struct UnexpectedRequest : StanzaErrorImpl<UnexpectedRequest> {};
|
||||||
|
|
||||||
|
using StanzaError = std::variant<BadRequest,
|
||||||
|
Conflict,
|
||||||
|
FeatureNotImplemented,
|
||||||
|
Forbidden,
|
||||||
|
Gone,
|
||||||
|
InternalServerError,
|
||||||
|
ItemNotFound,
|
||||||
|
JidMalformed,
|
||||||
|
NotAcceptable,
|
||||||
|
NotAllowed,
|
||||||
|
NotAuthorized,
|
||||||
|
PolicyViolation,
|
||||||
|
RecipientUnavailable,
|
||||||
|
Redirect,
|
||||||
|
RegistrationRequired,
|
||||||
|
RemoteServerNotFound,
|
||||||
|
RemoteServerTimeout,
|
||||||
|
ResourceConstraint,
|
||||||
|
ServiceUnavailable,
|
||||||
|
SubscriptionRequired,
|
||||||
|
UndefinedCondition,
|
||||||
|
UnexpectedRequest,
|
||||||
|
UnknownStanzaError>;
|
||||||
|
|
||||||
|
static_assert(std::is_same_v<typename std::variant_alternative_t<std::variant_size_v<StanzaError> - 1, StanzaError>, UnknownStanzaError>,
|
||||||
|
"'UnknownStanzaError' must be at the end of 'StanzaError' variant");
|
||||||
|
|
||||||
|
} // namespace larra::xmpp::stanza::error
|
|
@ -52,7 +52,7 @@ struct UnknownXmppError : BaseError {
|
||||||
return TryParse(element).value();
|
return TryParse(element).value();
|
||||||
}
|
}
|
||||||
friend constexpr auto operator<<(xmlpp::Element* element, const UnknownXmppError& obj) -> void {
|
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 {
|
[[nodiscard]] constexpr auto what() const noexcept -> const char* override {
|
||||||
return kErrorMessage.data();
|
return kErrorMessage.data();
|
||||||
|
@ -112,7 +112,8 @@ using StreamError = std::variant<error::stream::BadFormat,
|
||||||
error::stream::UnsupportedVersion,
|
error::stream::UnsupportedVersion,
|
||||||
error::stream::UnknownXmppError>;
|
error::stream::UnknownXmppError>;
|
||||||
|
|
||||||
static_assert(!std::is_same_v<typename std::variant_alternative_t<std::variant_size_v<StreamError> - 1, StreamError>, StreamError>,
|
static_assert(
|
||||||
"'UnknownXmppError' must be at the end of 'StreamError' variant");
|
std::is_same_v<typename std::variant_alternative_t<std::variant_size_v<StreamError> - 1, StreamError>, error::stream::UnknownXmppError>,
|
||||||
|
"'UnknownXmppError' must be at the end of 'StreamError' variant");
|
||||||
|
|
||||||
} // namespace larra::xmpp
|
} // namespace larra::xmpp
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace larra::xmpp::iq {
|
namespace larra::xmpp::iq {
|
||||||
auto operator<<(xmlpp::Element* element, const Bind& bind) -> void {
|
auto operator<<(xmlpp::Element* element, const Bind& bind) -> void {
|
||||||
element->set_attribute("xmlns", Bind::kDefaultNamespace);
|
element->set_namespace_declaration(Bind::kDefaultNamespace);
|
||||||
|
|
||||||
if(bind.jid) {
|
if(bind.jid) {
|
||||||
auto* jid_el = element->add_child_element("jid");
|
auto* jid_el = element->add_child_element("jid");
|
||||||
|
|
|
@ -21,7 +21,7 @@ auto RosterItem::Parse(xmlpp::Element* element) -> RosterItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto operator<<(xmlpp::Element* element, const Roster& self) -> void {
|
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);
|
S::Serialize(element, self);
|
||||||
}
|
}
|
||||||
auto Roster::Parse(xmlpp::Element* element) -> Roster {
|
auto Roster::Parse(xmlpp::Element* element) -> Roster {
|
||||||
|
|
Loading…
Reference in a new issue