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