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