libutil/rpc-types: Add Option type

Co-authored-by: piegames <git@piegames.de>
Change-Id: I75d5db5f567a3a86870ed2aceb36840ddcbb53db
This commit is contained in:
eldritch horrors
2026-07-16 12:09:24 +00:00
co-authored by piegames
parent 66090810f1
commit 7307475218
3 changed files with 123 additions and 0 deletions
+59
View File
@@ -8,12 +8,15 @@
#include "rpc.hh"
#include <capnp/any.h>
#include <capnp/common.h>
#include <concepts>
#include <cstdint>
#include <exception>
#include <optional>
#include <ranges>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
namespace nix::rpc {
@@ -93,6 +96,62 @@ struct Convert<Settings, std::map<std::string, std::string>>
}
};
namespace detail {
template<typename>
struct OptionArgT;
template<typename T>
struct OptionArgT<Option<T>>
{
using type = T;
};
template<>
struct OptionArgT<OptionInt64>
{
using type = int64_t;
};
template<typename T>
using OptionArg = typename OptionArgT<T>::type;
template<typename T>
concept OptionT = requires { OptionArgT<T>{}; };
template<typename T>
concept OptionReaderT = OptionT<typename T::Reads>;
}
template<detail::OptionT From, typename To>
struct Convert<From, std::optional<To>>
{
template<typename... Args>
requires requires(From::Reader r, Args... args) { to<To>(r.getSome(), args...); }
static std::optional<To> convert(const typename From::Reader & r, Args &&... args)
{
return r.isSome() ? std::optional<To>{std::in_place, to<To>(r.getSome(), args...)} : std::nullopt;
}
};
template<detail::OptionT O, typename T>
struct Fill<O, std::optional<T>>
{
template<typename... Args>
static void fill(O::Builder builder, const std::optional<T> & from, Args &&... args)
{
if (from.has_value()) {
LIX_RPC_FILL_GENERIC_DEPENDENT(builder, Some, from.value(), args...);
} else {
builder.setNone();
}
}
};
template<detail::OptionReaderT O, typename... Args>
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<To>{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);
+15
View File
@@ -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;
+49
View File
@@ -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 <capnp/blob.h>
#include <capnp/list.h>
#include <concepts>
#include <cstdint>
#include <gtest/gtest.h>
#include <optional>
namespace nix {
TEST(RpcConverters, DISABLED_optionConvertersCompile)
{
using namespace rpc;
// primitive inner
RPC_FILL_STRUCT(Option<OptionInt64>::Builder{nullptr}, initSome, std::optional<int64_t>{});
static_assert(std::same_as<decltype(from(OptionInt64::Reader{})), std::optional<int64_t>>);
// string inner, which is special (of course)
RPC_FILL_STRUCT(Option<Option<capnp::Text>>::Builder{nullptr}, initSome, std::optional<std::string>{});
static_assert(std::same_as<
decltype(to<std::optional<std::string>>(Option<capnp::Text>::Reader{})),
std::optional<std::string>>);
static_assert(std::same_as<
decltype(to<std::optional<std::string_view>>(Option<capnp::Text>::Reader{})),
std::optional<std::string_view>>);
// struct inner
RPC_FILL_STRUCT(Option<Option<rpc::Error>>::Builder{nullptr}, initSome, std::optional<ErrorInfo>{});
static_assert(std::same_as<decltype(from(Option<rpc::Error>::Reader{})), std::optional<ErrorInfo>>);
// list inner
RPC_FILL_STRUCT(
Option<Option<capnp::List<capnp::Text>>>::Builder{nullptr},
initSome,
std::optional<std::list<std::string>>{}
);
static_assert(
std::same_as<
decltype(to<std::optional<std::list<std::string>>>(Option<capnp::List<capnp::Text>>::Reader{})),
std::optional<std::list<std::string>>>
);
// nested inner
RPC_FILL_STRUCT(
Option<Option<OptionInt64>>::Builder{nullptr}, initSome, std::optional<std::optional<int64_t>>{}
);
static_assert(
std::same_as<decltype(from(Option<OptionInt64>::Reader{})), std::optional<std::optional<int64_t>>>
);
}
TEST(RpcErrorV1, shortMessage)
{
Error e{ErrorInfo{lvlWarn, HintFmt("test message %s", "data")}};