//! Run with //! //! ```not_rust //! cd examples && cargo run -p example-customize-path-rejection //! ``` use axum::{ async_trait, extract::{path::ErrorKind, rejection::PathRejection, FromRequest, RequestParts}, http::StatusCode, response::IntoResponse, routing::get, Router, }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::net::SocketAddr; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; #[tokio::main] async fn main() { tracing_subscriber::registry() .with(tracing_subscriber::EnvFilter::new( std::env::var("RUST_LOG") .unwrap_or_else(|_| "example_customize_path_rejection=debug".into()), )) .with(tracing_subscriber::fmt::layer()) .init(); // build our application with a route let app = Router::new().route("/users/:user_id/teams/:team_id", get(handler)); // run it let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); println!("listening on {}", addr); axum::Server::bind(&addr) .serve(app.into_make_service()) .await .unwrap(); } async fn handler(Path(params): Path) -> impl IntoResponse { axum::Json(params) } #[derive(Debug, Deserialize, Serialize)] struct Params { user_id: u32, team_id: u32, } // We define our own `Path` extractor that customizes the error from `axum::extract::Path` struct Path(T); #[async_trait] impl FromRequest for Path where // these trait bounds are copied from `impl FromRequest for axum::extract::path::Path` T: DeserializeOwned + Send, B: Send, { type Rejection = (StatusCode, axum::Json); async fn from_request(req: &mut RequestParts) -> Result { match axum::extract::Path::::from_request(req).await { Ok(value) => Ok(Self(value.0)), Err(rejection) => { let (status, body) = match rejection { PathRejection::FailedToDeserializePathParams(inner) => { let mut status = StatusCode::BAD_REQUEST; let kind = inner.into_kind(); let body = match &kind { ErrorKind::WrongNumberOfParameters { .. } => PathError { message: kind.to_string(), location: None, }, ErrorKind::ParseErrorAtKey { key, .. } => PathError { message: kind.to_string(), location: Some(key.clone()), }, ErrorKind::ParseErrorAtIndex { index, .. } => PathError { message: kind.to_string(), location: Some(index.to_string()), }, ErrorKind::ParseError { .. } => PathError { message: kind.to_string(), location: None, }, ErrorKind::InvalidUtf8InPathParam { key } => PathError { message: kind.to_string(), location: Some(key.clone()), }, ErrorKind::UnsupportedType { .. } => { // this error is caused by the programmer using an unsupported type // (such as nested maps) so respond with `500` instead status = StatusCode::INTERNAL_SERVER_ERROR; PathError { message: kind.to_string(), location: None, } } ErrorKind::Message(msg) => PathError { message: msg.clone(), location: None, }, _ => PathError { message: format!("Unhandled deserialization error: {}", kind), location: None, }, }; (status, body) } PathRejection::MissingPathParams(error) => ( StatusCode::INTERNAL_SERVER_ERROR, PathError { message: error.to_string(), location: None, }, ), _ => ( StatusCode::INTERNAL_SERVER_ERROR, PathError { message: format!("Unhandled path rejection: {}", rejection), location: None, }, ), }; Err((status, axum::Json(body))) } } } } #[derive(Serialize)] struct PathError { message: String, location: Option, }