Compare commits
1 commit
64e2aeb8aa
...
cd685ba101
Author | SHA1 | Date | |
---|---|---|---|
cd685ba101 |
3 changed files with 213 additions and 25 deletions
|
@ -1,31 +1,133 @@
|
|||
#pragma once
|
||||
#include <libxml++/libxml++.h>
|
||||
|
||||
#include <larra/serialization/auto.hpp>
|
||||
#include <larra/xml_language.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace larra::xmpp {
|
||||
|
||||
namespace message {
|
||||
struct Body {
|
||||
std::string content;
|
||||
std::optional<XmlLanguage> language;
|
||||
static constexpr auto kDefaultName = "body";
|
||||
constexpr auto operator==(const Body& other) const -> bool = default;
|
||||
friend constexpr auto operator<<(xmlpp::Element* node, const Body& message) -> void {
|
||||
node->add_child_text(message.content);
|
||||
}
|
||||
static constexpr auto Parse(xmlpp::Element* node) -> Body {
|
||||
auto ptr = node->get_first_child_text();
|
||||
if(!ptr) {
|
||||
throw std::runtime_error("Message::Body: [ Text node not found ]");
|
||||
}
|
||||
auto lang = node->get_attribute("lang", "xml");
|
||||
return {
|
||||
.content = ptr->get_content(), //
|
||||
.language = lang ? std::optional{XmlLanguage::Parse(lang->get_value())} : std::nullopt //
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
namespace type {
|
||||
|
||||
struct Chat {
|
||||
static constexpr auto TryParse(std::string_view value) -> std::optional<Chat> {
|
||||
return value == "chat" ? std::optional{Chat{}} : std::nullopt;
|
||||
}
|
||||
friend constexpr auto ToString(const Chat&) -> std::string {
|
||||
return "chat";
|
||||
}
|
||||
constexpr auto operator==(const Chat&) const -> bool {
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
static constexpr auto kChat = Chat{};
|
||||
|
||||
struct Error {
|
||||
static constexpr auto TryParse(std::string_view value) -> std::optional<Error> {
|
||||
return value == "error" ? std::optional{Error{}} : std::nullopt;
|
||||
}
|
||||
friend constexpr auto ToString(const Error&) -> std::string {
|
||||
return "error";
|
||||
}
|
||||
constexpr auto operator==(const Error&) const -> bool {
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
static constexpr auto kError = Error{};
|
||||
|
||||
struct GroupChat {
|
||||
static constexpr auto TryParse(std::string_view value) -> std::optional<GroupChat> {
|
||||
return value == "groupchat" ? std::optional{GroupChat{}} : std::nullopt;
|
||||
}
|
||||
friend constexpr auto ToString(const GroupChat&) -> std::string {
|
||||
return "groupchat";
|
||||
}
|
||||
constexpr auto operator==(const GroupChat&) const -> bool {
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
static constexpr auto kGroupChat = GroupChat{};
|
||||
|
||||
struct Headline {
|
||||
static constexpr auto TryParse(std::string_view value) -> std::optional<Headline> {
|
||||
return value == "headline" ? std::optional{Headline{}} : std::nullopt;
|
||||
}
|
||||
friend constexpr auto ToString(const Headline&) -> std::string {
|
||||
return "headline";
|
||||
}
|
||||
constexpr auto operator==(const Headline&) const -> bool {
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
static constexpr auto kHeadline = Headline{};
|
||||
|
||||
struct Normal {
|
||||
static constexpr auto Parse(std::string_view value) -> Normal {
|
||||
return value == "normal" ? Normal{}
|
||||
: throw std::runtime_error(
|
||||
std::format(R"(message::type::Normal Parsing error: [ expected "normal" but "{}" found ])", value));
|
||||
}
|
||||
friend constexpr auto ToString(const Normal&) -> std::string {
|
||||
return "normal";
|
||||
}
|
||||
constexpr auto operator==(const Normal&) const -> bool {
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
static constexpr auto kNormal = Normal{};
|
||||
|
||||
} // namespace type
|
||||
|
||||
using TypeVariant = std::variant<type::Chat, type::GroupChat, type::Headline, type::Error, type::Normal>;
|
||||
|
||||
struct Type : TypeVariant {
|
||||
using TypeVariant::variant;
|
||||
constexpr Type(TypeVariant variant) : TypeVariant(std::move(variant)) {
|
||||
}
|
||||
|
||||
static constexpr auto GetDefault() -> Type {
|
||||
return type::kNormal;
|
||||
}
|
||||
|
||||
friend constexpr auto ToString(const Type& value) -> std::string {
|
||||
return std::visit(
|
||||
[](const auto& value) {
|
||||
return ToString(value);
|
||||
},
|
||||
value);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace message
|
||||
|
||||
template <typename From, typename To>
|
||||
struct Message {
|
||||
static constexpr auto kDefaultName = "message";
|
||||
struct Body : std::string {
|
||||
using std::string::basic_string;
|
||||
constexpr Body(std::string str) : std::string{std::move(str)} {
|
||||
}
|
||||
static constexpr auto kDefaultName = "body";
|
||||
friend constexpr auto operator<<(xmlpp::Element* node, const Body& message) -> void {
|
||||
node->add_child_text(message);
|
||||
}
|
||||
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 ]");
|
||||
}
|
||||
return {ptr->get_content()};
|
||||
}
|
||||
};
|
||||
static auto Parse(xmlpp::Element* element) -> Message {
|
||||
return serialization::Parse<Message>(element);
|
||||
}
|
||||
|
@ -35,15 +137,20 @@ struct Message {
|
|||
constexpr auto operator==(const Message& other) const -> bool = default;
|
||||
From from;
|
||||
To to;
|
||||
std::string type;
|
||||
message::Type type;
|
||||
std::optional<std::string> id;
|
||||
XmlLanguage language;
|
||||
Body body;
|
||||
std::vector<message::Body> body;
|
||||
};
|
||||
|
||||
template <typename From, typename To>
|
||||
struct serialization::SerializationConfigT<Message<From, To>> {
|
||||
static constexpr auto kValue = serialization::SerializationConfig<Message<From, To>>{};
|
||||
static constexpr auto kValue = serialization::SerializationConfig<Message<From, To>>{} //
|
||||
.template With<"type">(serialization::AttributeConfig{})
|
||||
.template With<"body">(serialization::Config<std::vector<message::Body>>{});
|
||||
};
|
||||
|
||||
template <typename Info>
|
||||
struct serialization::AttributeSerializer<message::Type, Info> : serialization::AttributeSerializer<message::TypeVariant, Info> {};
|
||||
|
||||
} // namespace larra::xmpp
|
||||
|
|
|
@ -64,10 +64,24 @@ struct MetaInfo {
|
|||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct DefaultValue {};
|
||||
|
||||
template <typename T>
|
||||
requires requires {
|
||||
{ T::GetDefault() } -> std::convertible_to<T>;
|
||||
}
|
||||
struct DefaultValue<T> {
|
||||
static constexpr auto Default() {
|
||||
return T::GetDefault();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename MainT, std::size_t Element>
|
||||
struct FieldInfo {
|
||||
using Main = MainT;
|
||||
using Info = MetaInfo<Main>;
|
||||
using Type = Info::template TupleElement<Element>;
|
||||
static inline const std::string kName = [] {
|
||||
if constexpr(requires { Info::template TupleElement<Element>::kDefaultName; }) {
|
||||
return Info::template TupleElement<Element>::kDefaultName;
|
||||
|
@ -234,6 +248,9 @@ struct AttributeSerializer {
|
|||
static constexpr auto Parse(xmlpp::Element* element) -> T {
|
||||
auto node = element->get_attribute(Info::kName, Info::kNamespace);
|
||||
if(!node) {
|
||||
if constexpr(requires { DefaultValue<T>::Default(); }) {
|
||||
return DefaultValue<T>::Default();
|
||||
}
|
||||
throw AttributeParsingError(std::format("Attribute [{}{}{}: {}] parsing error",
|
||||
Info::kNamespace,
|
||||
Info::kNamespace == std::string{} ? "" : ":",
|
||||
|
@ -246,6 +263,19 @@ struct AttributeSerializer {
|
|||
return node->get_value();
|
||||
}
|
||||
}
|
||||
static constexpr auto TryParse(xmlpp::Element* element) -> std::optional<T>
|
||||
requires requires { T::TryParse(std::string_view{}); }
|
||||
{
|
||||
auto node = element->get_attribute(Info::kName, Info::kNamespace);
|
||||
if(!node) {
|
||||
throw AttributeParsingError(std::format("Attribute [{}{}{}: {}] parsing error",
|
||||
Info::kNamespace,
|
||||
Info::kNamespace == std::string{} ? "" : ":",
|
||||
Info::kName,
|
||||
nameof::nameof_full_type<T>()));
|
||||
}
|
||||
return T::TryParse(node->get_value());
|
||||
}
|
||||
static constexpr auto Serialize(xmlpp::Element* element, const T& obj) {
|
||||
if constexpr(requires {
|
||||
{ ToString(obj) } -> std::convertible_to<const std::string&>;
|
||||
|
@ -261,7 +291,12 @@ 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, Info::kNamespace);
|
||||
return node ? std::optional{AttributeSerializer<T, Info>::Parse(element)} : std::nullopt;
|
||||
return node ? std::optional{AttributeSerializer<T, Info>::Parse(element)} : [] -> std::optional<T> {
|
||||
if constexpr(requires { DefaultValue<T>::Default(); }) {
|
||||
return DefaultValue<T>::Default();
|
||||
}
|
||||
return std::nullopt;
|
||||
}();
|
||||
}
|
||||
static constexpr auto Serialize(xmlpp::Element* element, const std::optional<T>& obj) {
|
||||
if(obj) {
|
||||
|
@ -270,6 +305,52 @@ struct AttributeSerializer<std::optional<T>, Info> {
|
|||
}
|
||||
};
|
||||
|
||||
template <typename... Ts, typename Info>
|
||||
struct AttributeSerializer<std::variant<Ts...>, Info> {
|
||||
template <typename T>
|
||||
static constexpr auto FunctionForType(xmlpp::Element* element) {
|
||||
return [=] -> std::optional<T> {
|
||||
if constexpr(requires { AttributeSerializer<T, Info>::TryParse(element); }) {
|
||||
return AttributeSerializer<T, Info>::TryParse(element);
|
||||
} else {
|
||||
try {
|
||||
return AttributeSerializer<T, Info>::Parse(element);
|
||||
} catch(...) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
static constexpr auto Parse(xmlpp::Element* element) -> std::variant<Ts...> {
|
||||
using T = Info::Type;
|
||||
auto node = element->get_attribute(Info::kName, Info::kNamespace);
|
||||
if(!node) {
|
||||
if constexpr(requires { DefaultValue<T>::Default(); }) {
|
||||
return DefaultValue<T>::Default();
|
||||
}
|
||||
throw AttributeParsingError(std::format("Attribute [{}{}{}: {}] parsing error",
|
||||
Info::kNamespace,
|
||||
Info::kNamespace == std::string{} ? "" : ":",
|
||||
Info::kName,
|
||||
nameof::nameof_full_type<T>()));
|
||||
}
|
||||
return [&]<typename... TTs>(utempl::TypeList<TTs...>) {
|
||||
// operator* is safe because in or_else Parse returns Ts...[sizeof...(Ts) - 1] (not optional)
|
||||
return *utempl::FirstOf(utempl::Tuple{FunctionForType<TTs>(element)...}, std::optional<std::variant<Ts...>>{})
|
||||
.or_else([&] -> std::optional<std::variant<Ts...>> {
|
||||
return AttributeSerializer<decltype(utempl::Get<sizeof...(Ts) - 1>(utempl::kTypeList<Ts...>)), Info>::Parse(element);
|
||||
});
|
||||
}(utempl::TakeFrom<sizeof...(Ts) - 1>(utempl::kTypeList<Ts...>));
|
||||
}
|
||||
static constexpr auto Serialize(xmlpp::Element* element, const std::variant<Ts...>& obj) {
|
||||
std::visit(
|
||||
[&]<typename T>(const T& value) {
|
||||
AttributeSerializer<T, Info>::Serialize(element, value);
|
||||
},
|
||||
obj);
|
||||
}
|
||||
};
|
||||
|
||||
namespace impl {
|
||||
|
||||
template <typename T>
|
||||
|
|
|
@ -21,13 +21,13 @@ auto CreateTestData() {
|
|||
return doc;
|
||||
}
|
||||
|
||||
constexpr Message<BareJid, BareJid> kMessage{
|
||||
const Message<BareJid, BareJid> kMessage{
|
||||
.from = {.username = "user1", .server = "server.i2p"},
|
||||
.to = {.username = "user2", .server = "server.i2p"},
|
||||
.type = "chat",
|
||||
.type = message::type::kChat,
|
||||
.id = "1",
|
||||
.language = "en",
|
||||
.body = "hello" //
|
||||
.body = {message::Body{.content = "hello"}} //
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
|
Loading…
Reference in a new issue