diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 452e3b1..4b87c43 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -30,7 +30,7 @@ RUN groupadd docker &&\ USER dev # 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} 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/ diff --git a/.forgejo/workflows/pr_check.yaml b/.forgejo/workflows/pr_check.yaml index a9f6f7c..3a63ce1 100644 --- a/.forgejo/workflows/pr_check.yaml +++ b/.forgejo/workflows/pr_check.yaml @@ -13,9 +13,9 @@ jobs: pacman -S --noconfirm cmake make gcc ninja meson 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: | - export LLVM_VER=19.1.3 + export LLVM_VER=19.1.5 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 @@ -108,7 +108,7 @@ jobs: id: clang_format_check continue-on-error: true run: | - export LLVM_VER=19.1.3 + export LLVM_VER=19.1.5 export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}" cd ${{ github.workspace }} export REPO_FILES=$(cat repo_files_to_check.txt) @@ -150,7 +150,7 @@ jobs: id: clang_build continue-on-error: true run: | - export LLVM_VER=19.1.3 + export LLVM_VER=19.1.5 export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}" mkdir -p ${{ github.workspace }}/build_clang @@ -176,7 +176,7 @@ jobs: id: clang_tidy continue-on-error: true run: | - export LLVM_VER=19.1.3 + export LLVM_VER=19.1.5 export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}" cd ${{ github.workspace }} @@ -201,7 +201,7 @@ jobs: #- name: Clang unit tests with -fsanitize=address # run: | - # export LLVM_VER=19.1.3 + # export LLVM_VER=19.1.5 # export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}" # 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 diff --git a/library/include/larra/client/client.hpp b/library/include/larra/client/client.hpp index 9fbdd3f..d822e2a 100644 --- a/library/include/larra/client/client.hpp +++ b/library/include/larra/client/client.hpp @@ -107,6 +107,9 @@ struct Client { std::move(get_roster_response)); co_return; } + [[nodiscard]] auto Roster() const -> const std::vector& { + return this->roster.items; + } private: bool active = true; diff --git a/library/include/larra/message.hpp b/library/include/larra/message.hpp new file mode 100644 index 0000000..780a3b9 --- /dev/null +++ b/library/include/larra/message.hpp @@ -0,0 +1,163 @@ +#pragma once +#include +#include +#include +#include + +namespace larra::xmpp { + +namespace message { +struct Body { + std::string content; + std::optional 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 +struct TypeImpl { + static constexpr auto TryParse(std::string_view value) -> std::optional { + 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 { + static constexpr auto kName = "chat"; +} constexpr kChat{}; + +struct Error : TypeImpl { + static constexpr auto kName = "error"; +} constexpr kError{}; + +struct GroupChat : TypeImpl { + static constexpr auto kName = "groupchat"; +} constexpr kGroupChat{}; + +struct Headline : TypeImpl { + static constexpr auto kName = "headline"; +} constexpr kHeadline{}; + +struct Normal : TypeImpl { + 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; + +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 +struct Message { + static constexpr auto kDefaultName = "message"; + static auto Parse(xmlpp::Element* element) -> Message { + return serialization::Parse(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 id; + XmlLanguage language; + std::vector body; +}; + +template +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(element); + } + static auto TryParse(xmlpp::Element* element) -> std::optional { + auto attr = element->get_attribute("type"); + if(!attr || attr->get_value() != "error") { + return std::nullopt; + } + try { + return serialization::Parse(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 +using Message = std::variant, message::Message>; + +template +struct serialization::SerializationConfigT> { + static constexpr auto kValue = serialization::SerializationConfig>{} // + .template With<"type">(serialization::AttributeConfig{}) + .template With<"body">(serialization::Config>{}); +}; + +template +struct serialization::SerializationConfigT> { + static constexpr auto kValue = serialization::SerializationConfig>{} // + .template With<"error">(serialization::Config{}); +}; + +template +struct serialization::AttributeSerializer : serialization::AttributeSerializer {}; + +} // namespace larra::xmpp diff --git a/library/include/larra/serialization/auto.hpp b/library/include/larra/serialization/auto.hpp index 79486eb..e27f3a6 100644 --- a/library/include/larra/serialization/auto.hpp +++ b/library/include/larra/serialization/auto.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -16,10 +15,20 @@ template struct Tag {}; template -inline constexpr auto kSerializationConfig = std::monostate{}; +struct SerializationConfigT { + static constexpr auto kValue = std::monostate{}; +}; template -inline constexpr auto kDeserializationConfig = kSerializationConfig; +inline constexpr auto kSerializationConfig = SerializationConfigT::kValue; + +template +struct DeserializationConfigT { + static constexpr auto kValue = kSerializationConfig; +}; + +template +inline constexpr auto kDeserializationConfig = DeserializationConfigT::kValue; template constexpr auto Parse(xmlpp::Element* element, Tag = {}) -> T @@ -28,10 +37,7 @@ constexpr auto Parse(xmlpp::Element* element, Tag = {}) -> T template constexpr auto Parse(xmlpp::Element* element, Tag = {}) -> T; -template -constexpr auto TryParse(xmlpp::Element* element, Tag = {}) -> std::optional { - return Serialization>::Parse(element); -} +// TODO(sha512sum): Add TryParse template constexpr auto Serialize(xmlpp::Element* node, const T& element) -> void @@ -55,10 +61,24 @@ struct MetaInfo { } }; +template +struct DefaultValue {}; + +template + requires requires { + { T::GetDefault() } -> std::convertible_to; + } +struct DefaultValue { + static constexpr auto Default() { + return T::GetDefault(); + } +}; + template struct FieldInfo { using Main = MainT; using Info = MetaInfo
; + using Type = Info::template TupleElement; static inline const std::string kName = [] { if constexpr(requires { Info::template TupleElement::kDefaultName; }) { return Info::template TupleElement::kDefaultName; @@ -66,6 +86,13 @@ struct FieldInfo { return static_cast(Info::template kFieldName); } }(); + static inline const std::string kNamespace = [] { + if constexpr(requires { Info::template TupleElement::kDefaultNamespace; }) { + return Info::template TupleElement::kDefaultNamespace; + } else { + return std::string{}; + } + }(); }; template @@ -216,9 +243,16 @@ struct ElementSerializer, Config, Info> { template struct AttributeSerializer { 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) { - throw AttributeParsingError(std::format("Attribute [{}: {}] parsing error", Info::kName, nameof::nameof_full_type())); + if constexpr(requires { DefaultValue::Default(); }) { + return DefaultValue::Default(); + } + throw AttributeParsingError(std::format("Attribute [{}{}{}: {}] parsing error", + Info::kNamespace, + Info::kNamespace == std::string{} ? "" : ":", + Info::kName, + nameof::nameof_full_type())); } if constexpr(requires(std::string_view view) { T::Parse(view); }) { return T::Parse(node->get_value()); @@ -226,13 +260,26 @@ struct AttributeSerializer { return node->get_value(); } } + static constexpr auto TryParse(xmlpp::Element* element) -> std::optional + 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())); + } + return T::TryParse(node->get_value()); + } static constexpr auto Serialize(xmlpp::Element* element, const T& obj) { if constexpr(requires { { ToString(obj) } -> std::convertible_to; }) { - element->set_attribute(Info::kName, ToString(obj)); + element->set_attribute(Info::kName, ToString(obj), Info::kNamespace); } else { - element->set_attribute(Info::kName, obj); + element->set_attribute(Info::kName, obj, Info::kNamespace); } } }; @@ -240,8 +287,13 @@ struct AttributeSerializer { template struct AttributeSerializer, Info> { static constexpr auto Parse(xmlpp::Element* element) -> std::optional { - auto node = element->get_attribute(Info::kName); - return node ? std::optional{AttributeSerializer::Parse(element)} : std::nullopt; + auto node = element->get_attribute(Info::kName, Info::kNamespace); + return node ? std::optional{AttributeSerializer::Parse(element)} : [] -> std::optional { + if constexpr(requires { DefaultValue::Default(); }) { + return DefaultValue::Default(); + } + return std::nullopt; + }(); } static constexpr auto Serialize(xmlpp::Element* element, const std::optional& obj) { if(obj) { @@ -250,6 +302,52 @@ struct AttributeSerializer, Info> { } }; +template +struct AttributeSerializer, Info> { + template + static constexpr auto FunctionForType(xmlpp::Element* element) { + return [=] -> std::optional { + if constexpr(requires { AttributeSerializer::TryParse(element); }) { + return AttributeSerializer::TryParse(element); + } else { + try { + return AttributeSerializer::Parse(element); + } catch(...) { + return std::nullopt; + } + } + }; + } + static constexpr auto Parse(xmlpp::Element* element) -> std::variant { + using T = Info::Type; + auto node = element->get_attribute(Info::kName, Info::kNamespace); + if(!node) { + if constexpr(requires { DefaultValue::Default(); }) { + return DefaultValue::Default(); + } + throw AttributeParsingError(std::format("Attribute [{}{}{}: {}] parsing error", + Info::kNamespace, + Info::kNamespace == std::string{} ? "" : ":", + Info::kName, + nameof::nameof_full_type())); + } + return [&](utempl::TypeList) { + // operator* is safe because in or_else Parse returns Ts...[sizeof...(Ts) - 1] (not optional) + return *utempl::FirstOf(utempl::Tuple{FunctionForType(element)...}, std::optional>{}) + .or_else([&] -> std::optional> { + return AttributeSerializer(utempl::kTypeList)), Info>::Parse(element); + }); + }(utempl::TakeFrom(utempl::kTypeList)); + } + static constexpr auto Serialize(xmlpp::Element* element, const std::variant& obj) { + std::visit( + [&](const T& value) { + AttributeSerializer::Serialize(element, value); + }, + obj); + } +}; + namespace impl { template diff --git a/library/include/larra/stanza_error.hpp b/library/include/larra/stanza_error.hpp index 9931ac1..5ba6e23 100644 --- a/library/include/larra/stanza_error.hpp +++ b/library/include/larra/stanza_error.hpp @@ -1,21 +1,81 @@ #pragma once #include +#include #include #include #include namespace larra::xmpp::stanza::error { -struct StanzaBaseError : std::exception {}; +namespace type { + +// Not move +template +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())); + } + friend constexpr auto ToString(const Self&) -> std::string { + return Self::kName; + } +}; + +struct Modify : TypeBaseImpl { + static constexpr auto kName = "modify"; +} constexpr kModify{}; + +struct Cancel : TypeBaseImpl { + static constexpr auto kName = "cancel"; +} constexpr kCancel{}; + +struct Auth : TypeBaseImpl { + static constexpr auto kName = "auth"; +} constexpr kAuth{}; + +struct Wait : TypeBaseImpl { + static constexpr auto kName = "wait"; +} constexpr kWait{}; + +} // namespace type +using TypeVariant = std::variant; + +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 // C++20 modules very unstable in clangd :( -template -struct StanzaErrorImpl : StanzaBaseError { +template +struct StanzaErrorImpl : StanzaErrorBase { static constexpr auto kDefaultName = "error"; static constexpr auto kDefaultNamespace = "urn:ietf:params:xml:ns:xmpp-stanzas"; static inline const auto kKebabCaseName = static_cast(utils::ToKebabCaseName()); + constexpr StanzaErrorImpl(std::optional by, Type type) : by(std::move(by)), type(std::move(type)) { + } + constexpr StanzaErrorImpl() = default; + struct FieldInfo { + using Main = StanzaErrorImpl; + using Info = serialization::MetaInfo
; + 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 name = nameof::nameof_short_type(); @@ -27,37 +87,33 @@ struct StanzaErrorImpl : StanzaBaseError { }(); std::optional by{}; - std::string type; + Type type = Default{}; // TODO(unknown): Add "optional text children" support for stanza error. Check "XML Stanzas" -> "Syntax" for more details static constexpr auto TryParse(xmlpp::Element* element) -> std::optional { - if(not element) { + if(!element) { return std::nullopt; } auto by = element->get_attribute("by"); auto type = element->get_attribute("type"); - if(not type) { + if(!type) { return std::nullopt; } auto node = element->get_first_child(kKebabCaseName); - if(not node) { + if(!node) { return std::nullopt; } - T obj; - obj.type = type->get_value(); - if(by) { - obj.by = std::optional{by->get_value()}; - } - return obj; + return T{by ? std::optional{by->get_value()} : std::nullopt, // + serialization::AttributeSerializer::Parse(element)}; } 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); + element->set_attribute("type", ToString(obj.type)); if(obj.by) { element->set_attribute("by", *obj.by); } @@ -65,7 +121,7 @@ struct StanzaErrorImpl : StanzaBaseError { auto node = element->add_child_element(kKebabCaseName); node->set_namespace_declaration(kDefaultNamespace); } - constexpr auto operator==(const StanzaErrorImpl&) const -> bool { + constexpr auto operator==(const StanzaErrorImpl&) const -> bool { return true; }; [[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' -struct UnknownStanzaError : StanzaBaseError { +struct UnknownStanzaError : StanzaErrorBase { static constexpr auto kDefaultName = "stream:error"; static constexpr std::string_view kErrorMessage = "Unknown XMPP stream error"; @@ -95,28 +151,72 @@ struct UnknownStanzaError : StanzaBaseError { } }; -struct BadRequest : StanzaErrorImpl {}; -struct Conflict : StanzaErrorImpl {}; -struct FeatureNotImplemented : StanzaErrorImpl {}; -struct Forbidden : StanzaErrorImpl {}; -struct Gone : StanzaErrorImpl {}; -struct InternalServerError : StanzaErrorImpl {}; -struct ItemNotFound : StanzaErrorImpl {}; -struct JidMalformed : StanzaErrorImpl {}; -struct NotAcceptable : StanzaErrorImpl {}; -struct NotAllowed : StanzaErrorImpl {}; -struct NotAuthorized : StanzaErrorImpl {}; -struct PolicyViolation : StanzaErrorImpl {}; -struct RecipientUnavailable : StanzaErrorImpl {}; -struct Redirect : StanzaErrorImpl {}; -struct RegistrationRequired : StanzaErrorImpl {}; -struct RemoteServerNotFound : StanzaErrorImpl {}; -struct RemoteServerTimeout : StanzaErrorImpl {}; -struct ResourceConstraint : StanzaErrorImpl {}; -struct ServiceUnavailable : StanzaErrorImpl {}; -struct SubscriptionRequired : StanzaErrorImpl {}; -struct UndefinedCondition : StanzaErrorImpl {}; -struct UnexpectedRequest : StanzaErrorImpl {}; +struct BadRequest : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct Conflict : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct FeatureNotImplemented : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct Forbidden : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct Gone : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct InternalServerError : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct ItemNotFound : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct JidMalformed : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct NotAcceptable : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct NotAllowed : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct NotAuthorized : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct PolicyViolation : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct RecipientUnavailable : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct Redirect : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct RegistrationRequired : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct RemoteServerNotFound : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct RemoteServerTimeout : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct ResourceConstraint : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct ServiceUnavailable : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct SubscriptionRequired : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct UndefinedCondition : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; +struct UnexpectedRequest : StanzaErrorImpl { + using StanzaErrorImpl::StanzaErrorImpl; +}; using StanzaError = std::variant +struct serialization::AttributeSerializer + : serialization::AttributeSerializer {}; + +} // namespace larra::xmpp diff --git a/library/include/larra/xml_language.hpp b/library/include/larra/xml_language.hpp new file mode 100644 index 0000000..e897208 --- /dev/null +++ b/library/include/larra/xml_language.hpp @@ -0,0 +1,20 @@ +#pragma once +#include +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(view); + } + friend constexpr auto ToString(XmlLanguage value) -> std::string { + return std::move(static_cast(value)); + } +}; + +} // namespace larra::xmpp diff --git a/tests/iq.cpp b/tests/iq.cpp index 3545e51..1ea35bc 100644 --- a/tests/iq.cpp +++ b/tests/iq.cpp @@ -87,7 +87,7 @@ TEST(IQ, IqErrThrowVisitorThrow) { static constexpr auto throwErrMsg = "Stanza IQ Error: Forbidden"; try { std::visit(utempl::Overloaded([](iq::Result r) {}, IqErrThrowVisitor{visitorErrMsg}), std::move(iqRes)); - } catch(const stanza::error::StanzaBaseError& err) { + } catch(const stanza::error::StanzaErrorBase& err) { ASSERT_STREQ(throwErrMsg, err.what()); return; } 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'"; try { std::visit(utempl::Overloaded([](iq::Result 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"; } catch(const std::runtime_error& err) { 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'"; try { std::visit(utempl::Overloaded([](iq::Result 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"; } catch(const std::runtime_error& err) { ASSERT_STREQ(throwErrMsg, err.what()); diff --git a/tests/main.cpp b/tests/main.cpp index 45ab76b..f49e3ee 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -30,7 +30,7 @@ auto main(int argc, char** argv) -> int { // Define options po::options_description desc("Allowed options"); desc.add_options()("help,h", "Print help message")("log_level,l", - po::value()->default_value(SPDLOG_LEVEL_INFO), + po::value()->default_value(SPDLOG_LEVEL_OFF), "Set log level: 0=TRACE, 1=DEBUG, 2=INFO, 3=WARN, 4=ERROR, 5=CRITICAL, 6=OFF"); // Parse command-line arguments @@ -54,8 +54,10 @@ auto main(int argc, char** argv) -> int { } // Cmd options handling - spdlog::set_level(static_cast(vm["log_level"].as())); - std::println("\nEnvironment setup:\n\tCurrently set log level: {}\n", ToString(spdlog::get_level())); - + auto level = static_cast(vm["log_level"].as()); + 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(); -} \ No newline at end of file +} diff --git a/tests/message.cpp b/tests/message.cpp new file mode 100644 index 0000000..538f0ef --- /dev/null +++ b/tests/message.cpp @@ -0,0 +1,85 @@ +#include + +#include +#include + +namespace larra::xmpp { + +namespace { + +auto CreateTestData() { + auto doc = std::make_unique(); + 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(); + 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 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 kError{.from = {.username = "user1", .server = "server.i2p"}, // + .to = {.username = "user2", .server = "server.i2p"}, + .error = stanza::error::NotAuthorized{}}; + +} // namespace + +TEST(Parse, Message) { + using T = Message; + { + auto doc = CreateTestData(); + auto node = doc->get_root_node(); + auto message = Serialization::Parse(node); + EXPECT_EQ(message, static_cast(kMessage)); + } + + auto doc = CreateErrorTestData(); + auto node = doc->get_root_node(); + auto message = Serialization::Parse(node); + EXPECT_EQ(message, static_cast(kError)); +} + +TEST(Serialize, Message) { + auto expected = + R"( +hello +)"; + xmlpp::Document doc; + auto node = doc.create_root_node("message"); + Serialization>::Serialize(node, kMessage); + EXPECT_EQ(doc.write_to_string(), expected); + doc.~Document(); + new(&doc) xmlpp::Document{}; + node = doc.create_root_node("message"); + Serialization>::Serialize(node, kError); + EXPECT_EQ(doc.write_to_string(), R"( + +)"); +} + +} // namespace larra::xmpp diff --git a/tests/roster.cpp b/tests/roster.cpp index 6944a0c..75935fe 100644 --- a/tests/roster.cpp +++ b/tests/roster.cpp @@ -28,7 +28,7 @@ TEST(Roster, Print) { EXPECT_NO_THROW({ auto rosterStr = ToString(roster.payload); - EXPECT_EQ(kRosterPrintExpectedData.length(), rosterStr.capacity()); + EXPECT_EQ(kRosterPrintExpectedData.length(), rosterStr.length()); EXPECT_EQ(kRosterPrintExpectedData, rosterStr); }); } diff --git a/tests/xml_lang.cpp b/tests/xml_lang.cpp new file mode 100644 index 0000000..73c4c45 --- /dev/null +++ b/tests/xml_lang.cpp @@ -0,0 +1,42 @@ +#include + +#include +#include + +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 = + serialization::SerializationConfig{}; + +namespace tests::message { + +auto SomeStruct::Parse(xmlpp::Element* element) -> SomeStruct { + return serialization::Parse(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::Parse(node); + EXPECT_EQ(value.language, "en"); +} + +} // namespace larra::xmpp