Skip to main content

ruststream/
field.rs

1//! Compile-time field selectors: typed keys that read (and optionally write) one field of a
2//! source by monomorphization, with no hashing, boxing, or downcasting.
3//!
4//! A key is a zero-sized type implementing [`Field`] for the sources that carry its field. The
5//! runtime threads the source (a per-delivery context value or the shared application state) and
6//! resolves `key.get(src)` to a direct field access. A key middleware writes also implements
7//! [`FieldMut`]. Because a key implements [`Field`] only for the sources that actually carry its
8//! field, applying an inapplicable key is a compile error rather than a runtime miss.
9
10/// A compile-time key reading one typed field out of `Src`.
11///
12/// Implemented by a zero-sized selector type for each source that carries the field. Resolution is
13/// monomorphized to a direct field read: no hash, no `Box`, no downcast. The key is taken by value
14/// (selectors are `Copy` zero-sized types), and the read borrows from the source for the call.
15///
16/// # Examples
17///
18/// ```
19/// use ruststream::Field;
20///
21/// struct Delivery {
22///     sequence: u64,
23/// }
24///
25/// #[derive(Clone, Copy)]
26/// struct Sequence;
27///
28/// impl Field<Delivery> for Sequence {
29///     type Value<'a> = u64;
30///     fn get(self, src: &Delivery) -> u64 {
31///         src.sequence
32///     }
33/// }
34///
35/// let delivery = Delivery { sequence: 7 };
36/// assert_eq!(Sequence.get(&delivery), 7);
37/// ```
38pub trait Field<Src: ?Sized> {
39    /// The value read through this key, borrowed from the source for `'a`.
40    type Value<'a>
41    where
42        Src: 'a;
43
44    /// Reads this key's field out of `src`.
45    fn get(self, src: &Src) -> Self::Value<'_>;
46}
47
48/// A [`Field`]-style key that also names its context type and yields an owned value.
49///
50/// It powers the [`Ctx`](crate::runtime::Ctx) extractor: because the key carries its context
51/// as an associated type, a handler taking `Ctx(value): Ctx<Key>` needs no `&mut Context`
52/// parameter at all - the `#[subscriber]` macro projects the subscription's context type from
53/// the first `Ctx` key it sees. The value is owned (`'static`) on purpose: extractor values
54/// bind before the handler body runs, so borrowing from the context is not an option. Borrowed
55/// keys keep working through [`Field`] and `ctx.context(KEY)`.
56///
57/// Broker crates implement it next to [`Field`] on the same zero-sized keys; a key typically
58/// implements both, so it works as a context read and as an extractor.
59///
60/// # Examples
61///
62/// ```
63/// use ruststream::ContextField;
64///
65/// struct Delivery {
66///     sequence: u64,
67/// }
68///
69/// #[derive(Clone, Copy, Default)]
70/// struct Sequence;
71///
72/// impl ContextField for Sequence {
73///     type Context = Delivery;
74///     type Value = u64;
75///     fn read(self, src: &Delivery) -> u64 {
76///         src.sequence
77///     }
78/// }
79///
80/// let delivery = Delivery { sequence: 7 };
81/// assert_eq!(Sequence.read(&delivery), 7);
82/// ```
83pub trait ContextField: Default {
84    /// The per-delivery context type this key reads from.
85    type Context;
86
87    /// The owned value the key yields.
88    type Value: Send + 'static;
89
90    /// Reads this key's field out of `src`. Named apart from [`Field::get`] so a key
91    /// implementing both traits stays unambiguous to call.
92    fn read(self, src: &Self::Context) -> Self::Value;
93}
94
95/// A [`Field`] key middleware can also write, for per-delivery scratch values.
96///
97/// The read side ([`Field::get`]) is typically `Option<&T>` (the value may not have been set yet),
98/// while [`set`](FieldMut::set) takes the owned `T`.
99///
100/// # Examples
101///
102/// ```
103/// use ruststream::{Field, FieldMut};
104///
105/// #[derive(Default)]
106/// struct Ctx {
107///     user: Option<u64>,
108/// }
109///
110/// #[derive(Clone, Copy)]
111/// struct User;
112///
113/// impl Field<Ctx> for User {
114///     type Value<'a> = Option<&'a u64>;
115///     fn get(self, src: &Ctx) -> Option<&u64> {
116///         src.user.as_ref()
117///     }
118/// }
119///
120/// impl FieldMut<Ctx> for User {
121///     type Owned = u64;
122///     fn set(self, src: &mut Ctx, value: u64) {
123///         src.user = Some(value);
124///     }
125/// }
126///
127/// let mut ctx = Ctx::default();
128/// User.set(&mut ctx, 42);
129/// assert_eq!(User.get(&ctx), Some(&42));
130/// ```
131pub trait FieldMut<Src: ?Sized>: Field<Src> {
132    /// The owned value written through this key.
133    type Owned;
134
135    /// Writes `value` into `src` under this key.
136    fn set(self, src: &mut Src, value: Self::Owned);
137}
138
139/// Builds a handler's per-delivery context value from the broker message.
140///
141/// The runtime calls this once per delivery to construct the typed context the handler reads its
142/// broker fields off (by [`Field`] key). A broker with per-delivery fields implements it for its
143/// own context type, reading the fields off its concrete message; the blanket `impl` for `()`
144/// gives the zero-field default, so a broker that exposes nothing needs no implementation.
145///
146/// The built context is an owned value (it reads its fields out of the message rather than
147/// borrowing it), so it does not tie the handler's context type to the delivery lifetime; broker
148/// metadata is typically `Copy` (offsets, sequence numbers), so this is a stack copy.
149///
150/// # Examples
151///
152/// ```
153/// use ruststream::BuildContext;
154///
155/// struct Msg {
156///     offset: u64,
157/// }
158///
159/// // A broker context carrying one field, built from the message.
160/// struct OrdersContext {
161///     offset: u64,
162/// }
163///
164/// impl BuildContext<Msg> for OrdersContext {
165///     fn build(msg: &Msg) -> Self {
166///         Self { offset: msg.offset }
167///     }
168/// }
169///
170/// let msg = Msg { offset: 9 };
171/// let cx = OrdersContext::build(&msg);
172/// assert_eq!(cx.offset, 9);
173/// ```
174pub trait BuildContext<M: ?Sized> {
175    /// Builds the context value by reading fields out of `msg`.
176    fn build(msg: &M) -> Self;
177}
178
179impl<M: ?Sized> BuildContext<M> for () {
180    fn build(_msg: &M) -> Self {}
181}