Add Stream Errors

This commit is contained in:
sha512sum 2024-10-06 18:56:10 +00:00
parent 145bf7bd80
commit bb336da4d9
5 changed files with 320 additions and 60 deletions

View file

@ -55,6 +55,7 @@ CPMAddPackage(
CPMAddPackage("gh:zeux/pugixml@1.14") CPMAddPackage("gh:zeux/pugixml@1.14")
CPMAddPackage("gh:fmtlib/fmt#10.2.1") CPMAddPackage("gh:fmtlib/fmt#10.2.1")
CPMAddPackage("gh:Neargye/nameof@0.10.4")
CPMAddPackage( CPMAddPackage(
NAME spdlog NAME spdlog
@ -162,12 +163,14 @@ target_include_directories(larra_xmpp PUBLIC
if(TARGET Boost::pfr) if(TARGET Boost::pfr)
target_link_libraries(larra_xmpp PUBLIC 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}) OpenSSL::Crypto spdlog xmlplusplus ${LIBXML2_LIBRARIES})
else() else()
find_package(Boost 1.85.0 REQUIRED) find_package(Boost 1.85.0 REQUIRED)
target_link_libraries(larra_xmpp PUBLIC target_link_libraries(larra_xmpp PUBLIC
utempl::utempl ${Boost_LIBRARIES} pugixml::pugixml OpenSSL::SSL utempl::utempl ${Boost_LIBRARIES} pugixml::pugixml OpenSSL::SSL
nameof::nameof
OpenSSL::Crypto spdlog xmlplusplus ${LIBXML2_LIBRARIES}) OpenSSL::Crypto spdlog xmlplusplus ${LIBXML2_LIBRARIES})
endif() endif()

View file

@ -9,6 +9,7 @@
#include <boost/asio/use_awaitable.hpp> #include <boost/asio/use_awaitable.hpp>
#include <boost/asio/write.hpp> #include <boost/asio/write.hpp>
#include <boost/system/result.hpp> #include <boost/system/result.hpp>
#include <larra/serialization.hpp>
#include <larra/utils.hpp> #include <larra/utils.hpp>
#include <stack> #include <stack>
#include <utempl/utils.hpp> #include <utempl/utils.hpp>
@ -17,32 +18,6 @@ struct _xmlError;
namespace larra::xmpp { 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 { struct XmlGroup : xmlpp::Element {
using Element::Element; using Element::Element;
}; };
@ -240,16 +215,16 @@ struct RawXmlStream : Stream {
template <typename T> template <typename T>
auto Read(auto& stream) -> boost::asio::awaitable<T> { auto Read(auto& stream) -> boost::asio::awaitable<T> {
auto doc = co_await this->Read(stream); 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> template <typename T>
auto Read(auto& stream) -> boost::asio::awaitable<T> auto Read(auto& stream) -> boost::asio::awaitable<T>
requires requires(std::unique_ptr<xmlpp::Document> ptr) { 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> template <typename T>
@ -286,36 +261,9 @@ struct RawXmlStream : Stream {
template <AsXml T> template <AsXml T>
auto Send(const T& xso, auto& stream) const -> boost::asio::awaitable<void> { auto Send(const T& xso, auto& stream) const -> boost::asio::awaitable<void> {
xmlpp::Document doc; xmlpp::Document doc;
const std::string empty; using S = Serialization<std::decay_t<T>>;
const std::string namespaceStr = [&] -> std::string { S::Serialize(doc.create_root_node(S::kDefaultName, S::kDefaultNamespace, S::kPrefix), xso);
if constexpr(HasDefaultNamespace<T>) { co_await this->Send(doc, stream, S::kAddXmlDecl, S::kRemoveEnd);
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);
} }
auto Send(const AsXml auto& xso) -> boost::asio::awaitable<void> { auto Send(const AsXml auto& xso) -> boost::asio::awaitable<void> {

View 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

View 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
View 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