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}