libutil/rpc-types: Add map type

Co-authored-by: piegames <git@piegames.de>
Change-Id: I4215efb0e9839925d34ad45d47879148dbdfc4c2
This commit is contained in:
eldritch horrors
2026-07-16 12:09:24 +00:00
co-authored by piegames
parent 7307475218
commit 0e180a479f
3 changed files with 149 additions and 0 deletions
+85
View File
@@ -152,6 +152,91 @@ inline auto from(const O & r, Args &&... args)
return r.isSome() ? std::optional<To>{std::in_place, from(r.getSome(), args...)} : std::nullopt;
}
namespace detail {
template<typename>
struct MapArgsT;
template<typename Key, typename Value>
struct MapArgsT<Map<Key, Value>>
{
using key = Key;
using value = Value;
};
template<typename T>
using MapKey = typename MapArgsT<T>::key;
template<typename T>
using MapValue = typename MapArgsT<T>::value;
template<typename T>
concept MapT = requires { MapArgsT<T>{}; };
template<typename T>
concept MapReaderT = MapT<typename T::Reads>;
template<typename T>
concept CxxMapT = requires(T map) {
typename T::key_type;
typename T::mapped_type;
// disallow multimaps
{
map.emplace(std::declval<typename T::key_type>(), std::declval<typename T::mapped_type>())
} -> std::same_as<std::pair<typename T::iterator, bool>>;
};
}
template<detail::MapT RpcMap, detail::CxxMapT CxxMap>
struct Convert<RpcMap, CxxMap>
{
using NixKey = typename CxxMap::key_type;
using NixValue = typename CxxMap::mapped_type;
template<typename... Args>
requires requires(RpcMap::Entry::Reader r, Args... args) {
to<NixKey>(r.getKey(), args...);
to<NixValue>(r.getValue(), args...);
}
static CxxMap convert(const RpcMap::Reader & m, Args &&... args)
{
CxxMap result;
for (auto && e : m.getEntries()) {
result.emplace(to<NixKey>(e.getKey(), args...), to<NixValue>(e.getValue(), args...));
}
return result;
}
};
template<detail::MapT RpcMap, detail::CxxMapT CxxMap>
struct Fill<RpcMap, CxxMap>
{
template<typename... Args>
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<detail::MapReaderT Map, typename... Args>
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);
+10
View File
@@ -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;
+54
View File
@@ -53,6 +53,60 @@ TEST(RpcConverters, DISABLED_optionConvertersCompile)
);
}
TEST(RpcConverters, DISABLED_mapConvertersCompile)
{
using namespace rpc;
// primitive args
RPC_FILL_STRUCT(
(Option<Map<OptionInt64, OptionInt64>>::Builder{nullptr}),
initSome,
(std::map<std::optional<int64_t>, std::optional<int64_t>>{})
);
static_assert(std::same_as<
decltype(from(Map<OptionInt64, OptionInt64>::Reader{})),
std::map<std::optional<int64_t>, std::optional<int64_t>>>);
// string args
RPC_FILL_STRUCT(
(Option<Map<capnp::Data, capnp::Data>>::Builder{nullptr}),
initSome,
(std::map<std::string, std::string>{})
);
static_assert(std::same_as<
decltype(to<std::map<std::string, std::string>>(Map<capnp::Data, capnp::Data>::Reader{})),
std::map<std::string, std::string>>);
static_assert(std::same_as<
decltype(to<std::map<std::string_view, std::string_view>>(
Map<capnp::Data, capnp::Data>::Reader{}
)),
std::map<std::string_view, std::string_view>>);
// struct args
RPC_FILL_STRUCT(
(Option<Map<Option<OptionInt64>, Option<OptionInt64>>>::Builder{nullptr}),
initSome,
(std::map<std::optional<std::optional<int64_t>>, std::optional<std::optional<int64_t>>>{})
);
static_assert(std::same_as<
decltype(from(Map<Option<OptionInt64>, Option<rpc::Error>>::Reader{})),
std::map<std::optional<std::optional<int64_t>>, std::optional<ErrorInfo>>>);
// nested args
RPC_FILL_STRUCT(
(Option<Map<Map<OptionInt64, OptionInt64>, Map<OptionInt64, OptionInt64>>>::Builder{nullptr}),
initSome,
(std::map<
std::map<std::optional<int64_t>, std::optional<int64_t>>,
std::map<std::optional<int64_t>, std::optional<int64_t>>>{})
);
static_assert(std::same_as<
decltype(from(Map<Map<OptionInt64, OptionInt64>, Map<OptionInt64, OptionInt64>>::Reader{})),
std::map<
std::map<std::optional<int64_t>, std::optional<int64_t>>,
std::map<std::optional<int64_t>, std::optional<int64_t>>>>);
}
TEST(RpcErrorV1, shortMessage)
{
Error e{ErrorInfo{lvlWarn, HintFmt("test message %s", "data")}};