diff --git a/lix/libutil/types-rpc.hh b/lix/libutil/types-rpc.hh index 283ab52c4..0517fd1e2 100644 --- a/lix/libutil/types-rpc.hh +++ b/lix/libutil/types-rpc.hh @@ -152,6 +152,91 @@ inline auto from(const O & r, Args &&... args) return r.isSome() ? std::optional{std::in_place, from(r.getSome(), args...)} : std::nullopt; } +namespace detail { +template +struct MapArgsT; +template +struct MapArgsT> +{ + using key = Key; + using value = Value; +}; + +template +using MapKey = typename MapArgsT::key; +template +using MapValue = typename MapArgsT::value; + +template +concept MapT = requires { MapArgsT{}; }; +template +concept MapReaderT = MapT; + +template +concept CxxMapT = requires(T map) { + typename T::key_type; + typename T::mapped_type; + // disallow multimaps + { + map.emplace(std::declval(), std::declval()) + } -> std::same_as>; +}; +} + +template +struct Convert +{ + using NixKey = typename CxxMap::key_type; + using NixValue = typename CxxMap::mapped_type; + + template + requires requires(RpcMap::Entry::Reader r, Args... args) { + to(r.getKey(), args...); + to(r.getValue(), args...); + } + static CxxMap convert(const RpcMap::Reader & m, Args &&... args) + { + CxxMap result; + for (auto && e : m.getEntries()) { + result.emplace(to(e.getKey(), args...), to(e.getValue(), args...)); + } + return result; + } +}; + +template +struct Fill +{ + template + static void fill(RpcMap::Builder builder, const CxxMap & from, Args &&... args) + { + auto entries = builder.initEntries(from.size()); + size_t i = 0; + for (auto & [k, v] : from) { + LIX_RPC_FILL_GENERIC_DEPENDENT(entries[i], Key, k, args...); + LIX_RPC_FILL_GENERIC_DEPENDENT(entries[i], Value, v, args...); + ++i; + } + } +}; + +template + requires requires(Map::Reads::Entry::Reader r, Args... args) { + from(r.getKey(), args...); + from(r.getValue(), args...); + } +auto from(const Map & m, Args &&... args) +{ + std::map< + decltype(from(m.getEntries().begin()->getKey(), args...)), + decltype(from(m.getEntries().begin()->getValue(), args...))> + result; + for (auto && e : m.getEntries()) { + result.emplace(from(e.getKey(), args...), from(e.getValue(), args...)); + } + return result; +} + namespace error::v1 { std::string encodeLossy(const ::nix::ErrorInfo & e); std::optional<::nix::ErrorInfo> tryDecode(std::string_view source); diff --git a/lix/libutil/types.capnp b/lix/libutil/types.capnp index c192869d6..24a30e04e 100644 --- a/lix/libutil/types.capnp +++ b/lix/libutil/types.capnp @@ -61,6 +61,16 @@ struct OptionInt64 { } } +struct Map(Key, Value) { + # if this list contains multiple entries with the same key the *first* entry of each + # such subsequence must be used. all other entries MUST still be read and validated. + entries @0 :List(Entry); + struct Entry { + key @0 :Key; + value @1 :Value; + } +} + struct Settings { struct Setting { name @0 :Data; diff --git a/tests/unit/libutil/rpc.cc b/tests/unit/libutil/rpc.cc index ca69f8e70..28838b864 100644 --- a/tests/unit/libutil/rpc.cc +++ b/tests/unit/libutil/rpc.cc @@ -53,6 +53,60 @@ TEST(RpcConverters, DISABLED_optionConvertersCompile) ); } +TEST(RpcConverters, DISABLED_mapConvertersCompile) +{ + using namespace rpc; + + // primitive args + RPC_FILL_STRUCT( + (Option>::Builder{nullptr}), + initSome, + (std::map, std::optional>{}) + ); + static_assert(std::same_as< + decltype(from(Map::Reader{})), + std::map, std::optional>>); + + // string args + RPC_FILL_STRUCT( + (Option>::Builder{nullptr}), + initSome, + (std::map{}) + ); + static_assert(std::same_as< + decltype(to>(Map::Reader{})), + std::map>); + static_assert(std::same_as< + decltype(to>( + Map::Reader{} + )), + std::map>); + + // struct args + RPC_FILL_STRUCT( + (Option, Option>>::Builder{nullptr}), + initSome, + (std::map>, std::optional>>{}) + ); + static_assert(std::same_as< + decltype(from(Map, Option>::Reader{})), + std::map>, std::optional>>); + + // nested args + RPC_FILL_STRUCT( + (Option, Map>>::Builder{nullptr}), + initSome, + (std::map< + std::map, std::optional>, + std::map, std::optional>>{}) + ); + static_assert(std::same_as< + decltype(from(Map, Map>::Reader{})), + std::map< + std::map, std::optional>, + std::map, std::optional>>>); +} + TEST(RpcErrorV1, shortMessage) { Error e{ErrorInfo{lvlWarn, HintFmt("test message %s", "data")}};