1use crate::handler::{BoxedHandler, IntoHandler};
9use crate::logging::{debug, trace};
10use crate::route_constraint::RouteConstraints;
11use crate::{Error, HttpMethod, HttpRequest, HttpResponse};
12use std::collections::HashMap;
13use std::future::Future;
14use std::pin::Pin;
15use std::sync::Arc;
16
17pub type HandlerFn = Arc<
30 dyn Fn(HttpRequest) -> Pin<Box<dyn Future<Output = Result<HttpResponse, Error>> + Send>>
31 + Send
32 + Sync,
33>;
34
35pub type OptimizedHandler = BoxedHandler;
40
41#[derive(Clone)]
43pub struct Route {
44 pub method: HttpMethod,
45 pub path: String,
46 pub handler: BoxedHandler,
48 pub constraints: Option<RouteConstraints>,
50}
51
52impl Route {
53 #[inline]
58 pub fn new<H, Args>(method: HttpMethod, path: impl Into<String>, handler: H) -> Self
59 where
60 H: IntoHandler<Args>,
61 {
62 Self {
63 method,
64 path: path.into(),
65 handler: BoxedHandler::new(handler.into_handler()),
66 constraints: None,
67 }
68 }
69
70 #[inline]
72 pub fn from_legacy(method: HttpMethod, path: impl Into<String>, handler: HandlerFn) -> Self {
73 Self {
74 method,
75 path: path.into(),
76 handler: crate::handler::from_legacy_handler(handler),
77 constraints: None,
78 }
79 }
80
81 #[inline]
83 pub fn with_constraints(mut self, constraints: RouteConstraints) -> Self {
84 self.constraints = Some(constraints);
85 self
86 }
87}
88
89#[derive(Clone)]
96pub struct Router {
97 pub routes: Vec<Route>,
98}
99
100impl Router {
101 #[inline]
103 pub fn new() -> Self {
104 Self { routes: Vec::new() }
105 }
106
107 #[inline]
109 pub fn add_route(&mut self, route: Route) {
110 self.routes.push(route);
111 }
112
113 #[inline]
115 pub fn get<H, Args>(&mut self, path: impl Into<String>, handler: H) -> &mut Self
116 where
117 H: IntoHandler<Args>,
118 {
119 self.routes.push(Route::new(HttpMethod::GET, path, handler));
120 self
121 }
122
123 #[inline]
125 pub fn post<H, Args>(&mut self, path: impl Into<String>, handler: H) -> &mut Self
126 where
127 H: IntoHandler<Args>,
128 {
129 self.routes
130 .push(Route::new(HttpMethod::POST, path, handler));
131 self
132 }
133
134 #[inline]
136 pub fn put<H, Args>(&mut self, path: impl Into<String>, handler: H) -> &mut Self
137 where
138 H: IntoHandler<Args>,
139 {
140 self.routes.push(Route::new(HttpMethod::PUT, path, handler));
141 self
142 }
143
144 #[inline]
146 pub fn delete<H, Args>(&mut self, path: impl Into<String>, handler: H) -> &mut Self
147 where
148 H: IntoHandler<Args>,
149 {
150 self.routes
151 .push(Route::new(HttpMethod::DELETE, path, handler));
152 self
153 }
154
155 #[inline]
157 pub fn patch<H, Args>(&mut self, path: impl Into<String>, handler: H) -> &mut Self
158 where
159 H: IntoHandler<Args>,
160 {
161 self.routes
162 .push(Route::new(HttpMethod::PATCH, path, handler));
163 self
164 }
165
166 #[inline]
170 pub fn match_route(
171 &self,
172 method: &str,
173 path: &str,
174 ) -> Option<(BoxedHandler, HashMap<String, String>)> {
175 let path = path.split('?').next().unwrap_or(path);
177
178 for route in &self.routes {
179 if route.method.as_str() != method {
180 continue;
181 }
182
183 if let Some(params) = match_path(&route.path, path) {
184 return Some((route.handler.clone(), params));
185 }
186 }
187
188 None
189 }
190
191 #[inline]
197 pub async fn route(&self, mut request: HttpRequest) -> Result<HttpResponse, Error> {
198 debug!("Routing request: {} {}", request.method, request.path);
199
200 let (path, query_string) = request
202 .path
203 .split_once('?')
204 .map(|(p, q)| (p, Some(q)))
205 .unwrap_or((&request.path, None));
206
207 if let Some(query) = query_string {
208 trace!("Parsing query string: {}", query);
209 request.query_params = parse_query_string(query);
210 }
211
212 for route in &self.routes {
214 if route.method.as_str() != request.method {
215 continue;
216 }
217
218 if let Some(params) = match_path(&route.path, path) {
219 debug!(
220 "Route matched: {} {} -> {}",
221 request.method, path, route.path
222 );
223
224 if let Some(constraints) = &route.constraints {
226 trace!("Validating route constraints");
227 constraints.validate(¶ms)?;
228 }
229
230 request.path_params = params;
231
232 trace!("Dispatching handler");
235 return route.handler.call(request).await;
236 }
237 }
238
239 debug!("No route found for {} {}", request.method, path);
240 Err(Error::RouteNotFound(format!("{} {}", request.method, path)))
241 }
242}
243
244impl Default for Router {
245 fn default() -> Self {
246 Self::new()
247 }
248}
249
250fn match_path(pattern: &str, path: &str) -> Option<HashMap<String, String>> {
253 let pattern_parts: Vec<&str> = pattern.split('/').filter(|s| !s.is_empty()).collect();
254 let path_parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
255
256 if pattern_parts.len() != path_parts.len() {
257 return None;
258 }
259
260 let mut params = HashMap::new();
261
262 for (pattern_part, path_part) in pattern_parts.iter().zip(path_parts.iter()) {
263 if let Some(param_name) = pattern_part.strip_prefix(':') {
264 params.insert(param_name.to_string(), path_part.to_string());
266 } else if pattern_part != path_part {
267 return None;
269 }
270 }
271
272 Some(params)
273}
274
275#[inline]
279fn parse_query_string(query: &str) -> HashMap<String, String> {
280 crate::simd_parser::parse_query_string_fast(query)
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 async fn test_handler(_req: HttpRequest) -> Result<HttpResponse, Error> {
290 Ok(HttpResponse::ok())
291 }
292
293 #[test]
294 fn test_match_path_static() {
295 let pattern = "/users";
296 let path = "/users";
297 let result = match_path(pattern, path);
298 assert!(result.is_some());
299 assert_eq!(result.unwrap().len(), 0);
300 }
301
302 #[test]
303 fn test_match_path_with_param() {
304 let pattern = "/users/:id";
305 let path = "/users/123";
306 let result = match_path(pattern, path);
307 assert!(result.is_some());
308 let params = result.unwrap();
309 assert_eq!(params.get("id"), Some(&"123".to_string()));
310 }
311
312 #[test]
313 fn test_match_path_no_match() {
314 let pattern = "/users/:id";
315 let path = "/posts/123";
316 let result = match_path(pattern, path);
317 assert!(result.is_none());
318 }
319
320 #[test]
321 fn test_parse_query_string() {
322 let query = "name=john&age=30";
323 let params = parse_query_string(query);
324 assert_eq!(params.get("name"), Some(&"john".to_string()));
325 assert_eq!(params.get("age"), Some(&"30".to_string()));
326 }
327
328 #[test]
329 fn test_match_path_multiple_params() {
330 let pattern = "/users/:user_id/posts/:post_id";
331 let path = "/users/123/posts/456";
332 let result = match_path(pattern, path);
333 assert!(result.is_some());
334 let params = result.unwrap();
335 assert_eq!(params.get("user_id"), Some(&"123".to_string()));
336 assert_eq!(params.get("post_id"), Some(&"456".to_string()));
337 }
338
339 #[test]
340 fn test_match_path_trailing_slash() {
341 let pattern = "/users";
342 let path = "/users/";
343 let result = match_path(pattern, path);
344 assert!(result.is_some() || result.is_none());
346 }
347
348 #[test]
349 fn test_match_path_nested() {
350 let pattern = "/api/v1/users/:id";
351 let path = "/api/v1/users/123";
352 let result = match_path(pattern, path);
353 assert!(result.is_some());
354 let params = result.unwrap();
355 assert_eq!(params.get("id"), Some(&"123".to_string()));
356 }
357
358 #[test]
359 fn test_match_path_empty() {
360 let pattern = "/";
361 let path = "/";
362 let result = match_path(pattern, path);
363 assert!(result.is_some());
364 }
365
366 #[test]
367 fn test_parse_query_string_empty() {
368 let query = "";
369 let params = parse_query_string(query);
370 assert!(params.is_empty() || params.len() == 1);
372 }
373
374 #[test]
375 fn test_parse_query_string_special_chars() {
376 let query = "name=john%20doe&email=test%40example.com";
377 let params = parse_query_string(query);
378 assert!(params.contains_key("name"));
379 assert!(params.contains_key("email"));
380 }
381
382 #[test]
383 fn test_parse_query_string_no_value() {
384 let query = "flag&debug=true";
385 let params = parse_query_string(query);
386 assert!(params.contains_key("debug"));
387 assert_eq!(params.get("debug"), Some(&"true".to_string()));
388 }
389
390 #[test]
391 fn test_match_path_param_with_special_chars() {
392 let pattern = "/users/:id";
393 let path = "/users/abc-123";
394 let result = match_path(pattern, path);
395 assert!(result.is_some());
396 let params = result.unwrap();
397 assert_eq!(params.get("id"), Some(&"abc-123".to_string()));
398 }
399
400 #[test]
401 fn test_route_creation_optimized() {
402 let route = Route::new(HttpMethod::GET, "/users", test_handler);
404 assert_eq!(route.method, HttpMethod::GET);
405 assert_eq!(route.path, "/users");
406 }
407
408 #[test]
409 fn test_route_creation_legacy() {
410 let legacy_handler: HandlerFn =
412 Arc::new(|_req| Box::pin(async move { Ok(HttpResponse::ok()) }));
413 let route = Route::from_legacy(HttpMethod::GET, "/users", legacy_handler);
414 assert_eq!(route.method, HttpMethod::GET);
415 assert_eq!(route.path, "/users");
416 }
417
418 #[test]
419 fn test_router_fluent_api() {
420 let mut router = Router::new();
421 router
422 .get("/users", test_handler)
423 .post("/users", test_handler)
424 .put("/users/:id", test_handler)
425 .delete("/users/:id", test_handler);
426
427 assert_eq!(router.routes.len(), 4);
428 }
429
430 #[test]
431 fn test_router_add_route() {
432 let mut router = Router::new();
433 let route = Route::new(HttpMethod::GET, "/test", test_handler);
434 router.add_route(route);
435 assert_eq!(router.routes.len(), 1);
436 }
437
438 #[test]
439 fn test_router_multiple_routes() {
440 let mut router = Router::new();
441
442 for i in 0..5 {
443 router.get(format!("/test{}", i), test_handler);
444 }
445
446 assert_eq!(router.routes.len(), 5);
447 }
448
449 #[test]
450 fn test_parse_query_string_multiple_same_key() {
451 let query = "tag=rust&tag=web&tag=framework";
452 let params = parse_query_string(query);
453 assert!(params.contains_key("tag"));
455 }
456
457 #[test]
458 fn test_route_with_constraints() {
459 let constraints =
460 RouteConstraints::new().add("id", Box::new(crate::route_constraint::IntConstraint));
461
462 let route =
463 Route::new(HttpMethod::GET, "/users/:id", test_handler).with_constraints(constraints);
464
465 assert!(route.constraints.is_some());
466 }
467
468 #[tokio::test]
469 async fn test_router_dispatch() {
470 let mut router = Router::new();
471 router.get("/test", test_handler);
472
473 let req = HttpRequest::new("GET".to_string(), "/test".to_string());
474 let response = router.route(req).await.unwrap();
475 assert_eq!(response.status, 200);
476 }
477
478 #[tokio::test]
479 async fn test_router_dispatch_with_params() {
480 async fn param_handler(req: HttpRequest) -> Result<HttpResponse, Error> {
481 let id = req.param("id").unwrap();
482 Ok(HttpResponse::ok().with_body(id.as_bytes().to_vec()))
483 }
484
485 let mut router = Router::new();
486 router.get("/users/:id", param_handler);
487
488 let req = HttpRequest::new("GET".to_string(), "/users/123".to_string());
489 let response = router.route(req).await.unwrap();
490 assert_eq!(response.status, 200);
491 assert_eq!(String::from_utf8(response.body).unwrap(), "123");
492 }
493
494 #[tokio::test]
495 async fn test_router_404() {
496 let router = Router::new();
497 let req = HttpRequest::new("GET".to_string(), "/nonexistent".to_string());
498 let result = router.route(req).await;
499 assert!(matches!(result, Err(Error::RouteNotFound(_))));
500 }
501}