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}