From 4ab992b1542db8045c65e31de3828d338c09fa04 Mon Sep 17 00:00:00 2001 From: Alex Shabalin Date: Tue, 4 Jun 2024 16:08:20 +0200 Subject: [PATCH] Define policies to control how pool types and names are defined --- immer/extra/persist/common/type_traverse.hpp | 8 +- immer/extra/persist/json/json_immer.hpp | 60 +- immer/extra/persist/json/json_with_pool.hpp | 580 ++---------------- .../persist/json/json_with_pool_auto.hpp | 220 +------ immer/extra/persist/json/names.hpp | 26 + immer/extra/persist/json/persistable.hpp | 49 +- immer/extra/persist/json/policy.hpp | 145 +++++ immer/extra/persist/json/pools.hpp | 462 ++++++++++++++ immer/extra/persist/json/wrap.hpp | 144 +++++ test/extra/persist/CMakeLists.txt | 5 +- .../test_circular_dependency_conversion.cpp | 91 +-- test/extra/persist/test_conversion.cpp | 10 +- test/extra/persist/test_special_pool.cpp | 61 +- test/extra/persist/test_special_pool_auto.cpp | 33 +- .../persist/test_table_box_recursive.cpp | 14 +- 15 files changed, 1015 insertions(+), 893 deletions(-) create mode 100644 immer/extra/persist/json/names.hpp create mode 100644 immer/extra/persist/json/policy.hpp create mode 100644 immer/extra/persist/json/pools.hpp create mode 100644 immer/extra/persist/json/wrap.hpp diff --git a/immer/extra/persist/common/type_traverse.hpp b/immer/extra/persist/common/type_traverse.hpp index 2ad14c0b..bfc1e164 100644 --- a/immer/extra/persist/common/type_traverse.hpp +++ b/immer/extra/persist/common/type_traverse.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -125,7 +126,7 @@ constexpr auto insert_conditionally = [](auto map, auto pair) { * Generate a map (type, member_name) for all members of a given type, * recursively. */ -inline auto get_inner_types(const auto& type) +inline auto get_inner_types_map(const auto& type) { namespace hana = boost::hana; @@ -159,4 +160,9 @@ inline auto get_inner_types(const auto& type) return hana::to_map(result); } +inline auto get_inner_types(const auto& type) +{ + return boost::hana::keys(get_inner_types_map(type)); +} + } // namespace immer::persist::util diff --git a/immer/extra/persist/json/json_immer.hpp b/immer/extra/persist/json/json_immer.hpp index e535a600..765ea4d4 100644 --- a/immer/extra/persist/json/json_immer.hpp +++ b/immer/extra/persist/json/json_immer.hpp @@ -50,6 +50,12 @@ struct blackhole_output_archive } }; +// blackhole_output_archive doesn't care about names +struct empty_name_fn +{ + auto operator()(const auto& container) const { return ""; } +}; + template class error_duplicate_pool_name_found; @@ -85,16 +91,18 @@ constexpr bool is_pool_empty() * Adapted from cereal/archives/adapters.hpp */ -template +template class json_immer_output_archive : public cereal::OutputArchive< - json_immer_output_archive> + json_immer_output_archive> , public cereal::traits::TextArchive { public: + using pool_name_fn = PoolNameFn; + template explicit json_immer_output_archive(Args&&... args) - requires std::is_same_v + requires std::is_same_v : cereal::OutputArchive{this} , previous{std::forward(args)...} { @@ -102,7 +110,7 @@ class json_immer_output_archive template json_immer_output_archive(Pools pools_, Args&&... args) - requires std::is_same_v + requires std::is_same_v : cereal::OutputArchive{this} , previous{std::forward(args)...} , pools{std::move(pools_)} @@ -110,7 +118,7 @@ class json_immer_output_archive } template - json_immer_output_archive(Pools pools_, WrapF wrap_, Args&&... args) + json_immer_output_archive(Pools pools_, WrapFn wrap_, Args&&... args) : cereal::OutputArchive{this} , wrap{std::move(wrap_)} , previous{std::forward(args)...} @@ -197,7 +205,7 @@ class json_immer_output_archive } private: - template + template friend class json_immer_output_archive; // Recursively serializes the pools but not calling finalize @@ -207,7 +215,8 @@ class json_immer_output_archive auto ar = json_immer_output_archive{pools, wrap}; + decltype(wrap), + detail::empty_name_fn>{pools, wrap}; // Do not try to serialize pools again inside of this temporary // archive ar.finalized = true; @@ -215,11 +224,6 @@ class json_immer_output_archive return std::move(ar).get_output_pools(); }; - using Names = typename Pools::names_t; - using IsUnique = decltype(detail::are_type_names_unique(Names{})); - static_assert(IsUnique::value, - "Pool names for each type must be unique"); - auto prev = pools; while (true) { // Keep saving pools until everything is saved. @@ -232,22 +236,24 @@ class json_immer_output_archive } private: - WrapF wrap; + WrapFn wrap; Previous previous; Pools pools; bool finalized{false}; }; -template +template class json_immer_input_archive : public cereal::InputArchive< - json_immer_input_archive> + json_immer_input_archive> , public cereal::traits::TextArchive { public: + using pool_name_fn = PoolNameFn; + template json_immer_input_archive(Pools pools_, Args&&... args) - requires std::is_same_v + requires std::is_same_v : cereal::InputArchive{this} , previous{std::forward(args)...} , pools{std::move(pools_)} @@ -255,7 +261,7 @@ class json_immer_input_archive } template - json_immer_input_archive(Pools pools_, WrapF wrap_, Args&&... args) + json_immer_input_archive(Pools pools_, WrapFn wrap_, Args&&... args) : cereal::InputArchive{this} , wrap{std::move(wrap_)} , previous{std::forward(args)...} @@ -333,7 +339,7 @@ class json_immer_input_archive bool ignore_pool_exceptions = false; private: - WrapF wrap; + WrapFn wrap; Previous previous; Pools pools; @@ -347,19 +353,21 @@ class json_immer_input_archive namespace cereal { namespace traits { namespace detail { -template +template struct get_output_from_input< - immer::persist::json_immer_input_archive> + immer::persist:: + json_immer_input_archive> { - using type = - immer::persist::json_immer_output_archive; + using type = immer::persist:: + json_immer_output_archive; }; -template +template struct get_input_from_output< - immer::persist::json_immer_output_archive> + immer::persist:: + json_immer_output_archive> { - using type = - immer::persist::json_immer_input_archive; + using type = immer::persist:: + json_immer_input_archive; }; } // namespace detail } // namespace traits diff --git a/immer/extra/persist/json/json_with_pool.hpp b/immer/extra/persist/json/json_with_pool.hpp index 2ac72029..4ce5edb4 100644 --- a/immer/extra/persist/json/json_with_pool.hpp +++ b/immer/extra/persist/json/json_with_pool.hpp @@ -1,12 +1,7 @@ #pragma once -#include -#include -#include - -#include - -#include +#include +#include /** * to_json_with_pool @@ -14,506 +9,54 @@ namespace immer::persist { -namespace detail { - -namespace hana = boost::hana; - -/** - * Unimplemented class to generate a compile-time error and show what the type T - * is. - */ -template -class error_no_pool_for_the_given_type_check_get_pools_types_function; - -template -class error_missing_pool_for_type; - -template -struct storage_holder -{ - T value; - - auto& operator()() { return value; } - const auto& operator()() const { return value; } -}; - -template -auto make_storage_holder(T&& value) -{ - return storage_holder>{std::forward(value)}; -} - -template -struct shared_storage_holder -{ - std::shared_ptr ptr; - - auto& operator()() { return *ptr; } - const auto& operator()() const { return *ptr; } -}; - -template -auto make_shared_storage_holder(std::shared_ptr ptr) -{ - return shared_storage_holder{std::move(ptr)}; -} - -/** - * Pools and functions to serialize types that contain persistable data - * structures. - */ -template -struct output_pools -{ - using names_t = Names; - - Storage storage_; - - // To aling the interface with input_pools - Storage& storage() { return storage_; } - const Storage& storage() const { return storage_; } - - template - void save(Archive& ar) const - { - constexpr auto keys = hana::keys(names_t{}); - hana::for_each(keys, [&](auto key) { - constexpr auto name = names_t{}[key]; - ar(cereal::make_nvp(name.c_str(), storage()[key])); - }); - } - - template - auto& get_output_pool() - { - using Contains = decltype(hana::contains(storage(), hana::type_c)); - if constexpr (!Contains::value) { - auto err = - error_no_pool_for_the_given_type_check_get_pools_types_function< - T>{}; - } - return storage()[hana::type_c]; - } - - template - const auto& get_output_pool() const - { - using Contains = decltype(hana::contains(storage(), hana::type_c)); - if constexpr (!Contains::value) { - auto err = - error_no_pool_for_the_given_type_check_get_pools_types_function< - T>{}; - } - return storage()[hana::type_c]; - } - - friend bool operator==(const output_pools& left, const output_pools& right) - { - return left.storage() == right.storage(); - } -}; - -template -struct no_loader -{}; - -template -struct with_loader -{ - std::optional loader; - - auto& get_loader_from_per_type_pool() - { - if (!loader) { - auto& self = static_cast(*this); - loader.emplace(self.pool, self.transform); - } - return *loader; - } -}; - -/** - * A pool for one container type. - * Normally, the pool does not contain a loader, which is located inside the - * json_immer_input_archive. - * - * But in case of transformations, there is no json_immer_input_archive involved - * and it becomes convenient to have the corresponding loader stored here, too, - * via with_loader. - */ -template ::input_pool_t, - class TransformF = boost::hana::id_t, - class OldContainerType = boost::hana::id_t, - template class LoaderMixin = no_loader> -struct input_pool - : LoaderMixin, - typename container_traits< - Container>::template loader_t> -{ - using container_t = Container; - using old_container_t = OldContainerType; - - Pool pool = {}; - TransformF transform; - - input_pool() = default; - - explicit input_pool(Pool pool_) - requires std::is_same_v - : pool{std::move(pool_)} - { - } - - explicit input_pool(Pool pool_, TransformF transform_) - : pool{std::move(pool_)} - , transform{std::move(transform_)} - { - } - - template - auto with_transform(Func&& func) const - { - using value_type = typename Container::value_type; - using new_value_type = - std::decay_t()))>; - using NewContainer = - std::decay_t::transform( - func))>; - using TransF = std::function; - // the transform function must be filled in later - return input_pool{ - pool, TransF{}}; - } - - friend bool operator==(const input_pool& left, const input_pool& right) - { - return left.pool == right.pool; - } - - void merge_previous(const input_pool& original) - { - pool.merge_previous(original.pool); - } - - static auto generate_loader() - { - return std::optional::template loader_t>{}; - } -}; - -/** - * Transforms a given function into another function that: - * - If the given function is a function of one argument, nothing changes. - * - Otherwise, passes the given argument as the second argument for the - * function. - * - * In other words, takes a function of maybe two arguments and returns a - * function of just one argument. - */ -constexpr auto inject_argument = [](auto arg, auto func) { - return [arg = std::move(arg), func = std::move(func)](auto&& old) { - const auto is_valid = hana::is_valid(func, old); - if constexpr (std::decay_t::value) { - return func(old); - } else { - return func(old, arg); - } - }; -}; - -template -class input_pools -{ -private: - StorageF storage_; - -public: - using names_t = Names; - - input_pools() = default; - - explicit input_pools(StorageF storage) - : storage_{std::move(storage)} - { - } - - input_pools(StorageF storage, Names) - : storage_{std::move(storage)} - { - } - - auto& storage() { return storage_(); } - const auto& storage() const { return storage_(); } - - template - const auto& get_pool() - { - using Contains = - decltype(hana::contains(storage(), hana::type_c)); - if constexpr (!Contains::value) { - auto err = error_missing_pool_for_type{}; - } - return storage()[hana::type_c]; - } - - template - auto& get_loader_by_old_container() - { - constexpr auto find_key = [](const auto& storage) { - return hana::find_if(hana::keys(storage), [&](auto key) { - using type1 = typename std::decay_t< - decltype(storage[key])>::old_container_t; - return hana::type_c == hana::type_c; - }); - }; - using Key = decltype(find_key(storage())); - using IsJust = decltype(hana::is_just(Key{})); - if constexpr (!IsJust::value) { - auto err = error_missing_pool_for_type{}; - } - return storage()[Key{}.value()].get_loader_from_per_type_pool(); - } - - template - void load(Archive& ar) - { - constexpr auto keys = hana::keys(names_t{}); - hana::for_each(keys, [&](auto key) { - constexpr auto name = names_t{}[key]; - ar(cereal::make_nvp(name.c_str(), storage()[key].pool)); - }); - } - - friend bool operator==(const input_pools& left, const input_pools& right) - { - return left.storage() == right.storage(); - } - - /** - * ConversionMap is a map where keys are types of the original container - * (hana::type_c>) and the values are converting - * functions that are used to convert the pools. - * - * The main feature is that the converting function can also take the second - * argument, a function get_loader, which can be called with a container - * type (`get_loader(hana::type_c>)`) and will - * return a reference to a loader that can be used to load other containers - * that have already been converted. - * - * @see test/extra/persist/test_conversion.cpp - */ - template - auto transform_recursive(const ConversionMap& conversion_map, - const GetIdF& get_id) const - { - // This lambda is only used to determine types and should never be - // called. - constexpr auto fake_convert_container = [](auto new_type, - const auto& old_container) { - using NewContainerType = typename decltype(new_type)::type; - throw std::runtime_error{"This should never be called"}; - return NewContainerType{}; - }; - - // Return a pair where second is an optional. Optional is already - // populated if no transformation is required. - const auto transform_pair_initial = [fake_convert_container, - &conversion_map, - this](const auto& pair) { - using Contains = - decltype(hana::contains(conversion_map, hana::first(pair))); - if constexpr (Contains::value) { - // Look up the conversion function by the type from the - // original pool. - const auto& func = inject_argument( - fake_convert_container, conversion_map[hana::first(pair)]); - auto type_load = - storage()[hana::first(pair)].with_transform(func); - using NewContainer = typename decltype(type_load)::container_t; - return hana::make_pair(hana::type_c, - std::move(type_load)); - } else { - // If the conversion map doesn't mention the current type, - // we leave it as is. - return pair; - } - }; - - // I can't think of a better way yet to tie all the loaders/transforming - // functions together. - auto shared_storage = [&] { - auto new_storage = hana::fold_left( - storage(), - hana::make_map(), - [transform_pair_initial](auto map, auto pair) { - return hana::insert(map, transform_pair_initial(pair)); - }); - using NewStorage = decltype(new_storage); - return std::make_shared(new_storage); - }(); - - using NewStorage = std::decay_t; - - const auto convert_container = [get_id](auto get_data) { - return [get_id, get_data](auto new_type, - const auto& old_container) { - const auto id = get_id(old_container); - using Contains = decltype(hana::contains(get_data(), new_type)); - if constexpr (!Contains::value) { - auto err = error_missing_pool_for_type< - typename decltype(new_type)::type>{}; - } - auto& loader = - get_data()[new_type].get_loader_from_per_type_pool(); - return loader.load(id); - }; - }; - - // Important not to create a recursive reference to itself inside of the - // shared_storage. - const auto weak = std::weak_ptr{shared_storage}; - const auto get_data_weak = [weak]() -> auto& { - auto p = weak.lock(); - if (!p) { - throw std::logic_error{"weak ptr has expired"}; - } - return *p; - }; - - // Fill-in the transforming functions into shared_storage. - hana::for_each(hana::keys(*shared_storage), [&](auto key) { - using TypeLoad = std::decay_t; - const auto old_key = - hana::type_c; - using needs_conversion_t = - decltype(hana::contains(conversion_map, old_key)); - if constexpr (needs_conversion_t::value) { - const auto& old_func = conversion_map[old_key]; - (*shared_storage)[key].transform = - inject_argument(convert_container(get_data_weak), old_func); - } - }); - - auto holder = make_shared_storage_holder(std::move(shared_storage)); - return input_pools{std::move(holder)}; - } - - void merge_previous(const input_pools& original) - { - auto& s = storage(); - const auto& original_s = original.storage(); - hana::for_each(hana::keys(s), [&](auto key) { - s[key].merge_previous(original_s[key]); - }); - } - - static auto generate_loaders() - { - using Storage = std::decay_t()())>; - using Types = decltype(hana::keys(std::declval())); - auto storage = - hana::fold_left(Types{}, hana::make_map(), [](auto map, auto type) { - using TypePool = - std::decay_t()[type])>; - return hana::insert( - map, hana::make_pair(type, TypePool::generate_loader())); - }); - return storage; - } -}; - -inline auto generate_output_pools(auto type_names) -{ - auto storage = - hana::fold_left(type_names, hana::make_map(), [](auto map, auto pair) { - using Type = typename decltype(+hana::first(pair))::type; - return hana::insert( - map, - hana::make_pair( - hana::first(pair), - typename container_traits::output_pool_t{})); - }); - - using Storage = decltype(storage); - using Names = decltype(type_names); - return output_pools{storage}; -} - -inline auto generate_input_pools(auto type_names) -{ - auto storage = - hana::fold_left(type_names, hana::make_map(), [](auto map, auto pair) { - using Type = typename decltype(+hana::first(pair))::type; - return hana::insert( - map, hana::make_pair(hana::first(pair), input_pool{})); - }); - - auto storage_f = detail::make_storage_holder(std::move(storage)); - return input_pools{std::move(storage_f), type_names}; -} - -template -inline auto to_input_pools(const output_pools& output_pool) -{ - auto pool = generate_input_pools(Names{}); - boost::hana::for_each(boost::hana::keys(pool.storage()), [&](auto key) { - pool.storage()[key].pool = to_input_pool(output_pool.storage()[key]); - }); - return pool; -} - -} // namespace detail - -template -auto get_pools_types(const T&) -{ - return boost::hana::make_map(); -} - /** * Type T must provide a callable free function get_pools_types(const T&). */ -template -auto to_json_with_pool(const T& value0) +template Policy = default_policy> +auto to_json_with_pool(const T& value0, const Policy& policy = Policy{}) { auto os = std::ostringstream{}; { - auto pools = detail::generate_output_pools(get_pools_types(value0)); + auto pools = + detail::generate_output_pools(policy.get_pool_types(value0)); using Pools = std::decay_t; - auto ar = - immer::persist::json_immer_output_archive{os}; + auto ar = immer::persist::json_immer_output_archive< + Archive, + Pools, + decltype(policy.get_output_wrap_fn()), + decltype(policy.get_pool_name_fn(value0))>{ + pools, policy.get_output_wrap_fn(), os}; ar(CEREAL_NVP(value0)); } return os.str(); } -template +template auto load_pools(std::istream& is, const auto& wrap) { - const auto reload_pool = - [wrap](std::istream& is, Pools pools, bool ignore_pool_exceptions) { - auto restore = immer::util::istream_snapshot{is}; - const auto original_pools = pools; - auto ar = json_immer_input_archive{ - std::move(pools), wrap, is}; - ar.ignore_pool_exceptions = ignore_pool_exceptions; - /** - * NOTE: Critical to clear the pools before loading into it - * again. I hit a bug when pools contained a vector and every - * load would append to it, instead of replacing the contents. - */ - pools = {}; - ar(CEREAL_NVP(pools)); - pools.merge_previous(original_pools); - return pools; - }; + const auto reload_pool = [wrap](std::istream& is, + Pools pools, + bool ignore_pool_exceptions) { + auto restore = immer::util::istream_snapshot{is}; + const auto original_pools = pools; + auto ar = + json_immer_input_archive{std::move(pools), wrap, is}; + ar.ignore_pool_exceptions = ignore_pool_exceptions; + /** + * NOTE: Critical to clear the pools before loading into it + * again. I hit a bug when pools contained a vector and every + * load would append to it, instead of replacing the contents. + */ + pools = {}; + ar(CEREAL_NVP(pools)); + pools.merge_previous(original_pools); + return pools; + }; auto pools = Pools{}; if constexpr (detail::is_pool_empty()) { @@ -540,30 +83,38 @@ auto load_pools(std::istream& is, const auto& wrap) return pools; } -template -T from_json_with_pool(std::istream& is) +template Policy = default_policy> +T from_json_with_pool(std::istream& is, const Policy& policy = Policy{}) { - using Pools = std::decay_t())))>; - auto pools = load_pools(is, boost::hana::id); + using Pools = std::decay_t())))>; + using PoolNameFn = decltype(policy.get_pool_name_fn(std::declval())); + + const auto& wrap = policy.get_input_wrap_fn(); + + auto pools = load_pools(is, wrap); - auto ar = - immer::persist::json_immer_input_archive{std::move(pools), is}; + auto ar = immer::persist:: + json_immer_input_archive{ + std::move(pools), wrap, is}; auto value0 = T{}; ar(CEREAL_NVP(value0)); return value0; } -template -T from_json_with_pool(const std::string& input) +template Policy = default_policy> +T from_json_with_pool(const std::string& input, const Policy& policy = Policy{}) { auto is = std::istringstream{input}; - return from_json_with_pool(is); + return from_json_with_pool(is, policy); } -template -auto get_container_id(const detail::output_pools& pools, +template +auto get_container_id(const detail::output_pools& pools, const Container& container) { const auto& old_pool = @@ -580,9 +131,9 @@ auto get_container_id(const detail::output_pools& pools, * Given an output_pools and a map of transformations, produce a new type of * load pool with those transformations applied */ -template +template inline auto -transform_output_pool(const detail::output_pools& old_pools, +transform_output_pool(const detail::output_pools& old_pools, const ConversionMap& conversion_map) { const auto old_load_pools = to_input_pools(old_pools); @@ -598,15 +149,10 @@ transform_output_pool(const detail::output_pools& old_pools, * Given an old save pools and a new (transformed) load pools, effectively * convert the given container. */ -template -auto convert_container( - const detail::output_pools& old_save_pools, - detail::input_pools& new_load_pools, - const Container& container) +template +auto convert_container(const detail::output_pools& old_save_pools, + detail::input_pools& new_load_pools, + const Container& container) { const auto container_id = get_container_id(old_save_pools, container); auto& loader = diff --git a/immer/extra/persist/json/json_with_pool_auto.hpp b/immer/extra/persist/json/json_with_pool_auto.hpp index 2974f7d8..ded98869 100644 --- a/immer/extra/persist/json/json_with_pool_auto.hpp +++ b/immer/extra/persist/json/json_with_pool_auto.hpp @@ -1,233 +1,43 @@ #pragma once -#include #include -#include - -#include -#include -#include - -#include namespace immer::persist { -template -struct is_auto_ignored_type : boost::hana::false_ -{}; - -template -struct is_auto_ignored_type>> - : boost::hana::true_ -{}; - -template -struct is_auto_ignored_type>> - : boost::hana::true_ -{}; - -template <> -struct is_auto_ignored_type> - : boost::hana::true_ -{}; - -template <> -struct is_auto_ignored_type> : boost::hana::true_ -{}; - -template <> -struct is_auto_ignored_type> : boost::hana::true_ -{}; - -/** - * This wrapper is used to load a given container via persistable. - */ -template -struct persistable_loader_wrapper -{ - Container& value; - - template - typename container_traits::container_id::rep_t - save_minimal(const Archive&) const - { - throw std::logic_error{ - "Should never be called. persistable_loader_wrapper::save_minimal"}; - } - - template - void load_minimal( - const Archive& ar, - const typename container_traits::container_id::rep_t& - container_id) - { - persistable arch; - immer::persist::load_minimal(ar, arch, container_id); - value = std::move(arch).container; - } -}; - -constexpr auto is_persistable = boost::hana::is_valid( - [](auto&& obj) -> - typename container_traits>::output_pool_t {}); - -constexpr auto is_auto_ignored = [](const auto& value) { - return is_auto_ignored_type>{}; -}; - -/** - * Make a function that operates conditionally on its single argument, based on - * the given predicate. If the predicate is not satisfied, the function forwards - * its argument unchanged. - */ -constexpr auto make_conditional_func = [](auto pred, auto func) { - return [pred, func](auto&& value) -> decltype(auto) { - return boost::hana::if_(pred(value), func, boost::hana::id)( - std::forward(value)); - }; -}; - -// We must not try to persist types that are actually the pool itself, -// for example, `immer::map> leaves` etc. -constexpr auto exclude_internal_pool_types = [](auto wrap) { - namespace hana = boost::hana; - return make_conditional_func(hana::compose(hana::not_, is_auto_ignored), - wrap); -}; - -constexpr auto to_persistable = [](const auto& x) { - return persistable>(x); -}; - -constexpr auto to_persistable_loader = [](auto& value) { - using V = std::decay_t; - return persistable_loader_wrapper{value}; -}; - -/** - * This function will wrap a value in persistable if possible or will return a - * reference to its argument. - */ -constexpr auto wrap_for_saving = exclude_internal_pool_types( - make_conditional_func(is_persistable, to_persistable)); - -constexpr auto wrap_for_loading = exclude_internal_pool_types( - make_conditional_func(is_persistable, to_persistable_loader)); - -/** - * Generate a hana map of persistable members for the given type, recursively. - * Example: - * [(type_c>, "tracks")] - */ -auto get_pools_for_type(auto type) +template +auto to_json_with_auto_pool(const T& serializable, + const PoolsTypes& pools_types) { - namespace hana = boost::hana; - auto all_types_map = util::get_inner_types(type); - auto persistable = - hana::filter(hana::to_tuple(all_types_map), [&](auto pair) { - using T = typename decltype(+hana::first(pair))::type; - return is_persistable(T{}); - }); - return hana::to_map(persistable); + return to_json_with_pool(serializable, via_map_policy{}); } -template > -auto to_json_with_auto_pool(const T& serializable, - const PoolsTypes& pools_types, - const WrapF& wrap = wrap_for_saving) +template +T from_json_with_auto_pool(const std::string& input, + const PoolsTypes& pools_types) { - // In the future, wrap function may ignore certain user-provided types that - // should not be persisted. - static_assert( - std::is_same_v())), - const std::string&>, - "wrap must return a reference when it's not wrapping the type"); - static_assert( - std::is_same_v{})), - persistable>>, - "and a value when it's wrapping"); - - auto os = std::ostringstream{}; - { - auto pools = detail::generate_output_pools(pools_types); - using Pools = std::decay_t; - auto ar = json_immer_output_archive{pools, wrap, os}; - // value0 because that's now cereal saves the unnamed object by default, - // maybe change later. - ar(cereal::make_nvp("value0", serializable)); - } - return os.str(); + return from_json_with_pool(input, via_map_policy{}); } // Same as to_json_with_auto_pool but we don't generate any JSON. -template > -auto get_auto_pool(const T& serializable, - const PoolsTypes& pools_types, - const WrapF& wrap = wrap_for_saving) +template Policy = hana_struct_auto_policy> +auto get_auto_pool(const T& value0, const Policy& policy = Policy{}) { - // In the future, wrap function may ignore certain user-provided types that - // should not be persisted. - static_assert( - std::is_same_v())), - const std::string&>, - "wrap must return a reference when it's not wrapping the type"); - static_assert( - std::is_same_v{})), - persistable>>, - "and a value when it's wrapping"); - - auto pools = detail::generate_output_pools(pools_types); + const auto& wrap = policy.get_output_wrap_fn(); + auto pools = detail::generate_output_pools(policy.get_pool_types(value0)); using Pools = std::decay_t; { auto ar = json_immer_output_archive{pools, wrap}; + decltype(wrap), + detail::empty_name_fn>{pools, wrap}; // value0 because that's now cereal saves the unnamed object by default, // maybe change later. - ar(cereal::make_nvp("value0", serializable)); + ar(CEREAL_NVP(value0)); ar.finalize(); pools = std::move(ar).get_output_pools(); } return pools; } -template -T from_json_with_auto_pool(std::istream& is, const PoolsTypes& pools_types) -{ - namespace hana = boost::hana; - constexpr auto wrap = wrap_for_loading; - - using Pools = - std::decay_t; - - auto pools = load_pools(is, wrap); - - auto ar = - json_immer_input_archive{std::move(pools), wrap, is}; - // value0 because that's now cereal saves the unnamed object by default, - // maybe change later. - auto value0 = T{}; - ar(CEREAL_NVP(value0)); - return value0; -} - -template -T from_json_with_auto_pool(const std::string& input, - const PoolsTypes& pools_types) -{ - auto is = std::istringstream{input}; - return from_json_with_auto_pool(is, pools_types); -} - } // namespace immer::persist diff --git a/immer/extra/persist/json/names.hpp b/immer/extra/persist/json/names.hpp new file mode 100644 index 00000000..c044cfca --- /dev/null +++ b/immer/extra/persist/json/names.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +namespace immer::persist { + +struct get_demangled_name_fn +{ + template + auto operator()(const T&) const + { + return boost::core::demangle(typeid(std::decay_t).name()); + } +}; + +template +struct name_from_map_fn +{ + auto operator()(const auto& container) const + { + return Map{}[boost::hana::typeid_(container)].c_str(); + } +}; + +} // namespace immer::persist diff --git a/immer/extra/persist/json/persistable.hpp b/immer/extra/persist/json/persistable.hpp index 4089365b..be31fcdb 100644 --- a/immer/extra/persist/json/persistable.hpp +++ b/immer/extra/persist/json/persistable.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -69,20 +69,20 @@ struct persistable template -auto save_minimal( - const json_immer_output_archive, - WrapF>& ar, - const persistable& value) +auto save_minimal(const json_immer_output_archive, + WrapFn, + PoolNameFn>& ar, + const persistable& value) { auto& pool = - const_cast< - json_immer_output_archive, - WrapF>&>(ar) + const_cast, + WrapFn, + PoolNameFn>&>(ar) .get_output_pools() .template get_output_pool(); auto [pool2, id] = add_to_pool(value.container, std::move(pool)); @@ -94,27 +94,32 @@ auto save_minimal( // possible to have only load_minimal for a type without having save_minimal. template -auto save_minimal( - const json_immer_output_archive, - WrapF>& ar, - const persistable& value) -> +auto save_minimal(const json_immer_output_archive, + WrapFn, + PoolNameFn>& ar, + const persistable& value) -> typename container_traits::container_id::rep_t { throw std::logic_error{"Should never be called"}; } -template +template void load_minimal( - const json_immer_input_archive& ar, + const json_immer_input_archive& ar, persistable& value, const typename container_traits::container_id::rep_t& id) { auto& loader = - const_cast&>(ar) + const_cast< + json_immer_input_archive&>(ar) .template get_loader(); // Have to be specific because for vectors container_id is different from diff --git a/immer/extra/persist/json/policy.hpp b/immer/extra/persist/json/policy.hpp new file mode 100644 index 00000000..80091402 --- /dev/null +++ b/immer/extra/persist/json/policy.hpp @@ -0,0 +1,145 @@ +#pragma once + +#include +#include + +// Bring in all known pools to be able to wrap all immer types +#include +#include +#include + +namespace immer::persist { + +template +concept Policy = requires(Value value, T policy) { + policy.get_pool_types(value); + policy.get_output_wrap_fn(); + policy.get_input_wrap_fn(); + policy.get_pool_name_fn(value); +}; + +template +auto get_pools_names(const T&) +{ + return boost::hana::make_map(); +} + +template +auto get_pools_types(const T&) +{ + return boost::hana::make_set(); +} + +struct via_get_pools_names_policy +{ + template + auto get_pool_types(const T& value) const + { + return boost::hana::keys(get_pools_names(value)); + } + + auto get_output_wrap_fn() const { return boost::hana::id; } + auto get_input_wrap_fn() const { return boost::hana::id; } + + template + auto get_pool_name_fn(const T& value) const + { + using Map = decltype(get_pools_names(value)); + return name_from_map_fn{}; + } +}; + +struct via_get_pools_types_policy +{ + template + auto get_pool_types(const T& value) const + { + return get_pools_types(value); + } + + auto get_output_wrap_fn() const { return boost::hana::id; } + auto get_input_wrap_fn() const { return boost::hana::id; } + + template + auto get_pool_name_fn(const T& value) const + { + return get_demangled_name_fn{}; + } +}; + +struct hana_struct_auto_policy +{ + template + auto get_pool_types(const T& value) const + { + return get_pools_for_type(boost::hana::typeid_(value)); + } + + auto get_output_wrap_fn() const { return wrap_for_saving; } + auto get_input_wrap_fn() const { return wrap_for_loading; } + + template + auto get_pool_name_fn(const T&) const + { + return get_demangled_name_fn{}; + } +}; + +struct hana_struct_auto_member_name_policy +{ + template + auto get_pool_types(const T& value) const + { + return get_pools_for_type(boost::hana::typeid_(value)); + } + + auto get_output_wrap_fn() const { return wrap_for_saving; } + auto get_input_wrap_fn() const { return wrap_for_loading; } + + template + auto get_pool_name_fn(const T& value) const + { + using map_t = + decltype(get_named_pools_for_type(boost::hana::typeid_(value))); + return name_from_map_fn{}; + } +}; + +template +struct via_map_policy +{ + static_assert(boost::hana::is_a, + "via_map_policy accepts a map of types to pool names"); + + template + auto get_pool_types(const T& value) const + { + return boost::hana::keys(Map{}); + } + + auto get_output_wrap_fn() const + { + static_assert( + std::is_same_v())), + const std::string&>, + "wrap must return a reference when it's not wrapping the type"); + static_assert( + std::is_same_v{})), + persistable>>, + "and a value when it's wrapping"); + + return wrap_for_saving; + } + auto get_input_wrap_fn() const { return wrap_for_loading; } + + template + auto get_pool_name_fn(const T&) const + { + return name_from_map_fn{}; + } +}; + +using default_policy = via_get_pools_types_policy; + +} // namespace immer::persist diff --git a/immer/extra/persist/json/pools.hpp b/immer/extra/persist/json/pools.hpp new file mode 100644 index 00000000..5daadf40 --- /dev/null +++ b/immer/extra/persist/json/pools.hpp @@ -0,0 +1,462 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include + +namespace immer::persist { + +namespace detail { + +namespace hana = boost::hana; + +/** + * Unimplemented class to generate a compile-time error and show what the type T + * is. + */ +template +class error_no_pool_for_the_given_type_check_get_pools_types_function; + +template +class error_missing_pool_for_type; + +template +struct storage_holder +{ + T value; + + auto& operator()() { return value; } + const auto& operator()() const { return value; } +}; + +template +auto make_storage_holder(T&& value) +{ + return storage_holder>{std::forward(value)}; +} + +template +struct shared_storage_holder +{ + std::shared_ptr ptr; + + auto& operator()() { return *ptr; } + const auto& operator()() const { return *ptr; } +}; + +template +auto make_shared_storage_holder(std::shared_ptr ptr) +{ + return shared_storage_holder{std::move(ptr)}; +} + +/** + * Pools and functions to serialize types that contain persistable data + * structures. + */ +template +struct output_pools +{ + Storage storage_; + + // To aling the interface with input_pools + Storage& storage() { return storage_; } + const Storage& storage() const { return storage_; } + + template + void save(Archive& ar) const + { + using pool_name_fn = typename Archive::pool_name_fn; + using keys_t = decltype(hana::keys(storage())); + hana::for_each(keys_t{}, [&](auto key) { + using Container = typename decltype(key)::type; + const auto& name = pool_name_fn{}(Container{}); + ar(cereal::make_nvp(name, storage()[key])); + }); + } + + template + auto& get_output_pool() + { + using Contains = decltype(hana::contains(storage(), hana::type_c)); + if constexpr (!Contains::value) { + auto err = + error_no_pool_for_the_given_type_check_get_pools_types_function< + T>{}; + } + return storage()[hana::type_c]; + } + + template + const auto& get_output_pool() const + { + using Contains = decltype(hana::contains(storage(), hana::type_c)); + if constexpr (!Contains::value) { + auto err = + error_no_pool_for_the_given_type_check_get_pools_types_function< + T>{}; + } + return storage()[hana::type_c]; + } + + friend bool operator==(const output_pools& left, const output_pools& right) + { + return left.storage() == right.storage(); + } +}; + +template +struct no_loader +{}; + +template +struct with_loader +{ + std::optional loader; + + auto& get_loader_from_per_type_pool() + { + if (!loader) { + auto& self = static_cast(*this); + loader.emplace(self.pool, self.transform); + } + return *loader; + } +}; + +/** + * A pool for one container type. + * Normally, the pool does not contain a loader, which is located inside the + * json_immer_input_archive. + * + * But in case of transformations, there is no json_immer_input_archive involved + * and it becomes convenient to have the corresponding loader stored here, too, + * via with_loader. + */ +template ::input_pool_t, + class TransformF = boost::hana::id_t, + class OldContainerType = boost::hana::id_t, + template class LoaderMixin = no_loader> +struct input_pool + : LoaderMixin, + typename container_traits< + Container>::template loader_t> +{ + using container_t = Container; + using old_container_t = OldContainerType; + + Pool pool = {}; + TransformF transform; + + input_pool() = default; + + explicit input_pool(Pool pool_) + requires std::is_same_v + : pool{std::move(pool_)} + { + } + + explicit input_pool(Pool pool_, TransformF transform_) + : pool{std::move(pool_)} + , transform{std::move(transform_)} + { + } + + template + auto with_transform(Func&& func) const + { + using value_type = typename Container::value_type; + using new_value_type = + std::decay_t()))>; + using NewContainer = + std::decay_t::transform( + func))>; + using TransF = std::function; + // the transform function must be filled in later + return input_pool{ + pool, TransF{}}; + } + + friend bool operator==(const input_pool& left, const input_pool& right) + { + return left.pool == right.pool; + } + + void merge_previous(const input_pool& original) + { + pool.merge_previous(original.pool); + } + + static auto generate_loader() + { + return std::optional::template loader_t>{}; + } +}; + +/** + * Transforms a given function into another function that: + * - If the given function is a function of one argument, nothing changes. + * - Otherwise, passes the given argument as the second argument for the + * function. + * + * In other words, takes a function of maybe two arguments and returns a + * function of just one argument. + */ +constexpr auto inject_argument = [](auto arg, auto func) { + return [arg = std::move(arg), func = std::move(func)](auto&& old) { + const auto is_valid = hana::is_valid(func, old); + if constexpr (std::decay_t::value) { + return func(old); + } else { + return func(old, arg); + } + }; +}; + +template +class input_pools +{ +private: + StorageF storage_; + +public: + input_pools() = default; + + explicit input_pools(StorageF storage) + : storage_{std::move(storage)} + { + } + + auto& storage() { return storage_(); } + const auto& storage() const { return storage_(); } + + template + const auto& get_pool() + { + using Contains = + decltype(hana::contains(storage(), hana::type_c)); + if constexpr (!Contains::value) { + auto err = error_missing_pool_for_type{}; + } + return storage()[hana::type_c]; + } + + template + auto& get_loader_by_old_container() + { + constexpr auto find_key = [](const auto& storage) { + return hana::find_if(hana::keys(storage), [&](auto key) { + using type1 = typename std::decay_t< + decltype(storage[key])>::old_container_t; + return hana::type_c == hana::type_c; + }); + }; + using Key = decltype(find_key(storage())); + using IsJust = decltype(hana::is_just(Key{})); + if constexpr (!IsJust::value) { + auto err = error_missing_pool_for_type{}; + } + return storage()[Key{}.value()].get_loader_from_per_type_pool(); + } + + template + void load(Archive& ar) + { + using pool_name_fn = typename Archive::pool_name_fn; + using keys_t = decltype(hana::keys(storage())); + hana::for_each(keys_t{}, [&](auto key) { + using Container = typename decltype(key)::type; + const auto& name = pool_name_fn{}(Container{}); + ar(cereal::make_nvp(name, storage()[key].pool)); + }); + } + + friend bool operator==(const input_pools& left, const input_pools& right) + { + return left.storage() == right.storage(); + } + + /** + * ConversionMap is a map where keys are types of the original container + * (hana::type_c>) and the values are converting + * functions that are used to convert the pools. + * + * The main feature is that the converting function can also take the second + * argument, a function get_loader, which can be called with a container + * type (`get_loader(hana::type_c>)`) and will + * return a reference to a loader that can be used to load other containers + * that have already been converted. + * + * @see test/extra/persist/test_conversion.cpp + */ + template + auto transform_recursive(const ConversionMap& conversion_map, + const GetIdF& get_id) const + { + // This lambda is only used to determine types and should never be + // called. + constexpr auto fake_convert_container = [](auto new_type, + const auto& old_container) { + using NewContainerType = typename decltype(new_type)::type; + throw std::runtime_error{"This should never be called"}; + return NewContainerType{}; + }; + + // Return a pair where second is an optional. Optional is already + // populated if no transformation is required. + const auto transform_pair_initial = [fake_convert_container, + &conversion_map, + this](const auto& pair) { + using Contains = + decltype(hana::contains(conversion_map, hana::first(pair))); + if constexpr (Contains::value) { + // Look up the conversion function by the type from the + // original pool. + const auto& func = inject_argument( + fake_convert_container, conversion_map[hana::first(pair)]); + auto type_load = + storage()[hana::first(pair)].with_transform(func); + using NewContainer = typename decltype(type_load)::container_t; + return hana::make_pair(hana::type_c, + std::move(type_load)); + } else { + // If the conversion map doesn't mention the current type, + // we leave it as is. + return pair; + } + }; + + // I can't think of a better way yet to tie all the loaders/transforming + // functions together. + auto shared_storage = [&] { + auto new_storage = hana::fold_left( + storage(), + hana::make_map(), + [transform_pair_initial](auto map, auto pair) { + return hana::insert(map, transform_pair_initial(pair)); + }); + using NewStorage = decltype(new_storage); + return std::make_shared(new_storage); + }(); + + using NewStorage = std::decay_t; + + const auto convert_container = [get_id](auto get_data) { + return [get_id, get_data](auto new_type, + const auto& old_container) { + const auto id = get_id(old_container); + using Contains = decltype(hana::contains(get_data(), new_type)); + if constexpr (!Contains::value) { + auto err = error_missing_pool_for_type< + typename decltype(new_type)::type>{}; + } + auto& loader = + get_data()[new_type].get_loader_from_per_type_pool(); + return loader.load(id); + }; + }; + + // Important not to create a recursive reference to itself inside of the + // shared_storage. + const auto weak = std::weak_ptr{shared_storage}; + const auto get_data_weak = [weak]() -> auto& { + auto p = weak.lock(); + if (!p) { + throw std::logic_error{"weak ptr has expired"}; + } + return *p; + }; + + // Fill-in the transforming functions into shared_storage. + hana::for_each(hana::keys(*shared_storage), [&](auto key) { + using TypeLoad = std::decay_t; + const auto old_key = + hana::type_c; + using needs_conversion_t = + decltype(hana::contains(conversion_map, old_key)); + if constexpr (needs_conversion_t::value) { + const auto& old_func = conversion_map[old_key]; + (*shared_storage)[key].transform = + inject_argument(convert_container(get_data_weak), old_func); + } + }); + + auto holder = make_shared_storage_holder(std::move(shared_storage)); + return input_pools{std::move(holder)}; + } + + void merge_previous(const input_pools& original) + { + auto& s = storage(); + const auto& original_s = original.storage(); + hana::for_each(hana::keys(s), [&](auto key) { + s[key].merge_previous(original_s[key]); + }); + } + + static auto generate_loaders() + { + using Storage = std::decay_t()())>; + using Types = decltype(hana::keys(std::declval())); + auto storage = + hana::fold_left(Types{}, hana::make_map(), [](auto map, auto type) { + using TypePool = + std::decay_t()[type])>; + return hana::insert( + map, hana::make_pair(type, TypePool::generate_loader())); + }); + return storage; + } +}; + +inline auto generate_output_pools(auto types) +{ + auto storage = + hana::fold_left(types, hana::make_map(), [](auto map, auto type) { + using Type = typename decltype(type)::type; + return hana::insert( + map, + hana::make_pair( + type, typename container_traits::output_pool_t{})); + }); + + using Storage = decltype(storage); + return output_pools{storage}; +} + +inline auto generate_input_pools(auto types) +{ + auto storage = + hana::fold_left(types, hana::make_map(), [](auto map, auto type) { + using Type = typename decltype(type)::type; + return hana::insert(map, hana::make_pair(type, input_pool{})); + }); + + auto storage_f = detail::make_storage_holder(std::move(storage)); + return input_pools{std::move(storage_f)}; +} + +template +inline auto to_input_pools(const output_pools& output_pool) +{ + auto pool = generate_input_pools(boost::hana::keys(output_pool.storage())); + boost::hana::for_each(boost::hana::keys(pool.storage()), [&](auto key) { + pool.storage()[key].pool = to_input_pool(output_pool.storage()[key]); + }); + return pool; +} + +} // namespace detail + +} // namespace immer::persist diff --git a/immer/extra/persist/json/wrap.hpp b/immer/extra/persist/json/wrap.hpp new file mode 100644 index 00000000..9da74d27 --- /dev/null +++ b/immer/extra/persist/json/wrap.hpp @@ -0,0 +1,144 @@ +#pragma once + +#include +#include +#include + +namespace immer::persist { + +template +struct is_auto_ignored_type : boost::hana::false_ +{}; + +template +struct is_auto_ignored_type>> + : boost::hana::true_ +{}; + +template +struct is_auto_ignored_type>> + : boost::hana::true_ +{}; + +template <> +struct is_auto_ignored_type> + : boost::hana::true_ +{}; + +template <> +struct is_auto_ignored_type> : boost::hana::true_ +{}; + +template <> +struct is_auto_ignored_type> : boost::hana::true_ +{}; + +/** + * This wrapper is used to load a given container via persistable. + */ +template +struct persistable_loader_wrapper +{ + Container& value; + + template + typename container_traits::container_id::rep_t + save_minimal(const Archive&) const + { + throw std::logic_error{ + "Should never be called. persistable_loader_wrapper::save_minimal"}; + } + + template + void load_minimal( + const Archive& ar, + const typename container_traits::container_id::rep_t& + container_id) + { + persistable arch; + immer::persist::load_minimal(ar, arch, container_id); + value = std::move(arch).container; + } +}; + +constexpr auto is_persistable = boost::hana::is_valid( + [](auto&& obj) -> + typename container_traits>::output_pool_t {}); + +constexpr auto is_auto_ignored = [](const auto& value) { + return is_auto_ignored_type>{}; +}; + +/** + * Make a function that operates conditionally on its single argument, based on + * the given predicate. If the predicate is not satisfied, the function forwards + * its argument unchanged. + */ +constexpr auto make_conditional_func = [](auto pred, auto func) { + return [pred, func](auto&& value) -> decltype(auto) { + return boost::hana::if_(pred(value), func, boost::hana::id)( + std::forward(value)); + }; +}; + +// We must not try to persist types that are actually the pool itself, +// for example, `immer::map> leaves` etc. +constexpr auto exclude_internal_pool_types = [](auto wrap) { + namespace hana = boost::hana; + return make_conditional_func(hana::compose(hana::not_, is_auto_ignored), + wrap); +}; + +constexpr auto to_persistable = [](const auto& x) { + return persistable>(x); +}; + +constexpr auto to_persistable_loader = [](auto& value) { + using V = std::decay_t; + return persistable_loader_wrapper{value}; +}; + +/** + * This function will wrap a value in persistable if possible or will return a + * reference to its argument. + */ +constexpr auto wrap_for_saving = exclude_internal_pool_types( + make_conditional_func(is_persistable, to_persistable)); + +constexpr auto wrap_for_loading = exclude_internal_pool_types( + make_conditional_func(is_persistable, to_persistable_loader)); + +/** + * Generate a hana set of types of persistable members for the given type, + * recursively. Example: [type_c>] + */ +auto get_pools_for_type(auto type) +{ + namespace hana = boost::hana; + auto all_types_set = util::get_inner_types(type); + auto persistable = + hana::filter(hana::to_tuple(all_types_set), [&](auto type) { + using T = typename decltype(type)::type; + return is_persistable(T{}); + }); + return hana::to_set(persistable); +} + +/** + * Generate a hana map of persistable members for the given type, recursively. + * Example: + * [(type_c>, "tracks")] + */ +auto get_named_pools_for_type(auto type) +{ + namespace hana = boost::hana; + auto all_types_map = util::get_inner_types_map(type); + auto persistable = + hana::filter(hana::to_tuple(all_types_map), [&](auto pair) { + using T = typename decltype(+hana::first(pair))::type; + return is_persistable(T{}); + }); + return hana::to_map(persistable); +} + +} // namespace immer::persist diff --git a/test/extra/persist/CMakeLists.txt b/test/extra/persist/CMakeLists.txt index 2fe7182f..ab30a659 100644 --- a/test/extra/persist/CMakeLists.txt +++ b/test/extra/persist/CMakeLists.txt @@ -3,7 +3,6 @@ find_package(cereal REQUIRED) find_package(xxHash 0.8 CONFIG REQUIRED) if(${CXX_STANDARD} LESS 20) - # Maybe no particular reason, but we're not targeting older compilers anyway. message(FATAL_ERROR "persist requires C++20") endif() @@ -12,11 +11,11 @@ include(CTest) add_executable( persist-tests EXCLUDE_FROM_ALL test_vectors.cpp - test_special_pool.cpp - test_special_pool_auto.cpp test_champ.cpp test_xxhash.cpp test_box.cpp + test_special_pool.cpp + test_special_pool_auto.cpp test_conversion.cpp test_circular_dependency_conversion.cpp test_table_box_recursive.cpp diff --git a/test/extra/persist/test_circular_dependency_conversion.cpp b/test/extra/persist/test_circular_dependency_conversion.cpp index 2e556945..0b8fbf28 100644 --- a/test/extra/persist/test_circular_dependency_conversion.cpp +++ b/test/extra/persist/test_circular_dependency_conversion.cpp @@ -9,6 +9,7 @@ #include #include +#include #define DEFINE_OPERATIONS(name) \ bool operator==(const name& left, const name& right) \ @@ -28,6 +29,7 @@ using test::members; using test::serialize_members; using test::vector_one; namespace hana = boost::hana; +using json_t = nlohmann::json; template class show_type; @@ -236,9 +238,9 @@ TEST_CASE("Test exception while circular converting") }; }(); - const auto names = - immer::persist::get_pools_for_type(hana::type_c); - const auto model_pool = immer::persist::get_auto_pool(value, names); + const auto names = immer::persist::get_named_pools_for_type( + hana::type_c); + const auto model_pool = immer::persist::get_auto_pool(value); SECTION("Try to load") { @@ -406,9 +408,9 @@ TEST_CASE("Test circular dependency pools", "[conversion]") }; }(); - const auto names = - immer::persist::get_pools_for_type(hana::type_c); - const auto model_pools = immer::persist::get_auto_pool(value, names); + const auto names = immer::persist::get_named_pools_for_type( + hana::type_c); + const auto model_pools = immer::persist::get_auto_pool(value); /** * NOTE: There is a circular dependency between pools: to convert @@ -515,8 +517,8 @@ TEST_CASE("Test circular dependency pools", "[conversion]") (void) format_load_pools; // show_type qwe; - const auto format_names = - immer::persist::get_pools_for_type(hana::type_c); + const auto format_names = immer::persist::get_named_pools_for_type( + hana::type_c); SECTION("vector") { @@ -548,9 +550,9 @@ TEST_CASE("Test circular dependency pools", "[conversion]") SECTION("Compare structure") { - const auto format_twos_json = - immer::persist::to_json_with_auto_pool(format_twos, - format_names); + const auto format_twos_json = immer::persist::to_json_with_pool( + format_twos, + immer::persist::via_map_policy{}); const auto model_twos_json = immer::persist::to_json_with_auto_pool(value.twos, names); REQUIRE(model_twos_json == format_twos_json); @@ -621,11 +623,12 @@ TEST_CASE("Test circular dependency pools", "[conversion]") SECTION("Compare structure") { - const auto format_twos_json = - immer::persist::to_json_with_auto_pool(format_twos, - format_names); - const auto model_twos_json = - immer::persist::to_json_with_auto_pool(value.twos_map, names); + const auto format_twos_json = immer::persist::to_json_with_pool( + format_twos, + immer::persist::hana_struct_auto_member_name_policy{}); + const auto model_twos_json = immer::persist::to_json_with_pool( + value.twos_map, + immer::persist::hana_struct_auto_member_name_policy{}); REQUIRE(model_twos_json == format_twos_json); } } @@ -644,12 +647,13 @@ TEST_CASE("Test circular dependency pools", "[conversion]") SECTION("Compare structure") { - const auto format_twos_json = - immer::persist::to_json_with_auto_pool(format_twos, - format_names); + const auto format_twos_json = immer::persist::to_json_with_pool( + format_twos, + immer::persist::hana_struct_auto_member_name_policy{}); const auto model_twos_json = immer::persist::to_json_with_auto_pool(value.twos_set, names); - REQUIRE(model_twos_json == format_twos_json); + REQUIRE(json_t::parse(model_twos_json) == + json_t::parse(format_twos_json)); } } @@ -666,8 +670,9 @@ TEST_CASE("Test circular dependency pools", "[conversion]") }); return result; }(); - const auto format_json_str = - immer::persist::to_json_with_auto_pool(format_value, format_names); + const auto format_json_str = immer::persist::to_json_with_pool( + format_value, + immer::persist::hana_struct_auto_member_name_policy{}); const auto json_str = immer::persist::to_json_with_auto_pool(value, names); REQUIRE(format_json_str == json_str); @@ -693,43 +698,11 @@ TEST_CASE("Test circular dependency pools", "[conversion]") SECTION("XML also works") { - constexpr auto to_xml = - [](const auto& value0, const auto& names, const auto& wrap) { - auto os = std::ostringstream{}; - { - auto pools = - immer::persist::detail::generate_output_pools(names); - using Pools = std::decay_t; - auto ar = immer::persist::json_immer_output_archive< - cereal::XMLOutputArchive, - Pools, - decltype(wrap)>{pools, wrap, os}; - ar(CEREAL_NVP(value0)); - } - return os.str(); - }; - constexpr auto from_xml = [](const std::string& str, - auto& value0, - const auto& names, - const auto& wrap) { - auto is = std::istringstream{str}; - using Pools = std::decay_t< - decltype(immer::persist::detail::generate_input_pools(names))>; - auto pools = - immer::persist::load_pools( - is, wrap); - - auto ar = immer::persist::json_immer_input_archive< - cereal::XMLInputArchive, - Pools, - decltype(wrap)>{std::move(pools), wrap, is}; - ar(CEREAL_NVP(value0)); - }; - const auto xml_str = - to_xml(value, names, immer::persist::wrap_for_saving); - auto loaded_value = model::value_one{}; - from_xml( - xml_str, loaded_value, names, immer::persist::wrap_for_loading); + const auto xml_str = to_json_with_pool( + value, immer::persist::hana_struct_auto_member_name_policy{}); + const auto loaded_value = + from_json_with_pool( + xml_str, immer::persist::hana_struct_auto_member_name_policy{}); REQUIRE(value == loaded_value); } } diff --git a/test/extra/persist/test_conversion.cpp b/test/extra/persist/test_conversion.cpp index a8bfed1a..35451c2c 100644 --- a/test/extra/persist/test_conversion.cpp +++ b/test/extra/persist/test_conversion.cpp @@ -144,14 +144,14 @@ TEST_CASE("Convert between two hierarchies via JSON compatibility", "[conversion]") { const auto model_names = - immer::persist::get_pools_for_type(hana::type_c); + immer::persist::get_named_pools_for_type(hana::type_c); const auto format_names = - immer::persist::get_pools_for_type(hana::type_c); + immer::persist::get_named_pools_for_type(hana::type_c); (void) format_names; const auto value = model::make_example_history(); - const auto model_pools = immer::persist::get_auto_pool(value, model_names); + const auto model_pools = immer::persist::get_auto_pool(value); const auto map = hana::make_map( hana::make_pair(hana::type_c>, @@ -200,9 +200,7 @@ struct two_vectors TEST_CASE("Not every type is converted", "[conversion]") { - const auto names = - immer::persist::get_pools_for_type(hana::type_c); - const auto pools = immer::persist::get_auto_pool(two_vectors{}, names); + const auto pools = immer::persist::get_auto_pool(two_vectors{}); const auto map = hana::make_map(hana::make_pair(hana::type_c>, diff --git a/test/extra/persist/test_special_pool.cpp b/test/extra/persist/test_special_pool.cpp index 258b60df..737007a5 100644 --- a/test/extra/persist/test_special_pool.cpp +++ b/test/extra/persist/test_special_pool.cpp @@ -2,6 +2,8 @@ #include #include +#include + #include "utils.hpp" #include @@ -157,7 +159,7 @@ struct test_data * A special function that enumerates which types of pools are * required. Explicitly name each type, for simplicity. */ -inline auto get_pools_types(const test_data&) +inline auto get_pools_names(const test_data&) { auto names = hana::make_map( hana::make_pair(hana::type_c>, @@ -185,9 +187,19 @@ inline auto get_pools_types(const test_data&) return names; } -inline auto get_pools_types(const std::pair&) +auto get_pools_types(const test_data& val) +{ + return hana::keys(get_pools_names(val)); +} + +inline auto get_pools_names(const std::pair&) { - return get_pools_types(test_data{}); + return get_pools_names(test_data{}); +} + +auto get_pools_types(const std::pair& val) +{ + return hana::keys(get_pools_names(val)); } } // namespace @@ -368,8 +380,9 @@ TEST_CASE("Save with a special pool, special type is enclosed") REQUIRE(test1.flex_ints.container.identity() == test2.flex_ints.container.identity()); - const auto json_str = - immer::persist::to_json_with_pool(std::make_pair(test1, test2)); + const auto json_str = immer::persist::to_json_with_pool( + std::make_pair(test1, test2), + immer::persist::via_get_pools_types_policy{}); // REQUIRE(json_str == ""); @@ -415,9 +428,11 @@ TEST_CASE("Special pool loads empty test_data") { const auto value = test_data{}; - // const auto json_pool_str = - // immer::persist::to_json_with_pool(value); - // REQUIRE(json_pool_str == ""); + // const auto json_pool_str_ = immer::persist::to_json_with_pool< + // test_data, + // immer::persist::name_from_map_fn>( + // value); + // REQUIRE(json_pool_str_ == ""); const auto json_pool_str = R"({ "value0": { @@ -474,7 +489,7 @@ TEST_CASE("Special pool loads empty test_data") { auto loaded = immer::persist::from_json_with_pool>( - json_pool_str); + json_pool_str, immer::persist::via_get_pools_names_policy{}); REQUIRE(loaded == value); } } @@ -532,8 +547,12 @@ TEST_CASE("Special pool throws cereal::Exception") } })"; + const auto call = [&] { + return immer::persist::from_json_with_pool( + json_pool_str, immer::persist::via_get_pools_names_policy{}); + }; REQUIRE_THROWS_MATCHES( - immer::persist::from_json_with_pool(json_pool_str), + call(), ::cereal::Exception, MessageMatches(Catch::Matchers::ContainsSubstring( "Failed to load a container ID 99 from the " @@ -565,16 +584,10 @@ struct recursive_type auto get_pools_types(const recursive_type&) { - auto names = hana::make_map( - hana::make_pair(hana::type_c>, - BOOST_HANA_STRING("boxes")), - hana::make_pair( - hana::type_c>>>, - BOOST_HANA_STRING("vectors")) - - ); - return names; + return hana::tuple_t, + vector_one>>>; } + } // namespace TEST_CASE("Test recursive type") @@ -663,14 +676,8 @@ struct rec_map auto get_pools_types(const rec_map&) { - auto names = hana::make_map( - hana::make_pair(hana::type_c>, - BOOST_HANA_STRING("boxes")), - hana::make_pair(hana::type_c>>>, - BOOST_HANA_STRING("maps")) - - ); - return names; + return hana::tuple_t, + immer::map>>>; } } // namespace diff --git a/test/extra/persist/test_special_pool_auto.cpp b/test/extra/persist/test_special_pool_auto.cpp index 86428cc7..44542fb2 100644 --- a/test/extra/persist/test_special_pool_auto.cpp +++ b/test/extra/persist/test_special_pool_auto.cpp @@ -112,7 +112,7 @@ TEST_CASE("Auto-persisting") { constexpr auto names = [] { return hana::union_( - immer::persist::get_pools_for_type( + immer::persist::get_named_pools_for_type( hana::type_c), hana::make_map(hana::make_pair(hana::type_c>, BOOST_HANA_STRING("meta_meta")))); @@ -238,7 +238,7 @@ TEST_CASE("Test save and load small type") .ints = ints1, }; const auto pool_types = - immer::persist::get_pools_for_type(boost::hana::typeid_(value)); + immer::persist::get_named_pools_for_type(boost::hana::typeid_(value)); const auto json_str = immer::persist::to_json_with_auto_pool(value, pool_types); // REQUIRE(json_str == ""); @@ -439,9 +439,8 @@ TEST_CASE("Test table with a funny value") .twos_table = t1.insert(two2), }; - const auto names = - immer::persist::get_pools_for_type(hana::type_c); - + const auto names = immer::persist::get_named_pools_for_type( + hana::type_c); const auto json_str = immer::persist::to_json_with_auto_pool(value, names); // REQUIRE(json_str == ""); @@ -480,8 +479,8 @@ TEST_CASE("Test loading broken table") .twos_table = t1.insert(two2), }; - const auto names = - immer::persist::get_pools_for_type(hana::type_c); + const auto names = immer::persist::get_named_pools_for_type( + hana::type_c); const auto json_str = immer::persist::to_json_with_auto_pool(value, names); // REQUIRE(json_str == ""); @@ -706,7 +705,7 @@ DEFINE_OPERATIONS(key); DEFINE_OPERATIONS(value_one); DEFINE_OPERATIONS(value_two); -auto get_pools_types(const value_one&) +auto get_pools_names(const value_one&) { return hana::make_map( hana::make_pair(hana::type_c>, @@ -742,11 +741,13 @@ TEST_CASE("Test table with a funny value no auto") .twos_table = t1.insert(two2), }; - const auto json_str = immer::persist::to_json_with_pool(value); + const auto json_str = immer::persist::to_json_with_pool( + value, immer::persist::via_get_pools_names_policy{}); // REQUIRE(json_str == ""); const auto loaded = - immer::persist::from_json_with_pool(json_str); + immer::persist::from_json_with_pool( + json_str, immer::persist::via_get_pools_names_policy{}); REQUIRE(loaded == value); } @@ -783,10 +784,7 @@ TEST_CASE("Structure breaks when hash is changed") .map = {{123, "123"}, {456, "456"}}, }; - const auto names = - immer::persist::get_pools_for_type(hana::type_c); - - const auto out_pool = immer::persist::get_auto_pool(value, names); + const auto out_pool = immer::persist::get_auto_pool(value); constexpr auto convert_pair = [](const std::pair& old) { return std::make_pair(fmt::format("_{}_", old.first), old.second); @@ -817,10 +815,7 @@ TEST_CASE("Converting between incompatible keys") .table = {{901}, {902}}, }; - const auto names = - immer::persist::get_pools_for_type(hana::type_c); - - const auto ar = immer::persist::get_auto_pool(value, names); + const auto ar = immer::persist::get_auto_pool(value); constexpr auto convert_pair = [](const std::pair& old) { return std::make_pair(fmt::format("_{}_", old.first), old.second); @@ -943,7 +938,7 @@ struct with_variant TEST_CASE("It goes inside variant") { - auto names = immer::persist::get_pools_for_type( + auto names = immer::persist::get_named_pools_for_type( hana::type_c); using contains_t = decltype(names[hana::type_c>] == BOOST_HANA_STRING("ints")); diff --git a/test/extra/persist/test_table_box_recursive.cpp b/test/extra/persist/test_table_box_recursive.cpp index 7faf9d52..1b035d32 100644 --- a/test/extra/persist/test_table_box_recursive.cpp +++ b/test/extra/persist/test_table_box_recursive.cpp @@ -130,9 +130,6 @@ TEST_CASE("Test boxes and tables preserve structural sharing") .twos_table = t2.insert(two3), }; - const auto names = - immer::persist::get_pools_for_type(hana::type_c); - SECTION("The original box is reused") { const auto box1 = value.twos_table[key{"456"}].two; @@ -143,7 +140,8 @@ TEST_CASE("Test boxes and tables preserve structural sharing") REQUIRE(box1.impl() == box2.impl()); } - const auto json_str = immer::persist::to_json_with_auto_pool(value, names); + const auto json_str = immer::persist::to_json_with_pool( + value, immer::persist::hana_struct_auto_member_name_policy{}); // REQUIRE(json_str == ""); constexpr auto expected_json_str = R"( { @@ -189,8 +187,8 @@ TEST_CASE("Test boxes and tables preserve structural sharing") // Saving works, boxes are saved properly REQUIRE(json_t::parse(expected_json_str) == json_t::parse(json_str)); - const auto loaded = - immer::persist::from_json_with_auto_pool(json_str, names); + const auto loaded = immer::persist::from_json_with_pool( + json_str, immer::persist::hana_struct_auto_member_name_policy{}); REQUIRE(loaded == value); SECTION("Loaded value currently fails to reuse the box") @@ -205,8 +203,8 @@ TEST_CASE("Test boxes and tables preserve structural sharing") SECTION("Saved loaded value shows the broken sharing") { - const auto loaded_str = - immer::persist::to_json_with_auto_pool(loaded, names); + const auto loaded_str = immer::persist::to_json_with_pool( + loaded, immer::persist::hana_struct_auto_member_name_policy{}); // REQUIRE(str == ""); REQUIRE(json_t::parse(loaded_str) == json_t::parse(expected_json_str)); }