sha512sum
13d063915e
All checks were successful
PR Check / on-push-commit-check (push) Successful in 11m28s
233 lines
7.7 KiB
C++
233 lines
7.7 KiB
C++
#pragma once
|
|
#include <libxml++/libxml++.h>
|
|
#include <spdlog/spdlog.h>
|
|
|
|
#include <larra/serialization/error.hpp>
|
|
#include <nameof.hpp>
|
|
#include <ranges>
|
|
#include <string>
|
|
#include <utempl/utils.hpp>
|
|
|
|
namespace larra::xmpp {
|
|
|
|
template <typename T>
|
|
concept AsXml = requires(xmlpp::Element* element, const T& obj) {
|
|
element << obj;
|
|
{ T::kDefaultName } -> std::convertible_to<const std::string&>;
|
|
};
|
|
|
|
template <typename T>
|
|
concept HasDefaultName = requires {
|
|
{ T::kDefaultName } -> std::convertible_to<const std::string&>;
|
|
};
|
|
|
|
template <typename T>
|
|
concept HasDefaultNamespace = requires {
|
|
{ T::kDefaultNamespace } -> std::convertible_to<const std::string&>;
|
|
};
|
|
|
|
template <typename T>
|
|
concept HasDefaultPrefix = requires {
|
|
{ T::kPrefix } -> std::convertible_to<const std::string&>;
|
|
};
|
|
|
|
template <typename T>
|
|
concept HasAddXmlDecl = requires {
|
|
{ T::kAddXmlDecl } -> std::convertible_to<bool>;
|
|
};
|
|
|
|
template <typename T>
|
|
concept HasRemoveEnd = requires {
|
|
{ T::kRemoveEnd } -> std::convertible_to<bool>;
|
|
};
|
|
|
|
template <typename T>
|
|
concept HasTryParse = requires(xmlpp::Element* element) {
|
|
{ T::TryParse(element) } -> std::same_as<std::optional<T>>;
|
|
};
|
|
|
|
template <typename T = void>
|
|
struct SerializationBase {
|
|
inline static const std::string kDefaultName = [] {
|
|
if constexpr(HasDefaultName<T>) {
|
|
return T::kDefaultName;
|
|
} else {
|
|
return "";
|
|
}
|
|
}();
|
|
inline static const std::string kDefaultNamespace = [] {
|
|
if constexpr(HasDefaultNamespace<T>) {
|
|
return T::kDefaultNamespace;
|
|
} else {
|
|
return "";
|
|
}
|
|
}();
|
|
inline static const std::string kPrefix = [] {
|
|
if constexpr(HasDefaultPrefix<T>) {
|
|
return T::kPrefix;
|
|
} else {
|
|
return "";
|
|
}
|
|
}();
|
|
static constexpr bool kAddXmlDecl = [] {
|
|
if constexpr(HasAddXmlDecl<T>) {
|
|
return T::kAddXmlDecl;
|
|
} else {
|
|
return false;
|
|
}
|
|
}();
|
|
static constexpr bool kRemoveEnd = [] {
|
|
if constexpr(HasRemoveEnd<T>) {
|
|
return T::kRemoveEnd;
|
|
} else {
|
|
return false;
|
|
}
|
|
}();
|
|
static constexpr auto StartCheck(xmlpp::Element* element) -> bool {
|
|
if constexpr(requires {
|
|
{ T::StartCheck(element) } -> std::same_as<bool>;
|
|
}) {
|
|
return T::StartCheck(element);
|
|
} else {
|
|
return element && element->get_name() == kDefaultName;
|
|
}
|
|
};
|
|
};
|
|
|
|
template <typename T>
|
|
struct Serialization : SerializationBase<T> {
|
|
[[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> T {
|
|
if(!Serialization::StartCheck(element)) {
|
|
throw serialization::ParsingError{
|
|
std::format("[{}: {}] parsing error: [ StartCheck failed ]", Serialization::kDefaultName, nameof::nameof_full_type<T>())};
|
|
}
|
|
return T::Parse(element);
|
|
}
|
|
[[nodiscard]] static constexpr auto TryParse(xmlpp::Element* element) -> std::optional<T> {
|
|
if constexpr(HasTryParse<T>) {
|
|
return Serialization::StartCheck(element) ? T::TryParse(element) : std::nullopt;
|
|
} else {
|
|
try {
|
|
return Serialization::StartCheck(element) ? std::optional{T::Parse(element)} : std::nullopt;
|
|
} catch(const std::exception& e) {
|
|
SPDLOG_WARN("Type {}: Failed Parse but no TryParse found: {}", nameof::nameof_type<T>(), e.what());
|
|
} catch(...) {
|
|
SPDLOG_WARN("Type {}: Failed Parse but no TryParse found", nameof::nameof_type<T>());
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
};
|
|
static constexpr auto Serialize(xmlpp::Element* element, const T& object) -> void {
|
|
element << object;
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
struct Serialization<std::optional<T>> : SerializationBase<T> {
|
|
[[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> std::optional<T> {
|
|
return Serialization<T>::TryParse(element);
|
|
}
|
|
[[nodiscard]] static constexpr auto TryParse(xmlpp::Element* element) -> std::optional<std::optional<T>> {
|
|
auto response = Serialization<T>::TryParse(element);
|
|
return response ? *response : std::nullopt;
|
|
}
|
|
static constexpr auto Serialize(xmlpp::Element* element, const T& object) -> void {
|
|
element << object;
|
|
}
|
|
};
|
|
|
|
template <typename... Ts>
|
|
struct Serialization<std::variant<Ts...>> : SerializationBase<> {
|
|
static constexpr auto StartCheck(xmlpp::Element* element) {
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
template <typename T>
|
|
static constexpr auto FunctionForType(xmlpp::Element* element) {
|
|
return [=] -> std::optional<T> {
|
|
if(Serialization<T>::StartCheck(element)) {
|
|
return Serialization<T>::TryParse(element);
|
|
} else {
|
|
SPDLOG_DEBUG("StartCheck failed for type {}", nameof::nameof_type<T>());
|
|
return std::nullopt;
|
|
}
|
|
};
|
|
}
|
|
|
|
public:
|
|
[[nodiscard]] static constexpr auto TryParse(xmlpp::Element* element) -> std::optional<std::variant<Ts...>> {
|
|
return utempl::FirstOf(utempl::Tuple{FunctionForType<Ts>(element)...}, std::optional<std::variant<Ts...>>{});
|
|
}
|
|
[[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) {
|
|
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 Serialization<decltype(utempl::Get<sizeof...(Ts) - 1>(utempl::kTypeList<Ts...>))>::Parse(element);
|
|
});
|
|
}(utempl::TakeFrom<sizeof...(Ts) - 1>(utempl::kTypeList<Ts...>));
|
|
}
|
|
static constexpr auto Serialize(xmlpp::Element* element, const std::variant<Ts...>& object) -> void {
|
|
std::visit(
|
|
[&]<typename T>(const T& object) {
|
|
Serialization<T>::Serialize(element, object);
|
|
},
|
|
object);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct Serialization<std::monostate> : SerializationBase<> {
|
|
static constexpr auto StartCheck(xmlpp::Element*) -> bool {
|
|
return true;
|
|
};
|
|
[[nodiscard]] static constexpr auto TryParse(xmlpp::Element*) -> std::optional<std::monostate> {
|
|
return std::monostate{};
|
|
}
|
|
[[nodiscard]] static constexpr auto Parse(xmlpp::Element*) -> std::monostate {
|
|
return {};
|
|
}
|
|
static constexpr auto Serialize(xmlpp::Element*, const std::monostate&) -> void {
|
|
}
|
|
};
|
|
|
|
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
|