Skip to main content

trillium_client/
client_handler.rs

1use crate::{Conn, 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/// Each `Conn::exec` call runs handlers in two passes:
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 (loggers, metrics, status checkers) record cache hits and
33///    short-circuited responses, not just 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) construct a
40/// fresh `Conn` from a `Client` they own — typically a separate one without recursion into the
41/// same handler chain — execute it, and `std::mem::swap` it with the user's conn. See the
42/// `follow_redirects` and `retry` handlers for examples.
43///
44/// Pre-executing the same conn from inside `run` is undefined: the conn is mid-pipeline at that
45/// point and re-entering `IntoFuture` recurses through the same handler chain. Don't.
46///
47/// ## Type erasure
48///
49/// Implementors write [`ClientHandler`] using native `async fn` syntax. The crate type-erases
50/// handlers internally for storage on `Client`; [`Client::with_handler`] accepts any
51/// `impl ClientHandler`, and [`Client::downcast_handler`] is the way to recover the concrete type
52/// from a `Client` that has one installed.
53///
54/// [`Client::with_handler`]: crate::Client::with_handler
55/// [`Client::downcast_handler`]: crate::Client::downcast_handler
56pub trait ClientHandler: Send + Sync + 'static {
57    /// Forward-pass hook, called before the network round-trip in declared order.
58    ///
59    /// A handler can mutate the request, halt to short-circuit, or fail. The default
60    /// implementation is a no-op.
61    fn run(&self, conn: &mut Conn) -> impl Future<Output = Result<()>> + Send {
62        let _ = conn;
63        async { Ok(()) }
64    }
65
66    /// Reverse-pass hook, called after the network round-trip (or after a halt-skipped network
67    /// call) in *reverse* declared order. Always runs regardless of halt status or transport
68    /// error.
69    ///
70    /// A handler can observe the response, mutate it before passing it to upstream handlers,
71    /// recover from a transport-level error, or fail.
72    ///
73    /// **Transport errors.** If the network call failed (connect refused, TLS handshake error,
74    /// malformed HTTP frame, timeout), the framework stashes the error on
75    /// [`Conn::error`][crate::Conn::error] and runs `after_response` anyway. A handler that
76    /// recovers (stale-if-error cache, retry-with-fallback, circuit breaker) should:
77    /// 1. Inspect [`conn.error()`][crate::Conn::error] to detect the failure.
78    /// 2. Populate response state synthetically (`set_status`, `response_headers_mut`,
79    ///    `set_response_body`).
80    /// 3. Call [`conn.take_error()`][crate::Conn::take_error] to clear the error so the awaited
81    ///    conn returns `Ok`.
82    ///
83    /// If no handler clears the error, it propagates as `Err` from the awaited conn.
84    ///
85    /// The default implementation is a no-op.
86    fn after_response(&self, conn: &mut Conn) -> impl Future<Output = Result<()>> + Send {
87        let _ = conn;
88        async { Ok(()) }
89    }
90
91    /// Human-readable name for logging/debugging. Defaults to the type name.
92    fn name(&self) -> Cow<'static, str> {
93        std::any::type_name::<Self>().into()
94    }
95}
96
97/// Object-safe twin of [`ClientHandler`] used for internal type erasure. Users implement
98/// [`ClientHandler`] with native `async fn`; the blanket impl below adapts it.
99pub(crate) trait ObjectSafeClientHandler: Any + Send + Sync + 'static {
100    fn run<'a>(
101        &'a self,
102        conn: &'a mut Conn,
103    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>;
104    fn after_response<'a>(
105        &'a self,
106        conn: &'a mut Conn,
107    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>;
108    fn name(&self) -> Cow<'static, str>;
109    fn as_any(&self) -> &dyn Any;
110}
111
112impl<H: ClientHandler> ObjectSafeClientHandler for H {
113    fn run<'a>(
114        &'a self,
115        conn: &'a mut Conn,
116    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>> {
117        Box::pin(ClientHandler::run(self, conn))
118    }
119
120    fn after_response<'a>(
121        &'a self,
122        conn: &'a mut Conn,
123    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>> {
124        Box::pin(ClientHandler::after_response(self, conn))
125    }
126
127    fn name(&self) -> Cow<'static, str> {
128        ClientHandler::name(self)
129    }
130
131    fn as_any(&self) -> &dyn Any {
132        self
133    }
134}
135
136/// Internal `Arc`-shared, type-erased [`ClientHandler`]. Stored on a `Client` and cloned onto
137/// each conn it builds. Not exposed publicly — `Client::with_handler` accepts
138/// `impl ClientHandler` and `Client::downcast_handler` recovers the concrete type.
139#[derive(Clone)]
140pub(crate) struct ArcedClientHandler(Arc<dyn ObjectSafeClientHandler>);
141
142impl Debug for ArcedClientHandler {
143    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
144        f.debug_tuple("ArcedClientHandler")
145            .field(&self.0.name())
146            .finish()
147    }
148}
149
150impl ArcedClientHandler {
151    pub(crate) fn new(handler: impl ClientHandler) -> Self {
152        Self(Arc::new(handler))
153    }
154
155    pub(crate) fn downcast_ref<T: Any + 'static>(&self) -> Option<&T> {
156        self.0.as_any().downcast_ref()
157    }
158}
159
160impl ClientHandler for ArcedClientHandler {
161    async fn run(&self, conn: &mut Conn) -> Result<()> {
162        self.0.run(conn).await
163    }
164
165    async fn after_response(&self, conn: &mut Conn) -> Result<()> {
166        self.0.after_response(conn).await
167    }
168
169    fn name(&self) -> Cow<'static, str> {
170        self.0.name()
171    }
172}
173
174impl ClientHandler for () {}
175
176impl<H: ClientHandler> ClientHandler for Option<H> {
177    async fn run(&self, conn: &mut Conn) -> Result<()> {
178        if let Some(h) = self {
179            h.run(conn).await?;
180        }
181        Ok(())
182    }
183
184    async fn after_response(&self, conn: &mut Conn) -> Result<()> {
185        if let Some(h) = self {
186            h.after_response(conn).await?;
187        }
188        Ok(())
189    }
190
191    fn name(&self) -> Cow<'static, str> {
192        match self {
193            Some(h) => h.name(),
194            None => "None".into(),
195        }
196    }
197}
198
199macro_rules! reverse_after_response {
200    ($conn:ident, $name:ident) => {
201        log::trace!("after_response {}", $name.name());
202        $name.after_response($conn).await?;
203    };
204    ($conn:ident, $name:ident $($rest:ident)+) => {
205        reverse_after_response!($conn, $($rest)+);
206        log::trace!("after_response {}", $name.name());
207        $name.after_response($conn).await?;
208    };
209}
210
211macro_rules! impl_client_handler_tuple {
212    ($($name:ident)+) => {
213        impl<$($name: ClientHandler),+> ClientHandler for ($($name,)+) {
214            #[allow(non_snake_case)]
215            async fn run(&self, conn: &mut Conn) -> Result<()> {
216                let ($(ref $name,)+) = *self;
217                $(
218                    log::trace!("running {}", $name.name());
219                    $name.run(conn).await?;
220                    if conn.is_halted() {
221                        return Ok(());
222                    }
223                )+
224                Ok(())
225            }
226
227            #[allow(non_snake_case)]
228            async fn after_response(&self, conn: &mut Conn) -> Result<()> {
229                let ($(ref $name,)+) = *self;
230                reverse_after_response!(conn, $($name)+);
231                Ok(())
232            }
233
234            #[allow(non_snake_case)]
235            fn name(&self) -> Cow<'static, str> {
236                let ($(ref $name,)+) = *self;
237                format!(concat!("(\n", $(
238                        concat!("  {",stringify!($name) ,":},\n")
239                ),*, ")"), $($name = ($name).name()),*).into()
240            }
241        }
242    };
243}
244
245impl_client_handler_tuple! { A }
246impl_client_handler_tuple! { A B }
247impl_client_handler_tuple! { A B C }
248impl_client_handler_tuple! { A B C D }
249impl_client_handler_tuple! { A B C D E }
250impl_client_handler_tuple! { A B C D E F }
251impl_client_handler_tuple! { A B C D E F G }
252impl_client_handler_tuple! { A B C D E F G H }
253impl_client_handler_tuple! { A B C D E F G H I }
254impl_client_handler_tuple! { A B C D E F G H I J }
255impl_client_handler_tuple! { A B C D E F G H I J K }
256impl_client_handler_tuple! { A B C D E F G H I J K L }
257impl_client_handler_tuple! { A B C D E F G H I J K L M }
258impl_client_handler_tuple! { A B C D E F G H I J K L M N }
259impl_client_handler_tuple! { A B C D E F G H I J K L M N O }