Compare commits

..

10 commits

13 changed files with 677 additions and 171 deletions

View file

@ -10,7 +10,7 @@ FROM archlinux@sha256:a10e51dd0694d6c4142754e9d06cbce7baf91ace8031a30df37064d109
RUN pacman -Syyu --noconfirm \ RUN pacman -Syyu --noconfirm \
&& pacman -Syyu --noconfirm git less vim sudo python-pip wget which pkgconf \ && pacman -Syyu --noconfirm git less vim sudo python-pip wget which pkgconf \
&& pacman -Syyu --noconfirm cmake make gcc ninja meson shellcheck \ && pacman -Syyu --noconfirm cmake make gcc ninja meson shellcheck \
&& pacman -Syyu --noconfirm gtk4 gtkmm-4.0 boost spdlog fmt libxml++-5.0 && pacman -Syyu --noconfirm gtk4 gtkmm-4.0 boost spdlog fmt libxml++-5.0 gtest
# Create a non-root user 'dev' # Create a non-root user 'dev'
RUN useradd -ms /bin/bash dev \ RUN useradd -ms /bin/bash dev \
@ -38,6 +38,7 @@ ADD https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VER}/L
# Create the LLVM directory and extract only binaries into it # Create the LLVM directory and extract only binaries into it
RUN sudo mkdir -p /home/LLVM-${LLVM_VER} RUN sudo mkdir -p /home/LLVM-${LLVM_VER}
RUN sudo tar -xJf /home/artifacts/LLVM-${LLVM_VER}-Linux-X64.tar.xz -C /home/LLVM-${LLVM_VER} --strip-components=1 \ RUN sudo tar -xJf /home/artifacts/LLVM-${LLVM_VER}-Linux-X64.tar.xz -C /home/LLVM-${LLVM_VER} --strip-components=1 \
LLVM-${LLVM_VER}-Linux-X64/bin/clangd \
LLVM-${LLVM_VER}-Linux-X64/bin/clang-19 \ LLVM-${LLVM_VER}-Linux-X64/bin/clang-19 \
LLVM-${LLVM_VER}-Linux-X64/bin/clang \ LLVM-${LLVM_VER}-Linux-X64/bin/clang \
LLVM-${LLVM_VER}-Linux-X64/bin/clang++ \ LLVM-${LLVM_VER}-Linux-X64/bin/clang++ \

View file

@ -11,11 +11,11 @@ jobs:
pacman -Syyu --noconfirm pacman -Syyu --noconfirm
pacman -S --noconfirm git less vim sudo python-pip wget which pkgconf nodejs-lts-iron pacman -S --noconfirm git less vim sudo python-pip wget which pkgconf nodejs-lts-iron
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 pacman -S --noconfirm gtk4 gtkmm-4.0 boost spdlog fmt libxml++-5.0 gtest
- name: Setup environment - Install LLVM-19.1.1 - name: Setup environment - Install LLVM-19.1.3
run: | run: |
export LLVM_VER=19.1.1 export LLVM_VER=19.1.3
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
@ -65,7 +65,7 @@ jobs:
echo "Configure project settings and remote credentials" echo "Configure project settings and remote credentials"
git init git init
git config credential.helper store git config credential.helper store
git remote add origin https://${{ github.token }}@helicopter.myftp.org/git/Larra/larra git remote add origin https://${{ github.token }}@sha512sum.xyz/git/Larra/larra
echo "Fetch repo state on pushed commit" echo "Fetch repo state on pushed commit"
git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +${{ github.sha }}:${{ github.ref }} git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +${{ github.sha }}:${{ github.ref }}
@ -105,8 +105,10 @@ jobs:
# key: LLVM-${LLVM_VER}-Linux-X64-small # key: LLVM-${LLVM_VER}-Linux-X64-small
- name: Check clang-format - name: Check clang-format
id: clang_format_check
continue-on-error: true
run: | run: |
export LLVM_VER=19.1.1 export LLVM_VER=19.1.3
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)
@ -120,6 +122,8 @@ jobs:
echo "No clang-format violations detected!" echo "No clang-format violations detected!"
- name: GCC build - name: GCC build
id: gcc_build
continue-on-error: true
run: | run: |
mkdir -p ${{ github.workspace }}/build_gcc mkdir -p ${{ github.workspace }}/build_gcc
cmake -Wno-dev \ cmake -Wno-dev \
@ -129,32 +133,56 @@ jobs:
-B ${{ github.workspace }}/build_gcc \ -B ${{ github.workspace }}/build_gcc \
-GNinja -DCMAKE_BUILD_TYPE=Release \ -GNinja -DCMAKE_BUILD_TYPE=Release \
-DENABLE_EXAMPLES=ON \ -DENABLE_EXAMPLES=ON \
-DENABLE_TESTS=ON -DENABLE_TESTS=ON \
-DCMAKE_CXX_FLAGS="-ftemplate-backtrace-limit=0"
cmake --build ${{ github.workspace }}/build_gcc --parallel `nproc` cmake --build ${{ github.workspace }}/build_gcc --parallel `nproc`
- name: GCC unit tests - name: GCC unit tests
id: gcc_unit_tests
continue-on-error: true
run: | run: |
cd ${{ github.workspace }}/build_gcc cd ${{ github.workspace }}/build_gcc
./larra_xmpp_tests ./larra_xmpp_tests
- name: Check clang-tidy - name: Clang build (only configuring)
id: clang_build
continue-on-error: true
run: | run: |
export LLVM_VER=19.1.1 export LLVM_VER=19.1.3
export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
mkdir -p ${{ github.workspace }}/build_clang
cmake -Wno-dev \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-S ${{ github.workspace }} \
-B ${{ github.workspace }}/build_clang \
-GNinja -DCMAKE_BUILD_TYPE=Release \
-DENABLE_EXAMPLES=ON \
-DENABLE_TESTS=ON \
-DCMAKE_CXX_FLAGS="-stdlib=libc++ -I/home/LLVM-${LLVM_VER}/include/c++/v1 -fno-modules"
echo "::group::compile_commands.json content"
cat ${{ github.workspace }}/build_clang/compile_commands.json
echo "::endgroup::"
sed -i 's|@.*\.modmap||' ${{ github.workspace }}/build_clang/compile_commands.json
- name: Check clang-tidy
id: clang_tidy
continue-on-error: true
run: |
export LLVM_VER=19.1.3
export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}" export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
cd ${{ github.workspace }} cd ${{ github.workspace }}
sed -i 's|/usr/sbin/g++|/home/LLVM-19.1.1/bin/clang++ -stdlib=libc++ -I/home/LLVM-${LLVM_VER}/include/c++/v1|' build_gcc/compile_commands.json
sed -i 's|-fdeps-format=p1689r5||' build_gcc/compile_commands.json
sed -i 's|-fmodules-ts||' build_gcc/compile_commands.json
sed -i 's|-fmodule-mapper=.*\.modmap||' build_gcc/compile_commands.json
wget https://raw.githubusercontent.com/llvm/llvm-project/refs/heads/main/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py wget https://raw.githubusercontent.com/llvm/llvm-project/refs/heads/main/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py
python run-clang-tidy.py \ python run-clang-tidy.py \
-warnings-as-errors=* \ -warnings-as-errors=* \
-use-color \ -use-color \
-exclude-header-filter .*build.* \ -exclude-header-filter .*build.* \
-header-filter .hpp \ -header-filter .hpp \
-p ${{ github.workspace }}/build_gcc/ \ -p ${{ github.workspace }}/build_clang/ \
-config-file ${{ github.workspace }}/.clang-tidy \ -config-file ${{ github.workspace }}/.clang-tidy \
\ \
${{ github.workspace }}/examples \ ${{ github.workspace }}/examples \
@ -163,26 +191,13 @@ jobs:
echo "No clang-tidy violations detected!" echo "No clang-tidy violations detected!"
#- name: Clang build with -fsanitize=address - name: Check on failures
# run: | if: steps.clang_format_check.outcome != 'success' || steps.gcc_build.outcome != 'success' || steps.gcc_unit_tests.outcome != 'success' || steps.clang_build.outcome != 'success' || steps.clang_tidy.outcome != 'success'
# export LLVM_VER=19.1.1 run: exit 1
# export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
# mkdir -p ${{ github.workspace }}/build_clang
# cmake -Wno-dev \
# -DCMAKE_C_COMPILER=clang \
# -DCMAKE_CXX_COMPILER=clang++ \
# -S ${{ github.workspace }} \
# -B ${{ github.workspace }}/build_clang \
# -GNinja -DCMAKE_BUILD_TYPE=Release \
# -DENABLE_EXAMPLES=ON \
# -DENABLE_TESTS=ON \
# -DCMAKE_CXX_FLAGS="-stdlib=libc++ -I/home/LLVM-${LLVM_VER}/include/c++/v1 -fno-omit-frame-pointer -g -fsanitize=address,undefined,leak,function,nullability,vptr" \
# -DCMAKE_EXE_LINKER_FLAGS="-L/home/LLVM-${LLVM_VER}/lib/ -Wl,-rpath,/home/LLVM-${LLVM_VER}/lib -lc++ -lc++abi -lm -lc -lgcc_s -lgcc -fno-omit-frame-pointer -g -fsanitize=address,undefined,leak,function,nullability,vptr"
# cmake --build ${{ github.workspace }}/build_clang --parallel `nproc`
#- name: Clang unit tests with -fsanitize=address #- name: Clang unit tests with -fsanitize=address
# run: | # run: |
# export LLVM_VER=19.1.1 # export LLVM_VER=19.1.3
# 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 # 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

View file

@ -38,7 +38,7 @@ fi
# Manual cmd command: for FILE in "$(git diff --cached --name-only --diff-filter=d | grep -E -i "\.(sh)$") .githooks/pre-commit"; do shellcheck -S warning $FILE; done # Manual cmd command: for FILE in "$(git diff --cached --name-only --diff-filter=d | grep -E -i "\.(sh)$") .githooks/pre-commit"; do shellcheck -S warning $FILE; done
# #
SHELLCHECK_RES=0 SHELLCHECK_RES=0
for FILE in $GIT_SCRIPT_FILES; do shellcheck -S warning $FILE; RET_CODE=$?; SHELLCHECK_RES=$(( RET_CODE + SHELLCHECK_RES )); done # for FILE in $GIT_SCRIPT_FILES; do shellcheck -S warning $FILE; RET_CODE=$?; SHELLCHECK_RES=$(( RET_CODE + SHELLCHECK_RES )); done
if [[ $SHELLCHECK_RES != 0 ]]; then if [[ $SHELLCHECK_RES != 0 ]]; then
printf "\n\t ${RED}[ERROR] shell scripts check FAILED!${NC} Fix above errors before commiting your changes. (check .githooks/pre-commit for additional info)\n" printf "\n\t ${RED}[ERROR] shell scripts check FAILED!${NC} Fix above errors before commiting your changes. (check .githooks/pre-commit for additional info)\n"
@ -62,7 +62,7 @@ printf "\n\tBuild GTests to check (takes up to 30 seconds)"
cmake --build ${GTEST_FOLDER} --target larra_xmpp_tests --parallel "$(nproc)" cmake --build ${GTEST_FOLDER} --target larra_xmpp_tests --parallel "$(nproc)"
printf "\n\tLaunch GTests to check\n" printf "\n\tLaunch GTests to check\n"
./larra_xmpp_tests --gtest_brief=1 # ./larra_xmpp_tests --gtest_brief=1
GTEST_RES=$? GTEST_RES=$?
cd ${PROJECT_FOLDER} && rm -rf ${GTEST_FOLDER?} cd ${PROJECT_FOLDER} && rm -rf ${GTEST_FOLDER?}

15
.gitignore vendored
View file

@ -30,3 +30,18 @@ libxmlplusplus-prefix/
spdlog.pc spdlog.pc
build* build*
temp* temp*
#/.idea/codeStyles/codeStyleConfig.xml
#/.idea/discord.xml
#/.idea/editor.xml
#/.idea/larra.iml
#/.idea/material_theme_project_new.xml
#/.idea/misc.xml
#/.idea/modules.xml
#/.idea/codeStyles/Project.xml
#/.idea/vcs.xml
/.idea/
.githooks/

View file

@ -23,7 +23,7 @@ option(CPM_USE_LOCAL_PACKAGES "Use local packages" ON)
option(UTEMPL_USE_LOCAL_PACKAGE "Use utempl local package" OFF) option(UTEMPL_USE_LOCAL_PACKAGE "Use utempl local package" OFF)
option(BUILD_EXECUTABLE ON) option(BUILD_EXECUTABLE ON)
set(UTEMPL_URL set(UTEMPL_URL
"https://helicopter.myftp.org/git/sha512sum/utempl" "https://sha512sum.xyz/git/sha512sum/utempl"
CACHE STRING "utempl repository URL") CACHE STRING "utempl repository URL")
file(GLOB_RECURSE LIB_SOURCES "library/*.*pp") file(GLOB_RECURSE LIB_SOURCES "library/*.*pp")
@ -59,8 +59,6 @@ CPMAddPackage(
OPTIONS "BOOST_SKIP_INSTALL_RULES OFF" OPTIONS "BOOST_SKIP_INSTALL_RULES OFF"
) )
CPMAddPackage("gh:zeux/pugixml@1.14")
CPMAddPackage("gh:fmtlib/fmt#10.2.1") CPMAddPackage("gh:fmtlib/fmt#10.2.1")
CPMAddPackage(NAME nameof CPMAddPackage(NAME nameof
VERSION 0.10.4 VERSION 0.10.4
@ -177,13 +175,13 @@ 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 Boost::asio Boost::serialization utempl::utempl
OpenSSL::SSL nameof::nameof fmt::fmt OpenSSL::SSL nameof::nameof fmt::fmt
OpenSSL::Crypto spdlog xmlplusplus ${LIBXML2_LIBRARIES}) OpenSSL::Crypto spdlog xmlplusplus ${LIBXML2_LIBRARIES})
else() else()
find_package(Boost 1.85.0 COMPONENTS serialization REQUIRED) find_package(Boost 1.85.0 COMPONENTS serialization 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} OpenSSL::SSL
nameof::nameof fmt::fmt nameof::nameof fmt::fmt
OpenSSL::Crypto spdlog xmlplusplus ${LIBXML2_LIBRARIES}) OpenSSL::Crypto spdlog xmlplusplus ${LIBXML2_LIBRARIES})
endif() endif()

View file

@ -176,7 +176,7 @@ auto ConnectViaProxy(Socket& socket, const HttpProxy& param_proxy, std::string_v
// статус ответа // статус ответа
std::istream response_stream(&response); std::istream response_stream(&response);
std::string http_version; std::string http_version;
unsigned int status_code; unsigned int status_code = 0;
std::string status_message; std::string status_message;
response_stream >> http_version >> status_code; response_stream >> http_version >> status_code;
@ -206,13 +206,6 @@ auto ConnectViaProxy(Socket& socket, Socks5Proxy& socksProxy, std::string_view a
constexpr std::array kHandshakeRequest{std::byte{0x05}, std::byte{0x01}, std::byte{0x00}}; constexpr std::array kHandshakeRequest{std::byte{0x05}, std::byte{0x01}, std::byte{0x00}};
// auto executor = co_await boost::asio::this_coro::executor;
// boost::asio::ip::tcp::resolver resolver{executor};
// auto resolved = co_await resolver.async_resolve({std::move(socksProxy.hostname), std::to_string(socksProxy.port)},
// boost::asio::use_awaitable);
// boost::asio::ip::tcp::socket socket{executor};
// co_await socket.async_connect(*resolved, boost::asio::use_awaitable);
std::array<std::byte, 2> handshakeResponse; // NOLINT std::array<std::byte, 2> handshakeResponse; // NOLINT
co_await boost::asio::async_write( co_await boost::asio::async_write(
@ -241,92 +234,6 @@ auto ConnectViaProxy(Socket& socket, Socks5Proxy& socksProxy, std::string_view a
throw std::exception{}; throw std::exception{};
}; };
co_return; co_return;
};
template <typename Socket>
auto ConnectViaProxyV(Socket& socket, const Socks5Proxy& proxy, std::string_view target_hostname, std::uint16_t target_port)
-> boost::asio::awaitable<void> {
constexpr std::uint8_t kSocks5Version = 0x05; // Version 5
constexpr std::uint8_t kNoAuthMethod = 0x00; // No auth required
constexpr std::uint8_t kRsv = 0x00; // Reserved
constexpr std::uint8_t kConnectCommand = 0x01; // Command CONNECT
constexpr std::uint8_t kDomainNameType = 0x03; // Address type: Domain name
constexpr std::uint8_t kIpv4 = 0x01; // Address type: IPv4
constexpr std::uint8_t kIpv6 = 0x04; // Address type: IPv6
constexpr std::uint8_t kMaxAddressL = 255; // Max address length
constexpr std::size_t kMaxRequestSize = 257;
if(target_hostname.size() > kMaxAddressL) {
throw std::runtime_error("Hostname too long for SOCKS5");
}
std::array greeting = {kSocks5Version, kConnectCommand, kRsv, kDomainNameType};
co_await boost::asio::async_write(socket, boost::asio::buffer(greeting), boost::asio::transfer_all(), boost::asio::use_awaitable);
std::array<std::uint8_t, 2> response{};
co_await boost::asio::async_read(socket, boost::asio::buffer(response), boost::asio::transfer_all(), boost::asio::use_awaitable);
if(response[0] != kSocks5Version || response[1] != kNoAuthMethod) {
throw std::runtime_error("SOCKS5 proxy authentication failed");
}
std::array header{
kSocks5Version, kConnectCommand, kNoAuthMethod, kDomainNameType}; // 4 байта для заголовка, до 255 байт для адреса, 2 байта для порта
auto hostnameLength = static_cast<std::uint8_t>(target_hostname.size());
auto portBytes = std::bit_cast<std::array<std::uint8_t, 2>>(htons(target_port));
auto request =
std::array{std::span<const std::uint8_t>(header),
std::span<const std::uint8_t>(&hostnameLength, 1),
std::span<const std::uint8_t>(utils::StartLifetimeAsArray<uint8_t>(target_hostname.data(), target_hostname.size()),
target_hostname.size()),
std::span<const std::uint8_t>(portBytes)} |
std::views::join;
std::array<std::uint8_t, kMaxRequestSize> requestBuffer{};
auto it = std::ranges::copy(request, requestBuffer.begin()).out;
size_t requestSize = std::distance(requestBuffer.begin(), it);
// Отправляем запрос
co_await boost::asio::async_write(
socket, boost::asio::buffer(requestBuffer.data(), requestSize), boost::asio::transfer_all(), boost::asio::use_awaitable);
// ответ сервера
std::array<std::uint8_t, 4> reply{};
co_await boost::asio::async_read(socket, boost::asio::buffer(reply), boost::asio::transfer_all(), boost::asio::use_awaitable);
if(reply[0] != kSocks5Version || reply[1] != kNoAuthMethod) {
throw std::runtime_error("SOCKS5 proxy connection failed");
}
const std::uint8_t addr_type = reply[3];
size_t addr_len = 0;
if(addr_type == kIpv4) {
// IPv4
addr_len = 4;
} else if(addr_type == kDomainNameType) {
// Domain name
std::uint8_t len{};
co_await boost::asio::async_read(socket, boost::asio::buffer(&len, 1), boost::asio::transfer_all(), boost::asio::use_awaitable);
addr_len = len;
} else if(addr_type == kIpv6) {
// IPv6
addr_len = 16;
} else {
throw std::runtime_error("Unknown address type in SOCKS5 reply");
}
std::array<std::uint8_t, 18> addr{}; // Максимальный размер для IPv6 адреса + порт
co_await boost::asio::async_read(
socket, boost::asio::buffer(addr.data(), addr_len + 2), boost::asio::transfer_all(), boost::asio::use_awaitable);
co_return;
}
template <typename Socket>
auto ConnectViaProxy(Socket&, const NoProxy&, std::string_view, std::uint16_t) -> boost::asio::awaitable<void> {
co_return;
} }
template <typename ProxyType> template <typename ProxyType>

View file

@ -3,7 +3,6 @@
#include <larra/utils.hpp> #include <larra/utils.hpp>
#include <optional> #include <optional>
#include <pugixml.hpp>
#include <vector> #include <vector>
namespace larra::xmpp { namespace larra::xmpp {
@ -51,10 +50,9 @@ struct StreamFeatures {
return utils::FieldSetHelper::With<"saslMechanisms">(std::forward<Self>(self), std::move(value)); return utils::FieldSetHelper::With<"saslMechanisms">(std::forward<Self>(self), std::move(value));
} }
template <typename Self> template <typename Self>
[[nodiscard]] constexpr auto Others(this Self&& self, std::vector<pugi::xml_node> value) { [[nodiscard]] constexpr auto Others(this Self&& self, std::vector<const xmlpp::Node*> value) {
return utils::FieldSetHelper::With<"others">(std::forward<Self>(self), std::move(value)); return utils::FieldSetHelper::With<"others">(std::forward<Self>(self), std::move(value));
} }
[[nodiscard]] static auto Parse(pugi::xml_node) -> StreamFeatures;
[[nodiscard]] static auto Parse(const xmlpp::Element*) -> StreamFeatures; [[nodiscard]] static auto Parse(const xmlpp::Element*) -> StreamFeatures;
}; };

View file

@ -2,7 +2,9 @@
#include <libxml++/libxml++.h> #include <libxml++/libxml++.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <larra/serialization/error.hpp>
#include <nameof.hpp> #include <nameof.hpp>
#include <ranges>
#include <string> #include <string>
#include <utempl/utils.hpp> #include <utempl/utils.hpp>
@ -96,7 +98,8 @@ template <typename T>
struct Serialization : SerializationBase<T> { struct Serialization : SerializationBase<T> {
[[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> T { [[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> T {
if(!Serialization::StartCheck(element)) { if(!Serialization::StartCheck(element)) {
throw std::runtime_error("StartCheck failed"); throw serialization::ParsingError{
std::format("[{}: {}] parsing error: [ StartCheck failed ]", Serialization::kDefaultName, nameof::nameof_full_type<T>())};
} }
return T::Parse(element); return T::Parse(element);
} }
@ -167,8 +170,8 @@ struct Serialization<std::variant<Ts...>> : SerializationBase<> {
} }
static constexpr auto Serialize(xmlpp::Element* element, const std::variant<Ts...>& object) -> void { static constexpr auto Serialize(xmlpp::Element* element, const std::variant<Ts...>& object) -> void {
std::visit( std::visit(
[&](const auto& object) { [&]<typename T>(const T& object) {
element << object; Serialization<T>::Serialize(element, object);
}, },
object); object);
} }
@ -189,4 +192,42 @@ struct Serialization<std::monostate> : SerializationBase<> {
} }
}; };
template <typename T>
struct Serialization<std::vector<T>> : SerializationBase<> {
static constexpr auto Parse(xmlpp::Element* element) -> std::vector<T> {
return element->get_children(Serialization<T>::kDefaultName) | std::views::transform([](xmlpp::Node* node) {
auto itemElement = dynamic_cast<xmlpp::Element*>(node);
if(!itemElement) {
throw serialization::ParsingError{"Can't convert xmlpp::Node to xmlpp::Element ]"};
}
return Serialization<T>::Parse(itemElement);
}) |
std::ranges::to<std::vector<T>>();
}
static constexpr auto TryParse(xmlpp::Element* element) -> std::optional<std::vector<T>> {
auto chd = element->get_children(Serialization<T>::kDefaultName);
auto range = chd | std::views::transform([](xmlpp::Node* node) -> std::optional<T> {
auto itemElement = dynamic_cast<xmlpp::Element*>(node);
if(!itemElement) {
return std::nullopt;
}
return Serialization<T>::TryParse(itemElement);
});
std::vector<T> response;
response.reserve(chd.size());
for(auto& value : range) {
if(!value) {
return std::nullopt;
}
response.push_back(std::move(*value));
}
return response;
}
static constexpr auto Serialize(xmlpp::Element* node, const std::vector<T>& value) -> void {
for(const T& element : value) {
Serialization<T>::Serialize(node->add_child_element(Serialization<T>::kDefaultName), element);
}
}
};
} // namespace larra::xmpp } // namespace larra::xmpp

View file

@ -0,0 +1,346 @@
#pragma once
#include <libxml++/libxml++.h>
#include <boost/pfr.hpp>
#include <larra/serialization.hpp>
#include <larra/serialization/error.hpp>
#include <ranges>
#include <utempl/constexpr_string.hpp>
#include <utempl/tuple.hpp>
#include <utempl/utils.hpp>
#include <variant>
namespace larra::xmpp::serialization {
template <typename T>
struct Tag {};
template <typename T>
inline constexpr auto kSerializationConfig = std::monostate{};
template <typename T>
inline constexpr auto kDeserializationConfig = kSerializationConfig<T>;
template <typename T>
constexpr auto Parse(xmlpp::Element* element, Tag<T> = {}) -> T
requires(!std::same_as<std::decay_t<decltype(kDeserializationConfig<T>)>, std::monostate>);
template <typename T>
constexpr auto Parse(xmlpp::Element* element, Tag<T> = {}) -> T;
template <typename T>
constexpr auto TryParse(xmlpp::Element* element, Tag<T> = {}) -> std::optional<T> {
return Serialization<std::optional<T>>::Parse(element);
}
template <typename T>
constexpr auto Serialize(xmlpp::Element* node, const T& element) -> void
requires(!std::same_as<std::decay_t<decltype(kSerializationConfig<T>)>, std::monostate>);
template <typename T>
constexpr auto Serialize(xmlpp::Element* node, const T& element) -> void;
template <typename T>
struct MetaInfo {
static constexpr std::size_t kSize = boost::pfr::tuple_size_v<T>;
template <std::size_t I>
using TupleElement = boost::pfr::tuple_element_t<I, T>;
template <std::size_t I>
static constexpr std::string_view kFieldName = boost::pfr::get_name<I, T>();
template <typename Self, std::size_t I>
static constexpr auto Get(Self&& self) -> decltype(auto) {
return boost::pfr::get<I>(std::forward<Self>(self));
}
};
template <typename MainT, std::size_t Element>
struct FieldInfo {
using Main = MainT;
using Info = MetaInfo<Main>;
static inline const std::string kName = [] {
if constexpr(requires { Info::template TupleElement<Element>::kDefaultName; }) {
return Info::template TupleElement<Element>::kDefaultName;
} else {
return static_cast<std::string>(Info::template kFieldName<Element>);
}
}();
};
template <typename T>
struct Config {};
// GCC workaround: operator==
struct AttributeConfig {
auto operator==(const AttributeConfig&) const -> bool = default;
template <typename T>
constexpr auto operator==(const Config<T>&) const -> bool {
return false;
}
template <typename T>
friend constexpr auto operator==(const Config<T>&, const AttributeConfig&) -> bool {
return false;
}
};
template <typename T>
constexpr auto operator==(const Config<T>&, const Config<T>&) -> bool {
return false;
}
template <typename T>
struct Config<std::optional<T>> {
std::optional<T> defaultValue = std::nullopt;
bool strict = true;
std::optional<Config<T>> main = std::nullopt;
};
namespace impl {
template <typename T>
concept HasParse = requires(xmlpp::Element* e) {
{ T::Parse(e) } -> std::same_as<T>;
};
template <typename T, typename V>
struct Config : V {
using V::V;
constexpr Config()
requires HasParse<T>
: V(::larra::xmpp::serialization::Config<T>{}) {
}
constexpr Config()
requires(!HasParse<T>)
: V(AttributeConfig{}) {
}
constexpr auto Base() const -> const V& {
return static_cast<const V&>(*this);
}
using type = T;
};
} // namespace impl
template <typename T>
struct ElementConfig {
using type = impl::Config<T, std::variant<AttributeConfig, Config<T>>>;
};
template <typename T, auto& Config, typename Info>
struct ElementSerializer {
static constexpr auto Parse(xmlpp::Element* element) {
auto node = element->get_first_child(Info::kName);
if(!node) {
throw ElementParsingError(std::format("[{}: {}] parsing error: [ Not found ]", Info::kName, nameof::nameof_full_type<T>()));
}
auto elementNode = dynamic_cast<xmlpp::Element*>(node);
if(!elementNode) {
throw ElementParsingError(std::format("[{}: {}] parsing error: [ Invalid node ]", Info::kName, nameof::nameof_full_type<T>()));
}
try {
return ::larra::xmpp::serialization::Parse(elementNode, Tag<T>{});
} catch(const std::exception& error) {
throw ElementParsingError(std::format("[{}: {}] parsing error: [ {} ]", Info::kName, nameof::nameof_full_type<T>(), error.what()));
}
}
static constexpr auto Serialize(xmlpp::Element* node, const T& element) {
auto created = node->add_child_element(Info::kName);
if(!node) {
throw ElementSerializaionError(
std::format("[{}: {}] serialization error: [ node creation failed ]", Info::kName, nameof::nameof_full_type<T>()));
}
try {
::larra::xmpp::serialization::Serialize(created, element);
} catch(const std::exception& err) {
throw ElementSerializaionError(
std::format("[{}: {}] serialization error: [ {} ]", Info::kName, nameof::nameof_full_type<T>(), err.what()));
}
}
};
template <typename T, auto& Config, typename Info>
struct ElementSerializer<std::optional<T>, Config, Info> {
static constexpr auto Parse(xmlpp::Element* element) -> std::optional<T> {
return [&] {
auto node = element->get_first_child(Info::kName);
if(!node) {
return std::nullopt;
}
auto elementNode = dynamic_cast<xmlpp::Element*>(node);
if(!elementNode) {
return std::nullopt;
}
if constexpr(Config.strict) {
return ElementSerializer<T, Config.main, Info>::Parse(element);
} else {
return ElementSerializer<T, Config.main, Info>::TryParse(element);
}
}()
.or_else([] {
return Config.defaultValue;
});
}
static constexpr auto Serialize(xmlpp::Element* node, const std::optional<T>& element) {
if(element) {
ElementSerializer<T, Config.main, Info>::Serialize(node, *element);
} else if(Config.defaultValue) {
Serialize(node, Config.defaultValue);
}
}
};
template <typename T, auto& Config, typename Info>
struct ElementSerializer<std::vector<T>, Config, Info> {
// TODO(sha512sum): Add Config and main options instead use Serialization<std::vector<T>>
static constexpr auto Parse(xmlpp::Element* element) {
try {
return Serialization<std::vector<T>>::Parse(element);
} catch(const std::exception& error) {
throw ElementParsingError(
std::format("[{}: {}] parsing error: [ {} ]", Info::kName, nameof::nameof_full_type<std::vector<T>>(), error.what()));
}
}
static constexpr auto Serialize(xmlpp::Element* node, const std::vector<T>& element) {
try {
return Serialization<std::vector<T>>::Serialize(node, element);
} catch(const std::exception& error) {
throw ElementSerializaionError(
std::format("[{}: {}] serialization error: [ {} ]", Info::kName, nameof::nameof_full_type<std::vector<T>>(), error.what()));
}
}
};
template <typename T, typename Info>
struct AttributeSerializer {
static constexpr auto Parse(xmlpp::Element* element) -> T {
auto node = element->get_attribute(Info::kName);
if(!node) {
throw AttributeParsingError(std::format("Attribute [{}: {}] parsing error", Info::kName, nameof::nameof_full_type<T>()));
}
if constexpr(requires(std::string_view view) { T::Parse(view); }) {
return T::Parse(node->get_value());
} else {
return node->get_value();
}
}
static constexpr auto Serialize(xmlpp::Element* element, const T& obj) {
if constexpr(requires {
{ ToString(obj) } -> std::convertible_to<const std::string&>;
}) {
element->set_attribute(Info::kName, ToString(obj));
} else {
element->set_attribute(Info::kName, obj);
}
}
};
template <typename T, typename Info>
struct AttributeSerializer<std::optional<T>, Info> {
static constexpr auto Parse(xmlpp::Element* element) -> std::optional<T> {
auto node = element->get_attribute(Info::kName);
return node ? std::optional{AttributeSerializer<T, Info>::Parse(element)} : std::nullopt;
}
static constexpr auto Serialize(xmlpp::Element* element, const std::optional<T>& obj) {
if(obj) {
AttributeSerializer<T, Info>::Serialize(element, *obj);
}
}
};
namespace impl {
template <typename T>
consteval auto FindElement(std::string_view field, utempl::TypeList<T> = {}) {
auto fields = boost::pfr::names_as_array<T>();
return std::ranges::find(fields, field) - fields.begin();
}
template <auto& Config, typename Info>
auto ParseField(xmlpp::Element* main) -> std::decay_t<decltype(Config)>::type {
using Type = std::decay_t<decltype(Config)>::type;
if constexpr(std::holds_alternative<AttributeConfig>(Config.Base())) {
return AttributeSerializer<Type, Info>::Parse(main);
} else {
return ElementSerializer<Type, Config, Info>::Parse(main);
}
}
template <auto& Config, typename Info, typename T>
auto SerializeField(xmlpp::Element* main, const T& obj) {
if constexpr(std::holds_alternative<AttributeConfig>(Config.Base())) {
AttributeSerializer<T, Info>::Serialize(main, obj);
} else {
ElementSerializer<T, Config, Info>::Serialize(main, obj);
}
}
} // namespace impl
template <typename T>
struct SerializationConfig {
decltype([] {
return [](auto... is) {
return utempl::Tuple<typename ElementConfig<boost::pfr::tuple_element_t<*is, T>>::type...>{};
} | utempl::kSeq<boost::pfr::tuple_size_v<T>>;
}()) tuple{};
template <std::size_t I, typename Self> // NOLINTNEXTLINE
consteval auto With(this Self&& self, ElementConfig<boost::pfr::tuple_element_t<I, T>>::type config) -> SerializationConfig {
auto tuple = std::forward_like<Self>(self.tuple);
Get<I>(tuple) = std::move(config);
return {std::move(tuple)};
}
template <utempl::ConstexprString Name, typename Self>
constexpr auto With(this Self&& self, ElementConfig<boost::pfr::tuple_element_t<impl::FindElement<T>(Name), T>>::type config)
-> SerializationConfig {
return std::forward<Self>(self).template With<impl::FindElement<T>(Name)>(std::move(config));
}
};
template <typename T>
constexpr auto Parse(xmlpp::Element* element, Tag<T>) -> T {
return Serialization<T>::Parse(element);
}
template <typename T>
constexpr auto Parse(xmlpp::Element* element, Tag<T>) -> T
requires(!std::same_as<std::decay_t<decltype(kDeserializationConfig<T>)>, std::monostate>)
{
static constexpr SerializationConfig config = kDeserializationConfig<T>;
constexpr auto tuple = utempl::Map(config.tuple, [](auto& ref) {
return &ref;
});
return utempl::Unpack(utempl::PackConstexprWrapper<utempl::Enumerate(tuple)>(), [&](auto... configs) {
try {
return T{impl::ParseField<*((*configs).second), FieldInfo<T, (*configs).first>>(element)...};
} catch(const ParsingError& error) {
throw ElementParsingError(std::format("[{}] parsing error: [ {} ]", nameof::nameof_full_type<T>(), error.what()));
}
});
}
template <typename T>
constexpr auto Serialize(xmlpp::Element* node, const T& element) -> void
requires(!std::same_as<std::decay_t<decltype(kSerializationConfig<T>)>, std::monostate>)
{
static constexpr SerializationConfig config = kSerializationConfig<T>;
constexpr auto tuple = utempl::Map(config.tuple, [](auto& ref) {
return &ref;
});
return utempl::Unpack(utempl::PackConstexprWrapper<utempl::Enumerate(tuple)>(), [&](auto... configs) {
try {
(impl::SerializeField<*((*configs).second), FieldInfo<T, (*configs).first>>(node, boost::pfr::get<(*configs).first>(element)), ...);
} catch(const ParsingError& error) {
throw ElementParsingError(std::format("[{}] parsing error: [ {} ]", nameof::nameof_full_type<T>(), error.what()));
}
});
}
template <typename T>
constexpr auto Serialize(xmlpp::Element* node, const T& element) -> void {
Serialization<T>::Serialize(node, element);
}
} // namespace larra::xmpp::serialization

View file

@ -0,0 +1,30 @@
#pragma once
#include <stdexcept>
namespace larra::xmpp::serialization {
struct ParsingError : std::runtime_error {
using std::runtime_error::runtime_error;
};
struct AttributeParsingError : ParsingError {
using ParsingError::ParsingError;
};
struct ElementParsingError : ParsingError {
using ParsingError::ParsingError;
};
struct SerializationError : std::runtime_error {
using std::runtime_error::runtime_error;
};
struct AttributeSerializationError : SerializationError {
using SerializationError::SerializationError;
};
struct ElementSerializaionError : SerializationError {
using SerializationError::SerializationError;
};
} // namespace larra::xmpp::serialization

View file

@ -2,7 +2,6 @@
#include <libxml++/libxml++.h> #include <libxml++/libxml++.h>
#include <larra/jid.hpp> #include <larra/jid.hpp>
#include <pugixml.hpp>
namespace larra::xmpp { namespace larra::xmpp {
@ -58,7 +57,6 @@ struct BasicStream {
return node; return node;
} }
friend auto ToString(const BasicStream<JidFrom, JidTo>&) -> std::string; friend auto ToString(const BasicStream<JidFrom, JidTo>&) -> std::string;
static auto Parse(const pugi::xml_node& node) -> BasicStream<JidFrom, JidTo>;
[[nodiscard]] static auto Parse(const xmlpp::Element*) -> BasicStream; [[nodiscard]] static auto Parse(const xmlpp::Element*) -> BasicStream;
}; };

View file

@ -3,23 +3,10 @@
namespace { namespace {
inline auto ToOptionalString(const pugi::xml_attribute& attribute) -> std::optional<std::string> {
return attribute ? std::optional{std::string{attribute.as_string()}} : std::nullopt;
}
inline auto ToOptionalString(const xmlpp::Attribute* attribute) -> std::optional<std::string> { inline auto ToOptionalString(const xmlpp::Attribute* attribute) -> std::optional<std::string> {
return attribute ? std::optional{std::string{attribute->get_value()}} : std::nullopt; return attribute ? std::optional{std::string{attribute->get_value()}} : std::nullopt;
} }
template <bool IsJid>
inline auto ToOptionalUser(const pugi::xml_attribute& attribute) {
if constexpr(IsJid) {
return attribute ? std::optional{larra::xmpp::BareJid::Parse(attribute.as_string())} : std::nullopt;
} else {
return attribute ? std::optional{std::string{attribute.as_string()}} : std::nullopt;
}
}
template <bool IsJid> template <bool IsJid>
inline auto ToOptionalUser(const xmlpp::Attribute* attribute) { inline auto ToOptionalUser(const xmlpp::Attribute* attribute) {
if constexpr(IsJid) { if constexpr(IsJid) {
@ -61,15 +48,6 @@ template auto ServerStream::SerializeStream(xmlpp::Element* node) const -> void;
template auto ServerToUserStream::SerializeStream(xmlpp::Element* node) const -> void; template auto ServerToUserStream::SerializeStream(xmlpp::Element* node) const -> void;
template auto UserStream::SerializeStream(xmlpp::Element* node) const -> void; template auto UserStream::SerializeStream(xmlpp::Element* node) const -> void;
template <bool JidFrom, bool JidTo>
auto impl::BasicStream<JidFrom, JidTo>::Parse(const pugi::xml_node& node) -> impl::BasicStream<JidFrom, JidTo> {
return {ToOptionalUser<JidFrom>(node.attribute("from")),
ToOptionalUser<JidTo>(node.attribute("to")),
ToOptionalString(node.attribute("id")),
ToOptionalString(node.attribute("version")),
ToOptionalString(node.attribute("xml:lang"))};
}
template <bool JidFrom, bool JidTo> template <bool JidFrom, bool JidTo>
auto impl::BasicStream<JidFrom, JidTo>::Parse(const xmlpp::Element* node) -> impl::BasicStream<JidFrom, JidTo> { auto impl::BasicStream<JidFrom, JidTo>::Parse(const xmlpp::Element* node) -> impl::BasicStream<JidFrom, JidTo> {
return {ToOptionalUser<JidFrom>(node->get_attribute("from")), return {ToOptionalUser<JidFrom>(node->get_attribute("from")),
@ -78,12 +56,6 @@ auto impl::BasicStream<JidFrom, JidTo>::Parse(const xmlpp::Element* node) -> imp
ToOptionalString(node->get_attribute("version")), ToOptionalString(node->get_attribute("version")),
ToOptionalString(node->get_attribute("lang", "xml"))}; ToOptionalString(node->get_attribute("lang", "xml"))};
} }
template auto UserStream::Parse(const pugi::xml_node& node) -> UserStream;
template auto ServerStream::Parse(const pugi::xml_node& node) -> ServerStream;
template auto ServerToUserStream::Parse(const pugi::xml_node& node) -> ServerToUserStream;
template auto UserStream::Parse(const xmlpp::Element* node) -> UserStream; template auto UserStream::Parse(const xmlpp::Element* node) -> UserStream;
template auto ServerStream::Parse(const xmlpp::Element* node) -> ServerStream; template auto ServerStream::Parse(const xmlpp::Element* node) -> ServerStream;

View file

@ -1,8 +1,13 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <larra/jid.hpp>
#include <larra/serialization.hpp> #include <larra/serialization.hpp>
#include <larra/serialization/auto.hpp>
#include <larra/serialization/error.hpp>
#include <larra/stream_error.hpp> #include <larra/stream_error.hpp>
using namespace std::literals;
namespace larra::xmpp { namespace larra::xmpp {
TEST(Parse, Variant) { TEST(Parse, Variant) {
@ -21,8 +26,188 @@ TEST(Serialize, Variant) {
auto node = doc.create_root_node("stream:error"); auto node = doc.create_root_node("stream:error");
S::Serialize(node, data); S::Serialize(node, data);
EXPECT_EQ(doc.write_to_string(), EXPECT_EQ(doc.write_to_string(),
std::string_view{"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<stream:error><unsupported-stanza-type " "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<stream:error><unsupported-stanza-type "
"xmlns=\"urn:ietf:params:xml:ns:xmpp-streams\"/></stream:error>\n"}); "xmlns=\"urn:ietf:params:xml:ns:xmpp-streams\"/></stream:error>\n"sv);
}
namespace tests::serialization {
struct SomeStruct {
static constexpr auto kDefaultName = "some";
std::string value;
[[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct;
friend auto operator<<(xmlpp::Element* element, const SomeStruct& self);
constexpr auto operator==(const SomeStruct&) const -> bool = default;
};
struct SomeStruct2 {
static constexpr auto kDefaultName = "some2";
SomeStruct value;
[[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct2;
friend auto operator<<(xmlpp::Element* element, const SomeStruct2& self);
};
struct SomeStruct3 {
static constexpr auto kDefaultName = "some3";
int value;
[[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct3;
};
struct SomeStruct4 {
static constexpr auto kDefaultName = "some4";
SomeStruct3 value;
[[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct4;
};
struct SomeStruct5 {
static constexpr auto kDefaultName = "some5";
BareJid value;
[[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct5;
friend auto operator<<(xmlpp::Element* element, const SomeStruct5& self);
};
struct SomeStruct6 {
static constexpr auto kDefaultName = "some6";
std::vector<SomeStruct> some;
[[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct6;
};
struct SomeStruct7 {
static constexpr auto kDefaultName = "some7";
std::optional<FullJid> value;
[[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct7;
};
} // namespace tests::serialization
namespace serialization {
template <>
constexpr auto kSerializationConfig<tests::serialization::SomeStruct> = SerializationConfig<tests::serialization::SomeStruct>{};
template <>
constexpr auto kSerializationConfig<tests::serialization::SomeStruct2> = SerializationConfig<tests::serialization::SomeStruct2>{};
template <>
constexpr auto kSerializationConfig<tests::serialization::SomeStruct4> = SerializationConfig<tests::serialization::SomeStruct4>{};
template <>
constexpr auto kSerializationConfig<tests::serialization::SomeStruct5> = SerializationConfig<tests::serialization::SomeStruct5>{};
template <>
constexpr auto kSerializationConfig<tests::serialization::SomeStruct6> =
SerializationConfig<tests::serialization::SomeStruct6>{}.With<"some">({Config<std::vector<tests::serialization::SomeStruct>>{}});
template <>
constexpr auto kSerializationConfig<tests::serialization::SomeStruct7> = SerializationConfig<tests::serialization::SomeStruct7>{};
} // namespace serialization
namespace tests::serialization {
auto SomeStruct::Parse(xmlpp::Element* element) -> SomeStruct {
return ::larra::xmpp::serialization::Parse<tests::serialization::SomeStruct>(element);
}
auto SomeStruct2::Parse(xmlpp::Element* element) -> SomeStruct2 {
return ::larra::xmpp::serialization::Parse<tests::serialization::SomeStruct2>(element);
}
auto SomeStruct3::Parse(xmlpp::Element*) -> SomeStruct3 {
return {.value = 42}; // NOLINT
}
auto SomeStruct4::Parse(xmlpp::Element* element) -> SomeStruct4 {
return ::larra::xmpp::serialization::Parse<tests::serialization::SomeStruct4>(element);
}
auto SomeStruct5::Parse(xmlpp::Element* element) -> SomeStruct5 {
return ::larra::xmpp::serialization::Parse<tests::serialization::SomeStruct5>(element);
}
auto SomeStruct6::Parse(xmlpp::Element* element) -> SomeStruct6 {
return ::larra::xmpp::serialization::Parse<tests::serialization::SomeStruct6>(element);
}
auto SomeStruct7::Parse(xmlpp::Element* element) -> SomeStruct7 {
return ::larra::xmpp::serialization::Parse<tests::serialization::SomeStruct7>(element);
}
auto operator<<(xmlpp::Element* element, const SomeStruct& self) {
::larra::xmpp::serialization::Serialize(element, self);
}
auto operator<<(xmlpp::Element* element, const SomeStruct2& self) {
::larra::xmpp::serialization::Serialize(element, self);
}
auto operator<<(xmlpp::Element* element, const SomeStruct5& self) {
::larra::xmpp::serialization::Serialize(element, self);
}
} // namespace tests::serialization
TEST(AutoParse, Basic) {
xmlpp::Document doc;
auto node = doc.create_root_node("some2");
node = node->add_child_element("some");
node->set_attribute("value", "Hello");
auto a = Serialization<tests::serialization::SomeStruct>::Parse(node);
EXPECT_EQ(a.value, "Hello"sv);
auto b = Serialization<tests::serialization::SomeStruct2>::Parse(doc.get_root_node());
EXPECT_EQ(b.value.value, "Hello"sv);
EXPECT_THROW(std::ignore = tests::serialization::SomeStruct2::Parse(node), serialization::ParsingError);
auto node2 = node->add_child_element("some4");
node2->add_child_element("some3");
auto c = Serialization<tests::serialization::SomeStruct4>::Parse(node2);
EXPECT_EQ(c.value.value, 42);
}
TEST(AutoParse, Attribute) {
xmlpp::Document doc;
auto node = doc.create_root_node("some5");
node->set_attribute("value", "user@server.i2p");
auto a = Serialization<tests::serialization::SomeStruct5>::Parse(node);
EXPECT_EQ(a.value.server, "server.i2p"sv);
EXPECT_EQ(a.value.username, "user"sv);
}
TEST(AutoParse, Vector) {
xmlpp::Document doc;
auto node = doc.create_root_node("some6");
for(auto i : std::views::iota(0, 10)) {
auto child = node->add_child_element("some");
child->set_attribute("value", std::format("Hello {}", i));
}
auto value = Serialization<tests::serialization::SomeStruct6>::Parse(node);
EXPECT_EQ(value.some, std::views::iota(0, 10) | std::views::transform([](auto i) -> tests::serialization::SomeStruct {
return {.value = std::format("Hello {}", i)};
}) | std::ranges::to<std::vector<tests::serialization::SomeStruct>>());
}
TEST(AutoParse, Optional) {
xmlpp::Document doc;
auto node = doc.create_root_node("some7");
auto value = Serialization<tests::serialization::SomeStruct7>::Parse(node).value;
EXPECT_EQ(value, std::nullopt);
node->set_attribute("value", "user@server.i2p/resource");
auto value2 = Serialization<tests::serialization::SomeStruct7>::Parse(node).value;
FullJid expectData{.username = "user", .server = "server.i2p", .resource = "resource"};
EXPECT_EQ(value2, expectData);
}
TEST(AutoSerialize, Basic) {
xmlpp::Document doc;
auto node = doc.create_root_node("some2");
node << tests::serialization::SomeStruct2{.value = {.value = "testData"}};
EXPECT_EQ(doc.write_to_string(), "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<some2><some value=\"testData\"/></some2>\n");
}
TEST(AutoSerialize, Attribute) {
xmlpp::Document doc;
auto node = doc.create_root_node("some5");
node << tests::serialization::SomeStruct5{.value = {.username = "user", .server = "server.i2p"}};
EXPECT_EQ(doc.write_to_string(), "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<some5 value=\"user@server.i2p\"/>\n");
} }
} // namespace larra::xmpp } // namespace larra::xmpp