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::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::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#[derive(Debug, PartialEq, Eq)]
283#[non_exhaustive]
284pub enum ErrorKind {
285 WrongNumberOfParameters {
287 got: usize,
289 expected: usize,
291 },
292
293 ParseErrorAtKey {
297 key: String,
299 value: String,
301 expected_type: &'static str,
303 },
304
305 ParseErrorAtIndex {
309 index: usize,
311 value: String,
313 expected_type: &'static str,
315 },
316
317 ParseError {
321 value: String,
323 expected_type: &'static str,
325 },
326
327 InvalidUtf8InPathParam {
329 key: String,
331 },
332
333 UnsupportedType {
338 name: &'static str,
340 },
341
342 DeserializeError {
344 key: String,
346 value: String,
348 message: String,
350 },
351
352 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#[derive(Debug)]
406pub struct FailedToDeserializePathParams(PathDeserializationError);
407
408impl FailedToDeserializePathParams {
409 pub fn kind(&self) -> &ErrorKind {
411 &self.0.kind
412 }
413
414 pub fn into_kind(self) -> ErrorKind {
416 self.0.kind
417 }
418
419 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 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#[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 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#[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#[derive(Debug)]
558pub struct InvalidUtf8InPathParam {
559 key: Arc<str>,
560}
561
562impl InvalidUtf8InPathParam {
563 pub fn body_text(&self) -> String {
565 self.to_string()
566 }
567
568 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 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}