From f8154a088c8a41fcbcbdd9aff3d78aa91efa9247 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Thu, 30 Sep 2021 19:55:58 +0200 Subject: [PATCH] Add example showing up to customize extractor error (#356) Lots have been asking about this so makes sense to have an example for. Once this is merged I'll add a link to it in the docs. --- examples/customize-extractor-error/Cargo.toml | 13 +++ .../customize-extractor-error/src/main.rs | 97 +++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 examples/customize-extractor-error/Cargo.toml create mode 100644 examples/customize-extractor-error/src/main.rs diff --git a/examples/customize-extractor-error/Cargo.toml b/examples/customize-extractor-error/Cargo.toml new file mode 100644 index 00000000..7b558431 --- /dev/null +++ b/examples/customize-extractor-error/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "example-customize-extractor-error" +version = "0.1.0" +edition = "2018" +publish = false + +[dependencies] +axum = { path = "../.." } +tokio = { version = "1.0", features = ["full"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tracing = "0.1" +tracing-subscriber = "0.2" diff --git a/examples/customize-extractor-error/src/main.rs b/examples/customize-extractor-error/src/main.rs new file mode 100644 index 00000000..2a344306 --- /dev/null +++ b/examples/customize-extractor-error/src/main.rs @@ -0,0 +1,97 @@ +//! Run with +//! +//! ```not_rust +//! cargo run -p example-customize-extractor-error +//! ``` + +use axum::{ + async_trait, + extract::rejection::JsonRejection, + extract::{FromRequest, RequestParts}, + handler::post, + http::StatusCode, + BoxError, Router, +}; +use serde::{de::DeserializeOwned, Deserialize}; +use serde_json::{json, Value}; +use std::{borrow::Cow, net::SocketAddr}; + +#[tokio::main] +async fn main() { + // Set the RUST_LOG, if it hasn't been explicitly defined + if std::env::var_os("RUST_LOG").is_none() { + std::env::set_var("RUST_LOG", "example_customize_extractor_error=debug") + } + tracing_subscriber::fmt::init(); + + // build our application with a route + let app = Router::new().route("/users", post(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(Json(user): Json) { + dbg!(&user); +} + +#[derive(Debug, Deserialize)] +struct User { + id: i64, + username: String, +} + +// We define our own `Json` extactor that customizes the error from `axum::Json` +struct Json(T); + +#[async_trait] +impl FromRequest for Json +where + // 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 (status, body): (_, Cow<'_, str>) = match rejection { + JsonRejection::InvalidJsonBody(err) => ( + StatusCode::BAD_REQUEST, + format!("Invalid JSON request: {}", err).into(), + ), + JsonRejection::MissingJsonContentType(err) => { + (StatusCode::BAD_REQUEST, err.to_string().into()) + } + JsonRejection::BodyAlreadyExtracted(err) => { + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string().into()) + } + JsonRejection::HeadersAlreadyExtracted(err) => { + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string().into()) + } + err => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Unknown internal error: {}", err).into(), + ), + }; + + Err(( + status, + // we use `axum::Json` here to generate a JSON response + // body but you can use whatever response you want + axum::Json(json!({ "error": body })), + )) + } + } + } +}