Skip to main content

ruststream/runtime/
handler.rs

1//! Handler abstraction, the [`HandlerResult`] decision enum, and the [`Settle`] settlement unit
2//! returned to the router.
3
4use std::{convert::Infallible, future::Future, pin::Pin, sync::Arc, time::Duration};
5
6use super::context::Context;
7
8/// A boxed, owned continuation run after a message is settled. Private: a [`Settle`] hands it to
9/// the dispatcher, which spawns it; it never crosses the public API by itself.
10type AfterFut = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
11
12/// What the router should do with the message after the handler returns.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[non_exhaustive]
15pub enum HandlerResult {
16    /// Acknowledge the message; the broker will remove it from the queue.
17    Ack,
18    /// Negatively acknowledge the message; `requeue = true` asks the broker to redeliver.
19    Nack {
20        /// Whether the broker should redeliver the message.
21        requeue: bool,
22    },
23    /// Negatively acknowledge the message, asking the broker to redeliver it no sooner than
24    /// `delay` from now.
25    ///
26    /// The delay is a hint, honoured by brokers with native delayed redelivery (`JetStream`
27    /// `NAK` with delay); brokers without it fall back to an immediate requeue (see
28    /// [`IncomingMessage::nack_after`](crate::IncomingMessage::nack_after)).
29    NackAfter {
30        /// How long the broker should wait before redelivering.
31        delay: Duration,
32    },
33}
34
35impl From<Infallible> for HandlerResult {
36    fn from(never: Infallible) -> Self {
37        match never {}
38    }
39}
40
41impl HandlerResult {
42    /// Convenience constructor for [`Ack`](Self::Ack), for symmetry with [`retry`](Self::retry),
43    /// [`retry_after`](Self::retry_after), and [`drop`](Self::drop) - so a handler reads the same
44    /// whether it acks or nacks, and so [`and_after`](Self::and_after) chains off it.
45    ///
46    /// # Examples
47    ///
48    /// ```
49    /// use ruststream::runtime::HandlerResult;
50    ///
51    /// # fn check() -> Result<(), Box<dyn std::error::Error>> {
52    /// assert_eq!(HandlerResult::ack(), HandlerResult::Ack);
53    /// # Ok(())
54    /// # }
55    /// # check().unwrap();
56    /// ```
57    #[must_use]
58    pub const fn ack() -> Self {
59        Self::Ack
60    }
61
62    /// Attaches a post-settle continuation to this outcome, producing a [`Settle`].
63    ///
64    /// The dispatcher first settles the message by this outcome (ack / nack), then runs `fut` on a
65    /// tracked task that graceful shutdown drains. Use it for a non-critical side effect that must
66    /// not gate the settlement decision or affect redelivery: a notification, slow follow-up work,
67    /// a cache warm-up. The continuation runs after *any* settle, so `drop().and_after(..)` is
68    /// valid.
69    ///
70    /// # Cancel safety
71    ///
72    /// At-most-once: the message is already settled when `fut` runs, so a continuation that panics
73    /// or is lost on a crash never triggers redelivery. Do not put work whose loss must redeliver
74    /// the message in here; settle by outcome and let the broker retry instead.
75    ///
76    /// # Examples
77    ///
78    /// ```
79    /// use ruststream::runtime::HandlerResult;
80    ///
81    /// # fn check() -> Result<(), Box<dyn std::error::Error>> {
82    /// let settle = HandlerResult::ack().and_after(async move {
83    ///     // runs after this message is acked
84    /// });
85    /// assert_eq!(settle.outcome(), HandlerResult::Ack);
86    /// # Ok(())
87    /// # }
88    /// # check().unwrap();
89    /// ```
90    pub fn and_after<F>(self, fut: F) -> Settle
91    where
92        F: Future<Output = ()> + Send + 'static,
93    {
94        Settle {
95            outcome: self,
96            after: Some(Box::pin(fut)),
97        }
98    }
99
100    /// Convenience constructor for `Nack { requeue: true }`.
101    #[must_use]
102    pub const fn retry() -> Self {
103        Self::Nack { requeue: true }
104    }
105
106    /// Convenience constructor for [`NackAfter`](Self::NackAfter): redeliver, but not before
107    /// `delay` has passed - the not-ready-yet case (a dependency has not arrived, an upstream is
108    /// rate-limited), where an immediate redelivery would just spin.
109    #[must_use]
110    pub const fn retry_after(delay: Duration) -> Self {
111        Self::NackAfter { delay }
112    }
113
114    /// Convenience constructor for `Nack { requeue: false }`.
115    #[must_use]
116    pub const fn drop() -> Self {
117        Self::Nack { requeue: false }
118    }
119}
120
121/// The settlement of one dispatched message: the [`HandlerResult`] outcome the dispatcher acts on,
122/// plus an optional post-settle continuation.
123///
124/// `Settle` is the universal unit flowing through the handler pipeline. A single handler returns
125/// `impl Into<Settle>` and a batch handler returns `Vec<Settle>`; a plain [`HandlerResult`] return
126/// still works through [`From<HandlerResult>`](From), which leaves the continuation empty. Build one
127/// with a continuation via [`HandlerResult::and_after`].
128///
129/// The future never lives inside [`HandlerResult`], so that stays a small `Copy` decision enum
130/// (metrics, tracing, and batch settling all classify by the outcome inside `Settle`).
131///
132/// # Cancel safety
133///
134/// The continuation runs after the message is already settled, so it is at-most-once: a panic or a
135/// crash before it completes never redelivers the message. See [`HandlerResult::and_after`].
136///
137/// # Examples
138///
139/// ```
140/// use ruststream::runtime::{HandlerResult, Settle};
141///
142/// # fn check() -> Result<(), Box<dyn std::error::Error>> {
143/// // A plain outcome converts with no continuation.
144/// let plain: Settle = HandlerResult::ack().into();
145/// assert_eq!(plain.outcome(), HandlerResult::Ack);
146///
147/// // Or carry a continuation that runs after the settle.
148/// let with_after = HandlerResult::drop().and_after(async move { /* cleanup */ });
149/// assert_eq!(with_after.outcome(), HandlerResult::drop());
150/// # Ok(())
151/// # }
152/// # check().unwrap();
153/// ```
154#[must_use]
155pub struct Settle {
156    outcome: HandlerResult,
157    after: Option<AfterFut>,
158}
159
160impl Settle {
161    /// The outcome the dispatcher settles the message by.
162    #[must_use]
163    pub const fn outcome(&self) -> HandlerResult {
164        self.outcome
165    }
166
167    /// Takes the post-settle continuation out of this settlement, leaving none. The dispatcher
168    /// calls this after settling, to spawn the continuation on its tracked task set.
169    pub(crate) fn take_after(&mut self) -> Option<AfterFut> {
170        self.after.take()
171    }
172}
173
174impl std::fmt::Debug for Settle {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        f.debug_struct("Settle")
177            .field("outcome", &self.outcome)
178            .field("after", &self.after.is_some())
179            .finish()
180    }
181}
182
183impl From<HandlerResult> for Settle {
184    fn from(outcome: HandlerResult) -> Self {
185        Self {
186            outcome,
187            after: None,
188        }
189    }
190}
191
192/// Conversion into a [`Settle`], so `#[subscriber]` handlers can return a plain value instead of
193/// always constructing one.
194///
195/// Implemented for [`Settle`] (identity), [`HandlerResult`] (no continuation), `()` (always
196/// [`Ack`](HandlerResult::Ack)), `Result<_, E>` (`Ok` acks, `Err` drops), and `Result<Settle, E>`
197/// / `Result<HandlerResult, E>` (`Err` drops).
198pub trait IntoSettle {
199    /// Converts `self` into the settlement the dispatcher acts on.
200    fn into_settle(self) -> Settle;
201}
202
203impl IntoSettle for Settle {
204    fn into_settle(self) -> Settle {
205        self
206    }
207}
208
209impl IntoSettle for HandlerResult {
210    fn into_settle(self) -> Settle {
211        self.into()
212    }
213}
214
215impl IntoSettle for () {
216    fn into_settle(self) -> Settle {
217        HandlerResult::Ack.into()
218    }
219}
220
221impl<E> IntoSettle for Result<(), E> {
222    fn into_settle(self) -> Settle {
223        match self {
224            Ok(()) => HandlerResult::Ack,
225            Err(_) => HandlerResult::drop(),
226        }
227        .into()
228    }
229}
230
231impl<E> IntoSettle for Result<HandlerResult, E> {
232    fn into_settle(self) -> Settle {
233        self.unwrap_or_else(|_| HandlerResult::drop()).into()
234    }
235}
236
237impl<E> IntoSettle for Result<Settle, E> {
238    fn into_settle(self) -> Settle {
239        self.unwrap_or_else(|_| HandlerResult::drop().into())
240    }
241}
242
243/// A handler invoked on each input it is given.
244///
245/// The same trait serves both pipeline levels: a raw delivery (`Handler<M>` where
246/// `M: IncomingMessage`) and a decoded value (`Handler<T>`). Implementations are `Send + Sync` so a
247/// single handler can be shared across many concurrent inputs.
248///
249/// # Examples
250///
251/// Closures implement `Handler` automatically:
252///
253/// ```
254/// use ruststream::IncomingMessage;
255/// use ruststream::runtime::{Context, Handler, HandlerResult};
256///
257/// fn assert_handler<M, H>(_: H)
258/// where
259///     M: IncomingMessage,
260///     H: Handler<M>,
261/// {
262/// }
263///
264/// fn use_closure<M: IncomingMessage + 'static>() {
265///     // A closure may return any `Into<Settle>`, including a bare `HandlerResult`.
266///     assert_handler::<M, _>(|_msg: &M, _ctx: &mut Context| async { HandlerResult::Ack });
267/// }
268/// ```
269pub trait Handler<M, C = (), S = ()>: Send + Sync {
270    /// Handle one input, with the per-delivery [`Context`] (carrying the broker's typed context
271    /// `C` and the shared application state `S`). The returned [`Settle`] carries the outcome the
272    /// dispatcher settles by and any post-settle continuation.
273    fn handle(&self, msg: &M, ctx: &mut Context<'_, C, S>) -> impl Future<Output = Settle> + Send;
274}
275
276impl<M, C, S, F, Fut> Handler<M, C, S> for F
277where
278    F: Fn(&M, &mut Context<'_, C, S>) -> Fut + Send + Sync,
279    Fut: Future + Send,
280    Fut::Output: IntoSettle,
281{
282    fn handle(&self, msg: &M, ctx: &mut Context<'_, C, S>) -> impl Future<Output = Settle> + Send {
283        // Build the inner future before the async block so it owns the closure's output and the
284        // returned future is `Settle`-valued for any `Into<Settle>` return shape.
285        let fut = (self)(msg, ctx);
286        async move { fut.await.into_settle() }
287    }
288}
289
290impl<M, C, S, H> Handler<M, C, S> for Arc<H>
291where
292    H: Handler<M, C, S>,
293{
294    fn handle(&self, msg: &M, ctx: &mut Context<'_, C, S>) -> impl Future<Output = Settle> + Send {
295        (**self).handle(msg, ctx)
296    }
297}
298
299#[cfg(test)]
300mod tests {
301    use std::time::Duration;
302
303    use super::{HandlerResult, IntoSettle, Settle};
304
305    #[test]
306    fn convenience_constructors_map_to_variants() {
307        assert_eq!(HandlerResult::ack(), HandlerResult::Ack);
308        assert_eq!(
309            HandlerResult::retry(),
310            HandlerResult::Nack { requeue: true }
311        );
312        assert_eq!(
313            HandlerResult::drop(),
314            HandlerResult::Nack { requeue: false }
315        );
316        assert_eq!(
317            HandlerResult::retry_after(Duration::from_secs(2)),
318            HandlerResult::NackAfter {
319                delay: Duration::from_secs(2)
320            }
321        );
322    }
323
324    #[test]
325    fn into_settle_covers_every_return_shape() {
326        // Bare outcomes and unit / Result shapes never carry a continuation.
327        assert_outcome(HandlerResult::Ack.into_settle(), HandlerResult::Ack, false);
328        assert_outcome(().into_settle(), HandlerResult::Ack, false);
329        assert_outcome(Ok::<(), ()>(()).into_settle(), HandlerResult::Ack, false);
330        assert_outcome(
331            Err::<(), ()>(()).into_settle(),
332            HandlerResult::drop(),
333            false,
334        );
335        assert_outcome(
336            Ok::<HandlerResult, ()>(HandlerResult::retry()).into_settle(),
337            HandlerResult::retry(),
338            false,
339        );
340        assert_outcome(
341            Err::<HandlerResult, ()>(()).into_settle(),
342            HandlerResult::drop(),
343            false,
344        );
345
346        // A Settle (and a Result<Settle, E>) is the identity and keeps its continuation.
347        let with_after = HandlerResult::ack().and_after(async {});
348        assert_outcome(with_after.into_settle(), HandlerResult::Ack, true);
349        let ok: Result<Settle, ()> = Ok(HandlerResult::drop().and_after(async {}));
350        assert_outcome(ok.into_settle(), HandlerResult::drop(), true);
351        let err: Result<Settle, ()> = Err(());
352        assert_outcome(err.into_settle(), HandlerResult::drop(), false);
353    }
354
355    #[test]
356    fn and_after_carries_the_outcome_and_continuation() {
357        let settle = HandlerResult::ack().and_after(async {});
358        assert_eq!(settle.outcome(), HandlerResult::Ack);
359        assert!(format!("{settle:?}").contains("after: true"));
360
361        let plain: Settle = HandlerResult::retry().into();
362        assert_eq!(plain.outcome(), HandlerResult::retry());
363        assert!(format!("{plain:?}").contains("after: false"));
364    }
365
366    fn assert_outcome(mut settle: Settle, outcome: HandlerResult, has_after: bool) {
367        assert_eq!(settle.outcome(), outcome);
368        assert_eq!(settle.take_after().is_some(), has_after);
369    }
370}