1 //! Extractor that will get captures from the URL and parse them using 2 //! [`serde`]. 3 4 mod de; 5 6 use crate::{ 7 extract::{rejection::*, FromRequestParts}, 8 routing::url_params::UrlParams, 9 util::PercentDecodedStr, 10 }; 11 use async_trait::async_trait; 12 use axum_core::response::{IntoResponse, Response}; 13 use http::{request::Parts, StatusCode}; 14 use serde::de::DeserializeOwned; 15 use std::{fmt, sync::Arc}; 16 17 /// Extractor that will get captures from the URL and parse them using 18 /// [`serde`]. 19 /// 20 /// Any percent encoded parameters will be automatically decoded. The decoded 21 /// parameters must be valid UTF-8, otherwise `Path` will fail and return a `400 22 /// Bad Request` response. 23 /// 24 /// # Example 25 /// 26 /// These examples assume the `serde` feature of the [`uuid`] crate is enabled. 27 /// 28 /// [`uuid`]: https://crates.io/crates/uuid 29 /// 30 /// ```rust,no_run 31 /// use axum::{ 32 /// extract::Path, 33 /// routing::get, 34 /// Router, 35 /// }; 36 /// use uuid::Uuid; 37 /// 38 /// async fn users_teams_show( 39 /// Path((user_id, team_id)): Path<(Uuid, Uuid)>, 40 /// ) { 41 /// // ... 42 /// } 43 /// 44 /// let app = Router::new().route("/users/:user_id/team/:team_id", get(users_teams_show)); 45 /// # async { 46 /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); 47 /// # }; 48 /// ``` 49 /// 50 /// If the path contains only one parameter, then you can omit the tuple. 51 /// 52 /// ```rust,no_run 53 /// use axum::{ 54 /// extract::Path, 55 /// routing::get, 56 /// Router, 57 /// }; 58 /// use uuid::Uuid; 59 /// 60 /// async fn user_info(Path(user_id): Path<Uuid>) { 61 /// // ... 62 /// } 63 /// 64 /// let app = Router::new().route("/users/:user_id", get(user_info)); 65 /// # async { 66 /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); 67 /// # }; 68 /// ``` 69 /// 70 /// Path segments also can be deserialized into any type that implements 71 /// [`serde::Deserialize`]. This includes tuples and structs: 72 /// 73 /// ```rust,no_run 74 /// use axum::{ 75 /// extract::Path, 76 /// routing::get, 77 /// Router, 78 /// }; 79 /// use serde::Deserialize; 80 /// use uuid::Uuid; 81 /// 82 /// // Path segment labels will be matched with struct field names 83 /// #[derive(Deserialize)] 84 /// struct Params { 85 /// user_id: Uuid, 86 /// team_id: Uuid, 87 /// } 88 /// 89 /// async fn users_teams_show( 90 /// Path(Params { user_id, team_id }): Path<Params>, 91 /// ) { 92 /// // ... 93 /// } 94 /// 95 /// // When using tuples the path segments will be matched by their position in the route 96 /// async fn users_teams_create( 97 /// Path((user_id, team_id)): Path<(String, String)>, 98 /// ) { 99 /// // ... 100 /// } 101 /// 102 /// let app = Router::new().route( 103 /// "/users/:user_id/team/:team_id", 104 /// get(users_teams_show).post(users_teams_create), 105 /// ); 106 /// # async { 107 /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); 108 /// # }; 109 /// ``` 110 /// 111 /// If you wish to capture all path parameters you can use `HashMap` or `Vec`: 112 /// 113 /// ```rust,no_run 114 /// use axum::{ 115 /// extract::Path, 116 /// routing::get, 117 /// Router, 118 /// }; 119 /// use std::collections::HashMap; 120 /// 121 /// async fn params_map( 122 /// Path(params): Path<HashMap<String, String>>, 123 /// ) { 124 /// // ... 125 /// } 126 /// 127 /// async fn params_vec( 128 /// Path(params): Path<Vec<(String, String)>>, 129 /// ) { 130 /// // ... 131 /// } 132 /// 133 /// let app = Router::new() 134 /// .route("/users/:user_id/team/:team_id", get(params_map).post(params_vec)); 135 /// # async { 136 /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); 137 /// # }; 138 /// ``` 139 /// 140 /// # Providing detailed rejection output 141 /// 142 /// If the URI cannot be deserialized into the target type the request will be rejected and an 143 /// error response will be returned. See [`customize-path-rejection`] for an example of how to customize that error. 144 /// 145 /// [`serde`]: https://crates.io/crates/serde 146 /// [`serde::Deserialize`]: https://docs.rs/serde/1.0.127/serde/trait.Deserialize.html 147 /// [`customize-path-rejection`]: https://github.com/tokio-rs/axum/blob/main/examples/customize-path-rejection/src/main.rs 148 #[derive(Debug)] 149 pub struct Path<T>(pub T); 150 151 axum_core::__impl_deref!(Path); 152 153 #[async_trait] 154 impl<T, S> FromRequestParts<S> for Path<T> 155 where 156 T: DeserializeOwned + Send, 157 S: Send + Sync, 158 { 159 type Rejection = PathRejection; 160 from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection>161 async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> { 162 let params = match parts.extensions.get::<UrlParams>() { 163 Some(UrlParams::Params(params)) => params, 164 Some(UrlParams::InvalidUtf8InPathParam { key }) => { 165 let err = PathDeserializationError { 166 kind: ErrorKind::InvalidUtf8InPathParam { 167 key: key.to_string(), 168 }, 169 }; 170 let err = FailedToDeserializePathParams(err); 171 return Err(err.into()); 172 } 173 None => { 174 return Err(MissingPathParams.into()); 175 } 176 }; 177 178 T::deserialize(de::PathDeserializer::new(params)) 179 .map_err(|err| { 180 PathRejection::FailedToDeserializePathParams(FailedToDeserializePathParams(err)) 181 }) 182 .map(Path) 183 } 184 } 185 186 // this wrapper type is used as the deserializer error to hide the `serde::de::Error` impl which 187 // would otherwise be public if we used `ErrorKind` as the error directly 188 #[derive(Debug)] 189 pub(crate) struct PathDeserializationError { 190 pub(super) kind: ErrorKind, 191 } 192 193 impl PathDeserializationError { new(kind: ErrorKind) -> Self194 pub(super) fn new(kind: ErrorKind) -> Self { 195 Self { kind } 196 } 197 wrong_number_of_parameters() -> WrongNumberOfParameters<()>198 pub(super) fn wrong_number_of_parameters() -> WrongNumberOfParameters<()> { 199 WrongNumberOfParameters { got: () } 200 } 201 202 #[track_caller] unsupported_type(name: &'static str) -> Self203 pub(super) fn unsupported_type(name: &'static str) -> Self { 204 Self::new(ErrorKind::UnsupportedType { name }) 205 } 206 } 207 208 pub(super) struct WrongNumberOfParameters<G> { 209 got: G, 210 } 211 212 impl<G> WrongNumberOfParameters<G> { 213 #[allow(clippy::unused_self)] got<G2>(self, got: G2) -> WrongNumberOfParameters<G2>214 pub(super) fn got<G2>(self, got: G2) -> WrongNumberOfParameters<G2> { 215 WrongNumberOfParameters { got } 216 } 217 } 218 219 impl WrongNumberOfParameters<usize> { expected(self, expected: usize) -> PathDeserializationError220 pub(super) fn expected(self, expected: usize) -> PathDeserializationError { 221 PathDeserializationError::new(ErrorKind::WrongNumberOfParameters { 222 got: self.got, 223 expected, 224 }) 225 } 226 } 227 228 impl serde::de::Error for PathDeserializationError { 229 #[inline] custom<T>(msg: T) -> Self where T: fmt::Display,230 fn custom<T>(msg: T) -> Self 231 where 232 T: fmt::Display, 233 { 234 Self { 235 kind: ErrorKind::Message(msg.to_string()), 236 } 237 } 238 } 239 240 impl fmt::Display for PathDeserializationError { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 242 self.kind.fmt(f) 243 } 244 } 245 246 impl std::error::Error for PathDeserializationError {} 247 248 /// The kinds of errors that can happen we deserializing into a [`Path`]. 249 /// 250 /// This type is obtained through [`FailedToDeserializePathParams::kind`] or 251 /// [`FailedToDeserializePathParams::into_kind`] and is useful for building 252 /// more precise error messages. 253 #[derive(Debug, PartialEq, Eq)] 254 #[non_exhaustive] 255 pub enum ErrorKind { 256 /// The URI contained the wrong number of parameters. 257 WrongNumberOfParameters { 258 /// The number of actual parameters in the URI. 259 got: usize, 260 /// The number of expected parameters. 261 expected: usize, 262 }, 263 264 /// Failed to parse the value at a specific key into the expected type. 265 /// 266 /// This variant is used when deserializing into types that have named fields, such as structs. 267 ParseErrorAtKey { 268 /// The key at which the value was located. 269 key: String, 270 /// The value from the URI. 271 value: String, 272 /// The expected type of the value. 273 expected_type: &'static str, 274 }, 275 276 /// Failed to parse the value at a specific index into the expected type. 277 /// 278 /// This variant is used when deserializing into sequence types, such as tuples. 279 ParseErrorAtIndex { 280 /// The index at which the value was located. 281 index: usize, 282 /// The value from the URI. 283 value: String, 284 /// The expected type of the value. 285 expected_type: &'static str, 286 }, 287 288 /// Failed to parse a value into the expected type. 289 /// 290 /// This variant is used when deserializing into a primitive type (such as `String` and `u32`). 291 ParseError { 292 /// The value from the URI. 293 value: String, 294 /// The expected type of the value. 295 expected_type: &'static str, 296 }, 297 298 /// A parameter contained text that, once percent decoded, wasn't valid UTF-8. 299 InvalidUtf8InPathParam { 300 /// The key at which the invalid value was located. 301 key: String, 302 }, 303 304 /// Tried to serialize into an unsupported type such as nested maps. 305 /// 306 /// This error kind is caused by programmer errors and thus gets converted into a `500 Internal 307 /// Server Error` response. 308 UnsupportedType { 309 /// The name of the unsupported type. 310 name: &'static str, 311 }, 312 313 /// Catch-all variant for errors that don't fit any other variant. 314 Message(String), 315 } 316 317 impl fmt::Display for ErrorKind { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result318 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 319 match self { 320 ErrorKind::Message(error) => error.fmt(f), 321 ErrorKind::InvalidUtf8InPathParam { key } => write!(f, "Invalid UTF-8 in `{key}`"), 322 ErrorKind::WrongNumberOfParameters { got, expected } => { 323 write!( 324 f, 325 "Wrong number of path arguments for `Path`. Expected {expected} but got {got}" 326 )?; 327 328 if *expected == 1 { 329 write!(f, ". Note that multiple parameters must be extracted with a tuple `Path<(_, _)>` or a struct `Path<YourParams>`")?; 330 } 331 332 Ok(()) 333 } 334 ErrorKind::UnsupportedType { name } => write!(f, "Unsupported type `{name}`"), 335 ErrorKind::ParseErrorAtKey { 336 key, 337 value, 338 expected_type, 339 } => write!( 340 f, 341 "Cannot parse `{key}` with value `{value:?}` to a `{expected_type}`" 342 ), 343 ErrorKind::ParseError { 344 value, 345 expected_type, 346 } => write!(f, "Cannot parse `{value:?}` to a `{expected_type}`"), 347 ErrorKind::ParseErrorAtIndex { 348 index, 349 value, 350 expected_type, 351 } => write!( 352 f, 353 "Cannot parse value at index {index} with value `{value:?}` to a `{expected_type}`" 354 ), 355 } 356 } 357 } 358 359 /// Rejection type for [`Path`](super::Path) if the captured routes params couldn't be deserialized 360 /// into the expected type. 361 #[derive(Debug)] 362 pub struct FailedToDeserializePathParams(PathDeserializationError); 363 364 impl FailedToDeserializePathParams { 365 /// Get a reference to the underlying error kind. kind(&self) -> &ErrorKind366 pub fn kind(&self) -> &ErrorKind { 367 &self.0.kind 368 } 369 370 /// Convert this error into the underlying error kind. into_kind(self) -> ErrorKind371 pub fn into_kind(self) -> ErrorKind { 372 self.0.kind 373 } 374 375 /// Get the response body text used for this rejection. body_text(&self) -> String376 pub fn body_text(&self) -> String { 377 match self.0.kind { 378 ErrorKind::Message(_) 379 | ErrorKind::InvalidUtf8InPathParam { .. } 380 | ErrorKind::ParseError { .. } 381 | ErrorKind::ParseErrorAtIndex { .. } 382 | ErrorKind::ParseErrorAtKey { .. } => format!("Invalid URL: {}", self.0.kind), 383 ErrorKind::WrongNumberOfParameters { .. } | ErrorKind::UnsupportedType { .. } => { 384 self.0.kind.to_string() 385 } 386 } 387 } 388 389 /// Get the status code used for this rejection. status(&self) -> StatusCode390 pub fn status(&self) -> StatusCode { 391 match self.0.kind { 392 ErrorKind::Message(_) 393 | ErrorKind::InvalidUtf8InPathParam { .. } 394 | ErrorKind::ParseError { .. } 395 | ErrorKind::ParseErrorAtIndex { .. } 396 | ErrorKind::ParseErrorAtKey { .. } => StatusCode::BAD_REQUEST, 397 ErrorKind::WrongNumberOfParameters { .. } | ErrorKind::UnsupportedType { .. } => { 398 StatusCode::INTERNAL_SERVER_ERROR 399 } 400 } 401 } 402 } 403 404 impl IntoResponse for FailedToDeserializePathParams { into_response(self) -> Response405 fn into_response(self) -> Response { 406 axum_core::__log_rejection!( 407 rejection_type = Self, 408 body_text = self.body_text(), 409 status = self.status(), 410 ); 411 (self.status(), self.body_text()).into_response() 412 } 413 } 414 415 impl fmt::Display for FailedToDeserializePathParams { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result416 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 417 self.0.fmt(f) 418 } 419 } 420 421 impl std::error::Error for FailedToDeserializePathParams {} 422 423 /// Extractor that will get captures from the URL without deserializing them. 424 /// 425 /// In general you should prefer to use [`Path`] as it is higher level, however `RawPathParams` is 426 /// suitable if just want the raw params without deserializing them and thus saving some 427 /// allocations. 428 /// 429 /// Any percent encoded parameters will be automatically decoded. The decoded parameters must be 430 /// valid UTF-8, otherwise `RawPathParams` will fail and return a `400 Bad Request` response. 431 /// 432 /// # Example 433 /// 434 /// ```rust,no_run 435 /// use axum::{ 436 /// extract::RawPathParams, 437 /// routing::get, 438 /// Router, 439 /// }; 440 /// 441 /// async fn users_teams_show(params: RawPathParams) { 442 /// for (key, value) in ¶ms { 443 /// println!("{key:?} = {value:?}"); 444 /// } 445 /// } 446 /// 447 /// let app = Router::new().route("/users/:user_id/team/:team_id", get(users_teams_show)); 448 /// # let _: Router = app; 449 /// ``` 450 #[derive(Debug)] 451 pub struct RawPathParams(Vec<(Arc<str>, PercentDecodedStr)>); 452 453 #[async_trait] 454 impl<S> FromRequestParts<S> for RawPathParams 455 where 456 S: Send + Sync, 457 { 458 type Rejection = RawPathParamsRejection; 459 from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection>460 async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> { 461 let params = match parts.extensions.get::<UrlParams>() { 462 Some(UrlParams::Params(params)) => params, 463 Some(UrlParams::InvalidUtf8InPathParam { key }) => { 464 return Err(InvalidUtf8InPathParam { 465 key: Arc::clone(key), 466 } 467 .into()); 468 } 469 None => { 470 return Err(MissingPathParams.into()); 471 } 472 }; 473 474 Ok(Self(params.clone())) 475 } 476 } 477 478 impl RawPathParams { 479 /// Get an iterator over the path parameters. iter(&self) -> RawPathParamsIter<'_>480 pub fn iter(&self) -> RawPathParamsIter<'_> { 481 self.into_iter() 482 } 483 } 484 485 impl<'a> IntoIterator for &'a RawPathParams { 486 type Item = (&'a str, &'a str); 487 type IntoIter = RawPathParamsIter<'a>; 488 into_iter(self) -> Self::IntoIter489 fn into_iter(self) -> Self::IntoIter { 490 RawPathParamsIter(self.0.iter()) 491 } 492 } 493 494 /// An iterator over raw path parameters. 495 /// 496 /// Created with [`RawPathParams::iter`]. 497 #[derive(Debug)] 498 pub struct RawPathParamsIter<'a>(std::slice::Iter<'a, (Arc<str>, PercentDecodedStr)>); 499 500 impl<'a> Iterator for RawPathParamsIter<'a> { 501 type Item = (&'a str, &'a str); 502 next(&mut self) -> Option<Self::Item>503 fn next(&mut self) -> Option<Self::Item> { 504 let (key, value) = self.0.next()?; 505 Some((&**key, value.as_str())) 506 } 507 } 508 509 /// Rejection used by [`RawPathParams`] if a parameter contained text that, once percent decoded, 510 /// wasn't valid UTF-8. 511 #[derive(Debug)] 512 pub struct InvalidUtf8InPathParam { 513 key: Arc<str>, 514 } 515 516 impl InvalidUtf8InPathParam { 517 /// Get the response body text used for this rejection. body_text(&self) -> String518 pub fn body_text(&self) -> String { 519 self.to_string() 520 } 521 522 /// Get the status code used for this rejection. status(&self) -> StatusCode523 pub fn status(&self) -> StatusCode { 524 StatusCode::BAD_REQUEST 525 } 526 } 527 528 impl fmt::Display for InvalidUtf8InPathParam { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result529 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 530 write!(f, "Invalid UTF-8 in `{}`", self.key) 531 } 532 } 533 534 impl std::error::Error for InvalidUtf8InPathParam {} 535 536 impl IntoResponse for InvalidUtf8InPathParam { into_response(self) -> Response537 fn into_response(self) -> Response { 538 (self.status(), self.body_text()).into_response() 539 } 540 } 541 542 #[cfg(test)] 543 mod tests { 544 use super::*; 545 use crate::{routing::get, test_helpers::*, Router}; 546 use http::StatusCode; 547 use serde::Deserialize; 548 use std::collections::HashMap; 549 550 #[crate::test] extracting_url_params()551 async fn extracting_url_params() { 552 let app = Router::new().route( 553 "/users/:id", 554 get(|Path(id): Path<i32>| async move { 555 assert_eq!(id, 42); 556 }) 557 .post(|Path(params_map): Path<HashMap<String, i32>>| async move { 558 assert_eq!(params_map.get("id").unwrap(), &1337); 559 }), 560 ); 561 562 let client = TestClient::new(app); 563 564 let res = client.get("/users/42").send().await; 565 assert_eq!(res.status(), StatusCode::OK); 566 567 let res = client.post("/users/1337").send().await; 568 assert_eq!(res.status(), StatusCode::OK); 569 } 570 571 #[crate::test] extracting_url_params_multiple_times()572 async fn extracting_url_params_multiple_times() { 573 let app = Router::new().route("/users/:id", get(|_: Path<i32>, _: Path<String>| async {})); 574 575 let client = TestClient::new(app); 576 577 let res = client.get("/users/42").send().await; 578 assert_eq!(res.status(), StatusCode::OK); 579 } 580 581 #[crate::test] percent_decoding()582 async fn percent_decoding() { 583 let app = Router::new().route( 584 "/:key", 585 get(|Path(param): Path<String>| async move { param }), 586 ); 587 588 let client = TestClient::new(app); 589 590 let res = client.get("/one%20two").send().await; 591 592 assert_eq!(res.text().await, "one two"); 593 } 594 595 #[crate::test] supports_128_bit_numbers()596 async fn supports_128_bit_numbers() { 597 let app = Router::new() 598 .route( 599 "/i/:key", 600 get(|Path(param): Path<i128>| async move { param.to_string() }), 601 ) 602 .route( 603 "/u/:key", 604 get(|Path(param): Path<u128>| async move { param.to_string() }), 605 ); 606 607 let client = TestClient::new(app); 608 609 let res = client.get("/i/123").send().await; 610 assert_eq!(res.text().await, "123"); 611 612 let res = client.get("/u/123").send().await; 613 assert_eq!(res.text().await, "123"); 614 } 615 616 #[crate::test] wildcard()617 async fn wildcard() { 618 let app = Router::new() 619 .route( 620 "/foo/*rest", 621 get(|Path(param): Path<String>| async move { param }), 622 ) 623 .route( 624 "/bar/*rest", 625 get(|Path(params): Path<HashMap<String, String>>| async move { 626 params.get("rest").unwrap().clone() 627 }), 628 ); 629 630 let client = TestClient::new(app); 631 632 let res = client.get("/foo/bar/baz").send().await; 633 assert_eq!(res.text().await, "bar/baz"); 634 635 let res = client.get("/bar/baz/qux").send().await; 636 assert_eq!(res.text().await, "baz/qux"); 637 } 638 639 #[crate::test] captures_dont_match_empty_segments()640 async fn captures_dont_match_empty_segments() { 641 let app = Router::new().route("/:key", get(|| async {})); 642 643 let client = TestClient::new(app); 644 645 let res = client.get("/").send().await; 646 assert_eq!(res.status(), StatusCode::NOT_FOUND); 647 648 let res = client.get("/foo").send().await; 649 assert_eq!(res.status(), StatusCode::OK); 650 } 651 652 #[crate::test] str_reference_deserialize()653 async fn str_reference_deserialize() { 654 struct Param(String); 655 impl<'de> serde::Deserialize<'de> for Param { 656 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 657 where 658 D: serde::Deserializer<'de>, 659 { 660 let s = <&str as serde::Deserialize>::deserialize(deserializer)?; 661 Ok(Param(s.to_owned())) 662 } 663 } 664 665 let app = Router::new().route("/:key", get(|param: Path<Param>| async move { param.0 .0 })); 666 667 let client = TestClient::new(app); 668 669 let res = client.get("/foo").send().await; 670 assert_eq!(res.text().await, "foo"); 671 672 // percent decoding should also work 673 let res = client.get("/foo%20bar").send().await; 674 assert_eq!(res.text().await, "foo bar"); 675 } 676 677 #[crate::test] two_path_extractors()678 async fn two_path_extractors() { 679 let app = Router::new().route("/:a/:b", get(|_: Path<String>, _: Path<String>| async {})); 680 681 let client = TestClient::new(app); 682 683 let res = client.get("/a/b").send().await; 684 assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR); 685 assert_eq!( 686 res.text().await, 687 "Wrong number of path arguments for `Path`. Expected 1 but got 2. \ 688 Note that multiple parameters must be extracted with a tuple `Path<(_, _)>` or a struct `Path<YourParams>`", 689 ); 690 } 691 692 #[crate::test] deserialize_into_vec_of_tuples()693 async fn deserialize_into_vec_of_tuples() { 694 let app = Router::new().route( 695 "/:a/:b", 696 get(|Path(params): Path<Vec<(String, String)>>| async move { 697 assert_eq!( 698 params, 699 vec![ 700 ("a".to_owned(), "foo".to_owned()), 701 ("b".to_owned(), "bar".to_owned()) 702 ] 703 ); 704 }), 705 ); 706 707 let client = TestClient::new(app); 708 709 let res = client.get("/foo/bar").send().await; 710 assert_eq!(res.status(), StatusCode::OK); 711 } 712 713 #[crate::test] type_that_uses_deserialize_any()714 async fn type_that_uses_deserialize_any() { 715 use time::Date; 716 717 #[derive(Deserialize)] 718 struct Params { 719 a: Date, 720 b: Date, 721 c: Date, 722 } 723 724 let app = Router::new() 725 .route( 726 "/single/:a", 727 get(|Path(a): Path<Date>| async move { format!("single: {a}") }), 728 ) 729 .route( 730 "/tuple/:a/:b/:c", 731 get(|Path((a, b, c)): Path<(Date, Date, Date)>| async move { 732 format!("tuple: {a} {b} {c}") 733 }), 734 ) 735 .route( 736 "/vec/:a/:b/:c", 737 get(|Path(vec): Path<Vec<Date>>| async move { 738 let [a, b, c]: [Date; 3] = vec.try_into().unwrap(); 739 format!("vec: {a} {b} {c}") 740 }), 741 ) 742 .route( 743 "/vec_pairs/:a/:b/:c", 744 get(|Path(vec): Path<Vec<(String, Date)>>| async move { 745 let [(_, a), (_, b), (_, c)]: [(String, Date); 3] = vec.try_into().unwrap(); 746 format!("vec_pairs: {a} {b} {c}") 747 }), 748 ) 749 .route( 750 "/map/:a/:b/:c", 751 get(|Path(mut map): Path<HashMap<String, Date>>| async move { 752 let a = map.remove("a").unwrap(); 753 let b = map.remove("b").unwrap(); 754 let c = map.remove("c").unwrap(); 755 format!("map: {a} {b} {c}") 756 }), 757 ) 758 .route( 759 "/struct/:a/:b/:c", 760 get(|Path(params): Path<Params>| async move { 761 format!("struct: {} {} {}", params.a, params.b, params.c) 762 }), 763 ); 764 765 let client = TestClient::new(app); 766 767 let res = client.get("/single/2023-01-01").send().await; 768 assert_eq!(res.text().await, "single: 2023-01-01"); 769 770 let res = client 771 .get("/tuple/2023-01-01/2023-01-02/2023-01-03") 772 .send() 773 .await; 774 assert_eq!(res.text().await, "tuple: 2023-01-01 2023-01-02 2023-01-03"); 775 776 let res = client 777 .get("/vec/2023-01-01/2023-01-02/2023-01-03") 778 .send() 779 .await; 780 assert_eq!(res.text().await, "vec: 2023-01-01 2023-01-02 2023-01-03"); 781 782 let res = client 783 .get("/vec_pairs/2023-01-01/2023-01-02/2023-01-03") 784 .send() 785 .await; 786 assert_eq!( 787 res.text().await, 788 "vec_pairs: 2023-01-01 2023-01-02 2023-01-03", 789 ); 790 791 let res = client 792 .get("/map/2023-01-01/2023-01-02/2023-01-03") 793 .send() 794 .await; 795 assert_eq!(res.text().await, "map: 2023-01-01 2023-01-02 2023-01-03"); 796 797 let res = client 798 .get("/struct/2023-01-01/2023-01-02/2023-01-03") 799 .send() 800 .await; 801 assert_eq!(res.text().await, "struct: 2023-01-01 2023-01-02 2023-01-03"); 802 } 803 804 #[crate::test] wrong_number_of_parameters_json()805 async fn wrong_number_of_parameters_json() { 806 use serde_json::Value; 807 808 let app = Router::new() 809 .route("/one/:a", get(|_: Path<(Value, Value)>| async {})) 810 .route("/two/:a/:b", get(|_: Path<Value>| async {})); 811 812 let client = TestClient::new(app); 813 814 let res = client.get("/one/1").send().await; 815 assert!(res 816 .text() 817 .await 818 .starts_with("Wrong number of path arguments for `Path`. Expected 2 but got 1")); 819 820 let res = client.get("/two/1/2").send().await; 821 assert!(res 822 .text() 823 .await 824 .starts_with("Wrong number of path arguments for `Path`. Expected 1 but got 2")); 825 } 826 827 #[crate::test] raw_path_params()828 async fn raw_path_params() { 829 let app = Router::new().route( 830 "/:a/:b/:c", 831 get(|params: RawPathParams| async move { 832 params 833 .into_iter() 834 .map(|(key, value)| format!("{key}={value}")) 835 .collect::<Vec<_>>() 836 .join(" ") 837 }), 838 ); 839 840 let client = TestClient::new(app); 841 let res = client.get("/foo/bar/baz").send().await; 842 let body = res.text().await; 843 assert_eq!(body, "a=foo b=bar c=baz"); 844 } 845 } 846