1mod 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#[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 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#[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#[must_use]
283#[derive(Debug, PartialEq, Eq)]
284#[non_exhaustive]
285pub enum ErrorKind {
286 WrongNumberOfParameters {
288 got: usize,
290 expected: usize,
292 },
293
294 ParseErrorAtKey {
298 key: String,
300 value: String,
302 expected_type: &'static str,
304 },
305
306 ParseErrorAtIndex {
310 index: usize,
312 value: String,
314 expected_type: &'static str,
316 },
317
318 ParseError {
322 value: String,
324 expected_type: &'static str,
326 },
327
328 InvalidUtf8InPathParam {
330 key: String,
332 },
333
334 UnsupportedType {
339 name: &'static str,
341 },
342
343 DeserializeError {
345 key: String,
347 value: String,
349 message: String,
351 },
352
353 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#[derive(Debug)]
407pub struct FailedToDeserializePathParams(PathDeserializationError);
408
409impl FailedToDeserializePathParams {
410 pub fn kind(&self) -> &ErrorKind {
412 &self.0.kind
413 }
414
415 pub fn into_kind(self) -> ErrorKind {
417 self.0.kind
418 }
419
420 #[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 #[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#[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 #[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#[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#[derive(Debug)]
562pub struct InvalidUtf8InPathParam {
563 key: Arc<str>,
564}
565
566impl InvalidUtf8InPathParam {
567 #[must_use]
569 pub fn body_text(&self) -> String {
570 self.to_string()
571 }
572
573 #[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 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}