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 }