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