salvo_core/routing/
router.rs

1use std::fmt::{self, Debug, Formatter};
2use std::sync::Arc;
3use std::sync::atomic::{AtomicUsize, Ordering};
4
5use super::filters::{self, FnFilter, PathFilter};
6use super::{DetectMatched, Filter, PathState};
7use crate::handler::{Handler, WhenHoop};
8use crate::http::uri::Scheme;
9use crate::{Depot, Request};
10
11/// Route request to different handlers.
12///
13/// View [module level documentation](index.html) for more details.
14#[non_exhaustive]
15pub struct Router {
16    #[doc(hidden)]
17    pub id: usize,
18    /// The children of current router.
19    pub routers: Vec<Self>,
20    /// The filters of current router.
21    pub filters: Vec<Box<dyn Filter>>,
22    /// The middlewares of current router.
23    pub hoops: Vec<Arc<dyn Handler>>,
24    /// The final handler to handle request of current router.
25    pub goal: Option<Arc<dyn Handler>>,
26}
27
28impl Default for Router {
29    #[inline]
30    fn default() -> Self {
31        Self::new()
32    }
33}
34static NEXT_ROUTER_ID: AtomicUsize = AtomicUsize::new(1);
35
36impl Router {
37    /// Create a new `Router`.
38    #[inline]
39    pub fn new() -> Self {
40        Self {
41            id: NEXT_ROUTER_ID.fetch_add(1, Ordering::Relaxed),
42            routers: Vec::new(),
43            filters: Vec::new(),
44            hoops: Vec::new(),
45            goal: None,
46        }
47    }
48
49    /// Get current router's children reference.
50    #[inline]
51    #[must_use]
52    pub fn routers(&self) -> &Vec<Self> {
53        &self.routers
54    }
55    /// Get current router's children mutable reference.
56    #[inline]
57    pub fn routers_mut(&mut self) -> &mut Vec<Self> {
58        &mut self.routers
59    }
60
61    /// Get current router's middlewares reference.
62    #[inline]
63    #[must_use]
64    pub fn hoops(&self) -> &Vec<Arc<dyn Handler>> {
65        &self.hoops
66    }
67    /// Get current router's middlewares mutable reference.
68    #[inline]
69    pub fn hoops_mut(&mut self) -> &mut Vec<Arc<dyn Handler>> {
70        &mut self.hoops
71    }
72
73    /// Get current router's filters reference.
74    #[inline]
75    #[must_use]
76    pub fn filters(&self) -> &Vec<Box<dyn Filter>> {
77        &self.filters
78    }
79    /// Get current router's filters mutable reference.
80    #[inline]
81    pub fn filters_mut(&mut self) -> &mut Vec<Box<dyn Filter>> {
82        &mut self.filters
83    }
84
85    /// Detect current router is matched for current request.
86    pub async fn detect(
87        &self,
88        req: &mut Request,
89        path_state: &mut PathState,
90    ) -> Option<DetectMatched> {
91        Box::pin(async move {
92            for filter in &self.filters {
93                if !filter.filter(req, path_state).await {
94                    return None;
95                }
96            }
97            if !self.routers.is_empty() {
98                let original_cursor = path_state.cursor;
99                #[cfg(feature = "matched-path")]
100                let original_matched_parts_len = path_state.matched_parts.len();
101                for child in &self.routers {
102                    if let Some(dm) = child.detect(req, path_state).await {
103                        return Some(DetectMatched {
104                            hoops: [&self.hoops[..], &dm.hoops[..]].concat(),
105                            goal: dm.goal.clone(),
106                        });
107                    } else {
108                        #[cfg(feature = "matched-path")]
109                        path_state
110                            .matched_parts
111                            .truncate(original_matched_parts_len);
112                        path_state.cursor = original_cursor;
113                    }
114                }
115            }
116            if path_state.is_ended() {
117                path_state.once_ended = true;
118                if let Some(goal) = &self.goal {
119                    return Some(DetectMatched {
120                        hoops: self.hoops.clone(),
121                        goal: goal.clone(),
122                    });
123                }
124            }
125            None
126        })
127        .await
128    }
129
130    /// Insert a router at the beginning of current router, shifting all routers after it to the right.
131    #[inline]
132    #[must_use]
133    pub fn unshift(mut self, router: Self) -> Self {
134        self.routers.insert(0, router);
135        self
136    }
137    /// Insert a router at position `index` within current router, shifting all routers after it to the right.
138    #[inline]
139    #[must_use]
140    pub fn insert(mut self, index: usize, router: Self) -> Self {
141        self.routers.insert(index, router);
142        self
143    }
144
145    /// Push a router as child of current router.
146    #[inline]
147    #[must_use]
148    pub fn push(mut self, router: Self) -> Self {
149        self.routers.push(router);
150        self
151    }
152    /// Append all routers in a Vec as children of current router.
153    #[inline]
154    #[must_use]
155    pub fn append(mut self, others: &mut Vec<Self>) -> Self {
156        self.routers.append(others);
157        self
158    }
159
160    /// Add a handler as middleware, it will run the handler in current router or it's descendants
161    /// handle the request.
162    #[inline]
163    #[must_use]
164    pub fn with_hoop<H: Handler>(hoop: H) -> Self {
165        Self::new().hoop(hoop)
166    }
167
168    /// Add a handler as middleware, it will run the handler in current router or it's descendants
169    /// handle the request. This middleware is only effective when the filter returns true..
170    #[inline]
171    #[must_use]
172    pub fn with_hoop_when<H, F>(hoop: H, filter: F) -> Self
173    where
174        H: Handler,
175        F: Fn(&Request, &Depot) -> bool + Send + Sync + 'static,
176    {
177        Self::new().hoop_when(hoop, filter)
178    }
179
180    /// Add a handler as middleware, it will run the handler in current router or it's descendants
181    /// handle the request.
182    #[inline]
183    #[must_use]
184    pub fn hoop<H: Handler>(mut self, hoop: H) -> Self {
185        self.hoops.push(Arc::new(hoop));
186        self
187    }
188
189    /// Add a handler as middleware, it will run the handler in current router or it's descendants
190    /// handle the request. This middleware is only effective when the filter returns true..
191    #[inline]
192    #[must_use]
193    pub fn hoop_when<H, F>(mut self, hoop: H, filter: F) -> Self
194    where
195        H: Handler,
196        F: Fn(&Request, &Depot) -> bool + Send + Sync + 'static,
197    {
198        self.hoops.push(Arc::new(WhenHoop {
199            inner: hoop,
200            filter,
201        }));
202        self
203    }
204
205    /// Create a new router and set path filter.
206    ///
207    /// # Panics
208    ///
209    /// Panics if path value is not in correct format.
210    #[inline]
211    #[must_use]
212    pub fn with_path(path: impl Into<String>) -> Self {
213        Self::with_filter(PathFilter::new(path))
214    }
215
216    /// Create a new path filter for current router.
217    ///
218    /// # Panics
219    ///
220    /// Panics if path value is not in correct format.
221    #[inline]
222    #[must_use]
223    pub fn path(self, path: impl Into<String>) -> Self {
224        self.filter(PathFilter::new(path))
225    }
226
227    /// Create a new router and set filter.
228    #[inline]
229    #[must_use]
230    pub fn with_filter(filter: impl Filter + Sized) -> Self {
231        Self::new().filter(filter)
232    }
233    /// Add a filter for current router.
234    #[inline]
235    #[must_use]
236    pub fn filter(mut self, filter: impl Filter + Sized) -> Self {
237        self.filters.push(Box::new(filter));
238        self
239    }
240
241    /// Create a new router and set filter_fn.
242    #[inline]
243    #[must_use]
244    pub fn with_filter_fn<T>(func: T) -> Self
245    where
246        T: Fn(&mut Request, &mut PathState) -> bool + Send + Sync + 'static,
247    {
248        Self::with_filter(FnFilter(func))
249    }
250    /// Create a new FnFilter from Fn.
251    #[inline]
252    #[must_use]
253    pub fn filter_fn<T>(self, func: T) -> Self
254    where
255        T: Fn(&mut Request, &mut PathState) -> bool + Send + Sync + 'static,
256    {
257        self.filter(FnFilter(func))
258    }
259
260    /// Sets current router's handler.
261    #[inline]
262    #[must_use]
263    pub fn goal<H: Handler>(mut self, goal: H) -> Self {
264        self.goal = Some(Arc::new(goal));
265        self
266    }
267
268    /// When you want write router chain, this function will be useful,
269    /// You can write your custom logic in FnOnce.
270    #[inline]
271    #[must_use]
272    pub fn then<F>(self, func: F) -> Self
273    where
274        F: FnOnce(Self) -> Self,
275    {
276        func(self)
277    }
278
279    /// Add a [`SchemeFilter`] to current router.
280    ///
281    /// [`SchemeFilter`]: super::filters::HostFilter
282    #[inline]
283    #[must_use]
284    pub fn scheme(self, scheme: Scheme) -> Self {
285        self.filter(filters::scheme(scheme))
286    }
287
288    /// Add a [`HostFilter`] to current router.
289    ///
290    /// [`HostFilter`]: super::filters::HostFilter
291    #[inline]
292    #[must_use]
293    pub fn host(self, host: impl Into<String>) -> Self {
294        self.filter(filters::host(host))
295    }
296
297    /// Create a new [`HostFilter`] and set host filter.
298    ///
299    /// [`HostFilter`]: super::filters::HostFilter
300    #[inline]
301    #[must_use]
302    pub fn with_host(host: impl Into<String>) -> Self {
303        Self::with_filter(filters::host(host))
304    }
305
306    /// Add a [`PortFilter`] to current router.
307    ///
308    /// [`PortFilter`]: super::filters::PortFilter
309    #[inline]
310    #[must_use]
311    pub fn port(self, port: u16) -> Self {
312        self.filter(filters::port(port))
313    }
314
315    /// Create a new [`PortFilter`] and set port filter.
316    ///
317    /// [`PortFilter`]: super::filters::PortFilter
318    #[inline]
319    #[must_use]
320    pub fn with_port(port: u16) -> Self {
321        Self::with_filter(filters::port(port))
322    }
323
324    /// Creates a new child router with [`MethodFilter`] to filter GET method and set this child router's handler.
325    ///
326    /// [`MethodFilter`]: super::filters::MethodFilter
327    #[inline]
328    #[must_use]
329    pub fn get<H: Handler>(self, goal: H) -> Self {
330        self.push(Self::with_filter(filters::get()).goal(goal))
331    }
332
333    /// Create a new child router with [`MethodFilter`] to filter post method and set this child router's handler.
334    ///
335    /// [`MethodFilter`]: super::filters::MethodFilter
336    #[inline]
337    #[must_use]
338    pub fn post<H: Handler>(self, goal: H) -> Self {
339        self.push(Self::with_filter(filters::post()).goal(goal))
340    }
341
342    /// Create a new child router with [`MethodFilter`] to filter put method and set this child router's handler.
343    ///
344    /// [`MethodFilter`]: super::filters::MethodFilter
345    #[inline]
346    #[must_use]
347    pub fn put<H: Handler>(self, goal: H) -> Self {
348        self.push(Self::with_filter(filters::put()).goal(goal))
349    }
350
351    /// Create a new child router with [`MethodFilter`] to filter delete method and set this child router's handler.
352    ///
353    /// [`MethodFilter`]: super::filters::MethodFilter
354    #[inline]
355    #[must_use]
356    pub fn delete<H: Handler>(self, goal: H) -> Self {
357        self.push(Self::with_filter(filters::delete()).goal(goal))
358    }
359
360    /// Create a new child router with [`MethodFilter`] to filter patch method and set this child router's handler.
361    ///
362    /// [`MethodFilter`]: super::filters::MethodFilter
363    #[inline]
364    #[must_use]
365    pub fn patch<H: Handler>(self, goal: H) -> Self {
366        self.push(Self::with_filter(filters::patch()).goal(goal))
367    }
368
369    /// Create a new child router with [`MethodFilter`] to filter head method and set this child router's handler.
370    ///
371    /// [`MethodFilter`]: super::filters::MethodFilter
372    #[inline]
373    #[must_use]
374    pub fn head<H: Handler>(self, goal: H) -> Self {
375        self.push(Self::with_filter(filters::head()).goal(goal))
376    }
377
378    /// Create a new child router with [`MethodFilter`] to filter options method and set this child router's handler.
379    ///
380    /// [`MethodFilter`]: super::filters::MethodFilter
381    #[inline]
382    #[must_use]
383    pub fn options<H: Handler>(self, goal: H) -> Self {
384        self.push(Self::with_filter(filters::options()).goal(goal))
385    }
386}
387
388const SYMBOL_DOWN: &str = "│";
389const SYMBOL_TEE: &str = "├";
390const SYMBOL_ELL: &str = "└";
391const SYMBOL_RIGHT: &str = "─";
392impl Debug for Router {
393    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
394        fn print(f: &mut Formatter, prefix: &str, last: bool, router: &Router) -> fmt::Result {
395            let mut path = "".to_owned();
396            let mut others = Vec::with_capacity(router.filters.len());
397            if router.filters.is_empty() {
398                "!NULL!".clone_into(&mut path);
399            } else {
400                for filter in &router.filters {
401                    let info = format!("{filter:?}");
402                    if info.starts_with("path:") {
403                        info.split_once(':')
404                            .expect("`split_once` get `None`")
405                            .1
406                            .clone_into(&mut path)
407                    } else {
408                        let mut parts = info.splitn(2, ':').collect::<Vec<_>>();
409                        if !parts.is_empty() {
410                            others.push(parts.pop().expect("part should exists.").to_owned());
411                        }
412                    }
413                }
414            }
415            let cp = if last {
416                format!("{prefix}{SYMBOL_ELL}{SYMBOL_RIGHT}{SYMBOL_RIGHT}")
417            } else {
418                format!("{prefix}{SYMBOL_TEE}{SYMBOL_RIGHT}{SYMBOL_RIGHT}")
419            };
420            let hd = router
421                .goal
422                .as_ref()
423                .map(|goal| format!(" -> {}", goal.type_name()))
424                .unwrap_or_default();
425            if !others.is_empty() {
426                writeln!(f, "{cp}{path}[{}]{hd}", others.join(","))?;
427            } else {
428                writeln!(f, "{cp}{path}{hd}")?;
429            }
430            let routers = router.routers();
431            if !routers.is_empty() {
432                let np = if last {
433                    format!("{prefix}    ")
434                } else {
435                    format!("{prefix}{SYMBOL_DOWN}   ")
436                };
437                for (i, router) in routers.iter().enumerate() {
438                    print(f, &np, i == routers.len() - 1, router)?;
439                }
440            }
441            Ok(())
442        }
443        print(f, "", true, self)
444    }
445}
446
447#[cfg(test)]
448mod tests {
449    use super::{PathState, Router};
450    use crate::Response;
451    use crate::handler;
452    use crate::test::TestClient;
453
454    #[handler]
455    async fn fake_handler(_res: &mut Response) {}
456    #[test]
457    fn test_router_debug() {
458        let router = Router::default()
459            .push(
460                Router::with_path("users")
461                    .push(
462                        Router::with_path("{id}")
463                            .push(Router::with_path("emails").get(fake_handler)),
464                    )
465                    .push(
466                        Router::with_path("{id}/articles/{aid}")
467                            .get(fake_handler)
468                            .delete(fake_handler),
469                    ),
470            )
471            .push(
472                Router::with_path("articles")
473                    .push(
474                        Router::with_path("{id}/authors/{aid}")
475                            .get(fake_handler)
476                            .delete(fake_handler),
477                    )
478                    .push(
479                        Router::with_path("{id}")
480                            .get(fake_handler)
481                            .delete(fake_handler),
482                    ),
483            );
484        assert_eq!(
485            format!("{router:?}"),
486            r#"└──!NULL!
487    ├──users
488    │   ├──{id}
489    │   │   └──emails
490    │   │       └──[GET] -> salvo_core::routing::router::tests::fake_handler
491    │   └──{id}/articles/{aid}
492    │       ├──[GET] -> salvo_core::routing::router::tests::fake_handler
493    │       └──[DELETE] -> salvo_core::routing::router::tests::fake_handler
494    └──articles
495        ├──{id}/authors/{aid}
496        │   ├──[GET] -> salvo_core::routing::router::tests::fake_handler
497        │   └──[DELETE] -> salvo_core::routing::router::tests::fake_handler
498        └──{id}
499            ├──[GET] -> salvo_core::routing::router::tests::fake_handler
500            └──[DELETE] -> salvo_core::routing::router::tests::fake_handler
501"#
502        );
503    }
504    #[tokio::test]
505    async fn test_router_detect1() {
506        let router =
507            Router::default().push(Router::with_path("users").push(
508                Router::with_path("{id}").push(Router::with_path("emails").get(fake_handler)),
509            ));
510        let mut req = TestClient::get("http://local.host/users/12/emails").build();
511        let mut path_state = PathState::new(req.uri().path());
512        let matched = router.detect(&mut req, &mut path_state).await;
513        assert!(matched.is_some());
514    }
515    #[tokio::test]
516    async fn test_router_detect2() {
517        let router = Router::new()
518            .push(Router::with_path("users").push(Router::with_path("{id}").get(fake_handler)))
519            .push(Router::with_path("users").push(
520                Router::with_path("{id}").push(Router::with_path("emails").get(fake_handler)),
521            ));
522        let mut req = TestClient::get("http://local.host/users/12/emails").build();
523        let mut path_state = PathState::new(req.uri().path());
524        let matched = router.detect(&mut req, &mut path_state).await;
525        assert!(matched.is_some());
526    }
527    #[tokio::test]
528    async fn test_router_detect3() {
529        let router = Router::new().push(
530            Router::with_path("users").push(
531                Router::with_path(r"{id|\d+}").push(
532                    Router::new()
533                        .push(Router::with_path("facebook/insights/{**rest}").goal(fake_handler)),
534                ),
535            ),
536        );
537        let mut req = TestClient::get("http://local.host/users/12/facebook/insights").build();
538        let mut path_state = PathState::new(req.uri().path());
539        let matched = router.detect(&mut req, &mut path_state).await;
540        assert!(matched.is_some());
541
542        let mut req = TestClient::get("http://local.host/users/12/facebook/insights/23").build();
543        let mut path_state = PathState::new(req.uri().path());
544        let matched = router.detect(&mut req, &mut path_state).await;
545        // assert_eq!(format!("{:?}", path_state), "");
546        assert!(matched.is_some());
547    }
548    #[tokio::test]
549    async fn test_router_detect4() {
550        let router = Router::new().push(
551            Router::with_path("users").push(
552                Router::with_path(r"{id|\d+}").push(
553                    Router::new()
554                        .push(Router::with_path("facebook/insights/{*+rest}").goal(fake_handler)),
555                ),
556            ),
557        );
558        let mut req = TestClient::get("http://local.host/users/12/facebook/insights").build();
559        let mut path_state = PathState::new(req.uri().path());
560        let matched = router.detect(&mut req, &mut path_state).await;
561        // assert_eq!(format!("{:?}", path_state), "");
562        assert!(matched.is_none());
563
564        let mut req = TestClient::get("http://local.host/users/12/facebook/insights/23").build();
565        let mut path_state = PathState::new(req.uri().path());
566        let matched = router.detect(&mut req, &mut path_state).await;
567        assert!(matched.is_some());
568    }
569    #[tokio::test]
570    async fn test_router_detect5() {
571        let router = Router::new().push(
572            Router::with_path("users").push(
573                Router::with_path(r"{id|\d+}").push(
574                    Router::new().push(
575                        Router::with_path("facebook/insights")
576                            .push(Router::with_path("{**rest}").goal(fake_handler)),
577                    ),
578                ),
579            ),
580        );
581        let mut req = TestClient::get("http://local.host/users/12/facebook/insights").build();
582        let mut path_state = PathState::new(req.uri().path());
583        let matched = router.detect(&mut req, &mut path_state).await;
584        assert!(matched.is_some());
585
586        let mut req = TestClient::get("http://local.host/users/12/facebook/insights/23").build();
587        let mut path_state = PathState::new(req.uri().path());
588        let matched = router.detect(&mut req, &mut path_state).await;
589        assert!(matched.is_some());
590        assert_eq!(path_state.params["id"], "12");
591    }
592    #[tokio::test]
593    async fn test_router_detect6() {
594        let router = Router::new().push(
595            Router::with_path("users").push(
596                Router::with_path(r"{id|\d+}").push(
597                    Router::new().push(
598                        Router::with_path("facebook/insights")
599                            .push(Router::new().path("{*+rest}").goal(fake_handler)),
600                    ),
601                ),
602            ),
603        );
604        let mut req = TestClient::get("http://local.host/users/12/facebook/insights").build();
605        let mut path_state = PathState::new(req.uri().path());
606        let matched = router.detect(&mut req, &mut path_state).await;
607        assert!(matched.is_none());
608
609        let mut req = TestClient::get("http://local.host/users/12/facebook/insights/23").build();
610        let mut path_state = PathState::new(req.uri().path());
611        let matched = router.detect(&mut req, &mut path_state).await;
612        assert!(matched.is_some());
613    }
614    #[tokio::test]
615    async fn test_router_detect_utf8() {
616        let router = Router::new().push(
617            Router::with_path("用户").push(
618                Router::with_path(r"{id|\d+}").push(
619                    Router::new().push(
620                        Router::with_path("facebook/insights")
621                            .push(Router::with_path("{*+rest}").goal(fake_handler)),
622                    ),
623                ),
624            ),
625        );
626        let mut req =
627            TestClient::get("http://local.host/%E7%94%A8%E6%88%B7/12/facebook/insights").build();
628        let mut path_state = PathState::new(req.uri().path());
629        let matched = router.detect(&mut req, &mut path_state).await;
630        assert!(matched.is_none());
631
632        let mut req =
633            TestClient::get("http://local.host/%E7%94%A8%E6%88%B7/12/facebook/insights/23").build();
634        let mut path_state = PathState::new(req.uri().path());
635        let matched = router.detect(&mut req, &mut path_state).await;
636        assert!(matched.is_some());
637    }
638    #[tokio::test]
639    async fn test_router_detect9() {
640        let router = Router::new()
641            .push(Router::with_path("users/{sub|(images|css)}/{filename}").goal(fake_handler));
642        let mut req = TestClient::get("http://local.host/users/12/m.jpg").build();
643        let mut path_state = PathState::new(req.uri().path());
644        let matched = router.detect(&mut req, &mut path_state).await;
645        assert!(matched.is_none());
646
647        let mut req = TestClient::get("http://local.host/users/css/m.jpg").build();
648        let mut path_state = PathState::new(req.uri().path());
649        let matched = router.detect(&mut req, &mut path_state).await;
650        assert!(matched.is_some());
651    }
652    #[tokio::test]
653    async fn test_router_detect10() {
654        let router = Router::new()
655            .push(Router::with_path(r"users/{*sub|(images|css)/.+}").goal(fake_handler));
656        let mut req = TestClient::get("http://local.host/users/12/m.jpg").build();
657        let mut path_state = PathState::new(req.uri().path());
658        let matched = router.detect(&mut req, &mut path_state).await;
659        assert!(matched.is_none());
660
661        let mut req = TestClient::get("http://local.host/users/css/abc/m.jpg").build();
662        let mut path_state = PathState::new(req.uri().path());
663        let matched = router.detect(&mut req, &mut path_state).await;
664        assert!(matched.is_some());
665    }
666    #[tokio::test]
667    async fn test_router_detect11() {
668        let router = Router::new()
669            .push(Router::with_path(r"avatars/{width|\d+}x{height|\d+}.{ext}").goal(fake_handler));
670        let mut req = TestClient::get("http://local.host/avatars/321x641f.webp").build();
671        let mut path_state = PathState::new(req.uri().path());
672        let matched = router.detect(&mut req, &mut path_state).await;
673        assert!(matched.is_none());
674
675        let mut req = TestClient::get("http://local.host/avatars/320x640.webp").build();
676        let mut path_state = PathState::new(req.uri().path());
677        let matched = router.detect(&mut req, &mut path_state).await;
678        assert!(matched.is_some());
679    }
680    #[tokio::test]
681    async fn test_router_detect12() {
682        let router = Router::new()
683            .push(Router::with_path("/.well-known/acme-challenge/{token}").goal(fake_handler));
684
685        let mut req =
686            TestClient::get("http://local.host/.well-known/acme-challenge/q1XXrxIx79uXNl3I")
687                .build();
688        let mut path_state = PathState::new(req.uri().path());
689        let matched = router.detect(&mut req, &mut path_state).await;
690        assert!(matched.is_some());
691    }
692
693    #[tokio::test]
694    async fn test_router_detect13() {
695        let router = Router::new()
696            .path("user/{id|[0-9a-z]{8}(-[0-9a-z]{4}){3}-[0-9a-z]{12}}")
697            .get(fake_handler);
698        let mut req =
699            TestClient::get("http://local.host/user/726d694c-7af0-4bb0-9d22-706f7e38641e").build();
700        let mut path_state = PathState::new(req.uri().path());
701        let matched = router.detect(&mut req, &mut path_state).await;
702        assert!(matched.is_some());
703        let mut req =
704            TestClient::get("http://local.host/user/726d694c-7af0-4bb0-9d22-706f7e386e").build();
705        let mut path_state = PathState::new(req.uri().path());
706        let matched = router.detect(&mut req, &mut path_state).await;
707        assert!(matched.is_none());
708    }
709
710    #[tokio::test]
711    async fn test_router_detect_path_encoded() {
712        let router = Router::new().path("api/{p}").get(fake_handler);
713        let mut req = TestClient::get("http://127.0.0.1:6060/api/a%2fb%2fc").build();
714        let mut path_state = PathState::new(req.uri().path());
715        let matched = router.detect(&mut req, &mut path_state).await;
716        assert!(matched.is_some());
717        assert_eq!(path_state.params["p"], "a/b/c");
718    }
719}