Skip to main content

vox_types/
conduit.rs

1#![allow(async_fn_in_trait)]
2
3use std::future::Future;
4
5use facet::Facet;
6use facet_core::Shape;
7
8use crate::{MaybeSend, SelfRef};
9
10/// Maps a lifetime to a concrete message type.
11///
12/// Rust doesn't have higher-kinded types, so this trait bridges the gap:
13/// `F::Msg<'a>` gives you the message type for any lifetime `'a`.
14///
15/// The send path uses `Msg<'a>` (borrowed data serialized in place).
16/// The recv path uses `Msg<'static>` (owned, via `SelfRef`).
17pub trait MsgFamily: 'static {
18    type Msg<'a>: Facet<'a> + 'a;
19
20    fn shape() -> &'static Shape {
21        <Self::Msg<'static> as Facet<'static>>::SHAPE
22    }
23}
24
25/// Bidirectional typed transport. Wraps a [`Link`](crate::Link) and owns serialization.
26///
27/// Uses a `MsgFamily` so that the same type family serves both sides:
28/// - Send: `MsgFamily::Msg<'a>` for any `'a` (borrowed data serialized in place)
29/// - Recv: `MsgFamily::Msg<'static>` (owned, via `SelfRef`)
30///
31/// Two implementations:
32/// - `BareConduit`: Link + postcard. If the link dies, it's dead.
33/// - `StableConduit`: Link + postcard + seq/ack/replay. Handles reconnect
34///   transparently. Replay buffer stores encoded bytes (no clone needed).
35// r[impl conduit]
36pub trait Conduit {
37    type Msg: MsgFamily;
38    type Tx: ConduitTx<Msg = Self::Msg>;
39    type Rx: ConduitRx<Msg = Self::Msg>;
40
41    // r[impl conduit.split]
42    fn split(self) -> (Self::Tx, Self::Rx);
43}
44
45/// Sending half of a [`Conduit`].
46///
47/// Sending is split into a synchronous preparation phase and an async enqueue
48/// phase. Preparation may borrow from the input value, but it must produce an
49/// owned representation that survives across the first await point.
50pub trait ConduitTx {
51    type Msg: MsgFamily;
52    type Prepared: MaybeSend + 'static;
53    type Error: std::error::Error + MaybeSend + 'static;
54
55    /// Serialize one outbound message into an owned representation.
56    fn prepare_send(
57        &self,
58        item: <Self::Msg as MsgFamily>::Msg<'_>,
59    ) -> Result<Self::Prepared, Self::Error>;
60
61    /// Enqueue a previously prepared outbound message.
62    fn send_prepared(
63        &self,
64        prepared: Self::Prepared,
65    ) -> impl Future<Output = Result<(), Self::Error>> + MaybeSend + '_;
66
67    /// Graceful close of the outbound direction.
68    async fn close(self) -> std::io::Result<()>
69    where
70        Self: Sized;
71}
72
73/// Receiving half of a [`Conduit`].
74///
75/// Yields decoded values as [`SelfRef<Msg<'static>>`](SelfRef) (value + backing storage).
76/// Uses a precomputed `TypePlanCore` for fast plan-driven deserialization.
77/// The result of receiving a message from a conduit.
78pub type RecvResult<M, E> = Result<Option<SelfRef<<M as MsgFamily>::Msg<'static>>>, E>;
79
80pub trait ConduitRx {
81    type Msg: MsgFamily;
82    type Error: std::error::Error + MaybeSend + 'static;
83
84    /// Receive and decode the next message.
85    ///
86    /// Returns `Ok(None)` when the peer has closed.
87    fn recv(&mut self)
88    -> impl Future<Output = RecvResult<Self::Msg, Self::Error>> + MaybeSend + '_;
89}
90
91/// Yields new conduits from inbound connections.
92pub trait ConduitAcceptor {
93    type Conduit: Conduit;
94
95    async fn accept(&mut self) -> std::io::Result<Self::Conduit>;
96}
97
98/// Whether the session is acting as initiator or acceptor.
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum SessionRole {
101    Initiator,
102    Acceptor,
103}