diff --git a/examples/error-handling/Cargo.toml b/examples/error-handling/Cargo.toml index f30538cd..f090d579 100644 --- a/examples/error-handling/Cargo.toml +++ b/examples/error-handling/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -axum = { path = "../../axum" } +axum = { path = "../../axum", features=["matched-path"] } axum-extra = { path = "../../axum-extra" } chrono = { version = "0.4", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } diff --git a/examples/error-handling/src/custom_extractor.rs b/examples/error-handling/src/custom_extractor.rs new file mode 100644 index 00000000..711f3932 --- /dev/null +++ b/examples/error-handling/src/custom_extractor.rs @@ -0,0 +1,59 @@ +use axum::extract::MatchedPath; +use axum::{ + async_trait, + extract::{rejection::JsonRejection, FromRequest, RequestParts}, + http::StatusCode, + response::IntoResponse, + BoxError, +}; +use chrono::Utc; +use serde::de::DeserializeOwned; +use serde_json::{json, Value}; + +pub async fn handler(Json(value): Json) -> impl IntoResponse { + Json(dbg!(value)); +} + +// We define our own `Json` extractor that customizes the error from `axum::Json` +pub struct Json(T); + +#[async_trait] +impl FromRequest for Json +where + S: Send + Sync, + // these trait bounds are copied from `impl FromRequest for axum::Json` + T: DeserializeOwned, + B: axum::body::HttpBody + Send, + B::Data: Send, + B::Error: Into, +{ + type Rejection = (StatusCode, axum::Json); + + async fn from_request(req: &mut RequestParts) -> Result { + match axum::Json::::from_request(req).await { + Ok(value) => Ok(Self(value.0)), + Err(rejection) => { + // convert the error from `axum::Json` into whatever we want + let path = req + .extensions() + .get::() + .map(|x| x.as_str().to_owned()); + + let payload = json!({ + "message": rejection.to_string(), + "timestamp": Utc::now(), + "origin": "custom_extractor", + "path": path, + }); + + let code = match rejection { + JsonRejection::JsonDataError(_) | JsonRejection::MissingJsonContentType(_) => { + StatusCode::BAD_REQUEST + } + _ => StatusCode::INTERNAL_SERVER_ERROR, + }; + Err((code, axum::Json(payload))) + } + } + } +} diff --git a/examples/error-handling/src/main.rs b/examples/error-handling/src/main.rs index 45ee6cf8..eb794f42 100644 --- a/examples/error-handling/src/main.rs +++ b/examples/error-handling/src/main.rs @@ -1,24 +1,23 @@ mod with_rejection; +mod custom_extractor; +use axum::{routing::get, Router, Server}; use std::net::SocketAddr; -use axum::{Server, Router, routing::get}; 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(|_| "error_handling=debug".into()), + std::env::var("RUST_LOG").unwrap_or_else(|_| "error_handling=debug".into()), )) .with(tracing_subscriber::fmt::layer()) .init(); // Build our application with some routes let app = Router::new() - .route("/withRejection", get(with_rejection::handler)); + .route("/withRejection", get(with_rejection::handler)) + .route("/customExtractor", get(custom_extractor::handler)); // Run our application let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); @@ -27,4 +26,4 @@ async fn main() { .serve(app.into_make_service()) .await .unwrap(); -} \ No newline at end of file +}