From 6d3d2b2eb4909d9a4d94a53d6d3310dbd5ab9a1a Mon Sep 17 00:00:00 2001 From: Alex Shabalin Date: Tue, 19 Mar 2024 12:26:18 +0100 Subject: [PATCH] Try auto-archive with type conversion --- immer/extra/archive/champ/traits.hpp | 9 +- .../extra/archive/json/json_with_archive.hpp | 10 ++ .../archive/json/json_with_archive_auto.hpp | 85 ++++++++++--- .../archive/test_special_archive_auto.cpp | 120 ++++++++++++++++++ test/extra/archive/utils.hpp | 13 ++ 5 files changed, 217 insertions(+), 20 deletions(-) diff --git a/immer/extra/archive/champ/traits.hpp b/immer/extra/archive/champ/traits.hpp index 05811723..0f4b24a4 100644 --- a/immer/extra/archive/champ/traits.hpp +++ b/immer/extra/archive/champ/traits.hpp @@ -42,6 +42,13 @@ template struct container_traits> : champ_traits> -{}; +{ + template + static auto transform(F&& func) + { + using U = std::decay_t()))>; + return immer::table{}; + } +}; } // namespace immer::archive diff --git a/immer/extra/archive/json/json_with_archive.hpp b/immer/extra/archive/json/json_with_archive.hpp index 76e655aa..7794dcae 100644 --- a/immer/extra/archive/json/json_with_archive.hpp +++ b/immer/extra/archive/json/json_with_archive.hpp @@ -28,6 +28,9 @@ class error_no_archive_for_the_given_type_check_get_archives_types_function; template class error_duplicate_archive_name_found; +template +class error_missing_archive_for_type; + /** * Archives and functions to serialize types that contain archivable data * structures. @@ -112,6 +115,13 @@ struct archives_load template auto& get_loader() { + using Contains = + decltype(hana::contains(storage, hana::type_c)); + constexpr bool contains = hana::value(); + if constexpr (!contains) { + auto err = error_missing_archive_for_type{}; + } + auto& load = storage[hana::type_c]; if (!load.loader) { load.loader.emplace(load.archive); diff --git a/immer/extra/archive/json/json_with_archive_auto.hpp b/immer/extra/archive/json/json_with_archive_auto.hpp index 59003503..a344c27e 100644 --- a/immer/extra/archive/json/json_with_archive_auto.hpp +++ b/immer/extra/archive/json/json_with_archive_auto.hpp @@ -226,22 +226,13 @@ auto load_initial_auto_archives(std::istream& is, WrapF wrap) return archives; } -template -T from_json_with_auto_archive(std::istream& is, - const ArchivesTypes& archives_types) -{ - namespace hana = boost::hana; - constexpr auto wrap = wrap_for_loading; - using WrapF = std::decay_t; - - using Archives = - std::decay_t; - - constexpr auto reload_archive_auto = [wrap](std::istream& is, - auto archives) { - auto restore = util::istream_snapshot{is}; - auto previous = immer::archive::json_immer_input_archive{ - std::move(archives), is}; +constexpr auto reload_archive_auto = [](auto wrap) { + return [wrap](std::istream& is, auto archives) { + using Archives = std::decay_t; + using WrapF = std::decay_t; + auto restore = util::istream_snapshot{is}; + auto previous = + json_immer_input_archive{std::move(archives), is}; auto ar = json_immer_auto_input_archive{ previous, wrap}; /** @@ -253,14 +244,25 @@ T from_json_with_auto_archive(std::istream& is, ar(CEREAL_NVP(archives)); return archives; }; +}; + +template +T from_json_with_auto_archive(std::istream& is, + const ArchivesTypes& archives_types) +{ + namespace hana = boost::hana; + constexpr auto wrap = wrap_for_loading; + using WrapF = std::decay_t; + + using Archives = + std::decay_t; auto archives = load_archives(is, load_initial_auto_archives(is, wrap), - reload_archive_auto); + reload_archive_auto(wrap)); - auto previous = immer::archive::json_immer_input_archive{ - std::move(archives), is}; + auto previous = json_immer_input_archive{std::move(archives), is}; auto ar = json_immer_auto_input_archive{previous, wrap}; auto r = T{}; @@ -276,4 +278,49 @@ T from_json_with_auto_archive(const std::string& input, return from_json_with_auto_archive(is, archives_types); } +template +T from_json_with_auto_archive_with_conversion( + std::istream& is, + const ConversionsMap& map, + const ArchivesTypes& archives_types) +{ + constexpr auto wrap = wrap_for_loading; + using WrapF = std::decay_t; + + // Load the archives part for the old type + using OldArchives = + std::decay_t; + auto archives_old = + load_archives(is, + load_initial_auto_archives(is, wrap), + reload_archive_auto(wrap)); + + auto archives = archives_old.transform(map); + using Archives = decltype(archives); + + auto previous = json_immer_input_archive{std::move(archives), is}; + auto ar = json_immer_auto_input_archive{previous, + wrap}; + auto r = T{}; + ar(r); + return r; +} + +template +T from_json_with_auto_archive_with_conversion( + const std::string& input, + const ConversionsMap& map, + const ArchivesTypes& archives_types) +{ + auto is = std::istringstream{input}; + return from_json_with_auto_archive_with_conversion( + is, map, archives_types); +} + } // namespace immer::archive diff --git a/test/extra/archive/test_special_archive_auto.cpp b/test/extra/archive/test_special_archive_auto.cpp index cd336c72..1479fa9e 100644 --- a/test/extra/archive/test_special_archive_auto.cpp +++ b/test/extra/archive/test_special_archive_auto.cpp @@ -250,3 +250,123 @@ TEST_CASE("Test save and load small type") REQUIRE(loaded == value); } } + +namespace { + +using test::new_type; +using test::old_type; + +template +using map_t = immer::map>; + +template +using table_t = + immer::table>; + +// Some type that an application would serialize. Contains multiple vectors and +// maps to demonstrate structural sharing. +struct old_app_type +{ + BOOST_HANA_DEFINE_STRUCT(old_app_type, + (test::vector_one, vec), + (test::vector_one, vec2), + (map_t, map), + (map_t, map2), + (table_t, table) + + ); + + template + void serialize(Archive& ar) + { + serialize_members(ar, *this); + } +}; + +struct new_app_type +{ + BOOST_HANA_DEFINE_STRUCT(new_app_type, + (test::vector_one, vec), + (test::vector_one, vec2), + (map_t, map), + (map_t, map2), + (table_t, table) + + ); + + template + void serialize(Archive& ar) + { + serialize_members(ar, *this); + } +}; + +} // namespace + +TEST_CASE("Test conversion with auto-archive") +{ + const auto vec1 = test::vector_one{ + old_type{.data = 123}, + old_type{.data = 234}, + }; + const auto vec2 = vec1.push_back(old_type{.data = 345}); + + const auto map1 = [] { + auto map = map_t{}; + for (auto i = 0; i < 30; ++i) { + map = + std::move(map).set(fmt::format("x{}x", i), old_type{.data = i}); + } + return map; + }(); + const auto map2 = map1.set("345", old_type{.data = 345}); + + // Prepare a value of the old type that uses some structural sharing + // internally. + const auto value = old_app_type{ + .vec = vec1, + .vec2 = vec2, + .map = map1, + .map2 = map2, + .table = + { + old_type{"_51_", 51}, + old_type{"_52_", 52}, + old_type{"_53_", 53}, + }, + }; + + constexpr auto old_names = [] { + return immer::archive::get_archives_for_types( + hana::tuple_t, hana::make_map()); + }; + + using OldArchiveTypes = decltype(old_names()); + constexpr auto old_archive_types = OldArchiveTypes{}; + const auto [json_str, archives] = + immer::archive::to_json_with_auto_archive(value, old_archive_types); + // REQUIRE(json_str == ""); + + // Describe how to go from the old archive to the desired new archive. + // Convert all old archives with convert_old_type. + const auto archives_conversions = hana::unpack( + hana::transform(hana::keys(old_archive_types), + [&](auto key) { + return hana::make_pair(key, test::convert_old_type); + }), + hana::make_map); + + // Having a JSON from serializing old_app_type and a conversion function, + // we need to somehow load new_app_type. + const new_app_type full_load = immer::archive:: + from_json_with_auto_archive_with_conversion( + json_str, archives_conversions, old_archive_types); + + { + REQUIRE(full_load.vec == transform_vec(value.vec)); + REQUIRE(full_load.vec2 == transform_vec(value.vec2)); + REQUIRE(full_load.map == transform_map(value.map)); + REQUIRE(full_load.map2 == transform_map(value.map2)); + REQUIRE(full_load.table == transform_table(value.table)); + } +} diff --git a/test/extra/archive/utils.hpp b/test/extra/archive/utils.hpp index 807c91ff..647fa196 100644 --- a/test/extra/archive/utils.hpp +++ b/test/extra/archive/utils.hpp @@ -4,6 +4,8 @@ #include #include +#include + #include #include @@ -151,6 +153,17 @@ inline auto transform_map(const auto& map) return result; } +inline auto transform_table(const auto& table) +{ + auto result = immer::table>{}; + for (const auto& item : table) { + result = std::move(result).insert(convert_old_type(item)); + } + return result; +} + } // namespace test template <>