axum/extract/path/
mod.rs

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