1use crate::handler::{boxed, BoxHandler, IntoHandler};
9use http::Method;
10use std::collections::HashMap;
11
12#[non_exhaustive]
38pub enum Match {
39 Found {
42 handler: BoxHandler,
44 params: HashMap<String, String>,
46 },
47 MethodNotAllowed {
50 allow: Vec<Method>,
52 },
53 NotFound,
55 BadPath,
59}
60
61#[derive(Default)]
62struct Node {
63 statics: HashMap<String, Node>,
64 param: Option<(String, Box<Node>)>, wildcard: Option<(String, BoxHandlers)>, handlers: BoxHandlers,
67}
68
69#[derive(Default)]
70struct BoxHandlers(HashMap<Method, BoxHandler>);
71
72impl std::fmt::Debug for Node {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 f.debug_struct("Node").finish_non_exhaustive()
75 }
76}
77
78#[derive(Debug, Default)]
104pub struct Router {
105 root: Node,
106}
107
108impl Router {
109 pub fn new() -> Self {
111 Self::default()
112 }
113
114 pub fn add(&mut self, method: Method, pattern: &str, handler: BoxHandler) {
134 let mut node = &mut self.root;
135 let segments: Vec<&str> = split_segments(pattern);
136 for (i, seg) in segments.iter().enumerate() {
137 if let Some(name) = seg.strip_prefix('{').and_then(|s| s.strip_suffix("...}")) {
138 assert!(
140 i == segments.len() - 1,
141 "wildcard `{{{name}...}}` must be last segment"
142 );
143 let entry = node
144 .wildcard
145 .get_or_insert_with(|| (name.to_string(), BoxHandlers::default()));
146 entry.1 .0.insert(method, handler);
147 return;
148 } else if let Some(name) = seg.strip_prefix('{').and_then(|s| s.strip_suffix('}')) {
149 let entry = node
150 .param
151 .get_or_insert_with(|| (name.to_string(), Box::new(Node::default())));
152 node = entry.1.as_mut();
153 } else {
154 node = node.statics.entry(seg.to_string()).or_default();
155 }
156 }
157 node.handlers.0.insert(method, handler);
158 }
159
160 pub fn route(&self, method: &Method, path: &str) -> Match {
178 let decoded = match decode_segments(path) {
181 Some(d) => d,
182 None => return Match::BadPath,
183 };
184 let segments: Vec<&str> = decoded.iter().map(String::as_str).collect();
185 let mut params = HashMap::new();
186
187 let exact = Self::walk(&self.root, &segments, 0, &mut params);
189 if let Some(node) = exact {
190 if let Some(h) = node.handlers.0.get(method) {
191 return Match::Found {
192 handler: h.clone(),
193 params,
194 };
195 }
196 }
197 let exact_allow: Vec<Method> = exact
198 .map(|n| n.handlers.0.keys().cloned().collect())
199 .unwrap_or_default();
200
201 params.clear();
210 match Self::walk_wildcard(&self.root, &segments, 0, method, &mut params) {
211 Some(found @ Match::Found { .. }) => found,
212 Some(Match::MethodNotAllowed { allow: wild_allow }) => {
213 let mut allow = exact_allow;
214 for m in wild_allow {
215 if !allow.contains(&m) {
216 allow.push(m);
217 }
218 }
219 Match::MethodNotAllowed { allow }
220 }
221 _ if !exact_allow.is_empty() => Match::MethodNotAllowed { allow: exact_allow },
222 _ => Match::NotFound,
223 }
224 }
225
226 pub fn methods_for(&self, path: &str) -> Vec<Method> {
242 let decoded = match decode_segments(path) {
243 Some(d) => d,
244 None => return Vec::new(),
245 };
246 let segments: Vec<&str> = decoded.iter().map(String::as_str).collect();
247 let mut params = HashMap::new();
248 let mut out: Vec<Method> = Self::walk(&self.root, &segments, 0, &mut params)
249 .map(|n| n.handlers.0.keys().cloned().collect())
250 .unwrap_or_default();
251
252 params.clear();
255 if let Some(Match::MethodNotAllowed { allow }) =
256 Self::walk_wildcard(&self.root, &segments, 0, &Method::TRACE, &mut params)
257 {
258 for m in allow {
259 if !out.contains(&m) {
260 out.push(m);
261 }
262 }
263 }
264 out
265 }
266
267 fn walk<'a>(
268 node: &'a Node,
269 segs: &[&str],
270 i: usize,
271 params: &mut HashMap<String, String>,
272 ) -> Option<&'a Node> {
273 if i == segs.len() {
274 return Some(node);
275 }
276 let seg = segs[i];
277 if let Some(child) = node.statics.get(seg) {
278 if let Some(n) = Self::walk(child, segs, i + 1, params) {
279 return Some(n);
280 }
281 }
282 if let Some((name, child)) = &node.param {
283 params.insert(name.clone(), seg.to_string());
284 if let Some(n) = Self::walk(child, segs, i + 1, params) {
285 return Some(n);
286 }
287 params.remove(name);
288 }
289 None
290 }
291
292 fn walk_wildcard(
293 node: &Node,
294 segs: &[&str],
295 i: usize,
296 method: &Method,
297 params: &mut HashMap<String, String>,
298 ) -> Option<Match> {
299 if let Some((name, handlers)) = &node.wildcard {
300 let rest = segs[i..].join("/");
301 params.insert(name.clone(), rest);
302 return Some(match handlers.0.get(method) {
303 Some(h) => Match::Found {
304 handler: h.clone(),
305 params: std::mem::take(params),
306 },
307 None => Match::MethodNotAllowed {
308 allow: handlers.0.keys().cloned().collect(),
309 },
310 });
311 }
312 if i < segs.len() {
313 if let Some(child) = node.statics.get(segs[i]) {
314 if let Some(m) = Self::walk_wildcard(child, segs, i + 1, method, params) {
315 return Some(m);
316 }
317 }
318 if let Some((pname, child)) = &node.param {
319 params.insert(pname.clone(), segs[i].to_string());
320 if let Some(m) = Self::walk_wildcard(child, segs, i + 1, method, params) {
321 return Some(m);
322 }
323 params.remove(pname);
324 }
325 }
326 None
327 }
328}
329
330fn split_segments(path: &str) -> Vec<&str> {
331 path.split('/').filter(|s| !s.is_empty()).collect()
332}
333
334fn decode_segments(path: &str) -> Option<Vec<String>> {
340 split_segments(path)
341 .into_iter()
342 .map(crate::path::decode_path_segment)
343 .collect()
344}
345
346pub struct RouteBuilder<'r> {
372 router: &'r mut Router,
373 prefix: String,
374}
375
376impl<'r> RouteBuilder<'r> {
377 pub(crate) fn new(router: &'r mut Router) -> Self {
378 Self {
379 router,
380 prefix: String::new(),
381 }
382 }
383
384 fn full(&self, path: &str) -> String {
385 let mut p = self.prefix.clone();
386 if !path.starts_with('/') {
387 p.push('/');
388 }
389 p.push_str(path);
390 p
391 }
392
393 pub fn method<Marker, H>(&mut self, method: Method, path: &str, handler: H) -> &mut Self
397 where
398 H: IntoHandler<Marker>,
399 {
400 let full = self.full(path);
401 self.router
402 .add(method, &full, boxed(handler.into_handler()));
403 self
404 }
405
406 pub fn get<Marker, H>(&mut self, path: &str, handler: H) -> &mut Self
408 where
409 H: IntoHandler<Marker>,
410 {
411 self.method(Method::GET, path, handler)
412 }
413 pub fn post<Marker, H>(&mut self, path: &str, handler: H) -> &mut Self
415 where
416 H: IntoHandler<Marker>,
417 {
418 self.method(Method::POST, path, handler)
419 }
420 pub fn put<Marker, H>(&mut self, path: &str, handler: H) -> &mut Self
422 where
423 H: IntoHandler<Marker>,
424 {
425 self.method(Method::PUT, path, handler)
426 }
427 pub fn delete<Marker, H>(&mut self, path: &str, handler: H) -> &mut Self
429 where
430 H: IntoHandler<Marker>,
431 {
432 self.method(Method::DELETE, path, handler)
433 }
434
435 pub fn route(&mut self, path: &str, f: impl FnOnce(&mut RouteBuilder)) -> &mut Self {
439 let prefix = self.full(path);
440 let mut child = RouteBuilder {
441 router: self.router,
442 prefix,
443 };
444 f(&mut child);
445 self
446 }
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452 use crate::call::Call;
453 use bytes::Bytes;
454 use http::{HeaderMap, StatusCode, Uri};
455
456 fn build() -> Router {
457 let mut r = Router::new();
458 {
459 let mut b = RouteBuilder::new(&mut r);
460 b.get("/", |_c: Call| async { "root" });
461 b.route("/users", |b| {
462 b.get("/{id}", |c: Call| async move {
463 format!("user {}", c.param_raw("id").unwrap())
464 });
465 b.post("/", |_c: Call| async { (StatusCode::CREATED, "created") });
466 });
467 b.get("/files/{path...}", |c: Call| async move {
468 format!("file {}", c.param_raw("path").unwrap())
469 });
470 }
471 r
472 }
473
474 fn run(r: &Router, m: Method, path: &str) -> Match {
475 r.route(&m, path)
476 }
477
478 #[tokio::test]
479 async fn matches_static_and_param() {
480 let r = build();
481 match run(&r, Method::GET, "/users/7") {
482 Match::Found { handler, params } => {
483 assert_eq!(params.get("id").unwrap(), "7");
484 let mut c = Call::new(
485 Method::GET,
486 "/users/7".parse::<Uri>().unwrap(),
487 HeaderMap::new(),
488 Bytes::new(),
489 );
490 c.set_params(params);
491 let res = handler.handle(c).await;
492 assert_eq!(res.body, Bytes::from("user 7"));
493 }
494 _ => panic!("expected Found"),
495 }
496 }
497
498 fn build_shadowed() -> Router {
499 let mut r = Router::new();
500 {
501 let mut b = RouteBuilder::new(&mut r);
502 b.get("/files/{path...}", |c: Call| async move {
503 format!("wild:{}", c.param_raw("path").unwrap_or(""))
504 });
505 b.get("/files/special/x", |_c: Call| async { "static" });
506 b.post("/files/only-post", |_c: Call| async { "posted" });
507 }
508 r
509 }
510
511 #[test]
512 fn path_params_are_percent_decoded() {
513 let mut r = Router::new();
514 {
515 let mut b = RouteBuilder::new(&mut r);
516 b.get("/u/{name}", |_c: Call| async { "" });
517 }
518 match r.route(&Method::GET, "/u/John%20Doe") {
519 Match::Found { params, .. } => assert_eq!(params.get("name").unwrap(), "John Doe"),
520 _ => panic!("expected a match"),
521 }
522 }
523
524 #[test]
525 fn encoded_slash_does_not_create_a_segment() {
526 let mut r = Router::new();
527 {
528 let mut b = RouteBuilder::new(&mut r);
529 b.get("/u/{name}", |_c: Call| async { "" });
530 }
531 match r.route(&Method::GET, "/u/a%2Fb") {
534 Match::Found { params, .. } => assert_eq!(params.get("name").unwrap(), "a/b"),
535 _ => panic!("%2F must not manufacture a separator"),
536 }
537 }
538
539 #[test]
540 fn a_route_with_a_literal_space_is_reachable_encoded() {
541 let mut r = Router::new();
542 {
543 let mut b = RouteBuilder::new(&mut r);
544 b.get("/a b", |_c: Call| async { "" });
545 }
546 assert!(matches!(
547 r.route(&Method::GET, "/a%20b"),
548 Match::Found { .. }
549 ));
550 }
551
552 #[test]
553 fn malformed_encoding_is_a_bad_path() {
554 let mut r = Router::new();
555 {
556 let mut b = RouteBuilder::new(&mut r);
557 b.get("/u/{name}", |_c: Call| async { "" });
558 }
559 assert!(matches!(r.route(&Method::GET, "/u/%zz"), Match::BadPath));
560 assert!(matches!(r.route(&Method::GET, "/u/%FF"), Match::BadPath));
561 }
562
563 #[test]
564 fn wildcard_captures_decoded_segments() {
565 let mut r = Router::new();
566 {
567 let mut b = RouteBuilder::new(&mut r);
568 b.get("/f/{rest...}", |_c: Call| async { "" });
569 }
570 match r.route(&Method::GET, "/f/a%20b/c") {
571 Match::Found { params, .. } => assert_eq!(params.get("rest").unwrap(), "a b/c"),
572 _ => panic!("expected the wildcard to match"),
573 }
574 }
575
576 #[test]
577 fn wildcard_is_reachable_through_a_static_sibling() {
578 let r = build_shadowed();
579 match run(&r, Method::GET, "/files/special") {
580 Match::Found { params, .. } => {
581 assert_eq!(params.get("path").unwrap(), "special");
582 }
583 _ => panic!("wildcard should serve /files/special"),
584 }
585 }
586
587 #[test]
588 fn exact_match_still_wins_over_wildcard() {
589 let r = build_shadowed();
590 match run(&r, Method::GET, "/files/special/x") {
591 Match::Found { params, .. } => {
592 assert!(
593 !params.contains_key("path"),
594 "the static route captured a wildcard param"
595 );
596 }
597 _ => panic!("expected the static route"),
598 }
599 }
600
601 #[test]
602 fn allow_header_unions_exact_and_wildcard_methods() {
603 let r = build_shadowed();
604 match run(&r, Method::DELETE, "/files/only-post") {
605 Match::MethodNotAllowed { allow } => {
606 assert!(allow.contains(&Method::POST), "missing the exact method");
607 assert!(allow.contains(&Method::GET), "missing the wildcard method");
608 }
609 _ => panic!("expected 405"),
610 }
611 }
612
613 #[test]
614 fn abandoned_branch_params_do_not_leak_into_the_wildcard() {
615 let mut r = Router::new();
616 {
617 let mut b = RouteBuilder::new(&mut r);
618 b.post("/u/{id}/edit", |_c: Call| async { "edit" });
620 b.get("/u/{rest...}", |_c: Call| async { "wild" });
621 }
622 match r.route(&Method::GET, "/u/7/edit") {
623 Match::Found { params, .. } => {
624 assert_eq!(params.get("rest").unwrap(), "7/edit");
625 assert!(
626 !params.contains_key("id"),
627 "stale `id` leaked from the abandoned walk"
628 );
629 }
630 _ => panic!("the wildcard should have matched"),
631 }
632 }
633
634 #[test]
635 fn unknown_path_is_not_found() {
636 let r = build();
637 assert!(matches!(run(&r, Method::GET, "/nope"), Match::NotFound));
638 }
639
640 #[test]
641 fn known_path_wrong_method_is_405() {
642 let r = build();
643 match run(&r, Method::DELETE, "/users/7") {
644 Match::MethodNotAllowed { allow } => assert!(allow.contains(&Method::GET)),
645 _ => panic!("expected 405"),
646 }
647 }
648
649 #[test]
650 fn wildcard_captures_rest() {
651 let r = build();
652 match run(&r, Method::GET, "/files/a/b/c.txt") {
653 Match::Found { params, .. } => assert_eq!(params.get("path").unwrap(), "a/b/c.txt"),
654 _ => panic!("expected wildcard Found"),
655 }
656 }
657
658 #[tokio::test]
661 async fn route_builder_accepts_boxed_handler() {
662 let pre: BoxHandler = boxed((|_c: Call| async { "pre-boxed" }).into_handler());
663 let mut r = Router::new();
664 {
665 let mut b = RouteBuilder::new(&mut r);
666 b.get("/pre", pre);
667 }
668 match run(&r, Method::GET, "/pre") {
669 Match::Found { handler, .. } => {
670 let c = Call::new(
671 Method::GET,
672 "/pre".parse::<Uri>().unwrap(),
673 HeaderMap::new(),
674 Bytes::new(),
675 );
676 let res = handler.handle(c).await;
677 assert_eq!(res.body, Bytes::from("pre-boxed"));
678 }
679 _ => panic!("expected Found"),
680 }
681 }
682}