Add Stream Errors
This commit is contained in:
parent
145bf7bd80
commit
bb336da4d9
5 changed files with 320 additions and 60 deletions
|
@ -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()
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/system/result.hpp>
|
||||
#include <larra/serialization.hpp>
|
||||
#include <larra/utils.hpp>
|
||||
#include <stack>
|
||||
#include <utempl/utils.hpp>
|
||||
|
@ -17,32 +18,6 @@ struct _xmlError;
|
|||
|
||||
namespace larra::xmpp {
|
||||
|
||||
template <typename T>
|
||||
concept AsXml = requires(xmlpp::Element* element, const T& obj) {
|
||||
element << obj;
|
||||
{ T::kDefaultName } -> std::convertible_to<const std::string&>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept HasDefaultNamespace = requires {
|
||||
{ T::kDefaultNamespace } -> std::convertible_to<const std::string&>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept HasDefaultPrefix = requires {
|
||||
{ T::kPrefix } -> std::convertible_to<const std::string&>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept HasAddXmlDecl = requires {
|
||||
{ T::kAddXmlDecl } -> std::convertible_to<bool>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept HasRemoveEnd = requires {
|
||||
{ T::kRemoveEnd } -> std::convertible_to<bool>;
|
||||
};
|
||||
|
||||
struct XmlGroup : xmlpp::Element {
|
||||
using Element::Element;
|
||||
};
|
||||
|
@ -240,16 +215,16 @@ struct RawXmlStream : Stream {
|
|||
template <typename T>
|
||||
auto Read(auto& stream) -> boost::asio::awaitable<T> {
|
||||
auto doc = co_await this->Read(stream);
|
||||
co_return T::Parse(doc->get_root_node());
|
||||
co_return Serialization<T>::Parse(doc->get_root_node());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto Read(auto& stream) -> boost::asio::awaitable<T>
|
||||
requires requires(std::unique_ptr<xmlpp::Document> ptr) {
|
||||
{ T::Parse(std::move(ptr)) } -> std::same_as<T>;
|
||||
{ Serialization<T>::Parse(std::move(ptr)) } -> std::same_as<T>;
|
||||
}
|
||||
{
|
||||
co_return T::Parse(co_await this->Read(stream));
|
||||
co_return Serialization<T>::Parse(co_await this->Read(stream));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
@ -286,36 +261,9 @@ struct RawXmlStream : Stream {
|
|||
template <AsXml T>
|
||||
auto Send(const T& xso, auto& stream) const -> boost::asio::awaitable<void> {
|
||||
xmlpp::Document doc;
|
||||
const std::string empty;
|
||||
const std::string namespaceStr = [&] -> std::string {
|
||||
if constexpr(HasDefaultNamespace<T>) {
|
||||
return T::kDefaultNamespace;
|
||||
} else {
|
||||
return empty;
|
||||
}
|
||||
}();
|
||||
const std::string prefixStr = [&] -> decltype(auto) {
|
||||
if constexpr(HasDefaultPrefix<T>) {
|
||||
return T::kPrefix;
|
||||
} else {
|
||||
return empty;
|
||||
}
|
||||
}();
|
||||
const bool bAddXmlDecl = [&] -> bool {
|
||||
if constexpr(HasAddXmlDecl<T>) {
|
||||
return T::kAddXmlDecl;
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
const bool removeEnd = [&] -> bool {
|
||||
if constexpr(HasRemoveEnd<T>) {
|
||||
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<std::decay_t<T>>;
|
||||
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<void> {
|
||||
|
|
143
library/include/larra/serialization.hpp
Normal file
143
library/include/larra/serialization.hpp
Normal file
|
@ -0,0 +1,143 @@
|
|||
#pragma once
|
||||
#include <libxml++/libxml++.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <string>
|
||||
#include <utempl/utils.hpp>
|
||||
|
||||
namespace larra::xmpp {
|
||||
|
||||
template <typename T>
|
||||
concept AsXml = requires(xmlpp::Element* element, const T& obj) {
|
||||
element << obj;
|
||||
{ T::kDefaultName } -> std::convertible_to<const std::string&>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept HasDefaultName = requires {
|
||||
{ T::kDefaultName } -> std::convertible_to<const std::string&>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept HasDefaultNamespace = requires {
|
||||
{ T::kDefaultNamespace } -> std::convertible_to<const std::string&>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept HasDefaultPrefix = requires {
|
||||
{ T::kPrefix } -> std::convertible_to<const std::string&>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept HasAddXmlDecl = requires {
|
||||
{ T::kAddXmlDecl } -> std::convertible_to<bool>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept HasRemoveEnd = requires {
|
||||
{ T::kRemoveEnd } -> std::convertible_to<bool>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept HasTryParse = requires(xmlpp::Element* element) {
|
||||
{ T::TryParse(element) } -> std::same_as<std::optional<T>>;
|
||||
};
|
||||
|
||||
template <typename T = void>
|
||||
struct SerializationBase {
|
||||
inline static const std::string kDefaultName = [] {
|
||||
if constexpr(HasDefaultName<T>) {
|
||||
return T::kDefaultName;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}();
|
||||
inline static const std::string kDefaultNamespace = [] {
|
||||
if constexpr(HasDefaultNamespace<T>) {
|
||||
return T::kDefaultNamespace;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}();
|
||||
inline static const std::string kPrefix = [] {
|
||||
if constexpr(HasDefaultPrefix<T>) {
|
||||
return T::kPrefix;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}();
|
||||
static constexpr bool kAddXmlDecl = [] {
|
||||
if constexpr(HasAddXmlDecl<T>) {
|
||||
return T::kAddXmlDecl;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
static constexpr bool kRemoveEnd = [] {
|
||||
if constexpr(HasRemoveEnd<T>) {
|
||||
return T::kRemoveEnd;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct Serialization : SerializationBase<T> {
|
||||
[[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> T {
|
||||
return T::Parse(element);
|
||||
}
|
||||
[[nodiscard]] static constexpr auto TryParse(xmlpp::Element* element) -> std::optional<T> {
|
||||
if constexpr(HasTryParse<T>) {
|
||||
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 <typename T>
|
||||
struct Serialization<std::optional<T>> : SerializationBase<T> {
|
||||
[[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> std::optional<T> {
|
||||
return Serialization<T>::TryParse(element);
|
||||
}
|
||||
[[nodiscard]] static constexpr auto TryParse(xmlpp::Element* element) -> std::optional<std::optional<T>> {
|
||||
auto response = Serialization<T>::TryParse(element);
|
||||
return response ? *response : std::nullopt;
|
||||
}
|
||||
static constexpr auto Serialize(xmlpp::Element* element, const T& object) -> void {
|
||||
element << object;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
struct Serialization<std::variant<Ts...>> : SerializationBase<> {
|
||||
[[nodiscard]] static constexpr auto TryParse(xmlpp::Element* element) -> std::optional<std::variant<Ts...>> {
|
||||
return utempl::FirstOf(utempl::Tuple{[&] {
|
||||
return Serialization<Ts>::TryParse(element);
|
||||
}...},
|
||||
std::optional<std::variant<Ts...>>{});
|
||||
}
|
||||
[[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> std::variant<Ts...> {
|
||||
return Serialization::TryParse(element).value();
|
||||
}
|
||||
static constexpr auto Serialize(xmlpp::Element* element, const std::variant<Ts...>& object) -> void {
|
||||
std::visit(
|
||||
[&](const auto& object) {
|
||||
element << object;
|
||||
},
|
||||
object);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace larra::xmpp
|
138
library/include/larra/stream_error.hpp
Normal file
138
library/include/larra/stream_error.hpp
Normal file
|
@ -0,0 +1,138 @@
|
|||
#pragma once
|
||||
#include <libxml++/libxml++.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <nameof.hpp>
|
||||
#include <ranges>
|
||||
#include <variant>
|
||||
|
||||
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<char>(ch + ('a' - 'A')) : ch;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr auto ToKebabCaseName() -> std::string_view {
|
||||
static constexpr auto rawStr = nameof::nameof_short_type<T>();
|
||||
|
||||
constexpr auto str = [] {
|
||||
return rawStr | std::views::transform([](auto ch) {
|
||||
return impl::IsUpper(ch) ? std::array<char, 2>{'-', impl::ToLower(ch)} : std::array<char, 2>{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<char, size> response; // NOLINT
|
||||
std::ranges::copy(str() | std::views::drop(1), response.begin());
|
||||
return response;
|
||||
}();
|
||||
return {arr.data(), size};
|
||||
}
|
||||
|
||||
} // namespace impl
|
||||
|
||||
namespace error::stream {
|
||||
|
||||
template <typename T>
|
||||
struct BaseError : std::exception {
|
||||
static constexpr auto kDefaultName = "error";
|
||||
static constexpr auto kDefaultNamespace = "stream";
|
||||
static inline const auto kKebabCaseName = static_cast<std::string>(impl::ToKebabCaseName<T>());
|
||||
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<T>(), 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<char, size> 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<T> {
|
||||
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<BadFormat> {};
|
||||
struct BadNamespacePrefix : BaseError<BadNamespacePrefix> {};
|
||||
struct Conflict : BaseError<Conflict> {};
|
||||
struct ConnectionTimeout : BaseError<ConnectionTimeout> {};
|
||||
struct HostGone : BaseError<HostGone> {};
|
||||
struct HostUnknown : BaseError<HostUnknown> {};
|
||||
struct ImproperAdressing : BaseError<ImproperAdressing> {};
|
||||
struct InternalServerError : BaseError<InternalServerError> {};
|
||||
struct InvalidForm : BaseError<InvalidForm> {};
|
||||
struct InvalidNamespace : BaseError<InvalidNamespace> {};
|
||||
struct InvalidXml : BaseError<InvalidXml> {};
|
||||
struct NotAuthorized : BaseError<NotAuthorized> {};
|
||||
struct NotWellFormed : BaseError<NotWellFormed> {};
|
||||
struct PolicyViolation : BaseError<PolicyViolation> {};
|
||||
struct RemoteConnectionFailed : BaseError<RemoteConnectionFailed> {};
|
||||
struct Reset : BaseError<Reset> {};
|
||||
struct ResourceConstraint : BaseError<ResourceConstraint> {};
|
||||
struct RestrictedXml : BaseError<RestrictedXml> {};
|
||||
struct SeeOtherHost : BaseError<SeeOtherHost> {};
|
||||
struct SystemShutdown : BaseError<SystemShutdown> {};
|
||||
struct UndefinedCondition : BaseError<UndefinedCondition> {};
|
||||
struct UnsupportedEncoding : BaseError<UnsupportedEncoding> {};
|
||||
struct UnsupportedFeature : BaseError<UnsupportedFeature> {};
|
||||
struct UnsupportedStanzaType : BaseError<UnsupportedStanzaType> {};
|
||||
struct UnsupportedVersion : BaseError<UnsupportedVersion> {};
|
||||
|
||||
} // namespace error::stream
|
||||
|
||||
using StreamError = std::variant<error::stream::BadFormat,
|
||||
error::stream::BadNamespacePrefix,
|
||||
error::stream::Conflict,
|
||||
error::stream::ConnectionTimeout,
|
||||
error::stream::HostGone,
|
||||
error::stream::HostUnknown,
|
||||
error::stream::ImproperAdressing,
|
||||
error::stream::InternalServerError,
|
||||
error::stream::InvalidForm,
|
||||
error::stream::InvalidNamespace,
|
||||
error::stream::InvalidXml,
|
||||
error::stream::NotAuthorized,
|
||||
error::stream::NotWellFormed,
|
||||
error::stream::PolicyViolation,
|
||||
error::stream::RemoteConnectionFailed,
|
||||
error::stream::RestrictedXml,
|
||||
error::stream::SeeOtherHost,
|
||||
error::stream::SystemShutdown,
|
||||
error::stream::UndefinedCondition,
|
||||
error::stream::UnsupportedEncoding,
|
||||
error::stream::UnsupportedFeature,
|
||||
error::stream::UnsupportedStanzaType,
|
||||
error::stream::UnsupportedVersion>;
|
||||
|
||||
} // namespace larra::xmpp
|
28
tests/serialization.cpp
Normal file
28
tests/serialization.cpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <larra/serialization.hpp>
|
||||
#include <larra/stream_error.hpp>
|
||||
|
||||
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<StreamError>::Parse(node);
|
||||
EXPECT_TRUE(std::get_if<error::stream::UnsupportedStanzaType>(&streamError));
|
||||
}
|
||||
|
||||
TEST(Serialize, Variant) {
|
||||
using S = Serialization<StreamError>;
|
||||
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{"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<stream:error><unsupported-stanza-type "
|
||||
"xmlns=\"urn:ietf:params:xml:ns:xmpp-streams\"/></stream:error>\n"});
|
||||
}
|
||||
|
||||
} // namespace larra::xmpp
|
Loading…
Reference in a new issue