#pragma once /** * @file Reusable serialisers for serialization container types in a * length-prefixed manner. * * Used by both the Worker and Serve protocols. */ #include "types.hh" #include "serialise.hh" namespace nix { class Store; /** * Reusable serialisers for serialization container types in a * length-prefixed manner. * * @param T The type of the collection being serialised * * @param Inner This the most important parameter; this is the "inner" * protocol. The user of this will substitute `MyProtocol` or similar * when making a `MyProtocol::Serialiser>`. Note that the * inside is allowed to call to call `Inner::Serialiser` on different * types. This is especially important for `std::map` which doesn't have * a single `T` but one `K` and one `V`. */ template struct LengthPrefixedProtoHelper; /*! * \typedef LengthPrefixedProtoHelper::S * * Read this as simply `using S = Inner::Serialise;`. * * It would be nice to use that directly, but C++ doesn't seem to allow * it. The `typename` keyword needed to refer to `Inner` seems to greedy * (low precedence), and then C++ complains that `Serialise` is not a * type parameter but a real type. * * Making this `S` alias seems to be the only way to avoid these issues. */ #define LENGTH_PREFIXED_PROTO_HELPER(Inner, T) \ struct LengthPrefixedProtoHelper< Inner, T > \ { \ static T read(const Store & store, typename Inner::ReadConn conn); \ [[nodiscard]] static WireFormatGenerator write(const Store & store, typename Inner::WriteConn conn, const T & str); \ private: \ template using S = typename Inner::template Serialise; \ } template LENGTH_PREFIXED_PROTO_HELPER(Inner, std::vector); template LENGTH_PREFIXED_PROTO_HELPER(Inner, std::set); template LENGTH_PREFIXED_PROTO_HELPER(Inner, std::tuple); template #define DONT_SUBSTITUTE_KV_TYPE std::map LENGTH_PREFIXED_PROTO_HELPER(Inner, DONT_SUBSTITUTE_KV_TYPE); #undef DONT_SUBSTITUTE_KV_TYPE template std::vector LengthPrefixedProtoHelper>::read( const Store & store, typename Inner::ReadConn conn) { std::vector resSet; auto size = readNum(conn.from); while (size--) { resSet.push_back(S::read(store, conn)); } return resSet; } template WireFormatGenerator LengthPrefixedProtoHelper>::write( const Store & store, typename Inner::WriteConn conn, const std::vector & resSet) { co_yield resSet.size(); for (auto & key : resSet) { co_yield S::write(store, conn, key); } } template std::set LengthPrefixedProtoHelper>::read( const Store & store, typename Inner::ReadConn conn) { std::set resSet; auto size = readNum(conn.from); while (size--) { resSet.insert(S::read(store, conn)); } return resSet; } template WireFormatGenerator LengthPrefixedProtoHelper>::write( const Store & store, typename Inner::WriteConn conn, const std::set & resSet) { co_yield resSet.size(); for (auto & key : resSet) { co_yield S::write(store, conn, key); } } template std::map LengthPrefixedProtoHelper>::read( const Store & store, typename Inner::ReadConn conn) { std::map resMap; auto size = readNum(conn.from); while (size--) { auto k = S::read(store, conn); auto v = S::read(store, conn); resMap.insert_or_assign(std::move(k), std::move(v)); } return resMap; } template WireFormatGenerator LengthPrefixedProtoHelper>::write( const Store & store, typename Inner::WriteConn conn, const std::map & resMap) { co_yield resMap.size(); for (auto & i : resMap) { co_yield S::write(store, conn, i.first); co_yield S::write(store, conn, i.second); } } template std::tuple LengthPrefixedProtoHelper>::read( const Store & store, typename Inner::ReadConn conn) { return std::tuple { S::read(store, conn)..., }; } template WireFormatGenerator LengthPrefixedProtoHelper>::write( const Store & store, typename Inner::WriteConn conn, const std::tuple & res) { auto fullArgs = std::apply( [&](auto &... rest) { return std::tuple( std::cref(store), conn, rest... ); }, res ); return std::apply( [](auto & store, auto conn, const Us &... args) -> WireFormatGenerator { (co_yield S::write(store, conn, args), ...); }, fullArgs ); } }