Skip to main content

ruststream/runtime/
middleware.rs

1//! Middleware infrastructure: [`Layer`] composes wrappers around handlers, tower-style.
2//!
3//! A `Layer` consumes one handler and returns another. Stacking layers builds the final
4//! handler the router invokes. Layers run in the order they are applied: the outermost
5//! `with(...)` runs first.
6//!
7//! # Examples
8//!
9//! ```
10//! use ruststream::IncomingMessage;
11//! use ruststream::runtime::{Context, Handler, HandlerExt, HandlerResult, layers::TracingLayer};
12//!
13//! fn build<M: IncomingMessage + 'static>() -> impl Handler<M> {
14//!     let base = |_msg: &M, _ctx: &mut Context| async { HandlerResult::Ack };
15//!     base.with(TracingLayer::default())
16//! }
17//! ```
18
19use std::future::Future;
20
21use super::context::Context;
22use super::handler::{Handler, Settle};
23
24/// A function from one handler to another. Apply with [`HandlerExt::with`].
25pub trait Layer<H> {
26    /// The handler type produced by this layer.
27    type Handler;
28
29    /// Wrap `inner` and return the composed handler.
30    fn layer(&self, inner: H) -> Self::Handler;
31}
32
33/// A [`Layer`] that wraps a handler on *any* message type, not one fixed `H`.
34///
35/// [`Layer`] is checked per concrete handler (`L: Layer<H>`). That bound cannot be discharged when
36/// the handler types are hidden, which is exactly the case for a [`Router`](super::Router) mounted
37/// through [`include_router`](super::BrokerScope::include_router): its handlers are erased behind
38/// [`RouterDef`](super::RouterDef). `BlanketLayer` carries the wrapping as a generic method, so a
39/// layer that applies uniformly (logging, metrics) can wrap every router handler from one bound.
40///
41/// Implemented for [`Identity`], a [`Stack`] of blanket layers, and the bundled
42/// [`TracingLayer`](layers::TracingLayer). Implement it for a custom layer to let the app's global
43/// stack reach router handlers; a layer that only wraps specific handler types cannot be blanket.
44pub trait BlanketLayer: Send + Sync {
45    /// Wraps `handler`, returning the layered handler. `S` is the app's shared-state type, threaded
46    /// so a blanket layer wraps a router handler without fixing its state type.
47    fn apply<M, C, S, H>(&self, handler: H) -> impl Handler<M, C, S> + 'static
48    where
49        M: Send + Sync + 'static,
50        C: Send + 'static,
51        S: Send + Sync + 'static,
52        H: Handler<M, C, S> + 'static;
53}
54
55/// Convenience extension trait for fluent layer stacking on any [`Handler`].
56pub trait HandlerExt<M, C = (), S = ()>: Handler<M, C, S> + Sized {
57    /// Wrap this handler with the given layer.
58    fn with<L>(self, layer: L) -> L::Handler
59    where
60        L: Layer<Self>,
61    {
62        layer.layer(self)
63    }
64}
65
66impl<M, C, S, H> HandlerExt<M, C, S> for H where H: Handler<M, C, S> {}
67
68/// The identity [`Layer`]: returns the handler unchanged. The default global stack on
69/// [`RustStream`](super::RustStream).
70#[derive(Debug, Clone, Copy, Default)]
71pub struct Identity;
72
73impl<H> Layer<H> for Identity {
74    type Handler = H;
75
76    fn layer(&self, inner: H) -> H {
77        inner
78    }
79}
80
81impl BlanketLayer for Identity {
82    fn apply<M, C, S, H>(&self, handler: H) -> impl Handler<M, C, S> + 'static
83    where
84        M: Send + Sync + 'static,
85        C: Send + 'static,
86        S: Send + Sync + 'static,
87        H: Handler<M, C, S> + 'static,
88    {
89        handler
90    }
91}
92
93/// Composes two layers into one: `inner` is applied first (innermost), `outer` wraps it.
94///
95/// Built by chaining [`RustStream::layer`](super::RustStream::layer); you rarely name it directly.
96#[derive(Debug, Clone, Copy, Default)]
97pub struct Stack<Inner, Outer> {
98    inner: Inner,
99    outer: Outer,
100}
101
102impl<Inner, Outer> Stack<Inner, Outer> {
103    /// Composes `inner` (applied first) under `outer`.
104    #[must_use]
105    pub fn new(inner: Inner, outer: Outer) -> Self {
106        Self { inner, outer }
107    }
108}
109
110impl<H, Inner, Outer> Layer<H> for Stack<Inner, Outer>
111where
112    Inner: Layer<H>,
113    Outer: Layer<Inner::Handler>,
114{
115    type Handler = Outer::Handler;
116
117    fn layer(&self, inner: H) -> Self::Handler {
118        self.outer.layer(self.inner.layer(inner))
119    }
120}
121
122impl<Inner, Outer> BlanketLayer for Stack<Inner, Outer>
123where
124    Inner: BlanketLayer,
125    Outer: BlanketLayer,
126{
127    fn apply<M, C, S, H>(&self, handler: H) -> impl Handler<M, C, S> + 'static
128    where
129        M: Send + Sync + 'static,
130        C: Send + 'static,
131        S: Send + Sync + 'static,
132        H: Handler<M, C, S> + 'static,
133    {
134        // Same order as the static `Layer` impl: inner wraps first (innermost), outer outside.
135        self.outer
136            .apply::<M, C, S, _>(self.inner.apply::<M, C, S, _>(handler))
137    }
138}
139
140/// Bundled, opinionated middleware layers ready to drop into a handler stack.
141pub mod layers {
142    use tracing::{debug, info, instrument, warn};
143
144    use super::super::handler::HandlerResult;
145    use super::{BlanketLayer, Context, Future, Handler, Layer, Settle};
146
147    /// Logs every delivery and its outcome via [`tracing`]. Default level is `INFO` for the
148    /// outcome and `DEBUG` for arrival.
149    #[derive(Debug, Clone, Default)]
150    pub struct TracingLayer {
151        target: Option<&'static str>,
152    }
153
154    impl TracingLayer {
155        /// Constructs a layer that emits events under the given tracing target.
156        #[must_use]
157        pub const fn with_target(target: &'static str) -> Self {
158            Self {
159                target: Some(target),
160            }
161        }
162    }
163
164    impl<H> Layer<H> for TracingLayer {
165        type Handler = TracingHandler<H>;
166
167        fn layer(&self, inner: H) -> Self::Handler {
168            TracingHandler {
169                inner,
170                target: self.target,
171            }
172        }
173    }
174
175    impl BlanketLayer for TracingLayer {
176        fn apply<M, C, S, H>(&self, handler: H) -> impl Handler<M, C, S> + 'static
177        where
178            M: Send + Sync + 'static,
179            C: Send + 'static,
180            S: Send + Sync + 'static,
181            H: Handler<M, C, S> + 'static,
182        {
183            self.layer(handler)
184        }
185    }
186
187    /// Handler produced by [`TracingLayer::layer`].
188    #[derive(Debug, Clone)]
189    pub struct TracingHandler<H> {
190        inner: H,
191        target: Option<&'static str>,
192    }
193
194    impl<M, C, S, H> Handler<M, C, S> for TracingHandler<H>
195    where
196        M: Sync,
197        C: Send,
198        S: Send + Sync,
199        H: Handler<M, C, S>,
200    {
201        #[instrument(level = "trace", skip(self, msg, ctx), fields(target = self.target))]
202        fn handle(
203            &self,
204            msg: &M,
205            ctx: &mut Context<'_, C, S>,
206        ) -> impl Future<Output = Settle> + Send {
207            async move {
208                debug!(target: "ruststream::dispatch", "delivery received");
209                // Log the outcome inside the settlement; the continuation (if any) flows through.
210                let settle = self.inner.handle(msg, ctx).await;
211                match settle.outcome() {
212                    HandlerResult::Ack => {
213                        info!(target: "ruststream::dispatch", "handler ack");
214                    }
215                    HandlerResult::Nack { requeue } => {
216                        warn!(target: "ruststream::dispatch", requeue, "handler nack");
217                    }
218                    HandlerResult::NackAfter { delay } => {
219                        warn!(target: "ruststream::dispatch", ?delay, "handler delayed nack");
220                    }
221                }
222                settle
223            }
224        }
225    }
226}