Skip to main content

trillium_client/
client_handler.rs

1use crate::{Conn, ConnExt, Result};
2use std::{
3    any::Any,
4    borrow::Cow,
5    fmt::{self, Debug, Formatter},
6    future::Future,
7    pin::Pin,
8    sync::Arc,
9};
10
11/// Client middleware extension point.
12///
13/// [`ClientHandler`] is the composition primitive for trillium-client middleware. It mirrors the
14/// server-side [`trillium::Handler`] in spirit — handlers compose into tuples, halting on the conn
15/// short-circuits the chain — but differs in shape because the client has different ownership
16/// semantics: the conn is user-owned, so handlers take `&mut Conn` rather than owned `Conn`, and
17/// they return `Result<()>` because client execution can fail outright (TLS handshake, refresh
18/// token, signing, etc.).
19///
20/// [`trillium::Handler`]: https://docs.trillium.rs/trillium/trait.Handler.html
21///
22/// ## Lifecycle
23///
24/// Awaiting a [`Conn`] runs handlers in three steps:
25///
26/// 1. **Forward pass — `run`.** Each handler runs in declared order. A handler may mutate the
27///    request, short-circuit by [calling `Conn::halt`] + populating synthetic response state (cache
28///    hit, mocked response), or fail. If any handler halts, subsequent `run` methods are skipped.
29/// 2. **Network round-trip.** Skipped if the conn is halted.
30/// 3. **Reverse pass — `after_response`.** Each handler's `after_response` runs in *reverse* order,
31///    *regardless of halt status*. This mirrors `trillium::Handler::before_send` and lets handlers
32///    that observe the response record cache hits and short-circuited responses, not just
33///    transport-backed ones.
34///
35/// [calling `Conn::halt`]: crate::Conn::halt
36///
37/// ## Re-execution
38///
39/// Handlers that need to re-issue a request (follow-redirects, retry, auth-refresh) build a fresh
40/// `Conn` from `conn.client()` in `after_response`, configure it (filtered headers, replayed body,
41/// handler-internal state), and queue it via
42/// [`ConnExt::set_followup`][crate::ConnExt::set_followup]. The
43/// [`IntoFuture for &mut Conn`][std::future::IntoFuture] loop picks the follow-up up after the
44/// current cycle's `after_response` has fully unwound: it recycles the current response body,
45/// swaps the follow-up into place, and runs another full `(run → network → after_response)`
46/// cycle on it.
47///
48/// ## Handler-author affordances on `Conn`
49///
50/// Lifecycle-driving methods — queue a follow-up, stash or recover the transport-level error —
51/// live on the [`ConnExt`][crate::ConnExt] extension trait rather than directly on
52/// [`Conn`]. Bring them into scope with `use trillium_client::ConnExt;`. The split is
53/// intentional: those operations are meaningful only from inside a handler, and keeping them off
54/// `Conn`'s inherent surface stops them from appearing in IDE completion for user code that holds
55/// a `Conn` directly.
56///
57/// ## Type erasure
58///
59/// Implementors write [`ClientHandler`] using native `async fn` syntax. The crate type-erases
60/// handlers internally for storage on `Client`; [`Client::with_handler`] accepts any
61/// `impl ClientHandler`, and [`Client::downcast_handler`] is the way to recover the concrete type
62/// from a `Client` that has one installed.
63///
64/// [`Client::with_handler`]: crate::Client::with_handler
65/// [`Client::downcast_handler`]: crate::Client::downcast_handler
66pub trait ClientHandler: Send + Sync + 'static {
67    /// Forward-pass hook, called before the network round-trip in declared order.
68    ///
69    /// A handler can mutate the request, halt to short-circuit, or fail. The default
70    /// implementation is a no-op.
71    fn run(&self, conn: &mut Conn) -> impl Future<Output = Result<()>> + Send {
72        let _ = conn;
73        async { Ok(()) }
74    }
75
76    /// Reverse-pass hook, called after the network round-trip (or after a halt-skipped network
77    /// call) in *reverse* declared order. Always runs regardless of halt status or transport
78    /// error.
79    ///
80    /// A handler can observe the response, mutate it before passing it to upstream handlers,
81    /// recover from a transport-level error, or fail.
82    ///
83    /// **Transport errors.** If the network call failed (connect refused, TLS handshake error,
84    /// malformed HTTP frame, timeout), the framework stashes the error on the conn and runs
85    /// `after_response` anyway. A handler that recovers from an error should:
86    /// 1. Inspect [`conn.error()`][crate::ConnExt::error] to detect the failure.
87    /// 2. Populate response state synthetically (`set_status`, `response_headers_mut`,
88    ///    `set_response_body`) or enqueue a new followup conn.
89    /// 3. Call [`conn.take_error()`][crate::ConnExt::take_error] to clear the error so the awaited
90    ///    conn returns `Ok`.
91    ///
92    /// The `error` / `take_error` / `set_error` methods live on the
93    /// [`ConnExt`][crate::ConnExt] extension trait — `use
94    /// trillium_client::ConnExt;` to bring them into scope.
95    ///
96    /// If no handler clears the error, it propagates as `Err` from the awaited conn.
97    ///
98    /// The default implementation is a no-op.
99    fn after_response(&self, conn: &mut Conn) -> impl Future<Output = Result<()>> + Send {
100        let _ = conn;
101        async { Ok(()) }
102    }
103
104    /// Human-readable name for logging/debugging. Defaults to the type name.
105    fn name(&self) -> Cow<'static, str> {
106        std::any::type_name::<Self>().into()
107    }
108}
109
110/// Object-safe twin of [`ClientHandler`] used for internal type erasure. Users implement
111/// [`ClientHandler`] with native `async fn`; the blanket impl below adapts it.
112pub(crate) trait ObjectSafeClientHandler: Any + Send + Sync + 'static {
113    fn run<'a>(
114        &'a self,
115        conn: &'a mut Conn,
116    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>;
117    fn after_response<'a>(
118        &'a self,
119        conn: &'a mut Conn,
120    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>;
121    fn name(&self) -> Cow<'static, str>;
122    fn as_any(&self) -> &dyn Any;
123}
124
125impl<H: ClientHandler> ObjectSafeClientHandler for H {
126    fn run<'a>(
127        &'a self,
128        conn: &'a mut Conn,
129    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>> {
130        Box::pin(ClientHandler::run(self, conn))
131    }
132
133    fn after_response<'a>(
134        &'a self,
135        conn: &'a mut Conn,
136    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>> {
137        Box::pin(ClientHandler::after_response(self, conn))
138    }
139
140    fn name(&self) -> Cow<'static, str> {
141        ClientHandler::name(self)
142    }
143
144    fn as_any(&self) -> &dyn Any {
145        self
146    }
147}
148
149/// Internal `Arc`-shared, type-erased [`ClientHandler`]. Stored on a `Client` and cloned onto
150/// each conn it builds. Not exposed publicly — `Client::with_handler` accepts
151/// `impl ClientHandler` and `Client::downcast_handler` recovers the concrete type.
152#[derive(Clone)]
153pub(crate) struct ArcedClientHandler(Arc<dyn ObjectSafeClientHandler>);
154
155impl Debug for ArcedClientHandler {
156    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
157        f.debug_tuple("ArcedClientHandler")
158            .field(&self.0.name())
159            .finish()
160    }
161}
162
163impl ArcedClientHandler {
164    pub(crate) fn new(handler: impl ClientHandler) -> Self {
165        Self(Arc::new(handler))
166    }
167
168    pub(crate) fn downcast_ref<T: Any + 'static>(&self) -> Option<&T> {
169        self.0.as_any().downcast_ref()
170    }
171}
172
173impl ClientHandler for ArcedClientHandler {
174    async fn run(&self, conn: &mut Conn) -> Result<()> {
175        self.0.run(conn).await
176    }
177
178    async fn after_response(&self, conn: &mut Conn) -> Result<()> {
179        self.0.after_response(conn).await
180    }
181
182    fn name(&self) -> Cow<'static, str> {
183        self.0.name()
184    }
185}
186
187impl ClientHandler for () {}
188
189impl<H: ClientHandler> ClientHandler for Option<H> {
190    async fn run(&self, conn: &mut Conn) -> Result<()> {
191        if let Some(h) = self {
192            h.run(conn).await?;
193        }
194        Ok(())
195    }
196
197    async fn after_response(&self, conn: &mut Conn) -> Result<()> {
198        if let Some(h) = self {
199            h.after_response(conn).await?;
200        }
201        Ok(())
202    }
203
204    fn name(&self) -> Cow<'static, str> {
205        match self {
206            Some(h) => h.name(),
207            None => "None".into(),
208        }
209    }
210}
211
212macro_rules! reverse_after_response {
213    ($conn:ident, $name:ident) => {
214        log::trace!("after_response {}", $name.name());
215        $name.after_response($conn).await?;
216    };
217    ($conn:ident, $name:ident $($rest:ident)+) => {
218        reverse_after_response!($conn, $($rest)+);
219        log::trace!("after_response {}", $name.name());
220        $name.after_response($conn).await?;
221    };
222}
223
224macro_rules! impl_client_handler_tuple {
225    ($($name:ident)+) => {
226        impl<$($name: ClientHandler),+> ClientHandler for ($($name,)+) {
227            #[allow(non_snake_case)]
228            async fn run(&self, conn: &mut Conn) -> Result<()> {
229                let ($(ref $name,)+) = *self;
230                $(
231                    log::trace!("running {}", $name.name());
232                    $name.run(conn).await?;
233                    if conn.is_halted() {
234                        return Ok(());
235                    }
236                )+
237                Ok(())
238            }
239
240            #[allow(non_snake_case)]
241            async fn after_response(&self, conn: &mut Conn) -> Result<()> {
242                let ($(ref $name,)+) = *self;
243                reverse_after_response!(conn, $($name)+);
244                Ok(())
245            }
246
247            #[allow(non_snake_case)]
248            fn name(&self) -> Cow<'static, str> {
249                let ($(ref $name,)+) = *self;
250                format!(concat!("(\n", $(
251                        concat!("  {",stringify!($name) ,":},\n")
252                ),*, ")"), $($name = ($name).name()),*).into()
253            }
254        }
255    };
256}
257
258impl_client_handler_tuple! { A }
259impl_client_handler_tuple! { A B }
260impl_client_handler_tuple! { A B C }
261impl_client_handler_tuple! { A B C D }
262impl_client_handler_tuple! { A B C D E }
263impl_client_handler_tuple! { A B C D E F }
264impl_client_handler_tuple! { A B C D E F G }
265impl_client_handler_tuple! { A B C D E F G H }
266impl_client_handler_tuple! { A B C D E F G H I }
267impl_client_handler_tuple! { A B C D E F G H I J }
268impl_client_handler_tuple! { A B C D E F G H I J K }
269impl_client_handler_tuple! { A B C D E F G H I J K L }
270impl_client_handler_tuple! { A B C D E F G H I J K L M }
271impl_client_handler_tuple! { A B C D E F G H I J K L M N }
272impl_client_handler_tuple! { A B C D E F G H I J K L M N O }