diff --git a/lix/libutil/types-rpc.hh b/lix/libutil/types-rpc.hh index f04ff487b..283ab52c4 100644 --- a/lix/libutil/types-rpc.hh +++ b/lix/libutil/types-rpc.hh @@ -8,12 +8,15 @@ #include "rpc.hh" #include #include +#include #include #include +#include #include #include #include #include +#include namespace nix::rpc { @@ -93,6 +96,62 @@ struct Convert> } }; +namespace detail { +template +struct OptionArgT; +template +struct OptionArgT> +{ + using type = T; +}; +template<> +struct OptionArgT +{ + using type = int64_t; +}; + +template +using OptionArg = typename OptionArgT::type; + +template +concept OptionT = requires { OptionArgT{}; }; +template +concept OptionReaderT = OptionT; +} + +template +struct Convert> +{ + template + requires requires(From::Reader r, Args... args) { to(r.getSome(), args...); } + static std::optional convert(const typename From::Reader & r, Args &&... args) + { + return r.isSome() ? std::optional{std::in_place, to(r.getSome(), args...)} : std::nullopt; + } +}; + +template +struct Fill> +{ + template + static void fill(O::Builder builder, const std::optional & from, Args &&... args) + { + if (from.has_value()) { + LIX_RPC_FILL_GENERIC_DEPENDENT(builder, Some, from.value(), args...); + } else { + builder.setNone(); + } + } +}; + +template + requires requires(O o, Args... args) { from(o.getSome(), args...); } +inline auto from(const O & r, Args &&... args) +{ + using To = decltype(from(r.getSome(), args...)); + return r.isSome() ? std::optional{std::in_place, from(r.getSome(), args...)} : std::nullopt; +} + 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 ecf1771da..c192869d6 100644 --- a/lix/libutil/types.capnp +++ b/lix/libutil/types.capnp @@ -46,6 +46,21 @@ struct Error { traces @2 :List(Data); } +struct Option(T) { + union { + # make sure uninitialized options deserialize to none for some added safety + none @0 :Void; + some @1 :T; + } +} + +struct OptionInt64 { + union { + none @0 :Void; + some @1 :Int64; + } +} + struct Settings { struct Setting { name @0 :Data; diff --git a/tests/unit/libutil/rpc.cc b/tests/unit/libutil/rpc.cc index 178c112a0..ca69f8e70 100644 --- a/tests/unit/libutil/rpc.cc +++ b/tests/unit/libutil/rpc.cc @@ -1,9 +1,58 @@ +#include "lix/libutil/rpc.hh" #include "lix/libutil/error.hh" #include "lix/libutil/fmt.hh" #include "lix/libutil/types-rpc.hh" +#include "types.capnp.h" +#include +#include +#include +#include #include +#include namespace nix { +TEST(RpcConverters, DISABLED_optionConvertersCompile) +{ + using namespace rpc; + + // primitive inner + RPC_FILL_STRUCT(Option::Builder{nullptr}, initSome, std::optional{}); + static_assert(std::same_as>); + + // string inner, which is special (of course) + RPC_FILL_STRUCT(Option>::Builder{nullptr}, initSome, std::optional{}); + static_assert(std::same_as< + decltype(to>(Option::Reader{})), + std::optional>); + static_assert(std::same_as< + decltype(to>(Option::Reader{})), + std::optional>); + + // struct inner + RPC_FILL_STRUCT(Option>::Builder{nullptr}, initSome, std::optional{}); + static_assert(std::same_as::Reader{})), std::optional>); + + // list inner + RPC_FILL_STRUCT( + Option>>::Builder{nullptr}, + initSome, + std::optional>{} + ); + static_assert( + std::same_as< + decltype(to>>(Option>::Reader{})), + std::optional>> + ); + + // nested inner + RPC_FILL_STRUCT( + Option>::Builder{nullptr}, initSome, std::optional>{} + ); + static_assert( + std::same_as::Reader{})), std::optional>> + ); +} + TEST(RpcErrorV1, shortMessage) { Error e{ErrorInfo{lvlWarn, HintFmt("test message %s", "data")}};