// This file Copyright © Mnemosyne LLC. // It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only), // or any future license endorsed by Mnemosyne LLC. // License text can be found in the licenses/ folder. #pragma once #include #include #include #include #include #include #include #include #include #include "libtransmission/quark.h" #include "libtransmission/variant.h" namespace libtransmission::serializer { // These type traits are used for serialize() and deserialize() to sniff // out containers that support `push_back()`, `insert()`, `reserve()`, etc. // Example uses: (de)serializing std::vector, QStringList, small::set namespace detail { // NOLINTBEGIN(readability-identifier-naming) // use std-style naming for these traits // TODO(c++20): use std::remove_cvref_t (P0550R2) when GCC >= 9.1 template using remove_cvref_t = std::remove_cv_t>; // Type trait: is C a container with push_back (but not a string)? template inline constexpr bool is_push_back_range_v = false; template inline constexpr bool is_push_back_range_v< C, std::void_t< typename C::value_type, decltype(std::begin(std::declval())), decltype(std::end(std::declval())), decltype(std::declval().push_back( std::declval()))>> = !std::is_same_v>; // Type trait: is C a container with insert (like std::set)? template inline constexpr bool is_insert_range_v = false; template inline constexpr bool is_insert_range_v< C, std::void_t< typename C::value_type, decltype(std::begin(std::declval())), decltype(std::end(std::declval())), decltype(std::declval().insert(std::declval()))>> = true; // Type trait: is C a std::array? template inline constexpr bool is_std_array_v = false; template inline constexpr bool is_std_array_v> = true; template inline constexpr bool is_optional_v = false; template inline constexpr bool is_optional_v> = true; template tr_variant from_push_back_range(C const& src); template bool to_push_back_range(tr_variant const& src, C* ptgt); template tr_variant from_insert_range(C const& src); template bool to_insert_range(tr_variant const& src, C* ptgt); template tr_variant from_array(C const& src); template bool to_array(tr_variant const& src, C* ptgt); template tr_variant from_optional(std::optional const& src); template bool to_optional(tr_variant const& src, std::optional* ptgt); // Call reserve() if available, otherwise no-op template auto reserve_if_possible(C& c, std::size_t n) -> decltype(c.reserve(n), void()) { c.reserve(n); } template void reserve_if_possible(C& /*c*/, ...) // NOLINT(cert-dcl50-cpp) { } // NOLINTEND(readability-identifier-naming) } // namespace detail /** * Registry for `tr_variant` <-> `T` converters. * Used by the `serializable` helpers to load/save fields in a class. */ class Converters { public: template using Deserialize = bool (*)(tr_variant const& src, T* ptgt); template using Serialize = tr_variant (*)(T const& src); template static tr_variant serialize(T const& src) { if (converter_storage.serialize != nullptr) { return converter_storage.serialize(src); } if constexpr (detail::is_push_back_range_v) { return detail::from_push_back_range(src); } else if constexpr (detail::is_insert_range_v) { return detail::from_insert_range(src); } else if constexpr (detail::is_std_array_v) { return detail::from_array(src); } else if constexpr (detail::is_optional_v) { return detail::from_optional(src); } else { fmt::print(stderr, "ERROR: No serializer registered for type '{}'\n", typeid(T).name()); return {}; } } template static bool deserialize(tr_variant const& src, T* const ptgt) { if (converter_storage.deserialize != nullptr) { return converter_storage.deserialize(src, ptgt); } if constexpr (detail::is_push_back_range_v) { return detail::to_push_back_range(src, ptgt); } else if constexpr (detail::is_insert_range_v) { return detail::to_insert_range(src, ptgt); } else if constexpr (detail::is_std_array_v) { return detail::to_array(src, ptgt); } else if constexpr (detail::is_optional_v) { return detail::to_optional(src, ptgt); } else { fmt::print(stderr, "ERROR: No deserializer registered for type '{}'\n", typeid(T).name()); return false; } } // register a new tr_variant<->T converter. template static void add(Deserialize deserialize, Serialize serialize) { converter_storage = { deserialize, serialize }; } static void ensure_default_converters(); private: template struct ConverterStorage { Deserialize deserialize = nullptr; Serialize serialize = nullptr; }; template static inline ConverterStorage converter_storage; }; template [[nodiscard]] std::optional to_value(tr_variant const& var) { if (auto ret = T{}; Converters::deserialize(var, &ret)) { return ret; } return {}; } template [[nodiscard]] tr_variant to_variant(T const& val) { return Converters::serialize(val); } // --- /** * Helpers for converting structured types to/from a `tr_variant`. * * Types opt in by declaring a `fields` tuple, typically: * `static constexpr auto fields = std::tuple{ Field<&T::member>{key}, ... }`. */ /** * Describes a single serializable field in a struct. * * @tparam MemberPtr Pointer-to-member, e.g. `&MyStruct::my_field` * @tparam Key Key type used for lookup in the variant map (default: tr_quark) * * Example: * Field<&Settings::port>{ TR_KEY_peer_port } */ template struct Field; template struct Field { Key const key; explicit constexpr Field(Key key_in) noexcept : key{ std::move(key_in) } { } template void load(Derived* derived, tr_variant::Map const& map) const { static_assert(std::is_base_of_v); if (auto const iter = map.find(key); iter != std::end(map)) { (void)Converters::deserialize(iter->second, &(static_cast(derived)->*MemberPtr)); } } template void save(Derived const* derived, tr_variant::Map& map) const { static_assert(std::is_base_of_v); map.try_emplace(key, Converters::serialize(static_cast(derived)->*MemberPtr)); } }; /** * Load fields from a variant map into a target object. * Missing keys are silently ignored (fields retain their existing values). * If `src` is not a map, this is a no-op. * * @param tgt The object to populate * @param fields A tuple of Field<> descriptors * @param src The source variant (expected to be a Map) */ template void load(T& tgt, Fields const& fields, tr_variant const& src) { if (auto const* map = src.get_if()) { std::apply([&tgt, map](auto const&... field) { (field.load(&tgt, *map), ...); }, fields); } } /** * Save an object's fields to a variant map. * * @param src The object to serialize * @param fields A tuple of Field<> descriptors * @return A tr_variant::Map containing the serialized fields */ template [[nodiscard]] tr_variant::Map save(T const& src, Fields const& fields) { auto map = tr_variant::Map{ std::tuple_size_v> }; std::apply([&src, &map](auto const&... field) { (field.save(&src, map), ...); }, fields); return map; } // --- // N.B. This second `detail` block contains the implementations of // to_push_back_range, from_push_back_range, etc., which were forward- // declared above. They must be defined after `Converters` because // they call Converters::serialize/deserialize for each element. namespace detail { template tr_variant from_push_back_range(C const& src) { auto ret = tr_variant::Vector{}; ret.reserve(std::size(src)); for (auto const& elem : src) { ret.emplace_back(Converters::serialize(elem)); } return ret; } template bool to_push_back_range(tr_variant const& src, C* const ptgt) { auto const* const vec = src.get_if(); if (vec == nullptr) { return false; } auto tmp = C{}; reserve_if_possible(tmp, std::size(*vec)); for (auto const& elem : *vec) { typename C::value_type value{}; if (!Converters::deserialize(elem, &value)) { return false; } tmp.push_back(std::move(value)); } *ptgt = std::move(tmp); return true; } template tr_variant from_insert_range(C const& src) { auto ret = tr_variant::Vector{}; ret.reserve(std::size(src)); for (auto const& elem : src) { ret.emplace_back(Converters::serialize(elem)); } return ret; } template bool to_insert_range(tr_variant const& src, C* const ptgt) { auto const* const vec = src.get_if(); if (vec == nullptr) { return false; } auto tmp = C{}; for (auto const& elem : *vec) { typename C::value_type value{}; if (!Converters::deserialize(elem, &value)) { return false; } tmp.insert(std::move(value)); } *ptgt = std::move(tmp); return true; } template tr_variant from_array(C const& src) { auto ret = tr_variant::Vector{}; ret.reserve(std::size(src)); for (auto const& elem : src) { ret.emplace_back(Converters::serialize(elem)); } return ret; } template bool to_array(tr_variant const& src, C* const ptgt) { auto const* const vec = src.get_if(); if (vec == nullptr) { return false; } if (std::size(*vec) != std::size(*ptgt)) { return false; // Array size mismatch } auto tmp = C{}; for (std::size_t i = 0; i < std::size(*vec); ++i) { if (!Converters::deserialize((*vec)[i], &tmp[i])) { return false; } } *ptgt = std::move(tmp); return true; } template tr_variant from_optional(std::optional const& src) { static_assert(!is_optional_v); return src ? Converters::serialize(*src) : nullptr; } template bool to_optional(tr_variant const& src, std::optional* ptgt) { static_assert(!is_optional_v); if (src.index() == tr_variant::NullIndex) { ptgt->reset(); return true; } if (auto val = to_value(src)) { *ptgt = std::move(val); return true; } return false; } } // namespace detail } // namespace libtransmission::serializer