1 //! Extractor that will get captures from the URL and parse them using
2 //! [`serde`].
3 
4 mod de;
5 
6 use crate::{
7     extract::{rejection::*, FromRequestParts},
8     routing::url_params::UrlParams,
9     util::PercentDecodedStr,
10 };
11 use async_trait::async_trait;
12 use axum_core::response::{IntoResponse, Response};
13 use http::{request::Parts, StatusCode};
14 use serde::de::DeserializeOwned;
15 use std::{fmt, sync::Arc};
16 
17 /// Extractor that will get captures from the URL and parse them using
18 /// [`serde`].
19 ///
20 /// Any percent encoded parameters will be automatically decoded. The decoded
21 /// parameters must be valid UTF-8, otherwise `Path` will fail and return a `400
22 /// Bad Request` response.
23 ///
24 /// # Example
25 ///
26 /// These examples assume the `serde` feature of the [`uuid`] crate is enabled.
27 ///
28 /// [`uuid`]: https://crates.io/crates/uuid
29 ///
30 /// ```rust,no_run
31 /// use axum::{
32 ///     extract::Path,
33 ///     routing::get,
34 ///     Router,
35 /// };
36 /// use uuid::Uuid;
37 ///
38 /// async fn users_teams_show(
39 ///     Path((user_id, team_id)): Path<(Uuid, Uuid)>,
40 /// ) {
41 ///     // ...
42 /// }
43 ///
44 /// let app = Router::new().route("/users/:user_id/team/:team_id", get(users_teams_show));
45 /// # async {
46 /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
47 /// # };
48 /// ```
49 ///
50 /// If the path contains only one parameter, then you can omit the tuple.
51 ///
52 /// ```rust,no_run
53 /// use axum::{
54 ///     extract::Path,
55 ///     routing::get,
56 ///     Router,
57 /// };
58 /// use uuid::Uuid;
59 ///
60 /// async fn user_info(Path(user_id): Path<Uuid>) {
61 ///     // ...
62 /// }
63 ///
64 /// let app = Router::new().route("/users/:user_id", get(user_info));
65 /// # async {
66 /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
67 /// # };
68 /// ```
69 ///
70 /// Path segments also can be deserialized into any type that implements
71 /// [`serde::Deserialize`]. This includes tuples and structs:
72 ///
73 /// ```rust,no_run
74 /// use axum::{
75 ///     extract::Path,
76 ///     routing::get,
77 ///     Router,
78 /// };
79 /// use serde::Deserialize;
80 /// use uuid::Uuid;
81 ///
82 /// // Path segment labels will be matched with struct field names
83 /// #[derive(Deserialize)]
84 /// struct Params {
85 ///     user_id: Uuid,
86 ///     team_id: Uuid,
87 /// }
88 ///
89 /// async fn users_teams_show(
90 ///     Path(Params { user_id, team_id }): Path<Params>,
91 /// ) {
92 ///     // ...
93 /// }
94 ///
95 /// // When using tuples the path segments will be matched by their position in the route
96 /// async fn users_teams_create(
97 ///     Path((user_id, team_id)): Path<(String, String)>,
98 /// ) {
99 ///     // ...
100 /// }
101 ///
102 /// let app = Router::new().route(
103 ///     "/users/:user_id/team/:team_id",
104 ///     get(users_teams_show).post(users_teams_create),
105 /// );
106 /// # async {
107 /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
108 /// # };
109 /// ```
110 ///
111 /// If you wish to capture all path parameters you can use `HashMap` or `Vec`:
112 ///
113 /// ```rust,no_run
114 /// use axum::{
115 ///     extract::Path,
116 ///     routing::get,
117 ///     Router,
118 /// };
119 /// use std::collections::HashMap;
120 ///
121 /// async fn params_map(
122 ///     Path(params): Path<HashMap<String, String>>,
123 /// ) {
124 ///     // ...
125 /// }
126 ///
127 /// async fn params_vec(
128 ///     Path(params): Path<Vec<(String, String)>>,
129 /// ) {
130 ///     // ...
131 /// }
132 ///
133 /// let app = Router::new()
134 ///     .route("/users/:user_id/team/:team_id", get(params_map).post(params_vec));
135 /// # async {
136 /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
137 /// # };
138 /// ```
139 ///
140 /// # Providing detailed rejection output
141 ///
142 /// If the URI cannot be deserialized into the target type the request will be rejected and an
143 /// error response will be returned. See [`customize-path-rejection`] for an example of how to customize that error.
144 ///
145 /// [`serde`]: https://crates.io/crates/serde
146 /// [`serde::Deserialize`]: https://docs.rs/serde/1.0.127/serde/trait.Deserialize.html
147 /// [`customize-path-rejection`]: https://github.com/tokio-rs/axum/blob/main/examples/customize-path-rejection/src/main.rs
148 #[derive(Debug)]
149 pub struct Path<T>(pub T);
150 
151 axum_core::__impl_deref!(Path);
152 
153 #[async_trait]
154 impl<T, S> FromRequestParts<S> for Path<T>
155 where
156     T: DeserializeOwned + Send,
157     S: Send + Sync,
158 {
159     type Rejection = PathRejection;
160 
from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection>161     async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
162         let params = match parts.extensions.get::<UrlParams>() {
163             Some(UrlParams::Params(params)) => params,
164             Some(UrlParams::InvalidUtf8InPathParam { key }) => {
165                 let err = PathDeserializationError {
166                     kind: ErrorKind::InvalidUtf8InPathParam {
167                         key: key.to_string(),
168                     },
169                 };
170                 let err = FailedToDeserializePathParams(err);
171                 return Err(err.into());
172             }
173             None => {
174                 return Err(MissingPathParams.into());
175             }
176         };
177 
178         T::deserialize(de::PathDeserializer::new(params))
179             .map_err(|err| {
180                 PathRejection::FailedToDeserializePathParams(FailedToDeserializePathParams(err))
181             })
182             .map(Path)
183     }
184 }
185 
186 // this wrapper type is used as the deserializer error to hide the `serde::de::Error` impl which
187 // would otherwise be public if we used `ErrorKind` as the error directly
188 #[derive(Debug)]
189 pub(crate) struct PathDeserializationError {
190     pub(super) kind: ErrorKind,
191 }
192 
193 impl PathDeserializationError {
new(kind: ErrorKind) -> Self194     pub(super) fn new(kind: ErrorKind) -> Self {
195         Self { kind }
196     }
197 
wrong_number_of_parameters() -> WrongNumberOfParameters<()>198     pub(super) fn wrong_number_of_parameters() -> WrongNumberOfParameters<()> {
199         WrongNumberOfParameters { got: () }
200     }
201 
202     #[track_caller]
unsupported_type(name: &'static str) -> Self203     pub(super) fn unsupported_type(name: &'static str) -> Self {
204         Self::new(ErrorKind::UnsupportedType { name })
205     }
206 }
207 
208 pub(super) struct WrongNumberOfParameters<G> {
209     got: G,
210 }
211 
212 impl<G> WrongNumberOfParameters<G> {
213     #[allow(clippy::unused_self)]
got<G2>(self, got: G2) -> WrongNumberOfParameters<G2>214     pub(super) fn got<G2>(self, got: G2) -> WrongNumberOfParameters<G2> {
215         WrongNumberOfParameters { got }
216     }
217 }
218 
219 impl WrongNumberOfParameters<usize> {
expected(self, expected: usize) -> PathDeserializationError220     pub(super) fn expected(self, expected: usize) -> PathDeserializationError {
221         PathDeserializationError::new(ErrorKind::WrongNumberOfParameters {
222             got: self.got,
223             expected,
224         })
225     }
226 }
227 
228 impl serde::de::Error for PathDeserializationError {
229     #[inline]
custom<T>(msg: T) -> Self where T: fmt::Display,230     fn custom<T>(msg: T) -> Self
231     where
232         T: fmt::Display,
233     {
234         Self {
235             kind: ErrorKind::Message(msg.to_string()),
236         }
237     }
238 }
239 
240 impl fmt::Display for PathDeserializationError {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result241     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242         self.kind.fmt(f)
243     }
244 }
245 
246 impl std::error::Error for PathDeserializationError {}
247 
248 /// The kinds of errors that can happen we deserializing into a [`Path`].
249 ///
250 /// This type is obtained through [`FailedToDeserializePathParams::kind`] or
251 /// [`FailedToDeserializePathParams::into_kind`] and is useful for building
252 /// more precise error messages.
253 #[derive(Debug, PartialEq, Eq)]
254 #[non_exhaustive]
255 pub enum ErrorKind {
256     /// The URI contained the wrong number of parameters.
257     WrongNumberOfParameters {
258         /// The number of actual parameters in the URI.
259         got: usize,
260         /// The number of expected parameters.
261         expected: usize,
262     },
263 
264     /// Failed to parse the value at a specific key into the expected type.
265     ///
266     /// This variant is used when deserializing into types that have named fields, such as structs.
267     ParseErrorAtKey {
268         /// The key at which the value was located.
269         key: String,
270         /// The value from the URI.
271         value: String,
272         /// The expected type of the value.
273         expected_type: &'static str,
274     },
275 
276     /// Failed to parse the value at a specific index into the expected type.
277     ///
278     /// This variant is used when deserializing into sequence types, such as tuples.
279     ParseErrorAtIndex {
280         /// The index at which the value was located.
281         index: usize,
282         /// The value from the URI.
283         value: String,
284         /// The expected type of the value.
285         expected_type: &'static str,
286     },
287 
288     /// Failed to parse a value into the expected type.
289     ///
290     /// This variant is used when deserializing into a primitive type (such as `String` and `u32`).
291     ParseError {
292         /// The value from the URI.
293         value: String,
294         /// The expected type of the value.
295         expected_type: &'static str,
296     },
297 
298     /// A parameter contained text that, once percent decoded, wasn't valid UTF-8.
299     InvalidUtf8InPathParam {
300         /// The key at which the invalid value was located.
301         key: String,
302     },
303 
304     /// Tried to serialize into an unsupported type such as nested maps.
305     ///
306     /// This error kind is caused by programmer errors and thus gets converted into a `500 Internal
307     /// Server Error` response.
308     UnsupportedType {
309         /// The name of the unsupported type.
310         name: &'static str,
311     },
312 
313     /// Catch-all variant for errors that don't fit any other variant.
314     Message(String),
315 }
316 
317 impl fmt::Display for ErrorKind {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result318     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319         match self {
320             ErrorKind::Message(error) => error.fmt(f),
321             ErrorKind::InvalidUtf8InPathParam { key } => write!(f, "Invalid UTF-8 in `{key}`"),
322             ErrorKind::WrongNumberOfParameters { got, expected } => {
323                 write!(
324                     f,
325                     "Wrong number of path arguments for `Path`. Expected {expected} but got {got}"
326                 )?;
327 
328                 if *expected == 1 {
329                     write!(f, ". Note that multiple parameters must be extracted with a tuple `Path<(_, _)>` or a struct `Path<YourParams>`")?;
330                 }
331 
332                 Ok(())
333             }
334             ErrorKind::UnsupportedType { name } => write!(f, "Unsupported type `{name}`"),
335             ErrorKind::ParseErrorAtKey {
336                 key,
337                 value,
338                 expected_type,
339             } => write!(
340                 f,
341                 "Cannot parse `{key}` with value `{value:?}` to a `{expected_type}`"
342             ),
343             ErrorKind::ParseError {
344                 value,
345                 expected_type,
346             } => write!(f, "Cannot parse `{value:?}` to a `{expected_type}`"),
347             ErrorKind::ParseErrorAtIndex {
348                 index,
349                 value,
350                 expected_type,
351             } => write!(
352                 f,
353                 "Cannot parse value at index {index} with value `{value:?}` to a `{expected_type}`"
354             ),
355         }
356     }
357 }
358 
359 /// Rejection type for [`Path`](super::Path) if the captured routes params couldn't be deserialized
360 /// into the expected type.
361 #[derive(Debug)]
362 pub struct FailedToDeserializePathParams(PathDeserializationError);
363 
364 impl FailedToDeserializePathParams {
365     /// Get a reference to the underlying error kind.
kind(&self) -> &ErrorKind366     pub fn kind(&self) -> &ErrorKind {
367         &self.0.kind
368     }
369 
370     /// Convert this error into the underlying error kind.
into_kind(self) -> ErrorKind371     pub fn into_kind(self) -> ErrorKind {
372         self.0.kind
373     }
374 
375     /// Get the response body text used for this rejection.
body_text(&self) -> String376     pub fn body_text(&self) -> String {
377         match self.0.kind {
378             ErrorKind::Message(_)
379             | ErrorKind::InvalidUtf8InPathParam { .. }
380             | ErrorKind::ParseError { .. }
381             | ErrorKind::ParseErrorAtIndex { .. }
382             | ErrorKind::ParseErrorAtKey { .. } => format!("Invalid URL: {}", self.0.kind),
383             ErrorKind::WrongNumberOfParameters { .. } | ErrorKind::UnsupportedType { .. } => {
384                 self.0.kind.to_string()
385             }
386         }
387     }
388 
389     /// Get the status code used for this rejection.
status(&self) -> StatusCode390     pub fn status(&self) -> StatusCode {
391         match self.0.kind {
392             ErrorKind::Message(_)
393             | ErrorKind::InvalidUtf8InPathParam { .. }
394             | ErrorKind::ParseError { .. }
395             | ErrorKind::ParseErrorAtIndex { .. }
396             | ErrorKind::ParseErrorAtKey { .. } => StatusCode::BAD_REQUEST,
397             ErrorKind::WrongNumberOfParameters { .. } | ErrorKind::UnsupportedType { .. } => {
398                 StatusCode::INTERNAL_SERVER_ERROR
399             }
400         }
401     }
402 }
403 
404 impl IntoResponse for FailedToDeserializePathParams {
into_response(self) -> Response405     fn into_response(self) -> Response {
406         axum_core::__log_rejection!(
407             rejection_type = Self,
408             body_text = self.body_text(),
409             status = self.status(),
410         );
411         (self.status(), self.body_text()).into_response()
412     }
413 }
414 
415 impl fmt::Display for FailedToDeserializePathParams {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result416     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417         self.0.fmt(f)
418     }
419 }
420 
421 impl std::error::Error for FailedToDeserializePathParams {}
422 
423 /// Extractor that will get captures from the URL without deserializing them.
424 ///
425 /// In general you should prefer to use [`Path`] as it is higher level, however `RawPathParams` is
426 /// suitable if just want the raw params without deserializing them and thus saving some
427 /// allocations.
428 ///
429 /// Any percent encoded parameters will be automatically decoded. The decoded parameters must be
430 /// valid UTF-8, otherwise `RawPathParams` will fail and return a `400 Bad Request` response.
431 ///
432 /// # Example
433 ///
434 /// ```rust,no_run
435 /// use axum::{
436 ///     extract::RawPathParams,
437 ///     routing::get,
438 ///     Router,
439 /// };
440 ///
441 /// async fn users_teams_show(params: RawPathParams) {
442 ///     for (key, value) in &params {
443 ///         println!("{key:?} = {value:?}");
444 ///     }
445 /// }
446 ///
447 /// let app = Router::new().route("/users/:user_id/team/:team_id", get(users_teams_show));
448 /// # let _: Router = app;
449 /// ```
450 #[derive(Debug)]
451 pub struct RawPathParams(Vec<(Arc<str>, PercentDecodedStr)>);
452 
453 #[async_trait]
454 impl<S> FromRequestParts<S> for RawPathParams
455 where
456     S: Send + Sync,
457 {
458     type Rejection = RawPathParamsRejection;
459 
from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection>460     async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
461         let params = match parts.extensions.get::<UrlParams>() {
462             Some(UrlParams::Params(params)) => params,
463             Some(UrlParams::InvalidUtf8InPathParam { key }) => {
464                 return Err(InvalidUtf8InPathParam {
465                     key: Arc::clone(key),
466                 }
467                 .into());
468             }
469             None => {
470                 return Err(MissingPathParams.into());
471             }
472         };
473 
474         Ok(Self(params.clone()))
475     }
476 }
477 
478 impl RawPathParams {
479     /// Get an iterator over the path parameters.
iter(&self) -> RawPathParamsIter<'_>480     pub fn iter(&self) -> RawPathParamsIter<'_> {
481         self.into_iter()
482     }
483 }
484 
485 impl<'a> IntoIterator for &'a RawPathParams {
486     type Item = (&'a str, &'a str);
487     type IntoIter = RawPathParamsIter<'a>;
488 
into_iter(self) -> Self::IntoIter489     fn into_iter(self) -> Self::IntoIter {
490         RawPathParamsIter(self.0.iter())
491     }
492 }
493 
494 /// An iterator over raw path parameters.
495 ///
496 /// Created with [`RawPathParams::iter`].
497 #[derive(Debug)]
498 pub struct RawPathParamsIter<'a>(std::slice::Iter<'a, (Arc<str>, PercentDecodedStr)>);
499 
500 impl<'a> Iterator for RawPathParamsIter<'a> {
501     type Item = (&'a str, &'a str);
502 
next(&mut self) -> Option<Self::Item>503     fn next(&mut self) -> Option<Self::Item> {
504         let (key, value) = self.0.next()?;
505         Some((&**key, value.as_str()))
506     }
507 }
508 
509 /// Rejection used by [`RawPathParams`] if a parameter contained text that, once percent decoded,
510 /// wasn't valid UTF-8.
511 #[derive(Debug)]
512 pub struct InvalidUtf8InPathParam {
513     key: Arc<str>,
514 }
515 
516 impl InvalidUtf8InPathParam {
517     /// Get the response body text used for this rejection.
body_text(&self) -> String518     pub fn body_text(&self) -> String {
519         self.to_string()
520     }
521 
522     /// Get the status code used for this rejection.
status(&self) -> StatusCode523     pub fn status(&self) -> StatusCode {
524         StatusCode::BAD_REQUEST
525     }
526 }
527 
528 impl fmt::Display for InvalidUtf8InPathParam {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result529     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
530         write!(f, "Invalid UTF-8 in `{}`", self.key)
531     }
532 }
533 
534 impl std::error::Error for InvalidUtf8InPathParam {}
535 
536 impl IntoResponse for InvalidUtf8InPathParam {
into_response(self) -> Response537     fn into_response(self) -> Response {
538         (self.status(), self.body_text()).into_response()
539     }
540 }
541 
542 #[cfg(test)]
543 mod tests {
544     use super::*;
545     use crate::{routing::get, test_helpers::*, Router};
546     use http::StatusCode;
547     use serde::Deserialize;
548     use std::collections::HashMap;
549 
550     #[crate::test]
extracting_url_params()551     async fn extracting_url_params() {
552         let app = Router::new().route(
553             "/users/:id",
554             get(|Path(id): Path<i32>| async move {
555                 assert_eq!(id, 42);
556             })
557             .post(|Path(params_map): Path<HashMap<String, i32>>| async move {
558                 assert_eq!(params_map.get("id").unwrap(), &1337);
559             }),
560         );
561 
562         let client = TestClient::new(app);
563 
564         let res = client.get("/users/42").send().await;
565         assert_eq!(res.status(), StatusCode::OK);
566 
567         let res = client.post("/users/1337").send().await;
568         assert_eq!(res.status(), StatusCode::OK);
569     }
570 
571     #[crate::test]
extracting_url_params_multiple_times()572     async fn extracting_url_params_multiple_times() {
573         let app = Router::new().route("/users/:id", get(|_: Path<i32>, _: Path<String>| async {}));
574 
575         let client = TestClient::new(app);
576 
577         let res = client.get("/users/42").send().await;
578         assert_eq!(res.status(), StatusCode::OK);
579     }
580 
581     #[crate::test]
percent_decoding()582     async fn percent_decoding() {
583         let app = Router::new().route(
584             "/:key",
585             get(|Path(param): Path<String>| async move { param }),
586         );
587 
588         let client = TestClient::new(app);
589 
590         let res = client.get("/one%20two").send().await;
591 
592         assert_eq!(res.text().await, "one two");
593     }
594 
595     #[crate::test]
supports_128_bit_numbers()596     async fn supports_128_bit_numbers() {
597         let app = Router::new()
598             .route(
599                 "/i/:key",
600                 get(|Path(param): Path<i128>| async move { param.to_string() }),
601             )
602             .route(
603                 "/u/:key",
604                 get(|Path(param): Path<u128>| async move { param.to_string() }),
605             );
606 
607         let client = TestClient::new(app);
608 
609         let res = client.get("/i/123").send().await;
610         assert_eq!(res.text().await, "123");
611 
612         let res = client.get("/u/123").send().await;
613         assert_eq!(res.text().await, "123");
614     }
615 
616     #[crate::test]
wildcard()617     async fn wildcard() {
618         let app = Router::new()
619             .route(
620                 "/foo/*rest",
621                 get(|Path(param): Path<String>| async move { param }),
622             )
623             .route(
624                 "/bar/*rest",
625                 get(|Path(params): Path<HashMap<String, String>>| async move {
626                     params.get("rest").unwrap().clone()
627                 }),
628             );
629 
630         let client = TestClient::new(app);
631 
632         let res = client.get("/foo/bar/baz").send().await;
633         assert_eq!(res.text().await, "bar/baz");
634 
635         let res = client.get("/bar/baz/qux").send().await;
636         assert_eq!(res.text().await, "baz/qux");
637     }
638 
639     #[crate::test]
captures_dont_match_empty_segments()640     async fn captures_dont_match_empty_segments() {
641         let app = Router::new().route("/:key", get(|| async {}));
642 
643         let client = TestClient::new(app);
644 
645         let res = client.get("/").send().await;
646         assert_eq!(res.status(), StatusCode::NOT_FOUND);
647 
648         let res = client.get("/foo").send().await;
649         assert_eq!(res.status(), StatusCode::OK);
650     }
651 
652     #[crate::test]
str_reference_deserialize()653     async fn str_reference_deserialize() {
654         struct Param(String);
655         impl<'de> serde::Deserialize<'de> for Param {
656             fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
657             where
658                 D: serde::Deserializer<'de>,
659             {
660                 let s = <&str as serde::Deserialize>::deserialize(deserializer)?;
661                 Ok(Param(s.to_owned()))
662             }
663         }
664 
665         let app = Router::new().route("/:key", get(|param: Path<Param>| async move { param.0 .0 }));
666 
667         let client = TestClient::new(app);
668 
669         let res = client.get("/foo").send().await;
670         assert_eq!(res.text().await, "foo");
671 
672         // percent decoding should also work
673         let res = client.get("/foo%20bar").send().await;
674         assert_eq!(res.text().await, "foo bar");
675     }
676 
677     #[crate::test]
two_path_extractors()678     async fn two_path_extractors() {
679         let app = Router::new().route("/:a/:b", get(|_: Path<String>, _: Path<String>| async {}));
680 
681         let client = TestClient::new(app);
682 
683         let res = client.get("/a/b").send().await;
684         assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
685         assert_eq!(
686             res.text().await,
687             "Wrong number of path arguments for `Path`. Expected 1 but got 2. \
688             Note that multiple parameters must be extracted with a tuple `Path<(_, _)>` or a struct `Path<YourParams>`",
689         );
690     }
691 
692     #[crate::test]
deserialize_into_vec_of_tuples()693     async fn deserialize_into_vec_of_tuples() {
694         let app = Router::new().route(
695             "/:a/:b",
696             get(|Path(params): Path<Vec<(String, String)>>| async move {
697                 assert_eq!(
698                     params,
699                     vec![
700                         ("a".to_owned(), "foo".to_owned()),
701                         ("b".to_owned(), "bar".to_owned())
702                     ]
703                 );
704             }),
705         );
706 
707         let client = TestClient::new(app);
708 
709         let res = client.get("/foo/bar").send().await;
710         assert_eq!(res.status(), StatusCode::OK);
711     }
712 
713     #[crate::test]
type_that_uses_deserialize_any()714     async fn type_that_uses_deserialize_any() {
715         use time::Date;
716 
717         #[derive(Deserialize)]
718         struct Params {
719             a: Date,
720             b: Date,
721             c: Date,
722         }
723 
724         let app = Router::new()
725             .route(
726                 "/single/:a",
727                 get(|Path(a): Path<Date>| async move { format!("single: {a}") }),
728             )
729             .route(
730                 "/tuple/:a/:b/:c",
731                 get(|Path((a, b, c)): Path<(Date, Date, Date)>| async move {
732                     format!("tuple: {a} {b} {c}")
733                 }),
734             )
735             .route(
736                 "/vec/:a/:b/:c",
737                 get(|Path(vec): Path<Vec<Date>>| async move {
738                     let [a, b, c]: [Date; 3] = vec.try_into().unwrap();
739                     format!("vec: {a} {b} {c}")
740                 }),
741             )
742             .route(
743                 "/vec_pairs/:a/:b/:c",
744                 get(|Path(vec): Path<Vec<(String, Date)>>| async move {
745                     let [(_, a), (_, b), (_, c)]: [(String, Date); 3] = vec.try_into().unwrap();
746                     format!("vec_pairs: {a} {b} {c}")
747                 }),
748             )
749             .route(
750                 "/map/:a/:b/:c",
751                 get(|Path(mut map): Path<HashMap<String, Date>>| async move {
752                     let a = map.remove("a").unwrap();
753                     let b = map.remove("b").unwrap();
754                     let c = map.remove("c").unwrap();
755                     format!("map: {a} {b} {c}")
756                 }),
757             )
758             .route(
759                 "/struct/:a/:b/:c",
760                 get(|Path(params): Path<Params>| async move {
761                     format!("struct: {} {} {}", params.a, params.b, params.c)
762                 }),
763             );
764 
765         let client = TestClient::new(app);
766 
767         let res = client.get("/single/2023-01-01").send().await;
768         assert_eq!(res.text().await, "single: 2023-01-01");
769 
770         let res = client
771             .get("/tuple/2023-01-01/2023-01-02/2023-01-03")
772             .send()
773             .await;
774         assert_eq!(res.text().await, "tuple: 2023-01-01 2023-01-02 2023-01-03");
775 
776         let res = client
777             .get("/vec/2023-01-01/2023-01-02/2023-01-03")
778             .send()
779             .await;
780         assert_eq!(res.text().await, "vec: 2023-01-01 2023-01-02 2023-01-03");
781 
782         let res = client
783             .get("/vec_pairs/2023-01-01/2023-01-02/2023-01-03")
784             .send()
785             .await;
786         assert_eq!(
787             res.text().await,
788             "vec_pairs: 2023-01-01 2023-01-02 2023-01-03",
789         );
790 
791         let res = client
792             .get("/map/2023-01-01/2023-01-02/2023-01-03")
793             .send()
794             .await;
795         assert_eq!(res.text().await, "map: 2023-01-01 2023-01-02 2023-01-03");
796 
797         let res = client
798             .get("/struct/2023-01-01/2023-01-02/2023-01-03")
799             .send()
800             .await;
801         assert_eq!(res.text().await, "struct: 2023-01-01 2023-01-02 2023-01-03");
802     }
803 
804     #[crate::test]
wrong_number_of_parameters_json()805     async fn wrong_number_of_parameters_json() {
806         use serde_json::Value;
807 
808         let app = Router::new()
809             .route("/one/:a", get(|_: Path<(Value, Value)>| async {}))
810             .route("/two/:a/:b", get(|_: Path<Value>| async {}));
811 
812         let client = TestClient::new(app);
813 
814         let res = client.get("/one/1").send().await;
815         assert!(res
816             .text()
817             .await
818             .starts_with("Wrong number of path arguments for `Path`. Expected 2 but got 1"));
819 
820         let res = client.get("/two/1/2").send().await;
821         assert!(res
822             .text()
823             .await
824             .starts_with("Wrong number of path arguments for `Path`. Expected 1 but got 2"));
825     }
826 
827     #[crate::test]
raw_path_params()828     async fn raw_path_params() {
829         let app = Router::new().route(
830             "/:a/:b/:c",
831             get(|params: RawPathParams| async move {
832                 params
833                     .into_iter()
834                     .map(|(key, value)| format!("{key}={value}"))
835                     .collect::<Vec<_>>()
836                     .join(" ")
837             }),
838         );
839 
840         let client = TestClient::new(app);
841         let res = client.get("/foo/bar/baz").send().await;
842         let body = res.text().await;
843         assert_eq!(body, "a=foo b=bar c=baz");
844     }
845 }
846