Add message serialization and deserialization
All checks were successful
PR Check / on-push-commit-check (push) Successful in 22m27s
All checks were successful
PR Check / on-push-commit-check (push) Successful in 22m27s
This commit is contained in:
parent
48f99c0727
commit
cda854553c
12 changed files with 590 additions and 67 deletions
|
@ -30,7 +30,7 @@ RUN groupadd docker &&\
|
||||||
USER dev
|
USER dev
|
||||||
|
|
||||||
# Add cached layer with the latest LLVM-${LLVM_VER}
|
# Add cached layer with the latest LLVM-${LLVM_VER}
|
||||||
ARG LLVM_VER=19.1.1
|
ARG LLVM_VER=19.1.5
|
||||||
ENV LLVM_VER=${LLVM_VER}
|
ENV LLVM_VER=${LLVM_VER}
|
||||||
RUN sudo mkdir -p /home/artifacts
|
RUN sudo mkdir -p /home/artifacts
|
||||||
ADD https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VER}/LLVM-${LLVM_VER}-Linux-X64.tar.xz /home/artifacts/
|
ADD https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VER}/LLVM-${LLVM_VER}-Linux-X64.tar.xz /home/artifacts/
|
||||||
|
|
|
@ -13,9 +13,9 @@ jobs:
|
||||||
pacman -S --noconfirm cmake make gcc ninja meson
|
pacman -S --noconfirm cmake make gcc ninja meson
|
||||||
pacman -S --noconfirm gtk4 gtkmm-4.0 boost spdlog fmt libxml++-5.0 gtest
|
pacman -S --noconfirm gtk4 gtkmm-4.0 boost spdlog fmt libxml++-5.0 gtest
|
||||||
|
|
||||||
- name: Setup environment - Install LLVM-19.1.3
|
- name: Setup environment - Install LLVM-19.1.5
|
||||||
run: |
|
run: |
|
||||||
export LLVM_VER=19.1.3
|
export LLVM_VER=19.1.5
|
||||||
|
|
||||||
echo "::group::Download LLVM-${LLVM_VER}"
|
echo "::group::Download LLVM-${LLVM_VER}"
|
||||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VER}/LLVM-${LLVM_VER}-Linux-X64.tar.xz -O /LLVM-${LLVM_VER}-Linux-X64.tar.xz
|
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VER}/LLVM-${LLVM_VER}-Linux-X64.tar.xz -O /LLVM-${LLVM_VER}-Linux-X64.tar.xz
|
||||||
|
@ -108,7 +108,7 @@ jobs:
|
||||||
id: clang_format_check
|
id: clang_format_check
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
export LLVM_VER=19.1.3
|
export LLVM_VER=19.1.5
|
||||||
export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
|
export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
|
||||||
cd ${{ github.workspace }}
|
cd ${{ github.workspace }}
|
||||||
export REPO_FILES=$(cat repo_files_to_check.txt)
|
export REPO_FILES=$(cat repo_files_to_check.txt)
|
||||||
|
@ -150,7 +150,7 @@ jobs:
|
||||||
id: clang_build
|
id: clang_build
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
export LLVM_VER=19.1.3
|
export LLVM_VER=19.1.5
|
||||||
export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
|
export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
|
||||||
mkdir -p ${{ github.workspace }}/build_clang
|
mkdir -p ${{ github.workspace }}/build_clang
|
||||||
|
|
||||||
|
@ -176,7 +176,7 @@ jobs:
|
||||||
id: clang_tidy
|
id: clang_tidy
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
export LLVM_VER=19.1.3
|
export LLVM_VER=19.1.5
|
||||||
export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
|
export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
|
||||||
cd ${{ github.workspace }}
|
cd ${{ github.workspace }}
|
||||||
|
|
||||||
|
@ -201,7 +201,7 @@ jobs:
|
||||||
|
|
||||||
#- name: Clang unit tests with -fsanitize=address
|
#- name: Clang unit tests with -fsanitize=address
|
||||||
# run: |
|
# run: |
|
||||||
# export LLVM_VER=19.1.3
|
# export LLVM_VER=19.1.5
|
||||||
# export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
|
# export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
|
||||||
# cd ${{ github.workspace }}/build_clang
|
# cd ${{ github.workspace }}/build_clang
|
||||||
# ASAN_SYMBOLIZER_PATH=llvm-symbolizer ASAN_OPTIONS=detect_stack_use_after_return=1:check_initialization_order=1:detect_leaks=1:atexit=1:abort_on_error=1 ./larra_xmpp_tests --log_level=0
|
# ASAN_SYMBOLIZER_PATH=llvm-symbolizer ASAN_OPTIONS=detect_stack_use_after_return=1:check_initialization_order=1:detect_leaks=1:atexit=1:abort_on_error=1 ./larra_xmpp_tests --log_level=0
|
||||||
|
|
|
@ -107,6 +107,9 @@ struct Client {
|
||||||
std::move(get_roster_response));
|
std::move(get_roster_response));
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] auto Roster() const -> const std::vector<iq::RosterItem>& {
|
||||||
|
return this->roster.items;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool active = true;
|
bool active = true;
|
||||||
|
|
163
library/include/larra/message.hpp
Normal file
163
library/include/larra/message.hpp
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
#pragma once
|
||||||
|
#include <larra/serialization/auto.hpp>
|
||||||
|
#include <larra/stanza_error.hpp>
|
||||||
|
#include <larra/xml_language.hpp>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace larra::xmpp {
|
||||||
|
|
||||||
|
namespace message {
|
||||||
|
struct Body {
|
||||||
|
std::string content;
|
||||||
|
std::optional<XmlLanguage> language;
|
||||||
|
static constexpr auto kDefaultName = "body";
|
||||||
|
constexpr auto operator==(const Body& other) const -> bool = default;
|
||||||
|
friend constexpr auto operator<<(xmlpp::Element* node, const Body& message) -> void {
|
||||||
|
node->add_child_text(message.content);
|
||||||
|
}
|
||||||
|
static constexpr auto Parse(xmlpp::Element* node) -> Body {
|
||||||
|
auto ptr = node->get_first_child_text();
|
||||||
|
if(!ptr) {
|
||||||
|
throw std::runtime_error("Message::Body: [ Text node not found ]");
|
||||||
|
}
|
||||||
|
auto lang = node->get_attribute("lang", "xml");
|
||||||
|
return {
|
||||||
|
.content = ptr->get_content(), //
|
||||||
|
.language = lang ? std::optional{XmlLanguage::Parse(lang->get_value())} : std::nullopt //
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace type {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct TypeImpl {
|
||||||
|
static constexpr auto TryParse(std::string_view value) -> std::optional<T> {
|
||||||
|
return value == T::kName ? std::optional{T{}} : std::nullopt;
|
||||||
|
}
|
||||||
|
friend constexpr auto ToString(const T&) -> std::string {
|
||||||
|
return T::kName;
|
||||||
|
}
|
||||||
|
friend constexpr auto operator==(const T&, const T&) -> bool {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Chat : TypeImpl<Chat> {
|
||||||
|
static constexpr auto kName = "chat";
|
||||||
|
} constexpr kChat{};
|
||||||
|
|
||||||
|
struct Error : TypeImpl<Error> {
|
||||||
|
static constexpr auto kName = "error";
|
||||||
|
} constexpr kError{};
|
||||||
|
|
||||||
|
struct GroupChat : TypeImpl<GroupChat> {
|
||||||
|
static constexpr auto kName = "groupchat";
|
||||||
|
} constexpr kGroupChat{};
|
||||||
|
|
||||||
|
struct Headline : TypeImpl<Headline> {
|
||||||
|
static constexpr auto kName = "headline";
|
||||||
|
} constexpr kHeadline{};
|
||||||
|
|
||||||
|
struct Normal : TypeImpl<Normal> {
|
||||||
|
static constexpr auto kName = "normal";
|
||||||
|
static constexpr auto Parse(std::string_view value) -> Normal {
|
||||||
|
return value == kName ? Normal{}
|
||||||
|
: throw std::runtime_error(
|
||||||
|
std::format(R"(message::type::Normal Parsing error: [ expected "normal" but "{}" found ])", value));
|
||||||
|
}
|
||||||
|
} constexpr kNormal;
|
||||||
|
|
||||||
|
} // namespace type
|
||||||
|
|
||||||
|
using TypeVariant = std::variant<type::Chat, type::GroupChat, type::Headline, type::Error, type::Normal>;
|
||||||
|
|
||||||
|
struct Type : TypeVariant {
|
||||||
|
using TypeVariant::variant;
|
||||||
|
constexpr Type(TypeVariant variant) : TypeVariant(std::move(variant)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr auto GetDefault() -> Type {
|
||||||
|
return type::kNormal;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend constexpr auto ToString(const Type& value) -> std::string {
|
||||||
|
return std::visit(
|
||||||
|
[](const auto& value) {
|
||||||
|
return ToString(value);
|
||||||
|
},
|
||||||
|
value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename From, typename To>
|
||||||
|
struct Message {
|
||||||
|
static constexpr auto kDefaultName = "message";
|
||||||
|
static auto Parse(xmlpp::Element* element) -> Message {
|
||||||
|
return serialization::Parse<Message>(element);
|
||||||
|
}
|
||||||
|
friend auto operator<<(xmlpp::Element* element, const Message& message) -> void {
|
||||||
|
serialization::Serialize(element, message);
|
||||||
|
}
|
||||||
|
constexpr auto operator==(const Message& other) const -> bool = default;
|
||||||
|
From from;
|
||||||
|
To to;
|
||||||
|
message::Type type;
|
||||||
|
std::optional<std::string> id;
|
||||||
|
XmlLanguage language;
|
||||||
|
std::vector<message::Body> body;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename From, typename To>
|
||||||
|
struct Error {
|
||||||
|
static constexpr auto kDefaultName = "message";
|
||||||
|
static auto Parse(xmlpp::Element* element) -> Error {
|
||||||
|
auto attr = element->get_attribute("type");
|
||||||
|
if(!attr || attr->get_value() != "error") {
|
||||||
|
throw std::runtime_error{"attribute type: message::Type invalid"};
|
||||||
|
}
|
||||||
|
return serialization::Parse<Error>(element);
|
||||||
|
}
|
||||||
|
static auto TryParse(xmlpp::Element* element) -> std::optional<Error> {
|
||||||
|
auto attr = element->get_attribute("type");
|
||||||
|
if(!attr || attr->get_value() != "error") {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return serialization::Parse<Error>(element);
|
||||||
|
} catch(...) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
friend auto operator<<(xmlpp::Element* element, const Error& message) -> void {
|
||||||
|
element->set_attribute("type", "error");
|
||||||
|
serialization::Serialize(element, message);
|
||||||
|
}
|
||||||
|
constexpr auto operator==(const Error& other) const -> bool = default;
|
||||||
|
From from;
|
||||||
|
To to;
|
||||||
|
stanza::error::StanzaError error;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace message
|
||||||
|
|
||||||
|
template <typename From, typename To>
|
||||||
|
using Message = std::variant<message::Error<From, To>, message::Message<From, To>>;
|
||||||
|
|
||||||
|
template <typename From, typename To>
|
||||||
|
struct serialization::SerializationConfigT<message::Message<From, To>> {
|
||||||
|
static constexpr auto kValue = serialization::SerializationConfig<message::Message<From, To>>{} //
|
||||||
|
.template With<"type">(serialization::AttributeConfig{})
|
||||||
|
.template With<"body">(serialization::Config<std::vector<message::Body>>{});
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename From, typename To>
|
||||||
|
struct serialization::SerializationConfigT<message::Error<From, To>> {
|
||||||
|
static constexpr auto kValue = serialization::SerializationConfig<message::Error<From, To>>{} //
|
||||||
|
.template With<"error">(serialization::Config<stanza::error::StanzaError>{});
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Info>
|
||||||
|
struct serialization::AttributeSerializer<message::Type, Info> : serialization::AttributeSerializer<message::TypeVariant, Info> {};
|
||||||
|
|
||||||
|
} // namespace larra::xmpp
|
|
@ -4,7 +4,6 @@
|
||||||
#include <boost/pfr.hpp>
|
#include <boost/pfr.hpp>
|
||||||
#include <larra/serialization.hpp>
|
#include <larra/serialization.hpp>
|
||||||
#include <larra/serialization/error.hpp>
|
#include <larra/serialization/error.hpp>
|
||||||
#include <ranges>
|
|
||||||
#include <utempl/constexpr_string.hpp>
|
#include <utempl/constexpr_string.hpp>
|
||||||
#include <utempl/tuple.hpp>
|
#include <utempl/tuple.hpp>
|
||||||
#include <utempl/utils.hpp>
|
#include <utempl/utils.hpp>
|
||||||
|
@ -16,10 +15,20 @@ template <typename T>
|
||||||
struct Tag {};
|
struct Tag {};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline constexpr auto kSerializationConfig = std::monostate{};
|
struct SerializationConfigT {
|
||||||
|
static constexpr auto kValue = std::monostate{};
|
||||||
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline constexpr auto kDeserializationConfig = kSerializationConfig<T>;
|
inline constexpr auto kSerializationConfig = SerializationConfigT<T>::kValue;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct DeserializationConfigT {
|
||||||
|
static constexpr auto kValue = kSerializationConfig<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline constexpr auto kDeserializationConfig = DeserializationConfigT<T>::kValue;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
constexpr auto Parse(xmlpp::Element* element, Tag<T> = {}) -> T
|
constexpr auto Parse(xmlpp::Element* element, Tag<T> = {}) -> T
|
||||||
|
@ -28,10 +37,7 @@ constexpr auto Parse(xmlpp::Element* element, Tag<T> = {}) -> T
|
||||||
template <typename T>
|
template <typename T>
|
||||||
constexpr auto Parse(xmlpp::Element* element, Tag<T> = {}) -> T;
|
constexpr auto Parse(xmlpp::Element* element, Tag<T> = {}) -> T;
|
||||||
|
|
||||||
template <typename T>
|
// TODO(sha512sum): Add TryParse
|
||||||
constexpr auto TryParse(xmlpp::Element* element, Tag<T> = {}) -> std::optional<T> {
|
|
||||||
return Serialization<std::optional<T>>::Parse(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
constexpr auto Serialize(xmlpp::Element* node, const T& element) -> void
|
constexpr auto Serialize(xmlpp::Element* node, const T& element) -> void
|
||||||
|
@ -55,10 +61,24 @@ struct MetaInfo {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct DefaultValue {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
requires requires {
|
||||||
|
{ T::GetDefault() } -> std::convertible_to<T>;
|
||||||
|
}
|
||||||
|
struct DefaultValue<T> {
|
||||||
|
static constexpr auto Default() {
|
||||||
|
return T::GetDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
template <typename MainT, std::size_t Element>
|
template <typename MainT, std::size_t Element>
|
||||||
struct FieldInfo {
|
struct FieldInfo {
|
||||||
using Main = MainT;
|
using Main = MainT;
|
||||||
using Info = MetaInfo<Main>;
|
using Info = MetaInfo<Main>;
|
||||||
|
using Type = Info::template TupleElement<Element>;
|
||||||
static inline const std::string kName = [] {
|
static inline const std::string kName = [] {
|
||||||
if constexpr(requires { Info::template TupleElement<Element>::kDefaultName; }) {
|
if constexpr(requires { Info::template TupleElement<Element>::kDefaultName; }) {
|
||||||
return Info::template TupleElement<Element>::kDefaultName;
|
return Info::template TupleElement<Element>::kDefaultName;
|
||||||
|
@ -66,6 +86,13 @@ struct FieldInfo {
|
||||||
return static_cast<std::string>(Info::template kFieldName<Element>);
|
return static_cast<std::string>(Info::template kFieldName<Element>);
|
||||||
}
|
}
|
||||||
}();
|
}();
|
||||||
|
static inline const std::string kNamespace = [] {
|
||||||
|
if constexpr(requires { Info::template TupleElement<Element>::kDefaultNamespace; }) {
|
||||||
|
return Info::template TupleElement<Element>::kDefaultNamespace;
|
||||||
|
} else {
|
||||||
|
return std::string{};
|
||||||
|
}
|
||||||
|
}();
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -216,9 +243,16 @@ struct ElementSerializer<std::vector<T>, Config, Info> {
|
||||||
template <typename T, typename Info>
|
template <typename T, typename Info>
|
||||||
struct AttributeSerializer {
|
struct AttributeSerializer {
|
||||||
static constexpr auto Parse(xmlpp::Element* element) -> T {
|
static constexpr auto Parse(xmlpp::Element* element) -> T {
|
||||||
auto node = element->get_attribute(Info::kName);
|
auto node = element->get_attribute(Info::kName, Info::kNamespace);
|
||||||
if(!node) {
|
if(!node) {
|
||||||
throw AttributeParsingError(std::format("Attribute [{}: {}] parsing error", Info::kName, nameof::nameof_full_type<T>()));
|
if constexpr(requires { DefaultValue<T>::Default(); }) {
|
||||||
|
return DefaultValue<T>::Default();
|
||||||
|
}
|
||||||
|
throw AttributeParsingError(std::format("Attribute [{}{}{}: {}] parsing error",
|
||||||
|
Info::kNamespace,
|
||||||
|
Info::kNamespace == std::string{} ? "" : ":",
|
||||||
|
Info::kName,
|
||||||
|
nameof::nameof_full_type<T>()));
|
||||||
}
|
}
|
||||||
if constexpr(requires(std::string_view view) { T::Parse(view); }) {
|
if constexpr(requires(std::string_view view) { T::Parse(view); }) {
|
||||||
return T::Parse(node->get_value());
|
return T::Parse(node->get_value());
|
||||||
|
@ -226,13 +260,26 @@ struct AttributeSerializer {
|
||||||
return node->get_value();
|
return node->get_value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
static constexpr auto TryParse(xmlpp::Element* element) -> std::optional<T>
|
||||||
|
requires requires { T::TryParse(std::string_view{}); }
|
||||||
|
{
|
||||||
|
auto node = element->get_attribute(Info::kName, Info::kNamespace);
|
||||||
|
if(!node) {
|
||||||
|
throw AttributeParsingError(std::format("Attribute [{}{}{}: {}] parsing error",
|
||||||
|
Info::kNamespace,
|
||||||
|
Info::kNamespace == std::string{} ? "" : ":",
|
||||||
|
Info::kName,
|
||||||
|
nameof::nameof_full_type<T>()));
|
||||||
|
}
|
||||||
|
return T::TryParse(node->get_value());
|
||||||
|
}
|
||||||
static constexpr auto Serialize(xmlpp::Element* element, const T& obj) {
|
static constexpr auto Serialize(xmlpp::Element* element, const T& obj) {
|
||||||
if constexpr(requires {
|
if constexpr(requires {
|
||||||
{ ToString(obj) } -> std::convertible_to<const std::string&>;
|
{ ToString(obj) } -> std::convertible_to<const std::string&>;
|
||||||
}) {
|
}) {
|
||||||
element->set_attribute(Info::kName, ToString(obj));
|
element->set_attribute(Info::kName, ToString(obj), Info::kNamespace);
|
||||||
} else {
|
} else {
|
||||||
element->set_attribute(Info::kName, obj);
|
element->set_attribute(Info::kName, obj, Info::kNamespace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -240,8 +287,13 @@ struct AttributeSerializer {
|
||||||
template <typename T, typename Info>
|
template <typename T, typename Info>
|
||||||
struct AttributeSerializer<std::optional<T>, Info> {
|
struct AttributeSerializer<std::optional<T>, Info> {
|
||||||
static constexpr auto Parse(xmlpp::Element* element) -> std::optional<T> {
|
static constexpr auto Parse(xmlpp::Element* element) -> std::optional<T> {
|
||||||
auto node = element->get_attribute(Info::kName);
|
auto node = element->get_attribute(Info::kName, Info::kNamespace);
|
||||||
return node ? std::optional{AttributeSerializer<T, Info>::Parse(element)} : std::nullopt;
|
return node ? std::optional{AttributeSerializer<T, Info>::Parse(element)} : [] -> std::optional<T> {
|
||||||
|
if constexpr(requires { DefaultValue<T>::Default(); }) {
|
||||||
|
return DefaultValue<T>::Default();
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}();
|
||||||
}
|
}
|
||||||
static constexpr auto Serialize(xmlpp::Element* element, const std::optional<T>& obj) {
|
static constexpr auto Serialize(xmlpp::Element* element, const std::optional<T>& obj) {
|
||||||
if(obj) {
|
if(obj) {
|
||||||
|
@ -250,6 +302,52 @@ struct AttributeSerializer<std::optional<T>, Info> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename... Ts, typename Info>
|
||||||
|
struct AttributeSerializer<std::variant<Ts...>, Info> {
|
||||||
|
template <typename T>
|
||||||
|
static constexpr auto FunctionForType(xmlpp::Element* element) {
|
||||||
|
return [=] -> std::optional<T> {
|
||||||
|
if constexpr(requires { AttributeSerializer<T, Info>::TryParse(element); }) {
|
||||||
|
return AttributeSerializer<T, Info>::TryParse(element);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return AttributeSerializer<T, Info>::Parse(element);
|
||||||
|
} catch(...) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
static constexpr auto Parse(xmlpp::Element* element) -> std::variant<Ts...> {
|
||||||
|
using T = Info::Type;
|
||||||
|
auto node = element->get_attribute(Info::kName, Info::kNamespace);
|
||||||
|
if(!node) {
|
||||||
|
if constexpr(requires { DefaultValue<T>::Default(); }) {
|
||||||
|
return DefaultValue<T>::Default();
|
||||||
|
}
|
||||||
|
throw AttributeParsingError(std::format("Attribute [{}{}{}: {}] parsing error",
|
||||||
|
Info::kNamespace,
|
||||||
|
Info::kNamespace == std::string{} ? "" : ":",
|
||||||
|
Info::kName,
|
||||||
|
nameof::nameof_full_type<T>()));
|
||||||
|
}
|
||||||
|
return [&]<typename... TTs>(utempl::TypeList<TTs...>) {
|
||||||
|
// operator* is safe because in or_else Parse returns Ts...[sizeof...(Ts) - 1] (not optional)
|
||||||
|
return *utempl::FirstOf(utempl::Tuple{FunctionForType<TTs>(element)...}, std::optional<std::variant<Ts...>>{})
|
||||||
|
.or_else([&] -> std::optional<std::variant<Ts...>> {
|
||||||
|
return AttributeSerializer<decltype(utempl::Get<sizeof...(Ts) - 1>(utempl::kTypeList<Ts...>)), Info>::Parse(element);
|
||||||
|
});
|
||||||
|
}(utempl::TakeFrom<sizeof...(Ts) - 1>(utempl::kTypeList<Ts...>));
|
||||||
|
}
|
||||||
|
static constexpr auto Serialize(xmlpp::Element* element, const std::variant<Ts...>& obj) {
|
||||||
|
std::visit(
|
||||||
|
[&]<typename T>(const T& value) {
|
||||||
|
AttributeSerializer<T, Info>::Serialize(element, value);
|
||||||
|
},
|
||||||
|
obj);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
namespace impl {
|
namespace impl {
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|
|
@ -1,21 +1,81 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <libxml++/libxml++.h>
|
#include <libxml++/libxml++.h>
|
||||||
|
|
||||||
|
#include <larra/serialization/auto.hpp>
|
||||||
#include <larra/utils.hpp>
|
#include <larra/utils.hpp>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
namespace larra::xmpp::stanza::error {
|
namespace larra::xmpp::stanza::error {
|
||||||
|
|
||||||
struct StanzaBaseError : std::exception {};
|
namespace type {
|
||||||
|
|
||||||
|
// Not move
|
||||||
|
template <typename Self>
|
||||||
|
struct TypeBaseImpl {
|
||||||
|
static constexpr auto TryParse(std::string_view value) {
|
||||||
|
return value == Self::kName ? std::optional{Self{}} : std::nullopt;
|
||||||
|
}
|
||||||
|
static constexpr auto Parse(std::string_view value) {
|
||||||
|
return value == Self::kName ? Self{} : throw std::runtime_error(std::format("{}::Parse error", nameof::nameof_type<Self>()));
|
||||||
|
}
|
||||||
|
friend constexpr auto ToString(const Self&) -> std::string {
|
||||||
|
return Self::kName;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Modify : TypeBaseImpl<Modify> {
|
||||||
|
static constexpr auto kName = "modify";
|
||||||
|
} constexpr kModify{};
|
||||||
|
|
||||||
|
struct Cancel : TypeBaseImpl<Cancel> {
|
||||||
|
static constexpr auto kName = "cancel";
|
||||||
|
} constexpr kCancel{};
|
||||||
|
|
||||||
|
struct Auth : TypeBaseImpl<Auth> {
|
||||||
|
static constexpr auto kName = "auth";
|
||||||
|
} constexpr kAuth{};
|
||||||
|
|
||||||
|
struct Wait : TypeBaseImpl<Wait> {
|
||||||
|
static constexpr auto kName = "wait";
|
||||||
|
} constexpr kWait{};
|
||||||
|
|
||||||
|
} // namespace type
|
||||||
|
using TypeVariant = std::variant<type::Modify, type::Cancel, type::Auth>;
|
||||||
|
|
||||||
|
struct Type : TypeVariant {
|
||||||
|
using TypeVariant::variant;
|
||||||
|
constexpr Type(TypeVariant variant) : TypeVariant(std::move(variant)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
friend constexpr auto ToString(const Type& value) -> std::string {
|
||||||
|
return std::visit(
|
||||||
|
[](const auto& value) {
|
||||||
|
return ToString(value);
|
||||||
|
},
|
||||||
|
value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StanzaErrorBase : 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
|
// 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 :(
|
// C++20 modules very unstable in clangd :(
|
||||||
template <typename T>
|
template <typename T, typename Default>
|
||||||
struct StanzaErrorImpl : StanzaBaseError {
|
struct StanzaErrorImpl : StanzaErrorBase {
|
||||||
static constexpr auto kDefaultName = "error";
|
static constexpr auto kDefaultName = "error";
|
||||||
static constexpr auto kDefaultNamespace = "urn:ietf:params:xml:ns:xmpp-stanzas";
|
static constexpr auto kDefaultNamespace = "urn:ietf:params:xml:ns:xmpp-stanzas";
|
||||||
static inline const auto kKebabCaseName = static_cast<std::string>(utils::ToKebabCaseName<T>());
|
static inline const auto kKebabCaseName = static_cast<std::string>(utils::ToKebabCaseName<T>());
|
||||||
|
constexpr StanzaErrorImpl(std::optional<std::string> by, Type type) : by(std::move(by)), type(std::move(type)) {
|
||||||
|
}
|
||||||
|
constexpr StanzaErrorImpl() = default;
|
||||||
|
struct FieldInfo {
|
||||||
|
using Main = StanzaErrorImpl;
|
||||||
|
using Info = serialization::MetaInfo<Main>;
|
||||||
|
using Type = Type;
|
||||||
|
static inline const std::string kName = "type";
|
||||||
|
static inline const std::string kNamespace = "";
|
||||||
|
};
|
||||||
|
|
||||||
static constexpr auto kErrorMessage = [] -> std::string_view {
|
static constexpr auto kErrorMessage = [] -> std::string_view {
|
||||||
static constexpr auto name = nameof::nameof_short_type<T>();
|
static constexpr auto name = nameof::nameof_short_type<T>();
|
||||||
|
@ -27,37 +87,33 @@ struct StanzaErrorImpl : StanzaBaseError {
|
||||||
}();
|
}();
|
||||||
|
|
||||||
std::optional<std::string> by{};
|
std::optional<std::string> by{};
|
||||||
std::string type;
|
Type type = Default{};
|
||||||
|
|
||||||
// TODO(unknown): Add "optional text children" support for stanza error. Check "XML Stanzas" -> "Syntax" for more details
|
// 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> {
|
static constexpr auto TryParse(xmlpp::Element* element) -> std::optional<T> {
|
||||||
if(not element) {
|
if(!element) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto by = element->get_attribute("by");
|
auto by = element->get_attribute("by");
|
||||||
auto type = element->get_attribute("type");
|
auto type = element->get_attribute("type");
|
||||||
if(not type) {
|
if(!type) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto node = element->get_first_child(kKebabCaseName);
|
auto node = element->get_first_child(kKebabCaseName);
|
||||||
if(not node) {
|
if(!node) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
T obj;
|
return T{by ? std::optional{by->get_value()} : std::nullopt, //
|
||||||
obj.type = type->get_value();
|
serialization::AttributeSerializer<Type, FieldInfo>::Parse(element)};
|
||||||
if(by) {
|
|
||||||
obj.by = std::optional{by->get_value()};
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
}
|
||||||
static constexpr auto Parse(xmlpp::Element* element) -> T {
|
static constexpr auto Parse(xmlpp::Element* element) -> T {
|
||||||
return TryParse(element).value();
|
return TryParse(element).value();
|
||||||
}
|
}
|
||||||
friend constexpr auto operator<<(xmlpp::Element* element, const T& obj) -> void {
|
friend constexpr auto operator<<(xmlpp::Element* element, const T& obj) -> void {
|
||||||
element->set_attribute("type", obj.type);
|
element->set_attribute("type", ToString(obj.type));
|
||||||
if(obj.by) {
|
if(obj.by) {
|
||||||
element->set_attribute("by", *obj.by);
|
element->set_attribute("by", *obj.by);
|
||||||
}
|
}
|
||||||
|
@ -65,7 +121,7 @@ struct StanzaErrorImpl : StanzaBaseError {
|
||||||
auto node = element->add_child_element(kKebabCaseName);
|
auto node = element->add_child_element(kKebabCaseName);
|
||||||
node->set_namespace_declaration(kDefaultNamespace);
|
node->set_namespace_declaration(kDefaultNamespace);
|
||||||
}
|
}
|
||||||
constexpr auto operator==(const StanzaErrorImpl<T>&) const -> bool {
|
constexpr auto operator==(const StanzaErrorImpl&) const -> bool {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
[[nodiscard]] constexpr auto what() const noexcept -> const char* override {
|
[[nodiscard]] constexpr auto what() const noexcept -> const char* override {
|
||||||
|
@ -74,7 +130,7 @@ struct StanzaErrorImpl : StanzaBaseError {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper class to prevent parsing response stream into an expected return type if its name is an 'error'
|
// Helper class to prevent parsing response stream into an expected return type if its name is an 'error'
|
||||||
struct UnknownStanzaError : StanzaBaseError {
|
struct UnknownStanzaError : StanzaErrorBase {
|
||||||
static constexpr auto kDefaultName = "stream:error";
|
static constexpr auto kDefaultName = "stream:error";
|
||||||
static constexpr std::string_view kErrorMessage = "Unknown XMPP stream error";
|
static constexpr std::string_view kErrorMessage = "Unknown XMPP stream error";
|
||||||
|
|
||||||
|
@ -95,28 +151,72 @@ struct UnknownStanzaError : StanzaBaseError {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BadRequest : StanzaErrorImpl<BadRequest> {};
|
struct BadRequest : StanzaErrorImpl<BadRequest, type::Modify> {
|
||||||
struct Conflict : StanzaErrorImpl<Conflict> {};
|
using StanzaErrorImpl<BadRequest, type::Modify>::StanzaErrorImpl;
|
||||||
struct FeatureNotImplemented : StanzaErrorImpl<FeatureNotImplemented> {};
|
};
|
||||||
struct Forbidden : StanzaErrorImpl<Forbidden> {};
|
struct Conflict : StanzaErrorImpl<Conflict, type::Cancel> {
|
||||||
struct Gone : StanzaErrorImpl<Gone> {};
|
using StanzaErrorImpl<Conflict, type::Cancel>::StanzaErrorImpl;
|
||||||
struct InternalServerError : StanzaErrorImpl<InternalServerError> {};
|
};
|
||||||
struct ItemNotFound : StanzaErrorImpl<ItemNotFound> {};
|
struct FeatureNotImplemented : StanzaErrorImpl<FeatureNotImplemented, type::Cancel> {
|
||||||
struct JidMalformed : StanzaErrorImpl<JidMalformed> {};
|
using StanzaErrorImpl<FeatureNotImplemented, type::Cancel>::StanzaErrorImpl;
|
||||||
struct NotAcceptable : StanzaErrorImpl<NotAcceptable> {};
|
};
|
||||||
struct NotAllowed : StanzaErrorImpl<NotAllowed> {};
|
struct Forbidden : StanzaErrorImpl<Forbidden, type::Auth> {
|
||||||
struct NotAuthorized : StanzaErrorImpl<NotAuthorized> {};
|
using StanzaErrorImpl<Forbidden, type::Auth>::StanzaErrorImpl;
|
||||||
struct PolicyViolation : StanzaErrorImpl<PolicyViolation> {};
|
};
|
||||||
struct RecipientUnavailable : StanzaErrorImpl<RecipientUnavailable> {};
|
struct Gone : StanzaErrorImpl<Gone, type::Cancel> {
|
||||||
struct Redirect : StanzaErrorImpl<Redirect> {};
|
using StanzaErrorImpl<Gone, type::Cancel>::StanzaErrorImpl;
|
||||||
struct RegistrationRequired : StanzaErrorImpl<RegistrationRequired> {};
|
};
|
||||||
struct RemoteServerNotFound : StanzaErrorImpl<RemoteServerNotFound> {};
|
struct InternalServerError : StanzaErrorImpl<InternalServerError, type::Cancel> {
|
||||||
struct RemoteServerTimeout : StanzaErrorImpl<RemoteServerTimeout> {};
|
using StanzaErrorImpl<InternalServerError, type::Cancel>::StanzaErrorImpl;
|
||||||
struct ResourceConstraint : StanzaErrorImpl<ResourceConstraint> {};
|
};
|
||||||
struct ServiceUnavailable : StanzaErrorImpl<ServiceUnavailable> {};
|
struct ItemNotFound : StanzaErrorImpl<ItemNotFound, type::Cancel> {
|
||||||
struct SubscriptionRequired : StanzaErrorImpl<SubscriptionRequired> {};
|
using StanzaErrorImpl<ItemNotFound, type::Cancel>::StanzaErrorImpl;
|
||||||
struct UndefinedCondition : StanzaErrorImpl<UndefinedCondition> {};
|
};
|
||||||
struct UnexpectedRequest : StanzaErrorImpl<UnexpectedRequest> {};
|
struct JidMalformed : StanzaErrorImpl<JidMalformed, type::Modify> {
|
||||||
|
using StanzaErrorImpl<JidMalformed, type::Modify>::StanzaErrorImpl;
|
||||||
|
};
|
||||||
|
struct NotAcceptable : StanzaErrorImpl<NotAcceptable, type::Modify> {
|
||||||
|
using StanzaErrorImpl<NotAcceptable, type::Modify>::StanzaErrorImpl;
|
||||||
|
};
|
||||||
|
struct NotAllowed : StanzaErrorImpl<NotAllowed, type::Cancel> {
|
||||||
|
using StanzaErrorImpl<NotAllowed, type::Cancel>::StanzaErrorImpl;
|
||||||
|
};
|
||||||
|
struct NotAuthorized : StanzaErrorImpl<NotAuthorized, type::Auth> {
|
||||||
|
using StanzaErrorImpl<NotAuthorized, type::Auth>::StanzaErrorImpl;
|
||||||
|
};
|
||||||
|
struct PolicyViolation : StanzaErrorImpl<PolicyViolation, type::Modify> {
|
||||||
|
using StanzaErrorImpl<PolicyViolation, type::Modify>::StanzaErrorImpl;
|
||||||
|
};
|
||||||
|
struct RecipientUnavailable : StanzaErrorImpl<RecipientUnavailable, type::Wait> {
|
||||||
|
using StanzaErrorImpl<RecipientUnavailable, type::Wait>::StanzaErrorImpl;
|
||||||
|
};
|
||||||
|
struct Redirect : StanzaErrorImpl<Redirect, type::Modify> {
|
||||||
|
using StanzaErrorImpl<Redirect, type::Modify>::StanzaErrorImpl;
|
||||||
|
};
|
||||||
|
struct RegistrationRequired : StanzaErrorImpl<RegistrationRequired, type::Auth> {
|
||||||
|
using StanzaErrorImpl<RegistrationRequired, type::Auth>::StanzaErrorImpl;
|
||||||
|
};
|
||||||
|
struct RemoteServerNotFound : StanzaErrorImpl<RemoteServerNotFound, type::Cancel> {
|
||||||
|
using StanzaErrorImpl<RemoteServerNotFound, type::Cancel>::StanzaErrorImpl;
|
||||||
|
};
|
||||||
|
struct RemoteServerTimeout : StanzaErrorImpl<RemoteServerTimeout, type::Wait> {
|
||||||
|
using StanzaErrorImpl<RemoteServerTimeout, type::Wait>::StanzaErrorImpl;
|
||||||
|
};
|
||||||
|
struct ResourceConstraint : StanzaErrorImpl<ResourceConstraint, type::Wait> {
|
||||||
|
using StanzaErrorImpl<ResourceConstraint, type::Wait>::StanzaErrorImpl;
|
||||||
|
};
|
||||||
|
struct ServiceUnavailable : StanzaErrorImpl<ServiceUnavailable, type::Cancel> {
|
||||||
|
using StanzaErrorImpl<ServiceUnavailable, type::Cancel>::StanzaErrorImpl;
|
||||||
|
};
|
||||||
|
struct SubscriptionRequired : StanzaErrorImpl<SubscriptionRequired, type::Auth> {
|
||||||
|
using StanzaErrorImpl<SubscriptionRequired, type::Auth>::StanzaErrorImpl;
|
||||||
|
};
|
||||||
|
struct UndefinedCondition : StanzaErrorImpl<UndefinedCondition, type::Modify> {
|
||||||
|
using StanzaErrorImpl<UndefinedCondition, type::Modify>::StanzaErrorImpl;
|
||||||
|
};
|
||||||
|
struct UnexpectedRequest : StanzaErrorImpl<UnexpectedRequest, type::Modify> {
|
||||||
|
using StanzaErrorImpl<UnexpectedRequest, type::Modify>::StanzaErrorImpl;
|
||||||
|
};
|
||||||
|
|
||||||
using StanzaError = std::variant<BadRequest,
|
using StanzaError = std::variant<BadRequest,
|
||||||
Conflict,
|
Conflict,
|
||||||
|
@ -146,3 +246,11 @@ static_assert(std::is_same_v<typename std::variant_alternative_t<std::variant_si
|
||||||
"'UnknownStanzaError' must be at the end of 'StanzaError' variant");
|
"'UnknownStanzaError' must be at the end of 'StanzaError' variant");
|
||||||
|
|
||||||
} // namespace larra::xmpp::stanza::error
|
} // namespace larra::xmpp::stanza::error
|
||||||
|
|
||||||
|
namespace larra::xmpp {
|
||||||
|
|
||||||
|
template <typename Info>
|
||||||
|
struct serialization::AttributeSerializer<stanza::error::Type, Info>
|
||||||
|
: serialization::AttributeSerializer<stanza::error::TypeVariant, Info> {};
|
||||||
|
|
||||||
|
} // namespace larra::xmpp
|
||||||
|
|
20
library/include/larra/xml_language.hpp
Normal file
20
library/include/larra/xml_language.hpp
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
namespace larra::xmpp {
|
||||||
|
|
||||||
|
struct XmlLanguage : std::string {
|
||||||
|
static constexpr auto kDefaultName = "lang";
|
||||||
|
static constexpr auto kDefaultNamespace = "xml";
|
||||||
|
using std::string::basic_string;
|
||||||
|
constexpr XmlLanguage() : std::string{"en"} {};
|
||||||
|
constexpr XmlLanguage(std::string str) : std::string(std::move(str)) {
|
||||||
|
}
|
||||||
|
static constexpr auto Parse(std::string_view view) -> XmlLanguage {
|
||||||
|
return static_cast<std::string>(view);
|
||||||
|
}
|
||||||
|
friend constexpr auto ToString(XmlLanguage value) -> std::string {
|
||||||
|
return std::move(static_cast<std::string&>(value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace larra::xmpp
|
|
@ -87,7 +87,7 @@ TEST(IQ, IqErrThrowVisitorThrow) {
|
||||||
static constexpr auto throwErrMsg = "Stanza IQ Error: Forbidden";
|
static constexpr auto throwErrMsg = "Stanza IQ Error: Forbidden";
|
||||||
try {
|
try {
|
||||||
std::visit(utempl::Overloaded([](iq::Result<SomeStruct> r) {}, IqErrThrowVisitor{visitorErrMsg}), std::move(iqRes));
|
std::visit(utempl::Overloaded([](iq::Result<SomeStruct> r) {}, IqErrThrowVisitor{visitorErrMsg}), std::move(iqRes));
|
||||||
} catch(const stanza::error::StanzaBaseError& err) {
|
} catch(const stanza::error::StanzaErrorBase& err) {
|
||||||
ASSERT_STREQ(throwErrMsg, err.what());
|
ASSERT_STREQ(throwErrMsg, err.what());
|
||||||
return;
|
return;
|
||||||
} catch(const std::runtime_error& err) {
|
} catch(const std::runtime_error& err) {
|
||||||
|
@ -114,7 +114,7 @@ TEST(IQ, IqErrThrowVisitorThrowGet) {
|
||||||
static constexpr auto throwErrMsg = "Test Error: 'Get' is an invalid type for IQ result. Expected 'Result' or 'Error'";
|
static constexpr auto throwErrMsg = "Test Error: 'Get' is an invalid type for IQ result. Expected 'Result' or 'Error'";
|
||||||
try {
|
try {
|
||||||
std::visit(utempl::Overloaded([](iq::Result<SomeStruct> r) {}, IqErrThrowVisitor{"Test Error"}), std::move(iqRes));
|
std::visit(utempl::Overloaded([](iq::Result<SomeStruct> r) {}, IqErrThrowVisitor{"Test Error"}), std::move(iqRes));
|
||||||
} catch(const stanza::error::StanzaBaseError& err) {
|
} catch(const stanza::error::StanzaErrorBase& err) {
|
||||||
ASSERT_TRUE(false) << "\tERROR: Invalid throw type throw";
|
ASSERT_TRUE(false) << "\tERROR: Invalid throw type throw";
|
||||||
} catch(const std::runtime_error& err) {
|
} catch(const std::runtime_error& err) {
|
||||||
ASSERT_STREQ(throwErrMsg, err.what());
|
ASSERT_STREQ(throwErrMsg, err.what());
|
||||||
|
@ -141,7 +141,7 @@ TEST(IQ, IqErrThrowVisitorThrowSet) {
|
||||||
static constexpr auto throwErrMsg = "Test Error: 'Set' is an invalid type for IQ result. Expected 'Result' or 'Error'";
|
static constexpr auto throwErrMsg = "Test Error: 'Set' is an invalid type for IQ result. Expected 'Result' or 'Error'";
|
||||||
try {
|
try {
|
||||||
std::visit(utempl::Overloaded([](iq::Result<SomeStruct> r) {}, IqErrThrowVisitor{"Test Error"}), std::move(iqRes));
|
std::visit(utempl::Overloaded([](iq::Result<SomeStruct> r) {}, IqErrThrowVisitor{"Test Error"}), std::move(iqRes));
|
||||||
} catch(const stanza::error::StanzaBaseError& err) {
|
} catch(const stanza::error::StanzaErrorBase& err) {
|
||||||
ASSERT_TRUE(false) << "\tERROR: Invalid throw type throw";
|
ASSERT_TRUE(false) << "\tERROR: Invalid throw type throw";
|
||||||
} catch(const std::runtime_error& err) {
|
} catch(const std::runtime_error& err) {
|
||||||
ASSERT_STREQ(throwErrMsg, err.what());
|
ASSERT_STREQ(throwErrMsg, err.what());
|
||||||
|
|
|
@ -30,7 +30,7 @@ auto main(int argc, char** argv) -> int {
|
||||||
// Define options
|
// Define options
|
||||||
po::options_description desc("Allowed options");
|
po::options_description desc("Allowed options");
|
||||||
desc.add_options()("help,h", "Print help message")("log_level,l",
|
desc.add_options()("help,h", "Print help message")("log_level,l",
|
||||||
po::value<int>()->default_value(SPDLOG_LEVEL_INFO),
|
po::value<int>()->default_value(SPDLOG_LEVEL_OFF),
|
||||||
"Set log level: 0=TRACE, 1=DEBUG, 2=INFO, 3=WARN, 4=ERROR, 5=CRITICAL, 6=OFF");
|
"Set log level: 0=TRACE, 1=DEBUG, 2=INFO, 3=WARN, 4=ERROR, 5=CRITICAL, 6=OFF");
|
||||||
|
|
||||||
// Parse command-line arguments
|
// Parse command-line arguments
|
||||||
|
@ -54,8 +54,10 @@ auto main(int argc, char** argv) -> int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cmd options handling
|
// Cmd options handling
|
||||||
spdlog::set_level(static_cast<spdlog::level::level_enum>(vm["log_level"].as<int>()));
|
auto level = static_cast<spdlog::level::level_enum>(vm["log_level"].as<int>());
|
||||||
std::println("\nEnvironment setup:\n\tCurrently set log level: {}\n", ToString(spdlog::get_level()));
|
spdlog::set_level(level);
|
||||||
|
if(level != spdlog::level::level_enum::off) {
|
||||||
|
std::println("\nEnvironment setup:\n\tCurrently set log level: {}\n", ToString(spdlog::get_level()));
|
||||||
|
}
|
||||||
return RUN_ALL_TESTS();
|
return RUN_ALL_TESTS();
|
||||||
}
|
}
|
||||||
|
|
87
tests/message.cpp
Normal file
87
tests/message.cpp
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <larra/jid.hpp>
|
||||||
|
#include <larra/message.hpp>
|
||||||
|
|
||||||
|
namespace larra::xmpp {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
auto CreateTestData() {
|
||||||
|
auto doc = std::make_unique<xmlpp::Document>();
|
||||||
|
auto node = doc->create_root_node("message");
|
||||||
|
node->set_attribute("from", "user1@server.i2p");
|
||||||
|
node->set_attribute("to", "user2@server.i2p");
|
||||||
|
node->set_attribute("type", "chat");
|
||||||
|
node->set_attribute("id", "1");
|
||||||
|
node->set_attribute("lang", "en", "xml");
|
||||||
|
|
||||||
|
auto bodyNode = node->add_child_element("body");
|
||||||
|
bodyNode->add_child_text("hello");
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CreateErrorTestData() {
|
||||||
|
auto doc = std::make_unique<xmlpp::Document>();
|
||||||
|
auto node = doc->create_root_node("message");
|
||||||
|
node->set_attribute("from", "user1@server.i2p");
|
||||||
|
node->set_attribute("to", "user2@server.i2p");
|
||||||
|
node->set_attribute("type", "error");
|
||||||
|
auto error = node->add_child_element("error");
|
||||||
|
error->set_attribute("type", "auth");
|
||||||
|
auto notAuthorized = error->add_child_element("not-authorized");
|
||||||
|
notAuthorized->set_namespace_declaration("urn:ietf:params:xml:ns:xmpp-stanzas");
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message::Message<BareJid, BareJid> kMessage{
|
||||||
|
.from = {.username = "user1", .server = "server.i2p"},
|
||||||
|
.to = {.username = "user2", .server = "server.i2p"},
|
||||||
|
.type = message::type::kChat,
|
||||||
|
.id = "1",
|
||||||
|
.language = "en",
|
||||||
|
.body = {{.content = "hello"}} //
|
||||||
|
};
|
||||||
|
|
||||||
|
const message::Error<BareJid, BareJid> kError{.from = {.username = "user1", .server = "server.i2p"}, //
|
||||||
|
.to = {.username = "user2", .server = "server.i2p"},
|
||||||
|
.error = stanza::error::NotAuthorized{}};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(Parse, Message) {
|
||||||
|
using T = Message<BareJid, BareJid>;
|
||||||
|
{
|
||||||
|
auto doc = CreateTestData();
|
||||||
|
auto node = doc->get_root_node();
|
||||||
|
auto message = Serialization<T>::Parse(node);
|
||||||
|
EXPECT_EQ(message, static_cast<T>(kMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto doc = CreateErrorTestData();
|
||||||
|
auto node = doc->get_root_node();
|
||||||
|
auto message = Serialization<T>::Parse(node);
|
||||||
|
EXPECT_EQ(message, static_cast<T>(kError));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Serialize, Message) {
|
||||||
|
{
|
||||||
|
auto expected =
|
||||||
|
R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<message from="user1@server.i2p" to="user2@server.i2p" type="chat" id="1" xml:lang="en"><body>hello</body></message>
|
||||||
|
)";
|
||||||
|
xmlpp::Document doc;
|
||||||
|
auto node = doc.create_root_node("message");
|
||||||
|
Serialization<message::Message<BareJid, BareJid>>::Serialize(node, kMessage);
|
||||||
|
EXPECT_EQ(doc.write_to_string(), expected);
|
||||||
|
}
|
||||||
|
auto expected = R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<message type="error" from="user1@server.i2p" to="user2@server.i2p"><error type="auth"><not-authorized xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/></error></message>
|
||||||
|
)";
|
||||||
|
xmlpp::Document doc;
|
||||||
|
auto node = doc.create_root_node("message");
|
||||||
|
Serialization<message::Error<BareJid, BareJid>>::Serialize(node, kError);
|
||||||
|
EXPECT_EQ(doc.write_to_string(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace larra::xmpp
|
|
@ -28,7 +28,7 @@ TEST(Roster, Print) {
|
||||||
|
|
||||||
EXPECT_NO_THROW({
|
EXPECT_NO_THROW({
|
||||||
auto rosterStr = ToString(roster.payload);
|
auto rosterStr = ToString(roster.payload);
|
||||||
EXPECT_EQ(kRosterPrintExpectedData.length(), rosterStr.capacity());
|
EXPECT_EQ(kRosterPrintExpectedData.length(), rosterStr.length());
|
||||||
EXPECT_EQ(kRosterPrintExpectedData, rosterStr);
|
EXPECT_EQ(kRosterPrintExpectedData, rosterStr);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
42
tests/xml_lang.cpp
Normal file
42
tests/xml_lang.cpp
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <larra/serialization/auto.hpp>
|
||||||
|
#include <larra/xml_language.hpp>
|
||||||
|
|
||||||
|
namespace larra::xmpp {
|
||||||
|
|
||||||
|
namespace tests::message {
|
||||||
|
|
||||||
|
struct SomeStruct {
|
||||||
|
static constexpr auto kDefaultName = "some";
|
||||||
|
XmlLanguage language;
|
||||||
|
friend auto operator<<(xmlpp::Element*, const SomeStruct&) -> void;
|
||||||
|
static auto Parse(xmlpp::Element* node) -> SomeStruct;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace tests::message
|
||||||
|
template <>
|
||||||
|
constexpr auto serialization::kSerializationConfig<tests::message::SomeStruct> =
|
||||||
|
serialization::SerializationConfig<tests::message::SomeStruct>{};
|
||||||
|
|
||||||
|
namespace tests::message {
|
||||||
|
|
||||||
|
auto SomeStruct::Parse(xmlpp::Element* element) -> SomeStruct {
|
||||||
|
return serialization::Parse<SomeStruct>(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto operator<<(xmlpp::Element* element, const SomeStruct& some) -> void {
|
||||||
|
serialization::Serialize(element, some);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace tests::message
|
||||||
|
|
||||||
|
TEST(Parse, XmlLanguage) {
|
||||||
|
xmlpp::Document doc;
|
||||||
|
auto node = doc.create_root_node("some");
|
||||||
|
node->set_attribute("lang", "en", "xml");
|
||||||
|
auto value = Serialization<tests::message::SomeStruct>::Parse(node);
|
||||||
|
EXPECT_EQ(value.language, "en");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace larra::xmpp
|
Loading…
Reference in a new issue