Improve Path error when its extracted multiple times (#1023)

* Improve `Path` error

* Improve docs

* changelog

* Update axum/src/extract/path/mod.rs

Co-authored-by: Jonas Platte <jplatte+git@posteo.de>

* fix test

Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
This commit is contained in:
David Pedersen 2022-05-11 11:38:37 +02:00 committed by GitHub
parent 280334347b
commit 08cbade3cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 47 additions and 10 deletions

View file

@ -8,8 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
# Unreleased
- **added:** Add `WebSocket::protocol` to return the selected WebSocket subprotocol, if there is one. ([#1022])
- **fixed:** Improve error for `PathRejection::WrongNumberOfParameters` to hint at using
`Path<(String, String)>` or `Path<SomeStruct>` ([#1023])
- **fixed:** `PathRejection::WrongNumberOfParameters` now uses `500 Internal Server Error` since
its a programmer error and not a client error ([#1023])
[#1022]: https://github.com/tokio-rs/axum/pull/1022
[#1023]: https://github.com/tokio-rs/axum/pull/1023
# 0.5.5 (10. May, 2022)

View file

@ -66,8 +66,7 @@ use std::{
/// ```
///
/// Path segments also can be deserialized into any type that implements
/// [`serde::Deserialize`]. Path segment labels will be matched with struct
/// field names.
/// [`serde::Deserialize`]. This includes tuples and structs:
///
/// ```rust,no_run
/// use axum::{
@ -78,6 +77,7 @@ use std::{
/// use serde::Deserialize;
/// use uuid::Uuid;
///
/// // Path segment labels will be matched with struct field names
/// #[derive(Deserialize)]
/// struct Params {
/// user_id: Uuid,
@ -90,7 +90,17 @@ use std::{
/// // ...
/// }
///
/// let app = Router::new().route("/users/:user_id/team/:team_id", get(users_teams_show));
/// // When using tuples the path segments will be matched by their position in the route
/// async fn users_teams_create(
/// Path((user_id, team_id)): Path<(String, String)>,
/// ) {
/// // ...
/// }
///
/// let app = Router::new().route(
/// "/users/:user_id/team/:team_id",
/// get(users_teams_show).post(users_teams_create),
/// );
/// # async {
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
/// # };
@ -319,11 +329,19 @@ impl fmt::Display for ErrorKind {
match self {
ErrorKind::Message(error) => error.fmt(f),
ErrorKind::InvalidUtf8InPathParam { key } => write!(f, "Invalid UTF-8 in `{}`", key),
ErrorKind::WrongNumberOfParameters { got, expected } => write!(
f,
"Wrong number of parameters. Expected {} but got {}",
expected, got
),
ErrorKind::WrongNumberOfParameters { got, expected } => {
write!(
f,
"Wrong number of path arguments for `Path`. Expected {} but got {}",
expected, got
)?;
if *expected == 1 {
write!(f, ". Note that multiple parameters must be extracted with a tuple `Path<(_, _)>` or a struct `Path<YourParams>`")?;
}
Ok(())
}
ErrorKind::UnsupportedType { name } => write!(f, "Unsupported type `{}`", name),
ErrorKind::ParseErrorAtKey {
key,
@ -368,14 +386,13 @@ impl IntoResponse for FailedToDeserializePathParams {
let (status, body) = match self.0.kind {
ErrorKind::Message(_)
| ErrorKind::InvalidUtf8InPathParam { .. }
| ErrorKind::WrongNumberOfParameters { .. }
| ErrorKind::ParseError { .. }
| ErrorKind::ParseErrorAtIndex { .. }
| ErrorKind::ParseErrorAtKey { .. } => (
StatusCode::BAD_REQUEST,
format!("Invalid URL: {}", self.0.kind),
),
ErrorKind::UnsupportedType { .. } => {
ErrorKind::WrongNumberOfParameters { .. } | ErrorKind::UnsupportedType { .. } => {
(StatusCode::INTERNAL_SERVER_ERROR, self.0.kind.to_string())
}
};
@ -539,4 +556,19 @@ mod tests {
let res = client.get("/foo%20bar").send().await;
assert_eq!(res.text().await, "foo bar");
}
#[tokio::test]
async fn two_path_extractors() {
let app = Router::new().route("/:a/:b", get(|_: Path<String>, _: Path<String>| async {}));
let client = TestClient::new(app);
let res = client.get("/a/b").send().await;
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
assert_eq!(
res.text().await,
"Wrong number of path arguments for `Path`. Expected 1 but got 2. \
Note that multiple parameters must be extracted with a tuple `Path<(_, _)>` or a struct `Path<YourParams>`",
);
}
}