Skip to main content

volo_http/server/route/
router.rs

1//! [`Router`] implemententation for [`Server`].
2//!
3//! [`Router`] is a [`Service`] that can route a path to a [`MethodRouter`] or fallback to another
4//! [`Route`].
5//!
6//! See [`Router`] and [`Router::route`] for more details.
7//!
8//! [`Server`]: crate::server::Server
9
10use std::{collections::HashMap, convert::Infallible};
11
12use faststr::FastStr;
13use http::status::StatusCode;
14use metainfo::METAINFO;
15use motore::{ServiceExt, layer::Layer, service::Service};
16use volo::context::Context;
17
18use super::{
19    Fallback, Route,
20    method_router::MethodRouter,
21    utils::{Matcher, NEST_CATCH_PARAM, RouteId, StripPrefixLayer},
22};
23use crate::{
24    body::Body,
25    context::ServerContext,
26    request::Request,
27    response::Response,
28    server::{IntoResponse, handler::Handler},
29};
30
31/// The router for routing path to [`Service`]s or handlers.
32#[must_use]
33pub struct Router<B = Body, E = Infallible> {
34    matcher: Matcher,
35    /// Each route with the pattern reported as [`MatchedPath`]: the registered uri, or the prefix
36    /// for nested routes.
37    routes: HashMap<RouteId, (Endpoint<B, E>, FastStr)>,
38    fallback: Fallback<B, E>,
39    is_default_fallback: bool,
40}
41
42impl<B, E> Default for Router<B, E>
43where
44    B: Send + 'static,
45    E: 'static,
46{
47    fn default() -> Self {
48        Self::new()
49    }
50}
51
52impl<B, E> Router<B, E>
53where
54    B: Send + 'static,
55    E: 'static,
56{
57    /// Create a new router.
58    pub fn new() -> Self
59    where
60        E: 'static,
61    {
62        Self {
63            matcher: Default::default(),
64            routes: Default::default(),
65            fallback: Fallback::from_status_code(StatusCode::NOT_FOUND),
66            is_default_fallback: true,
67        }
68    }
69
70    /// Create a route for the given path with the given [`MethodRouter`].
71    ///
72    /// The uri matcher is based on [`matchit`](https://docs.rs/matchit/0.8.0/matchit/).  It
73    /// supports normal path and parameterized path.
74    ///
75    /// # Examples
76    ///
77    /// ## Normal path
78    ///
79    /// ```
80    /// use volo_http::server::route::{Router, get};
81    ///
82    /// async fn index() -> &'static str {
83    ///     "Hello, World"
84    /// }
85    ///
86    /// let router: Router = Router::new().route("/", get(index));
87    /// ```
88    ///
89    /// ## Path with Named Parameters
90    ///
91    /// Named parameters like `/{id}` match anything until the next `/` or the end of the path.
92    ///
93    /// The params can be extract by extractor `PathParamsMap`:
94    ///
95    /// ```
96    /// use volo::FastStr;
97    /// use volo_http::server::{
98    ///     param::PathParamsMap,
99    ///     route::{Router, get},
100    /// };
101    ///
102    /// async fn param(map: PathParamsMap) -> FastStr {
103    ///     map.get("id").unwrap().clone()
104    /// }
105    ///
106    /// let router: Router = Router::new().route("/user/{id}", get(param));
107    /// ```
108    ///
109    /// Or you can use `PathParams` directly:
110    ///
111    /// ```
112    /// use volo::FastStr;
113    /// use volo_http::server::{
114    ///     param::PathParams,
115    ///     route::{Router, get},
116    /// };
117    ///
118    /// async fn param(PathParams(id): PathParams<String>) -> String {
119    ///     id
120    /// }
121    ///
122    /// let router: Router = Router::new().route("/user/{id}", get(param));
123    /// ```
124    ///
125    /// More than one params are also supported:
126    ///
127    /// ```
128    /// use volo::FastStr;
129    /// use volo_http::server::{
130    ///     param::PathParams,
131    ///     route::{Router, get},
132    /// };
133    ///
134    /// async fn param(PathParams((user, post)): PathParams<(usize, usize)>) -> String {
135    ///     format!("user id: {user}, post id: {post}")
136    /// }
137    ///
138    /// let router: Router = Router::new().route("/user/{user}/post/{post}", get(param));
139    /// ```
140    ///
141    /// ## Path with Catch-all Parameters
142    ///
143    /// Catch-all parameters start with `*` and match anything until the end of the path. They must
144    /// always be at the **end** of the route.
145    ///
146    /// ```
147    /// use volo_http::server::{
148    ///     param::PathParams,
149    ///     route::{Router, get},
150    /// };
151    ///
152    /// async fn index() -> &'static str {
153    ///     "Hello, World"
154    /// }
155    ///
156    /// async fn fallback(PathParams(uri): PathParams<String>) -> String {
157    ///     format!("Path `{uri}` is not available")
158    /// }
159    ///
160    /// let router: Router = Router::new()
161    ///     .route("/", get(index))
162    ///     .route("/index", get(index))
163    ///     .route("/{*fallback}", get(fallback));
164    /// ```
165    ///
166    /// For more usage methods, please refer to:
167    /// [`matchit`](https://docs.rs/matchit/0.8.0/matchit/).
168    pub fn route<S>(mut self, uri: S, method_router: MethodRouter<B, E>) -> Self
169    where
170        S: AsRef<str>,
171    {
172        let route_id = self
173            .matcher
174            .insert(uri.as_ref())
175            .expect("Insert routing rule failed");
176
177        self.routes.insert(
178            route_id,
179            (
180                Endpoint::MethodRouter(method_router),
181                FastStr::new(uri.as_ref()),
182            ),
183        );
184
185        self
186    }
187
188    /// Create a route for the given path with a given [`Router`] and nest it into the current
189    /// router.
190    ///
191    /// The `uri` param is a prefix of the whole uri and will be stripped before calling the inner
192    /// router, and the inner [`Router`] will handle uri without the given prefix, but all params
193    /// will be kept.
194    ///
195    /// # Examples
196    ///
197    /// ```
198    /// use volo_http::server::{
199    ///     param::PathParams,
200    ///     route::{Router, get},
201    /// };
202    ///
203    /// async fn hello_world() -> &'static str {
204    ///     "Hello, World"
205    /// }
206    /// async fn handle_tid(PathParams(tid): PathParams<String>) -> String {
207    ///     tid
208    /// }
209    /// async fn uid_and_tid(PathParams((uid, tid)): PathParams<(String, String)>) -> String {
210    ///     format!("uid: {uid}, tid: {tid}")
211    /// }
212    ///
213    /// let post_router = Router::new()
214    ///     // http://<SERVER>/post/
215    ///     .route("/", get(hello_world))
216    ///     // http://<SERVER>/post/114
217    ///     .route("/{tid}", get(handle_tid));
218    /// let user_router = Router::new()
219    ///     // http://<SERVER>/user/114/name
220    ///     .route("/name", get(hello_world))
221    ///     // http://<SERVER>/user/114/tid/514
222    ///     .route("/post/{tid}", get(uid_and_tid));
223    ///
224    /// let router: Router = Router::new()
225    ///     .nest("/post", post_router)
226    ///     .nest("/user/{uid}/", user_router);
227    /// ```
228    pub fn nest<U>(self, uri: U, router: Router<B, E>) -> Self
229    where
230        U: AsRef<str>,
231    {
232        self.nest_route(uri.as_ref().to_owned(), Route::new(router))
233    }
234
235    /// Create a route for the given path with a given [`Service`] and nest it into the current
236    /// router.
237    ///
238    /// The service will handle any uri with the param `uri` as its prefix.
239    pub fn nest_service<U, S>(self, uri: U, service: S) -> Self
240    where
241        U: AsRef<str>,
242        S: Service<ServerContext, Request<B>, Error = E> + Send + Sync + 'static,
243        S::Response: IntoResponse,
244    {
245        self.nest_route(
246            uri.as_ref().to_owned(),
247            Route::new(service.map_response(IntoResponse::into_response)),
248        )
249    }
250
251    fn nest_route(mut self, prefix: String, route: Route<B, E>) -> Self {
252        let uri = if prefix.ends_with('/') {
253            format!("{prefix}{NEST_CATCH_PARAM}")
254        } else {
255            format!("{prefix}/{NEST_CATCH_PARAM}")
256        };
257
258        // Because we use `matchit::Router` for matching uri, for `/{*catch}`, `/xxx` matches it
259        // but `/` does not match. To solve the problem, we should also insert `/` for handling it.
260        let route_id = self
261            .matcher
262            .insert(prefix.clone())
263            .expect("Insert routing rule failed");
264        let pattern = FastStr::new(prefix.trim_end_matches('/'));
265
266        // If user uses `router.nest("/user", another)`, `/user`, `/user/`, `/user/{*catch}` should
267        // be inserted. But if user uses `/user/`, we will insert `/user/` and `/user/{*catch}`
268        // only.
269        if !prefix.ends_with('/') {
270            let prefix_with_slash = prefix + "/";
271            self.matcher
272                .insert_with_id(prefix_with_slash, route_id)
273                .expect("Insert routing rule failed");
274        }
275
276        self.matcher
277            .insert_with_id(uri, route_id)
278            .expect("Insert routing rule failed");
279
280        self.routes.insert(
281            route_id,
282            (
283                Endpoint::Service(Route::new(StripPrefixLayer.layer(route))),
284                pattern,
285            ),
286        );
287
288        self
289    }
290
291    /// Set a global fallback for router.
292    ///
293    /// If there is no route matches the current uri, router will call the fallback handler.
294    ///
295    /// Default is returning "404 Not Found".
296    pub fn fallback<H, T>(mut self, handler: H) -> Self
297    where
298        for<'a> H: Handler<T, B, E> + Clone + Send + Sync + 'a,
299        T: 'static,
300        E: 'static,
301    {
302        self.fallback = Fallback::from_handler(handler);
303        self
304    }
305
306    /// Set a global fallback for router.
307    ///
308    /// If there is no route matches the current uri, router will call the fallback service.
309    ///
310    /// Default is returning "404 Not Found".
311    pub fn fallback_service<S>(mut self, service: S) -> Self
312    where
313        for<'a> S: Service<ServerContext, Request<B>, Error = E> + Send + Sync + 'a,
314        S::Response: IntoResponse,
315    {
316        self.fallback = Fallback::from_service(service);
317        self
318    }
319
320    /// Merge another router to self.
321    ///
322    /// # Panics
323    ///
324    /// - Panics if the two router have routes with the same path.
325    ///
326    /// # Examples
327    ///
328    /// ```
329    /// use volo_http::server::route::{Router, get};
330    ///
331    /// async fn index() -> &'static str {
332    ///     "Hello, World"
333    /// }
334    ///
335    /// fn foo_router() -> Router {
336    ///     Router::new()
337    ///         .route("/foo/", get(index))
338    ///         .route("/foo/index", get(index))
339    /// }
340    ///
341    /// fn bar_router() -> Router {
342    ///     Router::new()
343    ///         .route("/bar/", get(index))
344    ///         .route("/bar/index", get(index))
345    /// }
346    ///
347    /// fn baz_router() -> Router {
348    ///     Router::new()
349    ///         .route("/baz/", get(index))
350    ///         .route("/baz/index", get(index))
351    /// }
352    ///
353    /// let app = Router::new()
354    ///     .merge(foo_router())
355    ///     .merge(bar_router())
356    ///     .merge(baz_router());
357    /// ```
358    pub fn merge(mut self, other: Self) -> Self {
359        let Router {
360            mut matcher,
361            mut routes,
362            fallback,
363            is_default_fallback,
364        } = other;
365
366        for (path, route_id) in matcher.drain() {
367            self.matcher
368                .insert_with_id(path, route_id)
369                .expect("Insert routing rule failed during merging router");
370        }
371        for (route_id, route) in routes.drain() {
372            if self.routes.insert(route_id, route).is_some() {
373                unreachable!()
374            }
375        }
376
377        match (self.is_default_fallback, is_default_fallback) {
378            (_, true) => {}
379            (true, false) => {
380                self.fallback = fallback;
381                self.is_default_fallback = false;
382            }
383            (false, false) => {
384                panic!("Merge `Router` failed because both `Router` have customized `fallback`")
385            }
386        }
387
388        self
389    }
390
391    /// Add a new inner layer to all routes in router.
392    ///
393    /// The layer's `Service` should be `Send + Sync + 'static`.
394    pub fn layer<L, B2, E2>(self, l: L) -> Router<B2, E2>
395    where
396        L: Layer<Route<B, E>> + Clone + Send + Sync + 'static,
397        L::Service: Service<ServerContext, Request<B2>, Error = E2> + Send + Sync + 'static,
398        <L::Service as Service<ServerContext, Request<B2>>>::Response: IntoResponse,
399        B2: 'static,
400    {
401        let routes = self
402            .routes
403            .into_iter()
404            .map(|(id, (route, pattern))| {
405                let route = route.layer(l.clone());
406                (id, (route, pattern))
407            })
408            .collect();
409
410        let fallback = self.fallback.layer(l.clone());
411
412        Router {
413            matcher: self.matcher,
414            routes,
415            fallback,
416            is_default_fallback: self.is_default_fallback,
417        }
418    }
419}
420
421impl<B, E> Service<ServerContext, Request<B>> for Router<B, E>
422where
423    B: Send + 'static,
424    E: 'static,
425{
426    type Response = Response;
427    type Error = E;
428
429    async fn call(
430        &self,
431        cx: &mut ServerContext,
432        req: Request<B>,
433    ) -> Result<Self::Response, Self::Error> {
434        if let Ok(matched) = self.matcher.at(req.uri().clone().path()) {
435            if let Some((route, pattern)) = self.routes.get(matched.value) {
436                if !matched.params.is_empty() {
437                    cx.params_mut().extend(matched.params);
438                }
439                MatchedPath::record(cx, pattern);
440                return route.call(cx, req).await;
441            }
442        }
443
444        // No route handles this request. An outer router may already have recorded its prefix
445        // before delegating here, so clear it: a fallback is not a route.
446        cx.extensions_mut().remove::<MatchedPath>();
447        let _ = METAINFO.try_with(|mi| mi.borrow_mut().remove_faststr::<MatchedPath>());
448
449        self.fallback.call(cx, req).await
450    }
451}
452
453/// The route pattern that matched the current request, e.g. `/users/{id}`.
454///
455/// Stored in [`ServerContext`] extensions for logging and metrics, this provides
456/// a stable route label instead of the concrete request path, keeping metric
457/// tag cardinality bounded.
458///
459/// For nested routers, the pattern combines each matched prefix with the inner
460/// route pattern. For [`Router::nest_service`], only the prefix is recorded.
461/// No pattern is recorded for requests handled by a fallback.
462///
463/// The same pattern is also stored in the request's [`METAINFO`] when one is in scope (the server
464/// always provides one), as a plain [`FastStr`] keyed by this type, so observers without access to
465/// the context, such as a future wrapper measuring the request from outside the router, can read it
466/// with `METAINFO.with(|mi| mi.borrow().get_faststr::<MatchedPath>().cloned())`.
467///
468/// ```
469/// use volo::context::Context;
470/// use volo_http::{context::ServerContext, server::route::MatchedPath};
471///
472/// fn route_of(cx: &ServerContext) -> Option<&str> {
473///     cx.extensions()
474///         .get::<MatchedPath>()
475///         .map(MatchedPath::as_str)
476/// }
477/// ```
478#[derive(Clone, Debug, PartialEq, Eq)]
479pub struct MatchedPath(FastStr);
480
481impl MatchedPath {
482    /// The pattern as a string slice.
483    pub fn as_str(&self) -> &str {
484        &self.0
485    }
486
487    /// Consume the value and return the pattern.
488    pub fn into_inner(self) -> FastStr {
489        self.0
490    }
491
492    fn record(cx: &mut ServerContext, pattern: &FastStr) {
493        let extensions = cx.extensions_mut();
494        let matched = match extensions.get_mut::<MatchedPath>() {
495            // Called from a nested router: the outer router recorded its prefix already.
496            Some(outer) => {
497                outer.0 = FastStr::from_string(outer.0.to_string() + pattern);
498                outer.0.clone()
499            }
500            None => {
501                extensions.insert(MatchedPath(pattern.clone()));
502                pattern.clone()
503            }
504        };
505        let _ = METAINFO.try_with(|mi| mi.borrow_mut().insert_faststr::<MatchedPath>(matched));
506    }
507}
508
509impl std::ops::Deref for MatchedPath {
510    type Target = str;
511
512    fn deref(&self) -> &Self::Target {
513        &self.0
514    }
515}
516
517impl AsRef<str> for MatchedPath {
518    fn as_ref(&self) -> &str {
519        &self.0
520    }
521}
522
523#[allow(clippy::large_enum_variant)]
524enum Endpoint<B = Body, E = Infallible> {
525    MethodRouter(MethodRouter<B, E>),
526    Service(Route<B, E>),
527}
528
529impl<B, E> Service<ServerContext, Request<B>> for Endpoint<B, E>
530where
531    B: Send + 'static,
532    E: 'static,
533{
534    type Response = Response;
535    type Error = E;
536
537    async fn call(
538        &self,
539        cx: &mut ServerContext,
540        req: Request<B>,
541    ) -> Result<Self::Response, Self::Error> {
542        match self {
543            Self::MethodRouter(mr) => mr.call(cx, req).await,
544            Self::Service(service) => service.call(cx, req).await,
545        }
546    }
547}
548
549impl<B, E> Default for Endpoint<B, E>
550where
551    B: Send + 'static,
552    E: 'static,
553{
554    fn default() -> Self {
555        Self::MethodRouter(Default::default())
556    }
557}
558
559impl<B, E> Endpoint<B, E>
560where
561    B: Send + 'static,
562    E: 'static,
563{
564    fn layer<L, B2, E2>(self, l: L) -> Endpoint<B2, E2>
565    where
566        L: Layer<Route<B, E>> + Clone + Send + Sync + 'static,
567        L::Service: Service<ServerContext, Request<B2>, Error = E2> + Send + Sync,
568        <L::Service as Service<ServerContext, Request<B2>>>::Response: IntoResponse,
569        B2: 'static,
570    {
571        match self {
572            Self::MethodRouter(mr) => Endpoint::MethodRouter(mr.layer(l)),
573            Self::Service(s) => Endpoint::Service(Route::new(
574                l.layer(s).map_response(IntoResponse::into_response),
575            )),
576        }
577    }
578}
579
580#[cfg(test)]
581mod router_tests {
582    use faststr::FastStr;
583    use http::{method::Method, status::StatusCode, uri::Uri};
584    use motore::service::Service;
585    use volo::context::Context;
586
587    use super::{MatchedPath, Router};
588    use crate::{
589        body::{Body, BodyConversion},
590        request::Request,
591        server::{
592            Server,
593            param::PathParamsVec,
594            route::method_router::any,
595            test_helpers::{TestServer, empty_cx},
596        },
597    };
598
599    async fn always_ok() {}
600    async fn teapot() -> StatusCode {
601        StatusCode::IM_A_TEAPOT
602    }
603
604    #[tokio::test]
605    async fn url_match() {
606        async fn is_ok(server: &TestServer<Router<Option<Body>>, Option<Body>>, uri: &str) -> bool {
607            server.call_route(Method::GET, uri, None).await.status() == StatusCode::OK
608        }
609        let router: Router<Option<Body>> = Router::new()
610            .route("/", any(always_ok))
611            .route("/catch/{id}", any(always_ok))
612            .route("/catch/{id}/another", any(always_ok))
613            .route("/catch/{id}/another/{uid}", any(always_ok))
614            .route("/catch/{id}/another/{uid}/again", any(always_ok))
615            .route("/catch/{id}/another/{uid}/again/{tid}", any(always_ok))
616            .route("/catch_all/{*all}", any(always_ok));
617        let server = Server::new(router).into_test_server();
618
619        assert!(is_ok(&server, "/").await);
620        assert!(is_ok(&server, "/catch/114").await);
621        assert!(is_ok(&server, "/catch/514").await);
622        assert!(is_ok(&server, "/catch/ll45l4").await);
623        assert!(is_ok(&server, "/catch/ll45l4/another").await);
624        assert!(is_ok(&server, "/catch/ll45l4/another/1919").await);
625        assert!(is_ok(&server, "/catch/ll45l4/another/1919/again").await);
626        assert!(is_ok(&server, "/catch/ll45l4/another/1919/again/810").await);
627        assert!(is_ok(&server, "/catch_all/114").await);
628        assert!(is_ok(&server, "/catch_all/114/514/1919/810").await);
629
630        assert!(!is_ok(&server, "/catch").await);
631        assert!(!is_ok(&server, "/catch/114/").await);
632        assert!(!is_ok(&server, "/catch/114/another/514/").await);
633        assert!(!is_ok(&server, "/catch/11/another/45/again/14/").await);
634        assert!(!is_ok(&server, "/catch_all").await);
635        assert!(!is_ok(&server, "/catch_all/").await);
636    }
637
638    #[tokio::test]
639    async fn matched_path() {
640        async fn matched_path_of(router: &Router<Option<Body>>, uri: &str) -> Option<String> {
641            let mut cx = empty_cx();
642            let req = Request::builder()
643                .method(Method::GET)
644                .uri(uri)
645                .body(None)
646                .unwrap();
647            let _ = router.call(&mut cx, req).await;
648            cx.extensions()
649                .get::<MatchedPath>()
650                .map(|path| path.as_str().to_owned())
651        }
652
653        let router: Router<Option<Body>> = Router::new()
654            .route("/", any(always_ok))
655            .route("/users/{id}", any(always_ok))
656            .route("/files/{*path}", any(always_ok))
657            .nest(
658                "/api/{version}",
659                Router::new().route("/items/{id}", any(always_ok)),
660            )
661            .nest("/legacy/", Router::new().route("/", any(always_ok)))
662            .nest(
663                "/v/{ver}",
664                Router::new().nest("/deep", Router::new().route("/leaf/{id}", any(always_ok))),
665            )
666            .merge(Router::new().route("/merged/{id}", any(always_ok)));
667
668        assert_eq!(matched_path_of(&router, "/").await.as_deref(), Some("/"));
669        // Different values, same route, same pattern.
670        assert_eq!(
671            matched_path_of(&router, "/users/42").await.as_deref(),
672            Some("/users/{id}")
673        );
674        assert_eq!(
675            matched_path_of(&router, "/users/43").await.as_deref(),
676            Some("/users/{id}")
677        );
678        assert_eq!(
679            matched_path_of(&router, "/files/a/b/c").await.as_deref(),
680            Some("/files/{*path}")
681        );
682        // Nested routers prepend their prefix.
683        assert_eq!(
684            matched_path_of(&router, "/api/v2/items/7").await.as_deref(),
685            Some("/api/{version}/items/{id}")
686        );
687        assert_eq!(
688            matched_path_of(&router, "/legacy/").await.as_deref(),
689            Some("/legacy/")
690        );
691        // Two levels of nesting: every prefix is composed, not only the innermost one.
692        assert_eq!(
693            matched_path_of(&router, "/v/3/deep/leaf/9")
694                .await
695                .as_deref(),
696            Some("/v/{ver}/deep/leaf/{id}")
697        );
698        // Merged routers keep their patterns.
699        assert_eq!(
700            matched_path_of(&router, "/merged/1").await.as_deref(),
701            Some("/merged/{id}")
702        );
703        // Fallbacks, top-level or inside a nested router, record nothing.
704        assert_eq!(matched_path_of(&router, "/nope").await, None);
705        assert_eq!(matched_path_of(&router, "/api/v2/nope").await, None);
706    }
707
708    #[tokio::test]
709    async fn matched_path_is_mirrored_into_metainfo() {
710        use std::cell::RefCell;
711
712        use metainfo::{METAINFO, MetaInfo};
713
714        // (from the context, from METAINFO) after one request
715        async fn both(
716            router: &Router<Option<Body>>,
717            uri: &str,
718        ) -> (Option<String>, Option<String>) {
719            METAINFO
720                .scope(RefCell::new(MetaInfo::default()), async {
721                    let mut cx = empty_cx();
722                    let req = Request::builder()
723                        .method(Method::GET)
724                        .uri(uri)
725                        .body(None)
726                        .unwrap();
727                    let _ = router.call(&mut cx, req).await;
728                    let from_cx = cx
729                        .extensions()
730                        .get::<MatchedPath>()
731                        .map(|p| p.as_str().to_owned());
732                    let from_mi = METAINFO.with(|mi| {
733                        mi.borrow()
734                            .get_faststr::<MatchedPath>()
735                            .map(|p| p.as_str().to_owned())
736                    });
737                    (from_cx, from_mi)
738                })
739                .await
740        }
741
742        let router: Router<Option<Body>> = Router::new().route("/users/{id}", any(always_ok)).nest(
743            "/api/{version}",
744            Router::new().route("/items/{id}", any(always_ok)),
745        );
746
747        let (cx, mi) = both(&router, "/users/42").await;
748        assert_eq!(cx.as_deref(), Some("/users/{id}"));
749        assert_eq!(mi, cx);
750
751        // Nested: METAINFO holds the composed pattern, not just the outer prefix.
752        let (cx, mi) = both(&router, "/api/v2/items/7").await;
753        assert_eq!(cx.as_deref(), Some("/api/{version}/items/{id}"));
754        assert_eq!(mi, cx);
755
756        // Cleared in both places on fallback, including after a nested prefix matched.
757        assert_eq!(both(&router, "/nope").await, (None, None));
758        assert_eq!(both(&router, "/api/v2/nope").await, (None, None));
759    }
760
761    #[tokio::test]
762    async fn matched_path_is_visible_mid_request() {
763        use std::cell::RefCell;
764
765        use metainfo::{METAINFO, MetaInfo};
766
767        use crate::{
768            context::ServerContext,
769            response::Response,
770            server::{
771                IntoResponse,
772                middleware::{Next, from_fn},
773            },
774        };
775
776        fn current() -> Option<String> {
777            METAINFO.with(|mi| {
778                mi.borrow()
779                    .get_faststr::<MatchedPath>()
780                    .map(|p| p.as_str().to_owned())
781            })
782        }
783
784        // A handler runs after the innermost match, so it must see the complete pattern.
785        async fn echo_matched_path() -> String {
786            current().unwrap_or_default()
787        }
788
789        // What a layer applied to the outer router sees at the start and at the end of its call.
790        struct Seen {
791            start: Option<String>,
792            end: Option<String>,
793        }
794
795        async fn probe(
796            cx: &mut ServerContext,
797            req: Request<Option<Body>>,
798            next: Next<Option<Body>>,
799        ) -> Response {
800            let start = current();
801            let resp = next.run(cx, req).await.into_response();
802            cx.extensions_mut().insert(Seen {
803                start,
804                end: current(),
805            });
806            resp
807        }
808
809        async fn run(router: &Router<Option<Body>>, uri: &str) -> (String, Seen) {
810            METAINFO
811                .scope(RefCell::new(MetaInfo::default()), async {
812                    let mut cx = empty_cx();
813                    let req = Request::builder()
814                        .method(Method::GET)
815                        .uri(uri)
816                        .body(None)
817                        .unwrap();
818                    let resp = router.call(&mut cx, req).await.unwrap();
819                    let body = resp.into_body().into_string().await.unwrap();
820                    let seen = cx.extensions_mut().remove::<Seen>().unwrap();
821                    (body, seen)
822                })
823                .await
824        }
825
826        let router: Router<Option<Body>> = Router::new()
827            .route("/users/{id}", any(echo_matched_path))
828            .nest(
829                "/api/{version}",
830                Router::new().route("/items/{id}", any(echo_matched_path)),
831            )
832            .layer(from_fn(probe));
833
834        // Plain route: the layer wraps the leaf, so the pattern is complete from the start.
835        let (body, seen) = run(&router, "/users/42").await;
836        assert_eq!(body, "/users/{id}");
837        assert_eq!(seen.start.as_deref(), Some("/users/{id}"));
838        assert_eq!(seen.end.as_deref(), Some("/users/{id}"));
839
840        // Nested route: the layer wraps the nested endpoint, so when its call starts only the
841        // prefix has matched; the handler and the end of the call see the complete pattern.
842        let (body, seen) = run(&router, "/api/v2/items/7").await;
843        assert_eq!(body, "/api/{version}/items/{id}");
844        assert_eq!(seen.start.as_deref(), Some("/api/{version}"));
845        assert_eq!(seen.end.as_deref(), Some("/api/{version}/items/{id}"));
846    }
847
848    #[tokio::test]
849    async fn router_fallback() {
850        async fn is_teapot(
851            server: &TestServer<Router<Option<Body>>, Option<Body>>,
852            uri: &str,
853        ) -> bool {
854            server.call_route(Method::GET, uri, None).await.status() == StatusCode::IM_A_TEAPOT
855        }
856        let router: Router<Option<Body>> = Router::new()
857            .route("/", any(always_ok))
858            .route("/catch/{id}", any(always_ok))
859            .route("/catch_all/{*all}", any(always_ok))
860            .fallback(teapot);
861        let server = Server::new(router).into_test_server();
862
863        assert!(is_teapot(&server, "//").await);
864        assert!(is_teapot(&server, "/catch/").await);
865        assert!(is_teapot(&server, "/catch_all/").await);
866
867        assert!(!is_teapot(&server, "/catch/114").await);
868        assert!(!is_teapot(&server, "/catch_all/514").await);
869        assert!(!is_teapot(&server, "/catch_all/114/514/1919/810").await);
870    }
871
872    #[tokio::test]
873    async fn nest_router() {
874        async fn uri_and_params(uri: Uri, params: PathParamsVec) -> String {
875            let mut v = vec![FastStr::from_string(uri.to_string())];
876            v.extend(params.into_iter().map(|(_, v)| v));
877            v.join("\n")
878        }
879        async fn get_res(
880            server: &TestServer<Router<Option<Body>>, Option<Body>>,
881            uri: &str,
882        ) -> String {
883            server
884                .call_route(Method::GET, uri, None)
885                .await
886                .into_string()
887                .await
888                .unwrap()
889        }
890
891        let router: Router<Option<Body>> = Router::new()
892            .nest(
893                // uri prefix without final slash ('/')
894                "/test-1",
895                Router::new()
896                    .route("/", any(uri_and_params))
897                    .route("/id/{id}", any(uri_and_params))
898                    .route("/catch/{*content}", any(uri_and_params)),
899            )
900            .nest(
901                // uri prefix with final slash ('/')
902                "/test-2/",
903                Router::new()
904                    .route("/", any(uri_and_params))
905                    .route("/id/{id}", any(uri_and_params))
906                    .route("/catch/{*content}", any(uri_and_params)),
907            )
908            .nest(
909                // uri prefix with a param without final slash ('/')
910                "/test-3/{catch}",
911                Router::new()
912                    .route("/", any(uri_and_params))
913                    .route("/id/{id}", any(uri_and_params))
914                    .route("/catch/{*content}", any(uri_and_params)),
915            )
916            .nest(
917                // uri prefix with a param and final slash ('/')
918                "/test-4/{catch}/",
919                Router::new()
920                    .route("/", any(uri_and_params))
921                    .route("/id/{id}", any(uri_and_params))
922                    .route("/catch/{*content}", any(uri_and_params)),
923            );
924        let server = Server::new(router).into_test_server();
925
926        // We register it as `/test-1`, so it does match.
927        assert_eq!(get_res(&server, "/test-1").await, "/");
928        assert_eq!(get_res(&server, "/test-1/").await, "/");
929        assert_eq!(get_res(&server, "/test-1/id/114").await, "/id/114\n114");
930        assert_eq!(
931            get_res(&server, "/test-1/catch/114/514/1919/810").await,
932            "/catch/114/514/1919/810\n114/514/1919/810",
933        );
934
935        // We register it as `/test-2/`, so `/test-2` does not match, but `/test-2/` does match.
936        assert!(get_res(&server, "/test-2").await.is_empty());
937        assert_eq!(get_res(&server, "/test-2/").await, "/");
938        assert_eq!(get_res(&server, "/test-2/id/114").await, "/id/114\n114");
939        assert_eq!(
940            get_res(&server, "/test-2/catch/114/514/1919/810").await,
941            "/catch/114/514/1919/810\n114/514/1919/810",
942        );
943
944        // The first param can be kept.
945        assert_eq!(get_res(&server, "/test-3/114").await, "/\n114");
946        assert_eq!(get_res(&server, "/test-3/114/").await, "/\n114");
947        assert_eq!(
948            get_res(&server, "/test-3/114/id/514").await,
949            "/id/514\n114\n514",
950        );
951        assert_eq!(
952            get_res(&server, "/test-3/114/catch/514/1919/810").await,
953            "/catch/514/1919/810\n114\n514/1919/810",
954        );
955
956        // It is also empty.
957        assert!(get_res(&server, "/test-4/114").await.is_empty());
958        assert_eq!(get_res(&server, "/test-4/114/").await, "/\n114");
959        assert_eq!(
960            get_res(&server, "/test-4/114/id/514").await,
961            "/id/514\n114\n514",
962        );
963        assert_eq!(
964            get_res(&server, "/test-4/114/catch/514/1919/810").await,
965            "/catch/514/1919/810\n114\n514/1919/810",
966        );
967    }
968
969    #[tokio::test]
970    async fn deep_nest_router() {
971        async fn uri_and_params(uri: Uri, params: PathParamsVec) -> String {
972            let mut v = vec![FastStr::from_string(uri.to_string())];
973            v.extend(params.into_iter().map(|(_, v)| v));
974            v.join("\n")
975        }
976        async fn get_res(
977            server: &TestServer<Router<Option<Body>>, Option<Body>>,
978            uri: &str,
979        ) -> String {
980            server
981                .call_route(Method::GET, uri, None)
982                .await
983                .into_string()
984                .await
985                .unwrap()
986        }
987
988        // rule: `/test-1/{catch1}/test-2/{catch2}/test-3/{nest-route}`
989        let router: Router<Option<Body>> = Router::new().nest(
990            "/test-1/{catch1}",
991            Router::new().nest(
992                "/test-2/{catch2}/",
993                Router::new().nest(
994                    "/test-3",
995                    Router::new()
996                        .route("/", any(uri_and_params))
997                        .route("/id/{id}", any(uri_and_params))
998                        .route("/catch/{*content}", any(uri_and_params)),
999                ),
1000            ),
1001        );
1002        let server = Server::new(router).into_test_server();
1003
1004        // catch1: 114
1005        // catch2: 514
1006        // inner-uri: /
1007        assert_eq!(
1008            get_res(&server, "/test-1/114/test-2/514/test-3/").await,
1009            "/\n114\n514",
1010        );
1011        // catch1: 114
1012        // catch2: 514
1013        // inner-uri: /id/1919
1014        // id: 1919
1015        assert_eq!(
1016            get_res(&server, "/test-1/114/test-2/514/test-3/id/1919").await,
1017            "/id/1919\n114\n514\n1919",
1018        );
1019        // catch1: 114
1020        // catch2: 514
1021        // inner-uri: /id/1919
1022        // id: 1919
1023        assert_eq!(
1024            get_res(&server, "/test-1/114/test-2/514/test-3/catch/1919/810").await,
1025            "/catch/1919/810\n114\n514\n1919/810",
1026        );
1027    }
1028
1029    #[tokio::test]
1030    async fn nest_router_with_query() {
1031        async fn get_query(uri: Uri) -> Result<String, StatusCode> {
1032            if let Some(query) = uri.query() {
1033                Ok(query.to_owned())
1034            } else {
1035                Err(StatusCode::BAD_REQUEST)
1036            }
1037        }
1038        async fn get_res(
1039            server: &TestServer<Router<Option<Body>>, Option<Body>>,
1040            uri: &str,
1041        ) -> Result<String, StatusCode> {
1042            let resp = server.call_route(Method::GET, uri, None).await;
1043            if resp.status().is_success() {
1044                Ok(resp
1045                    .into_string()
1046                    .await
1047                    .expect("response is not a valid string"))
1048            } else {
1049                Err(resp.status())
1050            }
1051        }
1052
1053        let router: Router<Option<Body>> =
1054            Router::new().nest("/nest", Router::new().route("/query", any(get_query)));
1055        let server = Server::new(router).into_test_server();
1056
1057        assert_eq!(
1058            get_res(&server, "/nest/query?foo=bar").await.unwrap(),
1059            "foo=bar",
1060        );
1061        assert_eq!(get_res(&server, "/nest/query?foo").await.unwrap(), "foo");
1062        assert_eq!(get_res(&server, "/nest/query?").await.unwrap(), "");
1063        assert!(get_res(&server, "/nest/query").await.is_err());
1064    }
1065
1066    #[tokio::test]
1067    async fn deep_nest_router_with_query() {
1068        async fn get_query(uri: Uri) -> Result<String, StatusCode> {
1069            if let Some(query) = uri.query() {
1070                Ok(query.to_owned())
1071            } else {
1072                Err(StatusCode::BAD_REQUEST)
1073            }
1074        }
1075        async fn get_res(
1076            server: &TestServer<Router<Option<Body>>, Option<Body>>,
1077            uri: &str,
1078        ) -> Result<String, StatusCode> {
1079            let resp = server.call_route(Method::GET, uri, None).await;
1080            if resp.status().is_success() {
1081                Ok(resp
1082                    .into_string()
1083                    .await
1084                    .expect("response is not a valid string"))
1085            } else {
1086                Err(resp.status())
1087            }
1088        }
1089
1090        let router: Router<Option<Body>> = Router::new().nest(
1091            "/nest-1",
1092            Router::new().nest(
1093                "/nest-2",
1094                Router::new().nest("/nest-3", Router::new().route("/query", any(get_query))),
1095            ),
1096        );
1097        let server = Server::new(router).into_test_server();
1098
1099        assert_eq!(
1100            get_res(&server, "/nest-1/nest-2/nest-3/query?foo=bar")
1101                .await
1102                .unwrap(),
1103            "foo=bar",
1104        );
1105        assert_eq!(
1106            get_res(&server, "/nest-1/nest-2/nest-3/query?foo")
1107                .await
1108                .unwrap(),
1109            "foo",
1110        );
1111        assert_eq!(
1112            get_res(&server, "/nest-1/nest-2/nest-3/query?")
1113                .await
1114                .unwrap(),
1115            "",
1116        );
1117        assert!(
1118            get_res(&server, "/nest-1/nest-2/nest-3/query")
1119                .await
1120                .is_err()
1121        );
1122    }
1123}