Skip to main content

tako_rs_core/router/
layers.rs

1//! Global middleware, fallbacks, timeouts, and error-handler wiring.
2
3use std::sync::Arc;
4use std::sync::atomic::Ordering;
5use std::time::Duration;
6
7use super::Router;
8use crate::handler::BoxHandler;
9use crate::handler::Handler;
10use crate::middleware::Next;
11use crate::responder::Responder;
12use crate::types::BoxMiddleware;
13use crate::types::Request;
14use crate::types::Response;
15
16/// Type alias for a global error handler function.
17///
18/// Called when a response has a server error status (5xx). Receives the original
19/// response and can transform it (e.g., to return JSON errors instead of plain text).
20pub type ErrorHandler = Arc<dyn Fn(Response) -> Response + Send + Sync + 'static>;
21
22impl Router {
23  /// Adds global middleware to the router.
24  ///
25  /// Global middleware is executed for all routes in the order it was added,
26  /// before any route-specific middleware. Middleware can modify requests,
27  /// generate responses, or perform side effects like logging or authentication.
28  ///
29  /// # Examples
30  ///
31  /// ```rust
32  /// use tako::{router::Router, middleware::Next, types::Request};
33  ///
34  /// let mut router = Router::new();
35  ///
36  /// // Logging middleware
37  /// router.middleware(|req, next| async move {
38  ///     println!("Request: {} {}", req.method(), req.uri());
39  ///     let response = next.run(req).await;
40  ///     println!("Response: {}", response.status());
41  ///     response
42  /// });
43  ///
44  /// // Authentication middleware
45  /// router.middleware(|req, next| async move {
46  ///     if req.headers().contains_key("authorization") {
47  ///         next.run(req).await
48  ///     } else {
49  ///         "Unauthorized".into_response()
50  ///     }
51  /// });
52  /// ```
53  pub fn middleware<F, Fut, R>(&self, f: F) -> &Self
54  where
55    F: Fn(Request, Next) -> Fut + Clone + Send + Sync + 'static,
56    Fut: std::future::Future<Output = R> + Send + 'static,
57    R: Responder + Send + 'static,
58  {
59    let mw: BoxMiddleware = Arc::new(move |req, next| {
60      let fut = f(req, next);
61      Box::pin(async move { fut.await.into_response() })
62    });
63
64    // RCU-style append: rebuild the Vec atomically against concurrent pushers.
65    // ArcSwap retries the closure on CAS conflict, so concurrent middleware
66    // registrations cannot lose entries.
67    self.middlewares.rcu(move |current| {
68      let mut next = Vec::with_capacity(current.len() + 1);
69      next.extend(current.iter().cloned());
70      next.push(mw.clone());
71      Arc::new(next)
72    });
73    self.has_global_middleware.store(true, Ordering::Release);
74    self
75  }
76
77  /// Sets a fallback handler that will be executed when no route matches.
78  ///
79  /// The fallback runs after global middlewares and can be used to implement
80  /// custom 404 pages, catch-all logic, or method-independent handlers.
81  ///
82  /// # Examples
83  ///
84  /// ```rust
85  /// use tako::{router::Router, Method, responder::Responder, types::Request};
86  ///
87  /// async fn not_found(_req: Request) -> impl Responder { "Not Found" }
88  ///
89  /// let mut router = Router::new();
90  /// router.route(Method::GET, "/", |_req| async { "Hello" });
91  /// router.fallback(not_found);
92  /// ```
93  pub fn fallback<F, Fut, R>(&mut self, handler: F) -> &mut Self
94  where
95    F: Fn(Request) -> Fut + Clone + Send + Sync + 'static,
96    Fut: std::future::Future<Output = R> + Send + 'static,
97    R: Responder + Send + 'static,
98  {
99    // Use the Request-arg handler impl to box the fallback
100    self.fallback = Some(BoxHandler::new::<F, (Request,)>(handler));
101    self
102  }
103
104  /// Sets a fallback handler that supports extractors (like `Path`, `Query`, etc.).
105  ///
106  /// Use this when your fallback needs to parse request data via extractors. If you
107  /// only need access to the raw `Request`, prefer `fallback` for simpler type inference.
108  ///
109  /// # Examples
110  ///
111  /// ```rust
112  /// use tako::{router::Router, responder::Responder, extractors::{path::Path, query::Query}};
113  ///
114  /// #[derive(serde::Deserialize)]
115  /// struct Q { q: Option<String> }
116  ///
117  /// async fn fallback_with_q(Path(_p): Path<String>, Query(_q): Query<Q>) -> impl Responder {
118  ///     "Not Found"
119  /// }
120  ///
121  /// let mut router = Router::new();
122  /// router.fallback_with_extractors(fallback_with_q);
123  /// ```
124  pub fn fallback_with_extractors<H, T>(&mut self, handler: H) -> &mut Self
125  where
126    H: Handler<T> + Clone + 'static,
127  {
128    self.fallback = Some(BoxHandler::new::<H, T>(handler));
129    self
130  }
131
132  /// Sets a default timeout for all routes.
133  ///
134  /// This timeout can be overridden on individual routes using `Route::timeout`.
135  /// When a request exceeds the timeout duration, the timeout fallback handler
136  /// is invoked (if configured) or a 408 Request Timeout response is returned.
137  ///
138  /// # Examples
139  ///
140  /// ```rust
141  /// use tako::router::Router;
142  /// use std::time::Duration;
143  ///
144  /// let mut router = Router::new();
145  /// router.timeout(Duration::from_secs(30));
146  /// ```
147  pub fn timeout(&mut self, duration: Duration) -> &mut Self {
148    self.timeout = Some(duration);
149    self
150  }
151
152  /// Sets a fallback handler that will be executed when a request times out.
153  ///
154  /// If no timeout fallback is set, a default 408 Request Timeout response is returned.
155  ///
156  /// # Examples
157  ///
158  /// ```rust
159  /// use tako::{router::Router, responder::Responder, types::Request};
160  /// use std::time::Duration;
161  ///
162  /// async fn timeout_handler(_req: Request) -> impl Responder {
163  ///     "Request took too long"
164  /// }
165  ///
166  /// let mut router = Router::new();
167  /// router.timeout(Duration::from_secs(30));
168  /// router.timeout_fallback(timeout_handler);
169  /// ```
170  pub fn timeout_fallback<F, Fut, R>(&mut self, handler: F) -> &mut Self
171  where
172    F: Fn(Request) -> Fut + Clone + Send + Sync + 'static,
173    Fut: std::future::Future<Output = R> + Send + 'static,
174    R: Responder + Send + 'static,
175  {
176    self.timeout_fallback = Some(BoxHandler::new::<F, (Request,)>(handler));
177    self
178  }
179
180  /// Sets a global error handler for 5xx responses.
181  ///
182  /// The error handler receives any response with a server error status and can
183  /// transform it (e.g., to return JSON-formatted errors instead of plain text).
184  ///
185  /// # Examples
186  ///
187  /// ```rust
188  /// use tako::router::Router;
189  /// use tako::body::TakoBody;
190  ///
191  /// let mut router = Router::new();
192  /// router.error_handler(|resp| {
193  ///     let status = resp.status();
194  ///     let body = format!(r#"{{"error": "{}"}}"#, status.canonical_reason().unwrap_or("Unknown"));
195  ///     let mut res = http::Response::new(TakoBody::from(body));
196  ///     *res.status_mut() = status;
197  ///     res.headers_mut().insert(
198  ///         http::header::CONTENT_TYPE,
199  ///         http::HeaderValue::from_static("application/json"),
200  ///     );
201  ///     res
202  /// });
203  /// ```
204  pub fn error_handler(
205    &mut self,
206    handler: impl Fn(Response) -> Response + Send + Sync + 'static,
207  ) -> &mut Self {
208    self.error_handler = Some(Arc::new(handler));
209    self
210  }
211
212  /// Sets a global error handler for 4xx responses.
213  ///
214  /// Mirrors [`Router::error_handler`] but fires for client errors. Useful for
215  /// converting bare 404 / 405 / 422 responses into structured error documents
216  /// (e.g. via [`crate::problem::default_problem_responder`]).
217  pub fn client_error_handler(
218    &mut self,
219    handler: impl Fn(Response) -> Response + Send + Sync + 'static,
220  ) -> &mut Self {
221    self.client_error_handler = Some(Arc::new(handler));
222    self
223  }
224
225  /// Convenience: install [`crate::problem::default_problem_responder`] for
226  /// both 4xx and 5xx so unhandled errors always render as
227  /// `application/problem+json`.
228  pub fn use_problem_json(&mut self) -> &mut Self {
229    let h: ErrorHandler = Arc::new(crate::problem::default_problem_responder);
230    self.error_handler = Some(h.clone());
231    self.client_error_handler = Some(h);
232    self
233  }
234}