Features: iq::Bind and iq::Roster #4
12 changed files with 346 additions and 29 deletions
12
.vscode/launch.json
vendored
12
.vscode/launch.json
vendored
|
@ -12,6 +12,18 @@
|
|||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "GCC: Build"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug: tests",
|
||||
"program": "${workspaceFolder}/build/larra_xmpp_tests",
|
||||
"args": [
|
||||
// --gtest_filter=POSTIVE_PATTERNS[-NEGATIVE_PATTERNS]
|
||||
// "--gtest_filter=Roster*"
|
||||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "GCC: Build"
|
||||
}
|
||||
]
|
||||
}
|
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
|
@ -104,7 +104,7 @@
|
|||
"presentation": {
|
||||
"clear": true
|
||||
},
|
||||
"hide": true,
|
||||
"hide": false,
|
||||
"group": {
|
||||
"kind": "build"
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include <spdlog/common.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
|
@ -7,6 +8,8 @@
|
|||
#include <larra/printer_stream.hpp>
|
||||
#include <print>
|
||||
|
||||
namespace iq = larra::xmpp::iq;
|
||||
|
||||
auto Coroutine() -> boost::asio::awaitable<void> {
|
||||
SPDLOG_INFO("Connecting client...");
|
||||
|
||||
|
@ -14,11 +17,32 @@ auto Coroutine() -> boost::asio::awaitable<void> {
|
|||
auto client = co_await larra::xmpp::client::CreateClient<larra::xmpp::PrintStream<boost::asio::ip::tcp::socket>>(
|
||||
larra::xmpp::PlainUserAccount{.jid = {.username = "test1", .server = "localhost"}, .password = "test1"},
|
||||
{.useTls = larra::xmpp::client::Options::kNever});
|
||||
|
||||
// rfc6120 7.1
|
||||
// After a client authenticates with a server,
|
||||
// it MUST bind a specific resource to the stream so that the server can properly address the client.
|
||||
co_await std::visit(
|
||||
[](auto& client) -> boost::asio::awaitable<void> {
|
||||
co_await client.CreateResourceBind();
|
||||
},
|
||||
client);
|
||||
|
||||
co_await std::visit(
|
||||
[](auto& client) -> boost::asio::awaitable<void> {
|
||||
co_await client.UpdateListOfContacts();
|
||||
},
|
||||
client);
|
||||
|
||||
// rfc6120 2.2
|
||||
// Upon authenticating with a server and binding a resource (thus becoming a connected resource as defined in [XMPP‑CORE]),
|
||||
// a client SHOULD request the roster before sending initial presence
|
||||
co_await std::visit(
|
||||
[](auto& client) -> boost::asio::awaitable<void> {
|
||||
SPDLOG_INFO("Send presence: Available");
|
||||
co_await client.Send(larra::xmpp::presence::c2s::Available{});
|
||||
},
|
||||
client);
|
||||
|
||||
} catch(const std::exception& err) {
|
||||
SPDLOG_ERROR("{}", err.what());
|
||||
co_return;
|
||||
|
|
50
library/include/larra/bind.hpp
Normal file
50
library/include/larra/bind.hpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
#include <libxml++/libxml++.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <larra/iq.hpp>
|
||||
#include <larra/jid.hpp>
|
||||
#include <optional>
|
||||
|
||||
namespace larra::xmpp::iq {
|
||||
|
||||
struct Bind {
|
||||
static constexpr auto kDefaultName = "bind";
|
||||
static constexpr auto kDefaultNamespace = "urn:ietf:params:xml:ns:xmpp-bind";
|
||||
|
||||
std::optional<FullJid> jid;
|
||||
|
||||
friend constexpr auto operator<<(xmlpp::Element* element, const Bind& bind) {
|
||||
element->set_attribute("xmlns", Bind::kDefaultNamespace);
|
||||
|
||||
if(bind.jid) {
|
||||
auto* jid_el = element->add_child_element("jid");
|
||||
jid_el->add_child_text(ToString(*bind.jid));
|
||||
}
|
||||
}
|
||||
[[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> Bind {
|
||||
const auto* jid_node = element->get_first_child("jid");
|
||||
if(!jid_node) {
|
||||
SPDLOG_DEBUG("No Jid Node at Iq::Bind");
|
||||
return {};
|
||||
}
|
||||
|
||||
auto* jid_el = dynamic_cast<const xmlpp::Element*>(jid_node);
|
||||
|
||||
if(!jid_el) {
|
||||
throw std::runtime_error("dynamic_cast to const xmlpp::Element* failed");
|
||||
}
|
||||
|
||||
const auto* text = jid_el->get_first_child_text();
|
||||
if(!jid_el) {
|
||||
throw std::runtime_error("No text at Iq::Bind jid child");
|
||||
}
|
||||
|
||||
return {.jid = (jid_node ? std::optional{FullJid::Parse(text->get_content())} : std::nullopt)};
|
||||
}
|
||||
};
|
||||
|
||||
using SetBind = Set<Bind>;
|
||||
using ResultBind = Result<Bind>;
|
||||
using IqBind = Iq<Bind>;
|
||||
|
||||
} // namespace larra::xmpp::iq
|
|
@ -11,16 +11,24 @@
|
|||
#include <boost/asio/ssl.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <charconv>
|
||||
#include <larra/bind.hpp>
|
||||
#include <larra/client/challenge_response.hpp>
|
||||
#include <larra/client/options.hpp>
|
||||
#include <larra/client/starttls_response.hpp>
|
||||
#include <larra/client/xmpp_client_stream_features.hpp>
|
||||
#include <larra/encryption.hpp>
|
||||
#include <larra/features.hpp>
|
||||
#include <larra/roster.hpp>
|
||||
#include <larra/stream.hpp>
|
||||
#include <larra/user_account.hpp>
|
||||
#include <larra/xml_stream.hpp>
|
||||
#include <ranges>
|
||||
#include <utility>
|
||||
|
||||
namespace rng = std::ranges;
|
||||
namespace views = std::views;
|
||||
namespace iq = larra::xmpp::iq;
|
||||
|
||||
namespace larra::xmpp {
|
||||
|
||||
constexpr auto kDefaultXmppPort = 5222;
|
||||
|
@ -29,12 +37,10 @@ constexpr auto kDefaultXmppPort = 5222;
|
|||
|
||||
namespace larra::xmpp::client {
|
||||
|
||||
namespace rng = std::ranges;
|
||||
namespace views = std::views;
|
||||
|
||||
template <typename Connection>
|
||||
struct Client {
|
||||
constexpr Client(BareJid jid, XmlStream<Connection> connection) : jid(std::move(jid)), connection(std::move(connection)) {};
|
||||
constexpr Client(BareJid jid, XmlStream<Connection> connection) : jid(std::move(jid)), connection(std::move(connection)) {
|
||||
}
|
||||
template <boost::asio::completion_token_for<void()> Token = boost::asio::use_awaitable_t<>>
|
||||
constexpr auto Close(Token token = {}) {
|
||||
this->active = false;
|
||||
|
@ -68,14 +74,49 @@ struct Client {
|
|||
auto Send(const T& object) -> boost::asio::awaitable<void> {
|
||||
co_await this->connection.Send(object);
|
||||
}
|
||||
[[nodiscard]] constexpr auto Jid() const -> const BareJid& {
|
||||
[[nodiscard]] constexpr auto Jid() const -> const FullJid& {
|
||||
return this->jid;
|
||||
}
|
||||
|
||||
auto CreateResourceBind() -> boost::asio::awaitable<void> {
|
||||
SPDLOG_INFO("Send IQ: Set::Bind");
|
||||
co_await this->Send(::iq::SetBind{.id = "1", .payload = {}});
|
||||
|
||||
auto set_bind_response = co_await connection.template Read<Iq<::iq::Bind>>();
|
||||
std::visit(utempl::Overloaded(
|
||||
[](auto error) {
|
||||
throw "Error response on IQ: Set::Bind: ''"; // TODO(unknown): Add exact error parsing
|
||||
},
|
||||
[&](::iq::ResultBind r) {
|
||||
jid.resource = std::move(r.payload.jid->resource);
|
||||
SPDLOG_INFO("Allocated resource: {}", jid.resource);
|
||||
}),
|
||||
set_bind_response);
|
||||
co_return;
|
||||
}
|
||||
|
||||
auto UpdateListOfContacts() -> boost::asio::awaitable<void> {
|
||||
Ivan-lis
commented
Maybe we can change namings later Maybe we can change namings later
|
||||
SPDLOG_INFO("Send IQ: Get::Roster");
|
||||
co_await this->Send(::iq::GetRoster{.id = "1", .from = std::format("{}@{}", "invalid_user", jid.server), .payload = {}});
|
||||
|
||||
const auto get_roster_response = co_await connection.template Read<Iq<::iq::Roster>>();
|
||||
std::visit(utempl::Overloaded(
|
||||
[](auto error) {
|
||||
throw "Error response on IQ: Get::Roster: ''"; // TODO(unknown): Add exact error parsing
|
||||
},
|
||||
[&](::iq::ResultRoster r) {
|
||||
roster = std::move(r.payload);
|
||||
SPDLOG_INFO("New roster: {}", ToString(roster));
|
||||
}),
|
||||
get_roster_response);
|
||||
co_return;
|
||||
}
|
||||
|
||||
private:
|
||||
bool active = true;
|
||||
XmlStream<Connection> connection{};
|
||||
BareJid jid;
|
||||
FullJid jid;
|
||||
::iq::Roster roster;
|
||||
};
|
||||
|
||||
struct StartTlsNegotiationError : std::runtime_error {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include <larra/serialization.hpp>
|
||||
#include <larra/stream_error.hpp>
|
||||
#include <larra/utils.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace larra::xmpp {
|
||||
|
@ -11,6 +12,8 @@ namespace iq {
|
|||
template <auto& Name, typename PayloadType>
|
||||
struct BaseImplWithPayload {
|
||||
std::string id;
|
||||
std::optional<std::string> from{};
|
||||
sha512sum
commented
Why std::string and not a more limited type like Jid ? Why std::string and not a more limited type like Jid ?
|
||||
std::optional<std::string> to{};
|
||||
PayloadType payload;
|
||||
static const inline std::string kName = Name;
|
||||
static constexpr auto kDefaultName = "iq";
|
||||
|
@ -19,16 +22,45 @@ struct BaseImplWithPayload {
|
|||
[[nodiscard]] constexpr auto Id(this Self&& self, std::string id) -> std::decay_t<Self> {
|
||||
return utils::FieldSetHelper::With<"id", BaseImplWithPayload>(std::forward<Self>(self), std::move(id));
|
||||
}
|
||||
template <typename Self>
|
||||
[[nodiscard]] constexpr auto To(this Self&& self, std::string to) -> std::decay_t<Self> {
|
||||
return utils::FieldSetHelper::With<"to", BaseImplWithPayload>(std::forward<Self>(self), std::move(to));
|
||||
}
|
||||
template <typename Self>
|
||||
[[nodiscard]] constexpr auto From(this Self&& self, std::string from) -> std::decay_t<Self> {
|
||||
return utils::FieldSetHelper::With<"from", BaseImplWithPayload>(std::forward<Self>(self), std::move(from));
|
||||
}
|
||||
template <typename NewPayloadType, typename Self>
|
||||
[[nodiscard]] constexpr auto Payload(this Self&& self, NewPayloadType value) {
|
||||
return utils::FieldSetHelper::With<"payload", BaseImplWithPayload, false>(std::forward<Self>(self), std::move(value));
|
||||
}
|
||||
friend constexpr auto operator<<(xmlpp::Element* element, const BaseImplWithPayload& self) {
|
||||
element->set_attribute("id", self.id);
|
||||
|
||||
if(self.to) {
|
||||
element->set_attribute("to", *self.to);
|
||||
}
|
||||
if(self.from) {
|
||||
element->set_attribute("from", *self.from);
|
||||
}
|
||||
element->set_attribute("type", kName);
|
||||
using S = Serialization<PayloadType>;
|
||||
S::Serialize(element->add_child_element(S::kDefaultName, S::kPrefix), self.payload);
|
||||
}
|
||||
|
||||
[[nodiscard]] static constexpr auto TryParse(xmlpp::Element* element) -> std::optional<BaseImplWithPayload> {
|
||||
return [&] -> std::optional<BaseImplWithPayload> {
|
||||
auto node = element->get_attribute("type");
|
||||
if(!node) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if(node->get_value() != Name) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return Parse(element);
|
||||
}();
|
||||
}
|
||||
|
||||
[[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> BaseImplWithPayload {
|
||||
auto node = element->get_attribute("type");
|
||||
if(!node) {
|
||||
|
@ -41,6 +73,8 @@ struct BaseImplWithPayload {
|
|||
if(!idNode) {
|
||||
throw std::runtime_error("Not found attribute id for parse Iq");
|
||||
}
|
||||
auto from = element->get_attribute("from");
|
||||
auto to = element->get_attribute("to");
|
||||
|
||||
using S = Serialization<PayloadType>;
|
||||
auto payload = element->get_first_child(S::kDefaultName);
|
||||
|
@ -51,9 +85,13 @@ struct BaseImplWithPayload {
|
|||
if(!payload2) {
|
||||
throw std::runtime_error("Invalid payload for parse Iq");
|
||||
}
|
||||
return {.id = idNode->get_value(), .payload = S::Parse(payload2)};
|
||||
return {.id = idNode->get_value(),
|
||||
.from = (from ? std::optional{from->get_value()} : std::nullopt),
|
||||
.to = (to ? std::optional{to->get_value()} : std::nullopt),
|
||||
.payload = S::Parse(payload2)};
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr auto kGetName = "get";
|
||||
|
||||
template <typename Payload>
|
||||
|
|
|
@ -7,24 +7,6 @@
|
|||
|
||||
namespace larra::xmpp {
|
||||
|
||||
struct BareJid {
|
||||
std::string username;
|
||||
std::string server;
|
||||
[[nodiscard]] static auto Parse(std::string_view jid) -> BareJid;
|
||||
friend auto ToString(const BareJid& jid) -> std::string;
|
||||
|
||||
constexpr auto operator==(const BareJid&) const -> bool = default;
|
||||
template <typename Self>
|
||||
[[nodiscard]] constexpr auto Username(this Self&& self, std::string username) -> std::decay_t<Self> {
|
||||
return utils::FieldSetHelper::With<"username", BareJid>(std::forward<Self>(self), std::move(username));
|
||||
}
|
||||
|
||||
template <typename Self>
|
||||
[[nodiscard]] constexpr auto Server(this Self&& self, std::string server) -> std::decay_t<Self> {
|
||||
return utils::FieldSetHelper::With<"server", BareJid>(std::forward<Self>(self), std::move(server));
|
||||
}
|
||||
};
|
||||
|
||||
struct BareResourceJid {
|
||||
std::string server;
|
||||
std::string resource;
|
||||
|
@ -69,6 +51,28 @@ struct FullJid {
|
|||
}
|
||||
};
|
||||
|
||||
struct BareJid {
|
||||
std::string username;
|
||||
std::string server;
|
||||
constexpr operator FullJid(this auto&& self) {
|
||||
return {.username = std::forward_like<decltype(self)>(self.username), .server = std::forward_like<decltype(self)>(self.server)};
|
||||
Ivan-lis marked this conversation as resolved
Outdated
sha512sum
commented
Extra copying if there is an rvalue. Better to forward depending on the type with which it is called. Extra copying if there is an rvalue. Better to forward depending on the type with which it is called.
|
||||
}
|
||||
|
||||
[[nodiscard]] static auto Parse(std::string_view jid) -> BareJid;
|
||||
friend auto ToString(const BareJid& jid) -> std::string;
|
||||
|
||||
constexpr auto operator==(const BareJid&) const -> bool = default;
|
||||
template <typename Self>
|
||||
[[nodiscard]] constexpr auto Username(this Self&& self, std::string username) -> std::decay_t<Self> {
|
||||
return utils::FieldSetHelper::With<"username", BareJid>(std::forward<Self>(self), std::move(username));
|
||||
}
|
||||
|
||||
template <typename Self>
|
||||
[[nodiscard]] constexpr auto Server(this Self&& self, std::string server) -> std::decay_t<Self> {
|
||||
return utils::FieldSetHelper::With<"server", BareJid>(std::forward<Self>(self), std::move(server));
|
||||
}
|
||||
};
|
||||
|
||||
using JidVariant = std::variant<BareJid, BareResourceJid, FullJid>;
|
||||
|
||||
struct Jid : JidVariant {
|
||||
|
|
72
library/include/larra/roster.hpp
Normal file
72
library/include/larra/roster.hpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
#pragma once
|
||||
#include <libxml++/libxml++.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <larra/iq.hpp>
|
||||
#include <larra/jid.hpp>
|
||||
#include <larra/utils.hpp>
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace larra::xmpp::iq {
|
||||
|
||||
struct Roster {
|
||||
static constexpr auto kDefaultName = "query";
|
||||
static constexpr auto kDefaultNamespace = "jabber:iq:roster";
|
||||
|
||||
std::vector<BareJid> items;
|
||||
|
||||
friend auto ToString(const Roster& roster) -> std::string {
|
||||
static constexpr std::string_view prefix = "Roster: [\n\t";
|
||||
static constexpr std::string_view suffix = "]";
|
||||
// \n\r\t
|
||||
std::size_t total_length = std::ranges::fold_left(roster.items | std::views::transform([](const auto& el) {
|
||||
return larra::xmpp::utils::AccumulateFieldLength(el) + 3;
|
||||
}),
|
||||
prefix.length() + suffix.length(),
|
||||
std::plus<>{});
|
||||
|
||||
std::string s;
|
||||
s.resize(total_length);
|
||||
s = prefix;
|
||||
for(const auto& el : roster.items) {
|
||||
s += ToString(el);
|
||||
s += "\n\t";
|
||||
}
|
||||
return s += suffix;
|
||||
}
|
||||
friend constexpr auto operator<<(xmlpp::Element* element, const Roster& roster) {
|
||||
element->set_attribute("xmlns", Roster::kDefaultNamespace);
|
||||
for(const auto& item : roster.items) {
|
||||
sha512sum marked this conversation as resolved
Outdated
sha512sum
commented
Why not range based for? Why not range based for?
|
||||
element->add_child_element("item")->set_attribute("jid", ToString(item));
|
||||
}
|
||||
}
|
||||
[[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> Roster {
|
||||
const auto& item_nodes = element->get_children("item");
|
||||
if(item_nodes.empty()) {
|
||||
SPDLOG_DEBUG("No items at Iq::Roster");
|
||||
}
|
||||
|
||||
return {.items = item_nodes | std::views::transform([](const xmlpp::Node* node) {
|
||||
auto item_element = dynamic_cast<const xmlpp::Element*>(node);
|
||||
if(!item_element) {
|
||||
throw std::runtime_error("Can't convert xmlpp::Node to xmlpp::Element");
|
||||
sha512sum
commented
You can create a structure that contains jid as an attribute and use automatic generation of serialization and deserialization You can create a structure that contains jid as an attribute and use automatic generation of serialization and deserialization
|
||||
}
|
||||
|
||||
auto jid_ptr = item_element->get_attribute("jid");
|
||||
if(!jid_ptr) {
|
||||
throw std::runtime_error("Not found attribute 'jid' for parse Roster item");
|
||||
}
|
||||
|
||||
return BareJid::Parse(jid_ptr->get_value());
|
||||
}) |
|
||||
std::ranges::to<std::vector<BareJid>>()};
|
||||
}
|
||||
};
|
||||
|
||||
using GetRoster = Get<Roster>;
|
||||
using ResultRoster = Result<Roster>;
|
||||
using IqRoster = Iq<Roster>;
|
||||
|
||||
} // namespace larra::xmpp::iq
|
|
@ -73,6 +73,25 @@ struct ErrorImpl : BaseError {
|
|||
}
|
||||
};
|
||||
|
||||
// Helper class to prevent parsing response stream into an expected return type if its name is a 'stream:error'
|
||||
struct UnknownXmppError : BaseError {
|
||||
static constexpr auto kDefaultName = "stream:error";
|
||||
static constexpr std::string_view kErrorMessage = "Unknown XMPP stream error";
|
||||
|
||||
static constexpr auto TryParse(xmlpp::Element* element) {
|
||||
return std::optional{UnknownXmppError{}};
|
||||
}
|
||||
static constexpr auto Parse(xmlpp::Element* element) {
|
||||
return TryParse(element).value();
|
||||
}
|
||||
friend constexpr auto operator<<(xmlpp::Element* element, const UnknownXmppError& obj) -> void {
|
||||
throw std::format("'{}' must never be written into the real stream!", kErrorMessage);
|
||||
}
|
||||
[[nodiscard]] constexpr auto what() const noexcept -> const char* override {
|
||||
return kErrorMessage.data();
|
||||
}
|
||||
};
|
||||
|
||||
struct BadFormat : ErrorImpl<BadFormat> {};
|
||||
struct BadNamespacePrefix : ErrorImpl<BadNamespacePrefix> {};
|
||||
struct Conflict : ErrorImpl<Conflict> {};
|
||||
|
@ -81,7 +100,7 @@ struct HostGone : ErrorImpl<HostGone> {};
|
|||
struct HostUnknown : ErrorImpl<HostUnknown> {};
|
||||
struct ImproperAdressing : ErrorImpl<ImproperAdressing> {};
|
||||
struct InternalServerError : ErrorImpl<InternalServerError> {};
|
||||
struct InvalidForm : ErrorImpl<InvalidForm> {};
|
||||
struct InvalidFrom : ErrorImpl<InvalidFrom> {};
|
||||
struct InvalidNamespace : ErrorImpl<InvalidNamespace> {};
|
||||
struct InvalidXml : ErrorImpl<InvalidXml> {};
|
||||
struct NotAuthorized : ErrorImpl<NotAuthorized> {};
|
||||
|
@ -109,7 +128,7 @@ using StreamError = std::variant<error::stream::BadFormat,
|
|||
error::stream::HostUnknown,
|
||||
error::stream::ImproperAdressing,
|
||||
error::stream::InternalServerError,
|
||||
error::stream::InvalidForm,
|
||||
error::stream::InvalidFrom,
|
||||
error::stream::InvalidNamespace,
|
||||
error::stream::InvalidXml,
|
||||
error::stream::NotAuthorized,
|
||||
|
@ -123,6 +142,10 @@ using StreamError = std::variant<error::stream::BadFormat,
|
|||
error::stream::UnsupportedEncoding,
|
||||
error::stream::UnsupportedFeature,
|
||||
error::stream::UnsupportedStanzaType,
|
||||
error::stream::UnsupportedVersion>;
|
||||
error::stream::UnsupportedVersion,
|
||||
error::stream::UnknownXmppError>;
|
||||
|
||||
static_assert(!std::is_same_v<typename std::variant_alternative_t<std::variant_size_v<StreamError> - 1, StreamError>, StreamError>,
|
||||
"'UnknownXmppError' must be at the end of 'StreamError' variant");
|
||||
|
||||
} // namespace larra::xmpp
|
||||
|
|
|
@ -295,4 +295,18 @@ struct RangeToWrapper : T {
|
|||
: T{std::forward<Args>(args)...} {};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept LengthCalculatable = requires(const T& obj) {
|
||||
{ obj.length() } -> std::convertible_to<std::size_t>;
|
||||
sha512sum marked this conversation as resolved
Outdated
sha512sum
commented
A comment that doesn't explain anything and just repeats the code. A comment that doesn't explain anything and just repeats the code.
|
||||
} || std::convertible_to<T, std::string>;
|
||||
|
||||
template <typename T>
|
||||
auto AccumulateFieldLength(const T& obj) -> std::size_t {
|
||||
std::size_t total_length = 0;
|
||||
boost::pfr::for_each_field(obj, [&](const LengthCalculatable auto& field) {
|
||||
total_length += field.length(); // Accumulate length of each field
|
||||
});
|
||||
return total_length;
|
||||
}
|
||||
|
||||
} // namespace larra::xmpp::utils
|
||||
|
|
36
tests/roster.cpp
Normal file
36
tests/roster.cpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <larra/jid.hpp>
|
||||
#include <larra/roster.hpp>
|
||||
|
||||
namespace larra::xmpp {
|
||||
|
||||
TEST(Roster, SerializeAndParse) {
|
||||
FullJid jid{.username = "test", .server = "server", .resource = "res"}; // NOLINT
|
||||
auto roster = iq::GetRoster{.id = "1", .from = ToString(jid), .payload = iq::Roster{.items = {{"u1", "s1"}, {"u2", "s2"}, {"u3", "s3"}}}};
|
||||
|
||||
xmlpp::Document doc;
|
||||
auto node = doc.create_root_node("iq");
|
||||
Ivan-lis marked this conversation as resolved
sha512sum
commented
Why not just throw all the necessary elements through the vector constructor in Roster constructor? Why not just throw all the necessary elements through the vector constructor in Roster constructor?
|
||||
node << roster;
|
||||
|
||||
auto parse_res = decltype(roster)::Parse(node);
|
||||
|
||||
ASSERT_EQ(roster.payload.items.size(), parse_res.payload.items.size());
|
||||
for(const auto& [idx, expect_el, parsed_el] : std::views::zip(std::views::iota(0), roster.payload.items, parse_res.payload.items)) {
|
||||
EXPECT_EQ(expect_el, parsed_el) << "Mismatched on idx: " << idx;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr std::string_view kRosterPrintExpectedData = "Roster: [\n\tu1@s1\n\tu2@s2\n\tu3@s3\n\t]";
|
||||
TEST(Roster, Print) {
|
||||
Ivan-lis marked this conversation as resolved
sha512sum
commented
Code left in the comment Code left in the comment
|
||||
FullJid jid{.username = "test", .server = "server", .resource = "res"}; // NOLINT
|
||||
auto roster = iq::GetRoster{.id = "1", .from = ToString(jid), .payload = iq::Roster{.items = {{"u1", "s1"}, {"u2", "s2"}, {"u3", "s3"}}}};
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
auto roster_str = ToString(roster.payload);
|
||||
EXPECT_EQ(kRosterPrintExpectedData.length(), roster_str.capacity());
|
||||
EXPECT_EQ(kRosterPrintExpectedData, roster_str);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace larra::xmpp
|
||||
Ivan-lis marked this conversation as resolved
sha512sum
commented
EXPECT_EQ and test content EXPECT_EQ and test content
|
|
@ -5,6 +5,9 @@
|
|||
#include <larra/serialization/auto.hpp>
|
||||
#include <larra/serialization/error.hpp>
|
||||
#include <larra/stream_error.hpp>
|
||||
#include <variant>
|
||||
|
||||
#include "utempl/utils.hpp"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
|
|
Loading…
Reference in a new issue
You can create a structure that contains jid as an attribute and use automatic generation of serialization and deserialization
I tried, but roster result should has 'jid' attr, while with Serialization with BareJid I got
<NO_NAME username="n" server="s" >
Expected ResultRoster
See how it's done in SomeStruct5 in tests/serialization.cpp
Interesting, looks like last time I did something wrong and got <username="n" server="s"> instead. Will try t use BareJid