1use bytes::Bytes;
12use hyper::{Method, Request, Response, StatusCode};
13use std::future::Future;
14use std::sync::Arc;
15
16pub mod extract;
17pub mod handler;
18pub mod middleware;
20pub mod static_dir;
22#[cfg(feature = "tower")]
24pub mod tower_compat;
25
26use crate::http::response::{Body, IntoResponse};
27use crate::routing::extract::PathParams;
28pub use handler::{BoxedFuture, BoxedHandler, Handler};
29
30const IDX_GET: usize = 0;
33const IDX_POST: usize = 1;
34const IDX_PUT: usize = 2;
35const IDX_DELETE: usize = 3;
36const IDX_OPTIONS: usize = 4;
37const IDX_HEAD: usize = 5;
38const IDX_PATCH: usize = 6;
39const IDX_TRACE: usize = 7;
40const IDX_CONNECT: usize = 8;
41const METHOD_COUNT: usize = 9;
42
43const METHOD_NAMES: [&str; METHOD_COUNT] = [
44 "GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE", "CONNECT",
45];
46
47#[inline]
48const fn method_index(m: &Method) -> Option<usize> {
49 match *m {
50 Method::GET => Some(IDX_GET),
51 Method::POST => Some(IDX_POST),
52 Method::PUT => Some(IDX_PUT),
53 Method::DELETE => Some(IDX_DELETE),
54 Method::OPTIONS => Some(IDX_OPTIONS),
55 Method::HEAD => Some(IDX_HEAD),
56 Method::PATCH => Some(IDX_PATCH),
57 Method::TRACE => Some(IDX_TRACE),
58 Method::CONNECT => Some(IDX_CONNECT),
59 _ => None,
60 }
61}
62
63#[derive(Clone)]
67pub struct MethodRouter<S> {
68 handlers: [Option<middleware::MethodHandler<S>>; METHOD_COUNT],
69 param_names: Arc<[Arc<str>]>,
74 matched_path: Arc<str>,
78 nest_prefix: Option<Arc<str>>,
82}
83
84impl<S> std::fmt::Debug for MethodRouter<S> {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 let methods = [
87 "GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE", "CONNECT",
88 ];
89 let mut dbg = f.debug_struct("MethodRouter");
90 for (i, name) in methods.iter().enumerate() {
91 let _ = dbg.field(name, &self.handlers[i].is_some());
92 }
93 let _ = dbg.field("param_names", &self.param_names);
94 let _ = dbg.field("matched_path", &self.matched_path);
95 let _ = dbg.field("nest_prefix", &self.nest_prefix);
96 dbg.finish()
97 }
98}
99
100impl<S> Default for MethodRouter<S>
101where
102 S: Clone + Send + Sync + 'static,
103{
104 fn default() -> Self {
105 Self::new()
106 }
107}
108
109impl<S> MethodRouter<S>
110where
111 S: Clone + Send + Sync + 'static,
112{
113 #[must_use]
115 pub fn new() -> Self {
116 Self {
117 handlers: [None, None, None, None, None, None, None, None, None],
118 param_names: Arc::from([]),
119 matched_path: Arc::from(""),
120 nest_prefix: None,
121 }
122 }
123
124 fn set<H, T>(mut self, idx: usize, handler: H) -> Self
125 where
126 H: Handler<T, S>,
127 T: 'static,
128 {
129 self.handlers[idx] = Some(middleware::MethodHandler::new(Arc::new(
130 move |req, state| handler.clone().call(req, state),
131 )));
132 self
133 }
134
135 #[must_use]
137 pub fn get<H, T>(self, handler: H) -> Self
138 where
139 H: Handler<T, S>,
140 T: 'static,
141 {
142 self.set(IDX_GET, handler)
143 }
144 #[must_use]
146 pub fn post<H, T>(self, handler: H) -> Self
147 where
148 H: Handler<T, S>,
149 T: 'static,
150 {
151 self.set(IDX_POST, handler)
152 }
153 #[must_use]
155 pub fn put<H, T>(self, handler: H) -> Self
156 where
157 H: Handler<T, S>,
158 T: 'static,
159 {
160 self.set(IDX_PUT, handler)
161 }
162 #[must_use]
164 pub fn delete<H, T>(self, handler: H) -> Self
165 where
166 H: Handler<T, S>,
167 T: 'static,
168 {
169 self.set(IDX_DELETE, handler)
170 }
171 #[must_use]
173 pub fn options<H, T>(self, handler: H) -> Self
174 where
175 H: Handler<T, S>,
176 T: 'static,
177 {
178 self.set(IDX_OPTIONS, handler)
179 }
180 #[must_use]
182 pub fn head<H, T>(self, handler: H) -> Self
183 where
184 H: Handler<T, S>,
185 T: 'static,
186 {
187 self.set(IDX_HEAD, handler)
188 }
189 #[must_use]
191 pub fn patch<H, T>(self, handler: H) -> Self
192 where
193 H: Handler<T, S>,
194 T: 'static,
195 {
196 self.set(IDX_PATCH, handler)
197 }
198 #[must_use]
200 pub fn trace<H, T>(self, handler: H) -> Self
201 where
202 H: Handler<T, S>,
203 T: 'static,
204 {
205 self.set(IDX_TRACE, handler)
206 }
207 #[must_use]
209 pub fn connect<H, T>(self, handler: H) -> Self
210 where
211 H: Handler<T, S>,
212 T: 'static,
213 {
214 self.set(IDX_CONNECT, handler)
215 }
216
217 fn allow_header(&self) -> String {
229 let mut out = String::with_capacity(56);
230 let implicit_head = self.handlers[IDX_GET].is_some() && self.handlers[IDX_HEAD].is_none();
231 for (i, name) in METHOD_NAMES.iter().enumerate() {
232 if self.handlers[i].is_some() {
233 if !out.is_empty() {
234 out.push(',');
235 }
236 out.push_str(name);
237 if i == IDX_GET && implicit_head {
238 out.push_str(",HEAD");
239 }
240 }
241 }
242 out
243 }
244
245 fn merge(mut self, mut other: Self, path: &str) -> Result<Self, RouterError> {
257 for (i, (mine, theirs)) in self
258 .handlers
259 .iter_mut()
260 .zip(other.handlers.iter_mut())
261 .enumerate()
262 {
263 if let Some(handler) = theirs.take() {
264 if mine.is_some() {
265 return Err(RouterError::MethodOverlap {
266 method: METHOD_NAMES[i],
267 path: path.to_string(),
268 });
269 }
270 *mine = Some(handler);
271 }
272 }
273 if self.nest_prefix.is_none() {
280 self.nest_prefix = other.nest_prefix;
281 }
282 Ok(self)
283 }
284
285 #[must_use]
289 pub fn hoop<F, Fut, Res>(self, middleware: F) -> Self
290 where
291 F: Fn(Request<Body>, middleware::Next<S>) -> Fut + Clone + Send + Sync + 'static,
292 Fut: Future<Output = Res> + Send + 'static,
293 Res: IntoResponse + Send + 'static,
294 {
295 self.hoop_at(middleware::MiddlewarePosition::First, middleware)
296 }
297
298 #[must_use]
300 pub fn hoop_at<F, Fut, Res>(
301 mut self,
302 position: middleware::MiddlewarePosition,
303 middleware: F,
304 ) -> Self
305 where
306 F: Fn(Request<Body>, middleware::Next<S>) -> Fut + Clone + Send + Sync + 'static,
307 Fut: Future<Output = Res> + Send + 'static,
308 Res: IntoResponse + Send + 'static,
309 {
310 let boxed: middleware::BoxedMiddleware<S> = Arc::new(move |req, next| {
311 let fut = middleware(req, next);
312 crate::routing::handler::ResponseFuture::Boxed(Box::pin(async move {
313 fut.await.into_response()
314 }))
315 });
316 for i in 0..METHOD_COUNT {
317 if let Some(handler) = &mut self.handlers[i] {
318 match position {
319 middleware::MiddlewarePosition::First => {
320 handler.middlewares.insert(0, boxed.clone());
321 }
322 middleware::MiddlewarePosition::Last => {
323 handler.middlewares.push(boxed.clone());
324 }
325 }
326 handler.compiled = None;
327 }
328 }
329 self
330 }
331
332 pub fn compile_in_place(&mut self) {
334 for i in 0..METHOD_COUNT {
335 if let Some(handler) = &mut self.handlers[i] {
336 handler.compile_in_place();
337 }
338 }
339 }
340
341 #[must_use]
343 pub fn with_state<S2>(self, state: &Arc<S>) -> MethodRouter<S2>
344 where
345 S2: Clone + Send + Sync + 'static,
346 S: Clone + Send + Sync + 'static,
347 {
348 let mut new_handlers: [Option<middleware::MethodHandler<S2>>; METHOD_COUNT] =
349 [None, None, None, None, None, None, None, None, None];
350 for (i, opt_handler) in self.handlers.iter().enumerate() {
351 if let Some(handler) = opt_handler {
352 let mut compiled_h = handler.clone();
353 compiled_h.compile_in_place();
354 let compiled_raw = compiled_h.compiled.unwrap_or(compiled_h.raw);
355 let state = state.clone();
356 let new_h: BoxedHandler<S2> =
357 Arc::new(move |req, _parent_state| compiled_raw(req, state.clone()));
358 new_handlers[i] = Some(middleware::MethodHandler::new(new_h));
359 }
360 }
361 MethodRouter {
362 handlers: new_handlers,
363 param_names: self.param_names,
364 matched_path: self.matched_path,
365 nest_prefix: self.nest_prefix,
366 }
367 }
368}
369
370pub fn get<H, T, S>(handler: H) -> MethodRouter<S>
374where
375 H: Handler<T, S>,
376 T: 'static,
377 S: Clone + Send + Sync + 'static,
378{
379 MethodRouter::new().get(handler)
380}
381
382pub fn post<H, T, S>(handler: H) -> MethodRouter<S>
384where
385 H: Handler<T, S>,
386 T: 'static,
387 S: Clone + Send + Sync + 'static,
388{
389 MethodRouter::new().post(handler)
390}
391
392pub fn put<H, T, S>(handler: H) -> MethodRouter<S>
394where
395 H: Handler<T, S>,
396 T: 'static,
397 S: Clone + Send + Sync + 'static,
398{
399 MethodRouter::new().put(handler)
400}
401
402pub fn delete<H, T, S>(handler: H) -> MethodRouter<S>
404where
405 H: Handler<T, S>,
406 T: 'static,
407 S: Clone + Send + Sync + 'static,
408{
409 MethodRouter::new().delete(handler)
410}
411
412pub fn patch<H, T, S>(handler: H) -> MethodRouter<S>
414where
415 H: Handler<T, S>,
416 T: 'static,
417 S: Clone + Send + Sync + 'static,
418{
419 MethodRouter::new().patch(handler)
420}
421
422pub fn options<H, T, S>(handler: H) -> MethodRouter<S>
424where
425 H: Handler<T, S>,
426 T: 'static,
427 S: Clone + Send + Sync + 'static,
428{
429 MethodRouter::new().options(handler)
430}
431
432pub fn head<H, T, S>(handler: H) -> MethodRouter<S>
434where
435 H: Handler<T, S>,
436 T: 'static,
437 S: Clone + Send + Sync + 'static,
438{
439 MethodRouter::new().head(handler)
440}
441
442pub fn trace<H, T, S>(handler: H) -> MethodRouter<S>
444where
445 H: Handler<T, S>,
446 T: 'static,
447 S: Clone + Send + Sync + 'static,
448{
449 MethodRouter::new().trace(handler)
450}
451
452pub fn connect<H, T, S>(handler: H) -> MethodRouter<S>
454where
455 H: Handler<T, S>,
456 T: 'static,
457 S: Clone + Send + Sync + 'static,
458{
459 MethodRouter::new().connect(handler)
460}
461
462pub fn any<H, T, S>(handler: H) -> MethodRouter<S>
466where
467 H: Handler<T, S>,
468 T: 'static,
469 S: Clone + Send + Sync + 'static,
470{
471 MethodRouter::new()
472 .get(handler.clone())
473 .post(handler.clone())
474 .put(handler.clone())
475 .delete(handler.clone())
476 .options(handler.clone())
477 .head(handler.clone())
478 .patch(handler.clone())
479 .trace(handler.clone())
480 .connect(handler)
481}
482
483#[derive(Clone)]
487pub struct Router<S = ()> {
488 routes: Vec<(String, MethodRouter<S>)>,
489 fallback: Option<BoxedHandler<S>>,
490 method_not_allowed_fallback: Option<BoxedHandler<S>>,
491 state: Option<Arc<S>>,
492 normalize_trailing_slash: bool,
494 #[cfg(feature = "tower")]
503 compiled: Option<CompiledRouter<S>>,
504}
505
506impl<S> std::fmt::Debug for Router<S> {
507 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
508 f.debug_struct("Router")
509 .field("route_count", &self.routes.len())
510 .field("has_fallback", &self.fallback.is_some())
511 .field(
512 "has_method_not_allowed_fallback",
513 &self.method_not_allowed_fallback.is_some(),
514 )
515 .finish_non_exhaustive()
516 }
517}
518
519impl<S> Default for Router<S>
520where
521 S: Clone + Send + Sync + 'static,
522{
523 fn default() -> Self {
524 Self::new()
525 }
526}
527
528#[allow(clippy::panic)]
533fn merge_optional<T>(a: Option<T>, b: Option<T>, panic_msg: &'static str) -> Option<T> {
534 match (a, b) {
535 (Some(_), Some(_)) => panic!("{panic_msg}"),
536 (Some(v), None) | (None, Some(v)) => Some(v),
537 (None, None) => None,
538 }
539}
540
541fn normalize_route_pattern(path: &str) -> String {
546 if !path.contains(':') && !path.contains('*') {
548 return path.to_string();
549 }
550 let segments: Vec<String> = path
551 .split('/')
552 .map(|segment| {
553 if segment.starts_with(':') && segment.len() > 1 {
554 format!("{{{}}}", &segment[1..])
555 } else if segment.starts_with('*') && segment.len() > 1 {
556 format!("{{{segment}}}")
558 } else {
559 segment.to_string()
560 }
561 })
562 .collect();
563 segments.join("/")
564}
565
566#[cfg(feature = "tower")]
570fn all_methods<H, S>(handler: H) -> MethodRouter<S>
571where
572 H: Handler<tower_compat::TowerServiceMarker, S>,
573 S: Clone + Send + Sync + 'static,
574{
575 any(handler)
576}
577
578impl<S> Router<S>
579where
580 S: Clone + Send + Sync + 'static,
581{
582 #[must_use]
584 pub fn new() -> Self {
585 Self {
586 routes: Vec::new(),
587 fallback: None,
588 method_not_allowed_fallback: None,
589 state: None,
590 normalize_trailing_slash: false,
591 #[cfg(feature = "tower")]
592 compiled: None,
593 }
594 }
595
596 #[must_use]
612 pub const fn normalize_trailing_slash(mut self) -> Self {
613 self.normalize_trailing_slash = true;
614 self
615 }
616
617 #[must_use]
634 pub fn no_index(mut self) -> Self {
635 if !self.routes.iter().any(|(path, _)| path == "/robots.txt") {
636 self = self.route(
637 "/robots.txt",
638 get(|| async { "User-agent: *\nDisallow: /\n" }),
639 );
640 }
641 self.hoop(|req: Request<Body>, next: middleware::Next<S>| async move {
642 let mut resp = next.run(req).await;
643 let _ = resp.headers_mut().insert(
644 hyper::header::HeaderName::from_static("x-robots-tag"),
645 hyper::header::HeaderValue::from_static("noindex, nofollow"),
646 );
647 resp
648 })
649 }
650
651 #[must_use]
662 pub fn with_state<S2>(self, state: S) -> Router<S2>
663 where
664 S2: Clone + Send + Sync + 'static,
665 {
666 let state_arc = Arc::new(state);
667
668 let new_routes = self
669 .routes
670 .into_iter()
671 .map(|(path, method_router)| (path, method_router.with_state(&state_arc)))
672 .collect();
673
674 let rebind = |handler: BoxedHandler<S>| -> BoxedHandler<S2> {
675 let state_arc = state_arc.clone();
676 Arc::new(move |req, _parent_state| handler(req, state_arc.clone()))
677 };
678 let new_fallback = self.fallback.map(rebind);
679 let new_method_not_allowed_fallback = self.method_not_allowed_fallback.map(rebind);
680
681 Router {
682 routes: new_routes,
683 fallback: new_fallback,
684 method_not_allowed_fallback: new_method_not_allowed_fallback,
685 state: None,
686 normalize_trailing_slash: self.normalize_trailing_slash,
687 #[cfg(feature = "tower")]
688 compiled: None,
689 }
690 }
691
692 #[allow(clippy::panic)]
704 fn push_or_merge_route(&mut self, path: String, method_router: MethodRouter<S>) {
705 #[cfg(feature = "tower")]
706 {
707 self.compiled = None;
708 }
709 if let Some(pos) = self.routes.iter().position(|(p, _)| *p == path) {
710 let (_, existing) = self.routes.remove(pos);
711 let merged = existing
712 .merge(method_router, &path)
713 .unwrap_or_else(|e| panic!("{e}"));
714 self.routes.insert(pos, (path, merged));
715 } else {
716 self.routes.push((path, method_router));
717 }
718 }
719
720 #[must_use]
727 pub fn route(mut self, path: &str, method_router: MethodRouter<S>) -> Self {
728 let normalized = normalize_route_pattern(path);
729 self.push_or_merge_route(normalized, method_router);
730 self
731 }
732
733 #[must_use]
736 pub const fn into_make_service(self) -> Self {
737 self
738 }
739
740 #[must_use]
763 pub fn serve_static(self, dir_path: impl AsRef<std::path::Path>) -> Self {
764 let sd = static_dir::ServeDir::new(&dir_path).index("index.html");
765 self.serve_dir("/", sd)
766 }
767
768 #[must_use]
775 pub fn serve_dir(mut self, prefix: &str, serve_dir: static_dir::ServeDir) -> Self {
776 let prefix = prefix.trim_end_matches('/');
777 let exact_route = if prefix.is_empty() { "/" } else { prefix };
778 let wildcard_route = format!("{prefix}/*path");
779
780 self = self.route(exact_route, serve_dir.clone().into_method_router());
781 self = self.route(&wildcard_route, serve_dir.into_method_router());
782 self
783 }
784
785 pub fn serve_file(self, path: &str, file_path: &str) -> Result<Self, std::io::Error> {
794 let content = std::fs::read(file_path)?;
795 let content_bytes = Bytes::from(content);
796 let mime_type = static_dir::guess_mime_type(std::path::Path::new(file_path));
797
798 Ok(self.route(
799 path,
800 get(move |_req: Request<Body>| {
801 let body_content = content_bytes.clone();
802 async move {
803 let mut resp = Response::new(Body::full(body_content));
804 let mime_val = hyper::header::HeaderValue::from_static(mime_type);
805 let _ = resp
806 .headers_mut()
807 .insert(hyper::header::CONTENT_TYPE, mime_val);
808 resp
809 }
810 }),
811 ))
812 }
813
814 #[must_use]
819 pub fn serve_file_dynamic(self, path: &str, file_path: &str) -> Self {
820 let file_path_str = file_path.to_string();
821 let mime_type = static_dir::guess_mime_type(std::path::Path::new(file_path));
822
823 self.route(
824 path,
825 get(move |_req: Request<Body>| {
826 let fp = file_path_str.clone();
827 async move {
828 let Ok(content) = tokio::fs::read(&fp).await else {
829 let mut resp = Response::new(Body::empty());
830 *resp.status_mut() = StatusCode::NOT_FOUND;
831 return resp;
832 };
833 let mut resp = Response::new(Body::full(Bytes::from(content)));
834 let mime_val = hyper::header::HeaderValue::from_static(mime_type);
835 let _ = resp
836 .headers_mut()
837 .insert(hyper::header::CONTENT_TYPE, mime_val);
838 resp
839 }
840 }),
841 )
842 }
843
844 #[must_use]
869 pub fn nest(mut self, prefix: &str, mut router: Self) -> Self {
870 let prefix = prefix.trim_end_matches('/');
871 for (path, mut method_router) in router.routes.drain(..) {
872 let nested_path = if path == "/" || path.is_empty() {
873 prefix.to_string()
874 } else {
875 format!("{prefix}{path}")
876 };
877 let final_path = if nested_path.is_empty() {
878 "/".to_string()
879 } else {
880 nested_path
881 };
882 let accumulated = method_router.nest_prefix.as_ref().map_or_else(
886 || prefix.to_string(),
887 |existing| format!("{prefix}{existing}"),
888 );
889 method_router.nest_prefix = Some(Arc::from(accumulated));
890 self.push_or_merge_route(final_path, method_router);
891 }
892 self
893 }
894
895 #[must_use]
911 pub fn merge(mut self, mut other: Self) -> Self {
912 #[cfg(feature = "tower")]
913 {
914 self.compiled = None;
915 }
916 for (path, method_router) in other.routes.drain(..) {
917 self.push_or_merge_route(path, method_router);
918 }
919 self.fallback = merge_optional(
920 self.fallback.take(),
921 other.fallback.take(),
922 "Cannot merge two `Router`s that both have a fallback",
923 );
924 self.method_not_allowed_fallback = merge_optional(
925 self.method_not_allowed_fallback.take(),
926 other.method_not_allowed_fallback.take(),
927 "Cannot merge two `Router`s that both have a method_not_allowed_fallback",
928 );
929 self
930 }
931
932 #[cfg(feature = "tower")]
938 #[must_use]
939 pub fn route_service<Svc, RespBody>(self, path: &str, service: Svc) -> Self
940 where
941 Svc: tower::Service<Request<Bytes>, Response = Response<RespBody>>
942 + Clone
943 + Send
944 + Sync
945 + 'static,
946 Svc::Future: Send + 'static,
947 Svc::Error: Into<crate::http::error::Error> + Send,
948 RespBody: hyper::body::Body<Data = Bytes> + Send + 'static,
949 RespBody::Error: Into<crate::http::error::Error>,
950 {
951 let handler = tower_compat::ServiceHandler {
952 service,
953 strip_prefix: None,
954 };
955 self.route(path, all_methods(handler))
956 }
957
958 #[must_use]
963 #[cfg(feature = "tower")]
964 pub fn nest_service<Svc, RespBody>(self, prefix: &str, service: Svc) -> Self
965 where
966 Svc: tower::Service<Request<Bytes>, Response = Response<RespBody>>
967 + Clone
968 + Send
969 + Sync
970 + 'static,
971 Svc::Future: Send + 'static,
972 Svc::Error: Into<crate::http::error::Error> + Send,
973 RespBody: hyper::body::Body<Data = Bytes> + Send + 'static,
974 RespBody::Error: Into<crate::http::error::Error>,
975 {
976 let prefix = prefix.trim_end_matches('/');
977 let exact = if prefix.is_empty() { "/" } else { prefix };
978 let wildcard = format!("{prefix}/*__tachyon_nest_rest");
979 let handler = tower_compat::ServiceHandler {
980 service,
981 strip_prefix: Some(Arc::from(prefix)),
982 };
983 self.route(exact, all_methods(handler.clone()))
984 .route(&wildcard, all_methods(handler))
985 }
986
987 #[must_use]
991 #[cfg(feature = "tower")]
992 pub fn fallback_service<Svc, RespBody>(mut self, service: Svc) -> Self
993 where
994 Svc: tower::Service<Request<Bytes>, Response = Response<RespBody>>
995 + Clone
996 + Send
997 + Sync
998 + 'static,
999 Svc::Future: Send + 'static,
1000 Svc::Error: Into<crate::http::error::Error> + Send,
1001 RespBody: hyper::body::Body<Data = Bytes> + Send + 'static,
1002 RespBody::Error: Into<crate::http::error::Error>,
1003 {
1004 let handler = tower_compat::ServiceHandler {
1005 service,
1006 strip_prefix: None,
1007 };
1008 self.fallback = Some(Arc::new(move |req, state| handler.clone().call(req, state)));
1009 self.compiled = None;
1010 self
1011 }
1012
1013 #[must_use]
1019 #[cfg(feature = "tower")]
1020 pub fn layer<L, RespBody>(self, layer: L) -> Self
1021 where
1022 L: tower::Layer<tower_compat::NextService<S>> + Clone + Send + Sync + 'static,
1023 L::Service: tower::Service<Request<Bytes>, Response = Response<RespBody>> + Send + 'static,
1024 <L::Service as tower::Service<Request<Bytes>>>::Future: Send + 'static,
1025 <L::Service as tower::Service<Request<Bytes>>>::Error:
1026 Into<crate::http::error::Error> + Send,
1027 RespBody: hyper::body::Body<Data = Bytes> + Send + 'static,
1028 RespBody::Error: Into<crate::http::error::Error>,
1029 {
1030 self.hoop_at(
1031 middleware::MiddlewarePosition::First,
1032 tower_compat::from_tower_layer(layer),
1033 )
1034 }
1035
1036 #[must_use]
1041 #[cfg(feature = "tower")]
1042 pub fn route_layer<L, RespBody>(mut self, layer: L) -> Self
1043 where
1044 L: tower::Layer<tower_compat::NextService<S>> + Clone + Send + Sync + 'static,
1045 L::Service: tower::Service<Request<Bytes>, Response = Response<RespBody>> + Send + 'static,
1046 <L::Service as tower::Service<Request<Bytes>>>::Future: Send + 'static,
1047 <L::Service as tower::Service<Request<Bytes>>>::Error:
1048 Into<crate::http::error::Error> + Send,
1049 RespBody: hyper::body::Body<Data = Bytes> + Send + 'static,
1050 RespBody::Error: Into<crate::http::error::Error>,
1051 {
1052 let mw = tower_compat::from_tower_layer(layer);
1053 for (_path, method_router) in &mut self.routes {
1054 let m = mw.clone();
1055 let old_mr = std::mem::take(method_router);
1056 *method_router = old_mr.hoop_at(middleware::MiddlewarePosition::Last, m);
1057 }
1058 self.compiled = None;
1059 self
1060 }
1061
1062 #[must_use]
1064 pub fn fallback<H, T>(mut self, handler: H) -> Self
1065 where
1066 H: Handler<T, S>,
1067 T: 'static,
1068 {
1069 self.fallback = Some(Arc::new(move |req, state| {
1070 let handler = handler.clone();
1071 handler.call(req, state)
1072 }));
1073 #[cfg(feature = "tower")]
1074 {
1075 self.compiled = None;
1076 }
1077 self
1078 }
1079
1080 #[must_use]
1084 pub fn method_not_allowed_fallback<H, T>(mut self, handler: H) -> Self
1085 where
1086 H: Handler<T, S>,
1087 T: 'static,
1088 {
1089 self.method_not_allowed_fallback = Some(Arc::new(move |req, state| {
1090 let handler = handler.clone();
1091 handler.call(req, state)
1092 }));
1093 #[cfg(feature = "tower")]
1094 {
1095 self.compiled = None;
1096 }
1097 self
1098 }
1099
1100 #[must_use]
1102 pub fn hoop<F, Fut, Res>(self, middleware: F) -> Self
1103 where
1104 F: Fn(Request<Body>, middleware::Next<S>) -> Fut + Clone + Send + Sync + 'static,
1105 Fut: Future<Output = Res> + Send + 'static,
1106 Res: IntoResponse + Send + 'static,
1107 {
1108 self.hoop_at(middleware::MiddlewarePosition::First, middleware)
1109 }
1110
1111 #[must_use]
1113 pub fn hoop_at<F, Fut, Res>(
1114 mut self,
1115 position: middleware::MiddlewarePosition,
1116 middleware: F,
1117 ) -> Self
1118 where
1119 F: Fn(Request<Body>, middleware::Next<S>) -> Fut + Clone + Send + Sync + 'static,
1120 Fut: Future<Output = Res> + Send + 'static,
1121 Res: IntoResponse + Send + 'static,
1122 {
1123 #[cfg(feature = "tower")]
1124 {
1125 self.compiled = None;
1126 }
1127 for (_path, method_router) in &mut self.routes {
1128 let mw = middleware.clone();
1129 let old_mr = std::mem::take(method_router);
1130 *method_router = old_mr.hoop_at(position, mw);
1131 }
1132
1133 if let Some(fallback) = self.fallback.take() {
1134 let mw = middleware.clone();
1135 self.fallback = Some(Arc::new(move |req, state| {
1136 let next = middleware::Next {
1137 handler: fallback.clone(),
1138 state,
1139 };
1140 let fut = mw(req, next);
1141 crate::routing::handler::ResponseFuture::Boxed(Box::pin(async move {
1142 fut.await.into_response()
1143 }))
1144 }));
1145 }
1146
1147 if let Some(handler) = self.method_not_allowed_fallback.take() {
1148 let mw = middleware;
1149 self.method_not_allowed_fallback = Some(Arc::new(move |req, state| {
1150 let next = middleware::Next {
1151 handler: handler.clone(),
1152 state,
1153 };
1154 let fut = mw(req, next);
1155 crate::routing::handler::ResponseFuture::Boxed(Box::pin(async move {
1156 fut.await.into_response()
1157 }))
1158 }));
1159 }
1160
1161 self
1162 }
1163
1164 #[allow(clippy::expect_used)]
1170 pub async fn handle_request(&self, req: Request<Body>) -> Response<Body>
1171 where
1172 S: Default,
1173 {
1174 let compiled = self.clone().compile().expect("Router compilation failed");
1175 compiled.handle_request(req).await
1176 }
1177
1178 pub fn compile(self) -> Result<CompiledRouter<S>, RouterError>
1187 where
1188 S: Default,
1189 {
1190 let mut matcher: matchit::Router<MethodRouter<S>> = matchit::Router::new();
1191
1192 let mut seen = std::collections::HashSet::new();
1193 for (path, mut method_router) in self.routes {
1194 if !seen.insert(path.clone()) {
1195 return Err(RouterError::DuplicateRoute(path));
1196 }
1197 method_router.compile_in_place();
1198 method_router.param_names = extract_param_names(&path);
1199 method_router.matched_path = Arc::from(path.as_str());
1200 matcher.insert(path, method_router)?;
1201 }
1202
1203 let state = self.state.unwrap_or_else(|| Arc::new(S::default()));
1204
1205 Ok(CompiledRouter {
1206 matcher,
1207 fallback: self.fallback,
1208 method_not_allowed_fallback: self.method_not_allowed_fallback,
1209 state,
1210 normalize_trailing_slash: self.normalize_trailing_slash,
1211 })
1212 }
1213}
1214
1215#[derive(Debug)]
1217pub enum RouterError {
1218 DuplicateRoute(String),
1220 MethodOverlap {
1226 method: &'static str,
1228 path: String,
1230 },
1231 Insert(matchit::InsertError),
1233}
1234
1235impl std::fmt::Display for RouterError {
1236 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1237 match self {
1238 Self::DuplicateRoute(path) => write!(f, "Duplicate route path registered: '{path}'"),
1239 Self::MethodOverlap { method, path } => {
1240 write!(
1241 f,
1242 "Overlapping method route: {method} {path} already exists"
1243 )
1244 }
1245 Self::Insert(e) => write!(f, "Router insert error: {e}"),
1246 }
1247 }
1248}
1249
1250impl std::error::Error for RouterError {}
1251
1252impl From<matchit::InsertError> for RouterError {
1253 fn from(e: matchit::InsertError) -> Self {
1254 Self::Insert(e)
1255 }
1256}
1257
1258#[derive(Clone)]
1262pub struct CompiledRouter<S> {
1263 matcher: matchit::Router<MethodRouter<S>>,
1264 fallback: Option<BoxedHandler<S>>,
1265 method_not_allowed_fallback: Option<BoxedHandler<S>>,
1266 state: Arc<S>,
1267 normalize_trailing_slash: bool,
1268}
1269
1270impl<S> std::fmt::Debug for CompiledRouter<S> {
1271 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1272 f.debug_struct("CompiledRouter")
1273 .field("has_fallback", &self.fallback.is_some())
1274 .field(
1275 "has_method_not_allowed_fallback",
1276 &self.method_not_allowed_fallback.is_some(),
1277 )
1278 .finish_non_exhaustive()
1279 }
1280}
1281
1282fn extract_param_names(path: &str) -> Arc<[Arc<str>]> {
1287 let mut names = Vec::new();
1288 let bytes = path.as_bytes();
1289 let mut i = 0;
1290 while i < bytes.len() {
1291 if bytes[i] == b'{'
1292 && let Some(end) = path[i + 1..].find('}')
1293 {
1294 let inner = &path[i + 1..i + 1 + end];
1295 let name = inner.strip_prefix('*').unwrap_or(inner);
1296 names.push(Arc::from(name));
1297 i += 1 + end + 1;
1298 } else {
1299 i += 1;
1300 }
1301 }
1302 Arc::from(names)
1303}
1304
1305pub(crate) fn percent_decode(s: &str) -> Option<std::borrow::Cow<'_, str>> {
1309 let bytes = s.as_bytes();
1310 if !bytes.contains(&b'%') {
1311 return Some(std::borrow::Cow::Borrowed(s));
1312 }
1313 let mut decoded = Vec::with_capacity(bytes.len());
1314 let mut i = 0;
1315 while i < bytes.len() {
1316 if bytes[i] == b'%' {
1317 if i + 2 < bytes.len() {
1318 let hex = &bytes[i + 1..i + 3];
1319 if let Ok(hex_str) = std::str::from_utf8(hex)
1320 && let Ok(val) = u8::from_str_radix(hex_str, 16)
1321 {
1322 decoded.push(val);
1323 i += 3;
1324 continue;
1325 }
1326 }
1327 return None; }
1329 decoded.push(bytes[i]);
1330 i += 1;
1331 }
1332 let s = String::from_utf8(decoded).ok()?;
1333 Some(std::borrow::Cow::Owned(s))
1334}
1335
1336pub(crate) fn strip_uri_prefix(req: &mut Request<Body>, prefix: &str) {
1347 let path = req.uri().path();
1348 let stripped = path.strip_prefix(prefix).unwrap_or(path);
1349 let new_path = if stripped.starts_with('/') {
1350 stripped.to_string()
1351 } else {
1352 format!("/{stripped}")
1353 };
1354 let path_and_query = match req.uri().query() {
1355 Some(q) if !q.is_empty() => format!("{new_path}?{q}"),
1356 _ => new_path,
1357 };
1358 let mut parts = req.uri().clone().into_parts();
1359 if let Ok(pq) = path_and_query.parse() {
1360 parts.path_and_query = Some(pq);
1361 }
1362 if let Ok(new_uri) = hyper::Uri::from_parts(parts) {
1363 *req.uri_mut() = new_uri;
1364 }
1365}
1366
1367fn strip_trailing_slash(req: &mut Request<Body>) {
1373 let path = req.uri().path();
1374 if path.len() <= 1 || !path.ends_with('/') {
1375 return;
1376 }
1377 let new_path = &path[..path.len() - 1];
1378 let path_and_query = match req.uri().query() {
1379 Some(q) if !q.is_empty() => format!("{new_path}?{q}"),
1380 _ => new_path.to_string(),
1381 };
1382 let mut parts = req.uri().clone().into_parts();
1383 if let Ok(pq) = path_and_query.parse() {
1384 parts.path_and_query = Some(pq);
1385 }
1386 if let Ok(new_uri) = hyper::Uri::from_parts(parts) {
1387 *req.uri_mut() = new_uri;
1388 }
1389}
1390
1391impl<S> CompiledRouter<S>
1392where
1393 S: Clone + Send + Sync + 'static,
1394{
1395 #[inline]
1412 pub async fn handle_request(&self, req: Request<Body>) -> Response<Body> {
1413 let mut req = req;
1414
1415 if self.normalize_trailing_slash {
1416 strip_trailing_slash(&mut req);
1417 }
1418
1419 let path = req.uri().path();
1420
1421 #[cfg(feature = "lets-encrypt")]
1422 if path.starts_with("/.well-known/acme-challenge/") {
1423 use hyper::StatusCode;
1424 let token = path
1425 .strip_prefix("/.well-known/acme-challenge/")
1426 .unwrap_or("");
1427 if let Some(key_auth) = crate::tls::acme::get_challenge(token) {
1428 return Response::builder()
1429 .status(StatusCode::OK)
1430 .header(hyper::header::CONTENT_TYPE, "text/plain")
1431 .body(Body::full(Bytes::copy_from_slice(key_auth.as_bytes())))
1432 .unwrap_or_else(|_| Response::new(Body::empty()));
1433 }
1434 }
1435
1436 let (method_router, params): RouteResolution<'_, S> = match self.resolve(path) {
1437 Some(r) => r,
1438 None => {
1439 return if let Some(fb) = &self.fallback {
1440 fb(req, self.state.clone()).await
1441 } else {
1442 Response::builder()
1443 .status(StatusCode::NOT_FOUND)
1444 .body(Body::full(Bytes::from_static(b"Not Found")))
1445 .unwrap_or_else(|_| Response::new(Body::empty()))
1446 };
1447 }
1448 };
1449
1450 if !params.is_empty() {
1453 let _ = req.extensions_mut().insert(PathParams(params));
1454 }
1455
1456 #[cfg(feature = "matched-path")]
1457 {
1458 let _ = req
1459 .extensions_mut()
1460 .insert(crate::routing::extract::MatchedPath(
1461 method_router.matched_path.clone(),
1462 ));
1463 }
1464
1465 if let Some(prefix) = &method_router.nest_prefix {
1470 #[cfg(feature = "original-uri")]
1471 {
1472 let original_uri = req.uri().clone();
1473 let _ = req
1474 .extensions_mut()
1475 .insert(crate::routing::extract::OriginalUri(original_uri));
1476 }
1477 strip_uri_prefix(&mut req, prefix);
1478 }
1479
1480 let method = req.method();
1481 let idx = method_index(method);
1482
1483 let is_head = *method == Method::HEAD;
1486 let falls_back_to_get =
1487 is_head && method_router.handlers[IDX_HEAD].is_none() && idx == Some(IDX_HEAD);
1488 let effective_idx = if falls_back_to_get {
1489 Some(IDX_GET)
1490 } else {
1491 idx
1492 };
1493
1494 let handler = effective_idx.and_then(|i| method_router.handlers[i].as_ref());
1495
1496 if let Some(h) = handler {
1497 let mut resp = h.call(req, self.state.clone()).await;
1498 if is_head {
1502 *resp.body_mut() = Body::empty();
1503 }
1504 resp
1505 } else if let Some(fb) = &self.method_not_allowed_fallback {
1506 fb(req, self.state.clone()).await
1509 } else {
1510 let allow = method_router.allow_header();
1512 Response::builder()
1513 .status(StatusCode::METHOD_NOT_ALLOWED)
1514 .header(hyper::header::ALLOW, &allow)
1515 .body(Body::full(Bytes::from_static(b"Method Not Allowed")))
1516 .unwrap_or_else(|_| Response::new(Body::empty()))
1517 }
1518 }
1519
1520 #[inline]
1522 fn resolve(&self, path: &str) -> Option<RouteResolution<'_, S>> {
1523 let m = self.matcher.at(path).ok()?;
1524 let params = if m.params.is_empty() {
1525 Vec::new()
1526 } else {
1527 let names = &m.value.param_names;
1531 let mut p = Vec::with_capacity(m.params.len());
1532 for (name, (_, v)) in names.iter().zip(m.params.iter()) {
1533 let decoded =
1534 percent_decode(v).map_or_else(|| v.to_string(), std::borrow::Cow::into_owned);
1535 p.push((name.clone(), decoded));
1536 }
1537 p
1538 };
1539 Some((m.value, params))
1540 }
1541}
1542
1543pub type RouteResolution<'a, S> = (&'a MethodRouter<S>, Vec<(Arc<str>, String)>);
1545
1546#[cfg(test)]
1549mod tests {
1550 #![allow(clippy::unwrap_used)]
1551 use super::*;
1552 use crate::routing::extract::Path;
1553 use serde::Deserialize;
1554
1555 #[derive(Debug, Deserialize)]
1556 struct IdParam {
1557 id: u32,
1558 }
1559
1560 async fn handle_root() -> &'static str {
1561 "root"
1562 }
1563 async fn handle_id(Path(p): Path<IdParam>) -> String {
1564 format!("id:{}", p.id)
1565 }
1566 async fn handle_post() -> &'static str {
1567 "post"
1568 }
1569 async fn handle_delete() -> &'static str {
1570 "deleted"
1571 }
1572
1573 fn make_req(method: &str, uri: &str) -> Request<Body> {
1574 Request::builder()
1575 .method(method)
1576 .uri(uri)
1577 .body(Body::empty())
1578 .expect("valid request")
1579 }
1580
1581 fn compile_app() -> CompiledRouter<()> {
1582 Router::new()
1583 .route("/", get(handle_root))
1584 .route(
1585 "/user/:id",
1586 get(handle_id).post(handle_post).delete(handle_delete),
1587 )
1588 .with_state::<()>(())
1589 .compile()
1590 .expect("compile router")
1591 }
1592
1593 #[tokio::test]
1596 async fn test_root_route() {
1597 let router = compile_app();
1598 let resp = router.handle_request(make_req("GET", "/")).await;
1599 assert_eq!(resp.status(), StatusCode::OK);
1600 }
1601
1602 #[tokio::test]
1603 async fn test_path_param_extraction() {
1604 use http_body_util::BodyExt;
1605 let router = compile_app();
1606 let resp = router.handle_request(make_req("GET", "/user/42")).await;
1607 assert_eq!(resp.status(), StatusCode::OK);
1608 let body = resp.into_body().collect().await.unwrap().to_bytes();
1609 assert_eq!(body.as_ref(), b"id:42");
1610 }
1611
1612 #[tokio::test]
1613 async fn test_not_found() {
1614 let router = compile_app();
1615 let resp = router.handle_request(make_req("GET", "/nonexistent")).await;
1616 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1617 }
1618
1619 #[tokio::test]
1620 async fn test_method_not_allowed_has_allow_header() {
1621 let router = compile_app();
1622 let resp = router.handle_request(make_req("PATCH", "/user/1")).await;
1624 assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
1625 let allow = resp
1626 .headers()
1627 .get(hyper::header::ALLOW)
1628 .expect("Allow header must be present");
1629 let allow_str = allow.to_str().expect("valid utf8");
1630 assert!(allow_str.contains("GET"), "Allow: {allow_str}");
1631 assert!(allow_str.contains("POST"), "Allow: {allow_str}");
1632 assert!(allow_str.contains("DELETE"), "Allow: {allow_str}");
1633 assert!(!allow_str.contains("PATCH"), "Allow: {allow_str}");
1634 }
1635
1636 #[tokio::test]
1639 async fn test_trailing_slash_not_stripped() {
1640 let router = compile_app();
1641 let resp = router.handle_request(make_req("GET", "/user/5/")).await;
1643 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1644 }
1645
1646 #[tokio::test]
1647 async fn test_trailing_slash_not_added() {
1648 let router = Router::new()
1650 .route("/about/", get(handle_root))
1651 .with_state::<()>(())
1652 .compile()
1653 .expect("compile");
1654 let resp = router.handle_request(make_req("GET", "/about")).await;
1655 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1656 }
1657
1658 #[tokio::test]
1661 async fn test_case_sensitive_routing() {
1662 let router = compile_app();
1664 let resp = router.handle_request(make_req("GET", "/User/1")).await;
1665 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1666 }
1667
1668 #[tokio::test]
1671 async fn test_custom_fallback() {
1672 let router = Router::new()
1673 .route("/", get(handle_root))
1674 .fallback(|_req: Request<Body>| async { (StatusCode::FOUND, "redirected") })
1675 .with_state::<()>(())
1676 .compile()
1677 .expect("compile");
1678 let resp = router.handle_request(make_req("GET", "/missing")).await;
1679 assert_eq!(resp.status(), StatusCode::FOUND);
1680 }
1681
1682 #[tokio::test]
1685 async fn test_overlapping_method_route_panics() {
1686 async fn v1() -> &'static str {
1687 "v1"
1688 }
1689 async fn v2() -> &'static str {
1690 "v2"
1691 }
1692
1693 let result = std::panic::catch_unwind(|| {
1696 Router::<()>::new()
1697 .route("/dup", get(v1))
1698 .route("/dup", get(v2))
1699 });
1700 assert!(result.is_err());
1701 }
1702
1703 #[tokio::test]
1704 async fn test_non_overlapping_methods_on_same_path_merge() {
1705 async fn handle_get() -> &'static str {
1706 "got"
1707 }
1708 async fn handle_post() -> &'static str {
1709 "posted"
1710 }
1711
1712 let app = Router::new()
1717 .route("/x", get(handle_get))
1718 .route("/x", post(handle_post))
1719 .with_state::<()>(())
1720 .compile()
1721 .expect("compile");
1722
1723 let get_resp = app.handle_request(make_req("GET", "/x")).await;
1724 assert_eq!(get_resp.status(), StatusCode::OK);
1725 let get_body = http_body_util::BodyExt::collect(get_resp.into_body())
1726 .await
1727 .unwrap()
1728 .to_bytes();
1729 assert_eq!(&get_body[..], b"got");
1730
1731 let post_resp = app.handle_request(make_req("POST", "/x")).await;
1732 assert_eq!(post_resp.status(), StatusCode::OK);
1733 let post_body = http_body_util::BodyExt::collect(post_resp.into_body())
1734 .await
1735 .unwrap()
1736 .to_bytes();
1737 assert_eq!(&post_body[..], b"posted");
1738 }
1739
1740 #[test]
1743 fn test_normalize_no_change() {
1744 assert_eq!(normalize_route_pattern("/static/path"), "/static/path");
1745 }
1746
1747 #[test]
1748 fn test_normalize_colon_param() {
1749 assert_eq!(normalize_route_pattern("/user/:id"), "/user/{id}");
1750 }
1751
1752 #[test]
1753 fn test_normalize_wildcard() {
1754 assert_eq!(normalize_route_pattern("/files/*path"), "/files/{*path}");
1755 }
1756
1757 #[test]
1758 fn test_normalize_mixed() {
1759 assert_eq!(
1760 normalize_route_pattern("/api/:version/files/*rest"),
1761 "/api/{version}/files/{*rest}"
1762 );
1763 }
1764
1765 #[tokio::test]
1768 async fn test_nested_router() {
1769 let api = Router::new().route("/status", get(handle_root));
1770 let app = Router::new()
1771 .nest("/api/v1", api)
1772 .with_state::<()>(())
1773 .compile()
1774 .expect("compile");
1775
1776 let resp = app.handle_request(make_req("GET", "/api/v1/status")).await;
1777 assert_eq!(resp.status(), StatusCode::OK);
1778 }
1779
1780 #[tokio::test]
1781 async fn test_nested_router_root() {
1782 let api = Router::new().route("/", get(handle_root));
1783 let app = Router::new()
1784 .nest("/api", api)
1785 .with_state::<()>(())
1786 .compile()
1787 .expect("compile");
1788
1789 let resp = app.handle_request(make_req("GET", "/api")).await;
1790 assert_eq!(resp.status(), StatusCode::OK);
1791 }
1792
1793 #[tokio::test]
1796 async fn test_nest_strips_prefix_from_uri() {
1797 use hyper::Uri;
1798
1799 async fn echo_uri(uri: Uri) -> String {
1800 uri.path().to_string()
1801 }
1802
1803 let api = Router::new().route("/users/{id}", get(echo_uri));
1804 let app = Router::new()
1805 .nest("/api", api)
1806 .with_state::<()>(())
1807 .compile()
1808 .expect("compile");
1809
1810 let resp = app.handle_request(make_req("GET", "/api/users/42")).await;
1811 assert_eq!(resp.status(), StatusCode::OK);
1812 let body = http_body_util::BodyExt::collect(resp.into_body())
1813 .await
1814 .unwrap()
1815 .to_bytes();
1816 assert_eq!(&body[..], b"/users/42");
1818 }
1819
1820 #[cfg(feature = "original-uri")]
1821 #[tokio::test]
1822 async fn test_nest_original_uri_preserves_full_path() {
1823 use crate::routing::extract::OriginalUri;
1824
1825 async fn echo_original(OriginalUri(uri): OriginalUri) -> String {
1826 uri.path().to_string()
1827 }
1828
1829 let api = Router::new().route("/users/{id}", get(echo_original));
1830 let app = Router::new()
1831 .nest("/api", api)
1832 .with_state::<()>(())
1833 .compile()
1834 .expect("compile");
1835
1836 let resp = app.handle_request(make_req("GET", "/api/users/42")).await;
1837 assert_eq!(resp.status(), StatusCode::OK);
1838 let body = http_body_util::BodyExt::collect(resp.into_body())
1839 .await
1840 .unwrap()
1841 .to_bytes();
1842 assert_eq!(&body[..], b"/api/users/42");
1844 }
1845
1846 #[tokio::test]
1847 async fn test_nest_two_levels_accumulates_prefix() {
1848 async fn echo_uri(uri: hyper::Uri) -> String {
1849 uri.path().to_string()
1850 }
1851
1852 let innermost = Router::new().route("/users", get(echo_uri));
1853 let v1 = Router::new().nest("/v1", innermost);
1854 let app = Router::new()
1855 .nest("/api", v1)
1856 .with_state::<()>(())
1857 .compile()
1858 .expect("compile");
1859
1860 let resp = app.handle_request(make_req("GET", "/api/v1/users")).await;
1861 assert_eq!(resp.status(), StatusCode::OK);
1862 let body = http_body_util::BodyExt::collect(resp.into_body())
1863 .await
1864 .unwrap()
1865 .to_bytes();
1866 assert_eq!(&body[..], b"/users");
1867 }
1868
1869 #[tokio::test]
1870 async fn test_non_nested_route_uri_unaffected() {
1871 async fn echo_uri(uri: hyper::Uri) -> String {
1873 uri.path().to_string()
1874 }
1875 let app = Router::new()
1876 .route("/users/{id}", get(echo_uri))
1877 .with_state::<()>(())
1878 .compile()
1879 .expect("compile");
1880
1881 let resp = app.handle_request(make_req("GET", "/users/7")).await;
1882 let body = http_body_util::BodyExt::collect(resp.into_body())
1883 .await
1884 .unwrap()
1885 .to_bytes();
1886 assert_eq!(&body[..], b"/users/7");
1887 }
1888
1889 #[cfg(feature = "matched-path")]
1892 #[tokio::test]
1893 async fn test_matched_path_returns_route_pattern() {
1894 use crate::routing::extract::MatchedPath;
1895
1896 async fn handler(path: MatchedPath) -> String {
1897 path.as_str().to_string()
1898 }
1899
1900 let app = Router::new()
1901 .route("/users/{id}", get(handler))
1902 .with_state::<()>(())
1903 .compile()
1904 .expect("compile");
1905
1906 let resp = app.handle_request(make_req("GET", "/users/99")).await;
1907 assert_eq!(resp.status(), StatusCode::OK);
1908 let body = http_body_util::BodyExt::collect(resp.into_body())
1909 .await
1910 .unwrap()
1911 .to_bytes();
1912 assert_eq!(&body[..], b"/users/{id}");
1913 }
1914
1915 #[cfg(feature = "matched-path")]
1916 #[tokio::test]
1917 async fn test_matched_path_missing_returns_500() {
1918 use crate::routing::extract::{FromRequestParts, MatchedPath};
1919 let mut parts = Request::builder().uri("/").body(()).unwrap().into_parts().0;
1922 let res = MatchedPath::from_request_parts(&mut parts, &());
1923 assert!(res.is_err());
1924 }
1925
1926 #[tokio::test]
1929 async fn test_any_dispatches_every_method() {
1930 async fn handler() -> &'static str {
1931 "any"
1932 }
1933 let app = Router::new()
1934 .route("/x", any(handler))
1935 .with_state::<()>(())
1936 .compile()
1937 .expect("compile");
1938
1939 for method in [
1940 "GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE",
1941 ] {
1942 let resp = app.handle_request(make_req(method, "/x")).await;
1943 assert_eq!(resp.status(), StatusCode::OK, "method: {method}");
1944 }
1945 }
1946
1947 #[tokio::test]
1948 async fn test_connect_route() {
1949 async fn handler() -> &'static str {
1950 "connected"
1951 }
1952 let app = Router::new()
1953 .route("/tunnel", connect(handler))
1954 .with_state::<()>(())
1955 .compile()
1956 .expect("compile");
1957
1958 let resp = app.handle_request(make_req("CONNECT", "/tunnel")).await;
1959 assert_eq!(resp.status(), StatusCode::OK);
1960 }
1961
1962 #[tokio::test]
1963 async fn test_extra_routing_features() {
1964 async fn dummy() -> &'static str {
1965 "ok"
1966 }
1967
1968 assert_eq!(method_index(&Method::OPTIONS), Some(4));
1970 assert_eq!(method_index(&Method::HEAD), Some(5));
1971 assert_eq!(method_index(&Method::TRACE), Some(7));
1972 assert_eq!(method_index(&Method::CONNECT), Some(8));
1973 assert_eq!(method_index(&Method::PATCH), Some(6));
1974
1975 let mr = MethodRouter::<()>::default();
1977 let dbg = format!("{mr:?}");
1978 assert!(dbg.contains("MethodRouter"));
1979
1980 let _mr2 = MethodRouter::<()>::new()
1982 .options(dummy)
1983 .head(dummy)
1984 .trace(dummy)
1985 .put(dummy)
1986 .delete(dummy)
1987 .patch(dummy);
1988
1989 let _mr3 = put::<_, _, ()>(dummy);
1990 let _mr4 = delete::<_, _, ()>(dummy);
1991 let _mr5 = patch::<_, _, ()>(dummy);
1992
1993 let r = Router::<()>::default();
1995 let r_dbg = format!("{r:?}");
1996 assert!(r_dbg.contains("Router"));
1997
1998 let compiled = r.compile().unwrap();
1999 let cr_dbg = format!("{compiled:?}");
2000 assert!(cr_dbg.contains("CompiledRouter"));
2001
2002 let dup_err = RouterError::DuplicateRoute("foo".to_string());
2003 assert!(dup_err.to_string().contains("Duplicate route"));
2004
2005 let bad_router = Router::new()
2007 .route("/user/:id", get(dummy))
2008 .route("/user/*path", get(dummy))
2009 .with_state::<()>(())
2010 .compile();
2011 assert!(bad_router.is_err());
2012 let insert_err = bad_router.unwrap_err();
2013 assert!(insert_err.to_string().contains("Router insert error"));
2014
2015 let dir = tempfile::tempdir().unwrap();
2017 std::fs::write(dir.path().join("index.html"), "hello static").unwrap();
2018 let app_static = Router::new()
2019 .serve_static(dir.path())
2020 .with_state::<()>(())
2021 .compile()
2022 .unwrap();
2023 let resp = app_static.handle_request(make_req("GET", "/")).await;
2024 assert_eq!(resp.status(), StatusCode::OK);
2025
2026 let file_path = dir.path().join("dynamic.txt");
2028 std::fs::write(&file_path, "hello dynamic").unwrap();
2029 let app_dynamic = Router::new()
2030 .serve_file_dynamic("/dyn", file_path.to_str().unwrap())
2031 .with_state::<()>(())
2032 .compile()
2033 .unwrap();
2034
2035 let resp_dyn = app_dynamic.handle_request(make_req("GET", "/dyn")).await;
2036 assert_eq!(resp_dyn.status(), StatusCode::OK);
2037
2038 let app_dyn_err = Router::new()
2040 .serve_file_dynamic("/dyn_err", "/nonexistent/file")
2041 .with_state::<()>(())
2042 .compile()
2043 .unwrap();
2044 let resp_dyn_err = app_dyn_err
2045 .handle_request(make_req("GET", "/dyn_err"))
2046 .await;
2047 assert_eq!(resp_dyn_err.status(), StatusCode::NOT_FOUND);
2048
2049 let sub = Router::new().route("/", get(dummy));
2051 let nested_empty = Router::new()
2052 .nest("", sub)
2053 .with_state::<()>(())
2054 .compile()
2055 .unwrap();
2056 let resp_nested = nested_empty.handle_request(make_req("GET", "/")).await;
2057 assert_eq!(resp_nested.status(), StatusCode::OK);
2058
2059 let r1 = Router::new().route("/r1", get(dummy));
2061 let r2 = Router::new().route("/r2", get(dummy));
2062 let merged = r1.merge(r2).with_state::<()>(()).compile().unwrap();
2063 assert_eq!(
2064 merged.handle_request(make_req("GET", "/r1")).await.status(),
2065 StatusCode::OK
2066 );
2067 assert_eq!(
2068 merged.handle_request(make_req("GET", "/r2")).await.status(),
2069 StatusCode::OK
2070 );
2071 }
2072
2073 #[tokio::test]
2074 async fn test_merge_adopts_the_one_fallback_present() {
2075 async fn h() -> &'static str {
2076 "h"
2077 }
2078 async fn fb() -> &'static str {
2079 "merged-fallback"
2080 }
2081
2082 let r1 = Router::new().route("/r1", get(h));
2083 let r2 = Router::new().route("/r2", get(h)).fallback(fb);
2084 let merged = r1.merge(r2).with_state::<()>(()).compile().unwrap();
2085
2086 let resp = merged.handle_request(make_req("GET", "/missing")).await;
2087 assert_eq!(resp.status(), StatusCode::OK);
2088 let body = http_body_util::BodyExt::collect(resp.into_body())
2089 .await
2090 .unwrap()
2091 .to_bytes();
2092 assert_eq!(&body[..], b"merged-fallback");
2093 }
2094
2095 #[test]
2096 fn test_merge_two_fallbacks_panics() {
2097 async fn fb1() -> &'static str {
2098 "fb1"
2099 }
2100 async fn fb2() -> &'static str {
2101 "fb2"
2102 }
2103
2104 let result = std::panic::catch_unwind(|| {
2105 let r1 = Router::<()>::new().fallback(fb1);
2106 let r2 = Router::<()>::new().fallback(fb2);
2107 r1.merge(r2)
2108 });
2109 assert!(result.is_err());
2110 }
2111
2112 #[tokio::test]
2115 async fn test_method_router_hoop_installs_middleware() {
2116 async fn handler() -> &'static str {
2117 "hi"
2118 }
2119 async fn tag_response(req: Request<Body>, next: middleware::Next<()>) -> Response<Body> {
2120 let mut resp = next.run(req).await;
2121 let _ = resp.headers_mut().insert(
2122 hyper::header::HeaderName::from_static("x-mr-hoop"),
2123 hyper::header::HeaderValue::from_static("yes"),
2124 );
2125 resp
2126 }
2127
2128 let mr = get(handler).hoop(tag_response);
2132 let app = Router::new()
2133 .route("/x", mr)
2134 .with_state::<()>(())
2135 .compile()
2136 .expect("compile");
2137
2138 let resp = app.handle_request(make_req("GET", "/x")).await;
2139 assert_eq!(resp.status(), StatusCode::OK);
2140 assert_eq!(resp.headers().get("x-mr-hoop").expect("header set"), "yes");
2141 }
2142
2143 #[tokio::test]
2146 async fn test_options_head_trace_free_functions() {
2147 async fn handle_options() -> &'static str {
2148 "opts"
2149 }
2150 async fn handle_trace() -> &'static str {
2151 "trace"
2152 }
2153 async fn handle_head() -> Response<Body> {
2154 Response::builder()
2155 .header("x-handler", "head")
2156 .body(Body::full(Bytes::from_static(b"head-only")))
2157 .unwrap_or_else(|_| Response::new(Body::empty()))
2158 }
2159 async fn handle_get_for_head() -> Response<Body> {
2160 Response::builder()
2161 .header("x-handler", "get")
2162 .body(Body::full(Bytes::from_static(b"get-for-head")))
2163 .unwrap_or_else(|_| Response::new(Body::empty()))
2164 }
2165 async fn handle_get_only() -> &'static str {
2166 "get-only"
2167 }
2168
2169 let app = Router::new()
2170 .route("/opts", options(handle_options))
2171 .route("/tracer", trace(handle_trace))
2172 .route("/headroute", head(handle_head).get(handle_get_for_head))
2176 .route("/getonly", get(handle_get_only))
2178 .with_state::<()>(())
2179 .compile()
2180 .expect("compile");
2181
2182 let resp = app.handle_request(make_req("OPTIONS", "/opts")).await;
2183 assert_eq!(resp.status(), StatusCode::OK);
2184 let body = http_body_util::BodyExt::collect(resp.into_body())
2185 .await
2186 .unwrap()
2187 .to_bytes();
2188 assert_eq!(&body[..], b"opts");
2189
2190 let resp = app.handle_request(make_req("TRACE", "/tracer")).await;
2191 assert_eq!(resp.status(), StatusCode::OK);
2192 let body = http_body_util::BodyExt::collect(resp.into_body())
2193 .await
2194 .unwrap()
2195 .to_bytes();
2196 assert_eq!(&body[..], b"trace");
2197
2198 let resp = app.handle_request(make_req("HEAD", "/headroute")).await;
2200 assert_eq!(resp.status(), StatusCode::OK);
2201 assert_eq!(
2202 resp.headers()
2203 .get("x-handler")
2204 .map(hyper::header::HeaderValue::as_bytes),
2205 Some(&b"head"[..])
2206 );
2207 let body = http_body_util::BodyExt::collect(resp.into_body())
2208 .await
2209 .unwrap()
2210 .to_bytes();
2211 assert!(body.is_empty(), "HEAD responses must have an empty body");
2212
2213 let get_resp = app.handle_request(make_req("GET", "/headroute")).await;
2215 assert_eq!(
2216 get_resp
2217 .headers()
2218 .get("x-handler")
2219 .map(hyper::header::HeaderValue::as_bytes),
2220 Some(&b"get"[..])
2221 );
2222 let get_body = http_body_util::BodyExt::collect(get_resp.into_body())
2223 .await
2224 .unwrap()
2225 .to_bytes();
2226 assert_eq!(&get_body[..], b"get-for-head");
2227
2228 let resp = app.handle_request(make_req("HEAD", "/getonly")).await;
2230 assert_eq!(resp.status(), StatusCode::OK);
2231 let body = http_body_util::BodyExt::collect(resp.into_body())
2232 .await
2233 .unwrap()
2234 .to_bytes();
2235 assert!(body.is_empty(), "HEAD responses must have an empty body");
2236 }
2237
2238 #[tokio::test]
2241 async fn test_into_make_service_returns_usable_router() {
2242 async fn handler() -> &'static str {
2243 "ims"
2244 }
2245
2246 let app = Router::new()
2247 .route("/x", get(handler))
2248 .into_make_service()
2249 .with_state::<()>(())
2250 .compile()
2251 .expect("compile");
2252
2253 let resp = app.handle_request(make_req("GET", "/x")).await;
2254 assert_eq!(resp.status(), StatusCode::OK);
2255 }
2256
2257 #[tokio::test]
2260 async fn test_method_not_allowed_fallback_overrides_default_405() {
2261 async fn get_handler() -> &'static str {
2262 "got"
2263 }
2264 async fn custom_405() -> (StatusCode, &'static str) {
2265 (StatusCode::IM_A_TEAPOT, "custom-405")
2266 }
2267
2268 let app = Router::new()
2269 .route("/x", get(get_handler))
2270 .method_not_allowed_fallback(custom_405)
2271 .with_state::<()>(())
2272 .compile()
2273 .expect("compile");
2274
2275 let resp = app.handle_request(make_req("POST", "/x")).await;
2278 assert_eq!(resp.status(), StatusCode::IM_A_TEAPOT);
2279 let body = http_body_util::BodyExt::collect(resp.into_body())
2280 .await
2281 .unwrap()
2282 .to_bytes();
2283 assert_eq!(&body[..], b"custom-405");
2284 }
2285
2286 #[tokio::test]
2289 async fn test_hoop_wraps_fallback_and_method_not_allowed_fallback() {
2290 async fn get_handler() -> &'static str {
2291 "got"
2292 }
2293 async fn custom_fallback() -> &'static str {
2294 "custom-fallback"
2295 }
2296 async fn custom_405() -> &'static str {
2297 "custom-405"
2298 }
2299 async fn tag_response(req: Request<Body>, next: middleware::Next<()>) -> Response<Body> {
2300 let mut resp = next.run(req).await;
2301 let _ = resp.headers_mut().insert(
2302 hyper::header::HeaderName::from_static("x-hoop"),
2303 hyper::header::HeaderValue::from_static("wrapped"),
2304 );
2305 resp
2306 }
2307
2308 let app = Router::new()
2312 .route("/x", get(get_handler))
2313 .fallback(custom_fallback)
2314 .method_not_allowed_fallback(custom_405)
2315 .hoop(tag_response)
2316 .with_state::<()>(())
2317 .compile()
2318 .expect("compile");
2319
2320 let resp = app.handle_request(make_req("GET", "/x")).await;
2322 assert_eq!(resp.status(), StatusCode::OK);
2323 assert_eq!(
2324 resp.headers().get("x-hoop").expect("wraps route"),
2325 "wrapped"
2326 );
2327
2328 let resp = app.handle_request(make_req("GET", "/missing")).await;
2330 assert_eq!(resp.status(), StatusCode::OK);
2331 assert_eq!(
2332 resp.headers().get("x-hoop").expect("wraps fallback"),
2333 "wrapped"
2334 );
2335 let body = http_body_util::BodyExt::collect(resp.into_body())
2336 .await
2337 .unwrap()
2338 .to_bytes();
2339 assert_eq!(&body[..], b"custom-fallback");
2340
2341 let resp = app.handle_request(make_req("POST", "/x")).await;
2343 assert_eq!(resp.status(), StatusCode::OK);
2344 assert_eq!(
2345 resp.headers().get("x-hoop").expect("wraps 405 fallback"),
2346 "wrapped"
2347 );
2348 let body = http_body_util::BodyExt::collect(resp.into_body())
2349 .await
2350 .unwrap()
2351 .to_bytes();
2352 assert_eq!(&body[..], b"custom-405");
2353 }
2354
2355 #[test]
2358 fn test_compile_returns_duplicate_route_err() {
2359 async fn handler() -> &'static str {
2360 "dup"
2361 }
2362
2363 let router = Router::<()> {
2371 routes: vec![
2372 ("/dup".to_string(), get(handler)),
2373 ("/dup".to_string(), get(handler)),
2374 ],
2375 fallback: None,
2376 method_not_allowed_fallback: None,
2377 state: None,
2378 normalize_trailing_slash: false,
2379 #[cfg(feature = "tower")]
2380 compiled: None,
2381 };
2382
2383 let result = router.compile();
2384 assert!(matches!(result, Err(RouterError::DuplicateRoute(ref p)) if p == "/dup"));
2385 }
2386
2387 #[tokio::test]
2390 async fn test_nest_strips_prefix_preserves_query_string() {
2391 async fn echo_full(uri: hyper::Uri) -> String {
2392 uri.query()
2393 .map_or_else(|| uri.path().to_string(), |q| format!("{}?{q}", uri.path()))
2394 }
2395
2396 let api = Router::new().route("/users/{id}", get(echo_full));
2397 let app = Router::new()
2398 .nest("/api", api)
2399 .with_state::<()>(())
2400 .compile()
2401 .expect("compile");
2402
2403 let resp = app
2404 .handle_request(make_req("GET", "/api/users/42?active=true"))
2405 .await;
2406 assert_eq!(resp.status(), StatusCode::OK);
2407 let body = http_body_util::BodyExt::collect(resp.into_body())
2408 .await
2409 .unwrap()
2410 .to_bytes();
2411 assert_eq!(&body[..], b"/users/42?active=true");
2412 }
2413
2414 #[tokio::test]
2417 async fn test_normalize_trailing_slash_preserves_query_string() {
2418 async fn echo_full(uri: hyper::Uri) -> String {
2419 uri.query()
2420 .map_or_else(|| uri.path().to_string(), |q| format!("{}?{q}", uri.path()))
2421 }
2422
2423 let app = Router::new()
2424 .route("/about", get(echo_full))
2425 .normalize_trailing_slash()
2426 .with_state::<()>(())
2427 .compile()
2428 .expect("compile");
2429
2430 let resp = app.handle_request(make_req("GET", "/about/?x=1")).await;
2431 assert_eq!(resp.status(), StatusCode::OK);
2432 let body = http_body_util::BodyExt::collect(resp.into_body())
2433 .await
2434 .unwrap()
2435 .to_bytes();
2436 assert_eq!(&body[..], b"/about?x=1");
2437 }
2438
2439 #[tokio::test]
2440 async fn test_normalize_trailing_slash_no_trailing_slash_is_untouched() {
2441 async fn handler() -> &'static str {
2442 "no-trailing"
2443 }
2444
2445 let app = Router::new()
2446 .route("/about", get(handler))
2447 .normalize_trailing_slash()
2448 .with_state::<()>(())
2449 .compile()
2450 .expect("compile");
2451
2452 let resp = app.handle_request(make_req("GET", "/about")).await;
2455 assert_eq!(resp.status(), StatusCode::OK);
2456
2457 let root_app = Router::new()
2460 .route("/", get(handler))
2461 .normalize_trailing_slash()
2462 .with_state::<()>(())
2463 .compile()
2464 .expect("compile");
2465 let resp = root_app.handle_request(make_req("GET", "/")).await;
2466 assert_eq!(resp.status(), StatusCode::OK);
2467 }
2468}