Files
lix/lix/libutil/rpc.hh
T
piegamesandeldritch horrors c739701077 rpc.hh: Document footgun in LIX_TRY_AWAIT_RPC
*piegames is tired of C++*

Change-Id: I9d6dd935afaadf809d5a779fbcc64f48b4af1cba
2026-07-14 17:47:07 +00:00

313 lines
11 KiB
C++

#pragma once
///@file RPC helper functions
#include "lix/libutil/result.hh"
#include "lix/libutil/charptr-cast.hh"
#include "lix/libutil/rpc-fwd.hh"
#include <capnp/blob.h>
#include <capnp/common.h>
#include <capnp/list.h>
#include <concepts>
#include <kj/async.h>
#include <ranges>
#include <source_location>
#include <string>
#include <string_view>
#include <type_traits>
namespace kj {
class Exception;
}
namespace nix::rpc {
template<typename To, typename From>
requires requires(From f) { Convert<typename From::Reads, To>{}; }
To to(const From & from, auto &&... args)
{
return Convert<typename From::Reads, To>::convert(from, args...);
}
// conversions for capnp Text
template<>
struct Convert<capnp::Text, std::string_view>
{
static std::string_view convert(const capnp::Text::Reader & t, auto &&...)
{
return std::string_view(t.begin(), t.size());
}
};
template<>
struct Convert<capnp::Text, std::string>
{
static std::string convert(const capnp::Text::Reader & t, auto &&...)
{
return std::string(to<std::string_view>(t));
}
};
// conversions for capnp Data
template<>
struct Convert<capnp::Data, std::string_view>
{
static std::string_view convert(const capnp::Data::Reader & t, auto &&...)
{
return std::string_view(t.asChars().begin(), t.size());
}
};
template<>
struct Convert<capnp::Data, std::string>
{
static std::string convert(const capnp::Data::Reader & t, auto &&...)
{
return std::string(to<std::string_view>(t));
}
};
// conversions for collections. order is maintained during conversion and turns
// into insertion order for the result, i.e. sets and maps will only retain the
// first element in the list that compares equivalent to some other list entry.
template<typename Elem, typename To>
requires requires(To t, capnp::List<Elem>::Reader list) {
typename To::value_type;
t.insert(t.end(), to<typename To::value_type>(list[0]));
}
struct Convert<capnp::List<Elem>, To>
{
static To convert(const capnp::List<Elem>::Reader & list, auto &&... args)
{
To result;
for (auto && t : list) {
result.insert(result.end(), to<typename To::value_type>(t, args...));
}
return result;
}
};
template<typename From>
requires std::convertible_to<std::ranges::range_value_t<From>, std::string>
struct Fill<capnp::List<capnp::Text>, From>
{
static void fill(capnp::List<capnp::Text>::Builder builder, const From & from, auto &&... args)
{
size_t i = 0;
for (const std::string & e : from) {
builder.set(i++, e);
}
}
};
template<typename From>
requires std::convertible_to<std::ranges::range_value_t<From>, std::string_view>
struct Fill<capnp::List<capnp::Data>, From>
{
static void fill(capnp::List<capnp::Data>::Builder builder, const From & from, auto &&... args)
{
size_t i = 0;
for (auto && e : from) {
std::string_view sv = e;
builder.set(i++, {charptr_cast<const unsigned char *>(sv.begin()), sv.size()});
}
}
};
template<typename Elem, typename From>
requires requires { Fill<Elem, std::ranges::range_value_t<From>>{}; }
struct Fill<capnp::List<Elem>, From>
{
static void fill(capnp::List<Elem>::Builder builder, const From & from, auto &&... args)
{
size_t i = 0;
for (auto && e : from) {
Fill<Elem, std::ranges::range_value_t<From>>::fill(builder[i++], e, args...);
}
}
};
// blanket converter that turns a `from` into a `to`
template<typename Rpc, typename To>
requires requires(typename Rpc::Reader r) {
{
from(r)
} -> std::same_as<To>;
}
struct Convert<Rpc, To>
{
static To convert(const typename Rpc::Reader & r, auto &&... args)
{
return from(r, args...);
}
};
namespace detail {
// strings to Data and Text
template<typename Builder, std::convertible_to<std::string_view> From>
inline void
doFill(auto && builder, void (Builder::*Field)(::capnp::Data::Reader), From && f, auto &&...)
{
std::string_view sv = f;
(builder.*Field)({charptr_cast<const unsigned char *>(sv.begin()), sv.size()});
}
template<typename Builder, std::convertible_to<std::string> From>
inline void
doFill(auto && builder, void (Builder::*field)(::capnp::Text::Reader), From && f, auto &&...)
{
(builder.*field)(f);
}
// structs
template<typename Builder, typename From, typename Inner>
inline void doFill(auto && builder, Inner (Builder::*field)(), From && f, auto &&... args)
{
Fill<typename Inner::Builds, std::remove_cvref_t<From>>::fill(
(builder.*field)(), std::forward<From>(f), args...
);
}
// lists and other primitives. lists must be distinguished by having builders.
template<typename Builder, typename From, typename Inner, typename Init>
requires requires {
requires std::same_as<Init, capnp::uint>;
typename Inner::Builds;
} || std::convertible_to<From, Init>
inline void doFill(auto && builder, Inner (Builder::*field)(Init), From && f, auto &&... args)
{
if constexpr (requires {
requires std::same_as<Init, capnp::uint>;
typename Inner::Builds;
})
{
Fill<typename Inner::Builds, std::remove_cvref_t<From>>::fill(
(builder.*field)(f.size()), std::forward<From>(f), args...
);
} else {
(builder.*field)(std::forward<From>(f));
}
}
template<typename T>
inline constexpr bool IsRpcListV = false;
template<typename T, capnp::Kind K>
inline constexpr bool IsRpcListV<capnp::List<T, K>> = true;
template<typename T>
concept RpcList = IsRpcListV<T>;
}
#define LIX_RPC_FILL(fobj, ffield, fsource, ...) \
(::nix::rpc::detail::doFill(fobj, &decltype(fobj)::ffield, (fsource), ##__VA_ARGS__))
#define LIX_RPC_FILL_LIST(fobj, ffield, fsource, ...) \
(::nix::rpc::detail::doFill( \
fobj, \
([]<typename Builder, typename Inner, typename Init>(Inner (Builder::*field)(Init)) { \
return field; \
})(&decltype(fobj)::ffield), \
(fsource), \
##__VA_ARGS__ \
))
#define LIX_RPC_FILL_STRUCT(fobj, ffield, fsource, ...) \
(::nix::rpc::detail::doFill( \
fobj, \
([]<typename Builder, typename Inner>(Inner (Builder::*field)()) { \
return field; \
})(&decltype(fobj)::ffield), \
(fsource), \
##__VA_ARGS__ \
))
// set a value regardless of its kind. *only* use this in templates or it'll fail to compile.
#define LIX_RPC_FILL_GENERIC_DEPENDENT(fobj, ffield, fsource, ...) \
({ \
if constexpr (requires { \
requires ::nix::rpc::detail::RpcList< \
typename decltype(fobj.get##ffield())::Builds>; \
}) \
{ \
LIX_RPC_FILL_LIST(fobj, init##ffield, (fsource), ##__VA_ARGS__); \
} else if constexpr (requires { \
{ LIX_RPC_FILL(fobj, set##ffield, (fsource), ##__VA_ARGS__) }; \
}) \
{ \
LIX_RPC_FILL(fobj, set##ffield, (fsource), ##__VA_ARGS__); \
} else { \
LIX_RPC_FILL_STRUCT(fobj, init##ffield, (fsource), ##__VA_ARGS__); \
} \
})
namespace detail {
std::exception_ptr unwrapErrorRaw(kj::Exception & e, std::source_location loc);
std::exception_ptr unwrapErrorV1(kj::Exception & e, std::source_location loc);
[[noreturn]]
void rethrowAsErrorV1();
kj::Promise<nix::Result<void>> inline rewrapNoexcept(
kj::Promise<void> && promise, std::source_location loc = std::source_location::current()
)
{
return promise.then(
[]() -> nix::Result<void> { return result::success(); },
[loc](kj::Exception && e) -> nix::Result<void> { return result::failure(unwrapErrorRaw(e, loc)); }
);
}
template<typename T>
kj::Promise<nix::Result<T>>
rewrapNoexcept(kj::Promise<T> && promise, std::source_location loc = std::source_location::current())
{
return promise.then(
[](T result) -> nix::Result<T> { return result; },
[loc](kj::Exception && e) -> nix::Result<T> { return result::failure(unwrapErrorRaw(e, loc)); }
);
}
kj::Promise<nix::Result<void>> inline rewrapV1(
kj::Promise<void> && promise, std::source_location loc = std::source_location::current()
)
{
return promise.then(
[]() -> nix::Result<void> { return result::success(); },
[loc](kj::Exception && e) -> nix::Result<void> { return result::failure(unwrapErrorV1(e, loc)); }
);
}
template<typename T>
kj::Promise<nix::Result<T>>
rewrapV1(kj::Promise<T> && promise, std::source_location loc = std::source_location::current())
{
return promise.then(
[](T result) -> nix::Result<T> { return result; },
[loc](kj::Exception && e) -> nix::Result<T> { return result::failure(unwrapErrorV1(e, loc)); }
);
}
}
#define LIX_WRAP_RPC_PROMISE_NOEXCEPT(...) (::nix::rpc::detail::rewrapNoexcept(__VA_ARGS__))
#define LIX_WRAP_RPC_PROMISE_V1(...) (::nix::rpc::detail::rewrapV1(__VA_ARGS__))
// IMPORTANT! Keep the result of this in a variable, or readers will dereference dangling pointers!
#define LIX_TRY_AWAIT_RPC_NOEXCEPT(...) (LIX_TRY_AWAIT(LIX_WRAP_RPC_PROMISE_NOEXCEPT(__VA_ARGS__)))
// IMPORTANT! Keep the result of this in a variable, or readers will dereference dangling pointers!
#define LIX_TRY_AWAIT_RPC_V1(...) (LIX_TRY_AWAIT(LIX_WRAP_RPC_PROMISE_V1(__VA_ARGS__)))
[[noreturn]]
inline void rethrow_as_rpc_error()
{
detail::rethrowAsErrorV1();
}
#ifdef LIX_UR_COMPILER_UWU
#define RPC_FILL LIX_RPC_FILL
#define RPC_FILL_LIST LIX_RPC_FILL_LIST
#define RPC_FILL_STRUCT LIX_RPC_FILL_STRUCT
#define TRY_AWAIT_RPC_NOEXCEPT LIX_TRY_AWAIT_RPC_NOEXCEPT
#define TRY_AWAIT_RPC_V1 LIX_TRY_AWAIT_RPC_V1
#define TRY_AWAIT_RPC LIX_TRY_AWAIT_RPC_V1
#endif
}