| use crate::{ |
| body::{Bytes, HttpBody}, |
| extract::{rejection::*, FromRequest}, |
| BoxError, |
| }; |
| use async_trait::async_trait; |
| use axum_core::response::{IntoResponse, Response}; |
| use bytes::{BufMut, BytesMut}; |
| use http::{ |
| header::{self, HeaderMap, HeaderValue}, |
| Request, StatusCode, |
| }; |
| use serde::{de::DeserializeOwned, Serialize}; |
| |
| /// JSON Extractor / Response. |
| /// |
| /// When used as an extractor, it can deserialize request bodies into some type that |
| /// implements [`serde::Deserialize`]. The request will be rejected (and a [`JsonRejection`] will |
| /// be returned) if: |
| /// |
| /// - The request doesn't have a `Content-Type: application/json` (or similar) header. |
| /// - The body doesn't contain syntactically valid JSON. |
| /// - The body contains syntactically valid JSON but it couldn't be deserialized into the target |
| /// type. |
| /// - Buffering the request body fails. |
| /// |
| /// ⚠️ Since parsing JSON requires consuming the request body, the `Json` extractor must be |
| /// *last* if there are multiple extractors in a handler. |
| /// See ["the order of extractors"][order-of-extractors] |
| /// |
| /// [order-of-extractors]: crate::extract#the-order-of-extractors |
| /// |
| /// See [`JsonRejection`] for more details. |
| /// |
| /// # Extractor example |
| /// |
| /// ```rust,no_run |
| /// use axum::{ |
| /// extract, |
| /// routing::post, |
| /// Router, |
| /// }; |
| /// use serde::Deserialize; |
| /// |
| /// #[derive(Deserialize)] |
| /// struct CreateUser { |
| /// email: String, |
| /// password: String, |
| /// } |
| /// |
| /// async fn create_user(extract::Json(payload): extract::Json<CreateUser>) { |
| /// // payload is a `CreateUser` |
| /// } |
| /// |
| /// let app = Router::new().route("/users", post(create_user)); |
| /// # async { |
| /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); |
| /// # }; |
| /// ``` |
| /// |
| /// When used as a response, it can serialize any type that implements [`serde::Serialize`] to |
| /// `JSON`, and will automatically set `Content-Type: application/json` header. |
| /// |
| /// # Response example |
| /// |
| /// ``` |
| /// use axum::{ |
| /// extract::Path, |
| /// routing::get, |
| /// Router, |
| /// Json, |
| /// }; |
| /// use serde::Serialize; |
| /// use uuid::Uuid; |
| /// |
| /// #[derive(Serialize)] |
| /// struct User { |
| /// id: Uuid, |
| /// username: String, |
| /// } |
| /// |
| /// async fn get_user(Path(user_id) : Path<Uuid>) -> Json<User> { |
| /// let user = find_user(user_id).await; |
| /// Json(user) |
| /// } |
| /// |
| /// async fn find_user(user_id: Uuid) -> User { |
| /// // ... |
| /// # unimplemented!() |
| /// } |
| /// |
| /// let app = Router::new().route("/users/:id", get(get_user)); |
| /// # async { |
| /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); |
| /// # }; |
| /// ``` |
| #[derive(Debug, Clone, Copy, Default)] |
| #[cfg_attr(docsrs, doc(cfg(feature = "json")))] |
| #[must_use] |
| pub struct Json<T>(pub T); |
| |
| #[async_trait] |
| impl<T, S, B> FromRequest<S, B> for Json<T> |
| where |
| T: DeserializeOwned, |
| B: HttpBody + Send + 'static, |
| B::Data: Send, |
| B::Error: Into<BoxError>, |
| S: Send + Sync, |
| { |
| type Rejection = JsonRejection; |
| |
| async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> { |
| if json_content_type(req.headers()) { |
| let bytes = Bytes::from_request(req, state).await?; |
| let deserializer = &mut serde_json::Deserializer::from_slice(&bytes); |
| |
| let value = match serde_path_to_error::deserialize(deserializer) { |
| Ok(value) => value, |
| Err(err) => { |
| let rejection = match err.inner().classify() { |
| serde_json::error::Category::Data => JsonDataError::from_err(err).into(), |
| serde_json::error::Category::Syntax | serde_json::error::Category::Eof => { |
| JsonSyntaxError::from_err(err).into() |
| } |
| serde_json::error::Category::Io => { |
| if cfg!(debug_assertions) { |
| // we don't use `serde_json::from_reader` and instead always buffer |
| // bodies first, so we shouldn't encounter any IO errors |
| unreachable!() |
| } else { |
| JsonSyntaxError::from_err(err).into() |
| } |
| } |
| }; |
| return Err(rejection); |
| } |
| }; |
| |
| Ok(Json(value)) |
| } else { |
| Err(MissingJsonContentType.into()) |
| } |
| } |
| } |
| |
| fn json_content_type(headers: &HeaderMap) -> bool { |
| let content_type = if let Some(content_type) = headers.get(header::CONTENT_TYPE) { |
| content_type |
| } else { |
| return false; |
| }; |
| |
| let content_type = if let Ok(content_type) = content_type.to_str() { |
| content_type |
| } else { |
| return false; |
| }; |
| |
| let mime = if let Ok(mime) = content_type.parse::<mime::Mime>() { |
| mime |
| } else { |
| return false; |
| }; |
| |
| let is_json_content_type = mime.type_() == "application" |
| && (mime.subtype() == "json" || mime.suffix().map_or(false, |name| name == "json")); |
| |
| is_json_content_type |
| } |
| |
| axum_core::__impl_deref!(Json); |
| |
| impl<T> From<T> for Json<T> { |
| fn from(inner: T) -> Self { |
| Self(inner) |
| } |
| } |
| |
| impl<T> IntoResponse for Json<T> |
| where |
| T: Serialize, |
| { |
| fn into_response(self) -> Response { |
| // Use a small initial capacity of 128 bytes like serde_json::to_vec |
| // https://docs.rs/serde_json/1.0.82/src/serde_json/ser.rs.html#2189 |
| let mut buf = BytesMut::with_capacity(128).writer(); |
| match serde_json::to_writer(&mut buf, &self.0) { |
| Ok(()) => ( |
| [( |
| header::CONTENT_TYPE, |
| HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()), |
| )], |
| buf.into_inner().freeze(), |
| ) |
| .into_response(), |
| Err(err) => ( |
| StatusCode::INTERNAL_SERVER_ERROR, |
| [( |
| header::CONTENT_TYPE, |
| HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()), |
| )], |
| err.to_string(), |
| ) |
| .into_response(), |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::{routing::post, test_helpers::*, Router}; |
| use serde::Deserialize; |
| use serde_json::{json, Value}; |
| |
| #[crate::test] |
| async fn deserialize_body() { |
| #[derive(Debug, Deserialize)] |
| struct Input { |
| foo: String, |
| } |
| |
| let app = Router::new().route("/", post(|input: Json<Input>| async { input.0.foo })); |
| |
| let client = TestClient::new(app); |
| let res = client.post("/").json(&json!({ "foo": "bar" })).send().await; |
| let body = res.text().await; |
| |
| assert_eq!(body, "bar"); |
| } |
| |
| #[crate::test] |
| async fn consume_body_to_json_requires_json_content_type() { |
| #[derive(Debug, Deserialize)] |
| struct Input { |
| foo: String, |
| } |
| |
| let app = Router::new().route("/", post(|input: Json<Input>| async { input.0.foo })); |
| |
| let client = TestClient::new(app); |
| let res = client.post("/").body(r#"{ "foo": "bar" }"#).send().await; |
| |
| let status = res.status(); |
| |
| assert_eq!(status, StatusCode::UNSUPPORTED_MEDIA_TYPE); |
| } |
| |
| #[crate::test] |
| async fn json_content_types() { |
| async fn valid_json_content_type(content_type: &str) -> bool { |
| println!("testing {content_type:?}"); |
| |
| let app = Router::new().route("/", post(|Json(_): Json<Value>| async {})); |
| |
| let res = TestClient::new(app) |
| .post("/") |
| .header("content-type", content_type) |
| .body("{}") |
| .send() |
| .await; |
| |
| res.status() == StatusCode::OK |
| } |
| |
| assert!(valid_json_content_type("application/json").await); |
| assert!(valid_json_content_type("application/json; charset=utf-8").await); |
| assert!(valid_json_content_type("application/json;charset=utf-8").await); |
| assert!(valid_json_content_type("application/cloudevents+json").await); |
| assert!(!valid_json_content_type("text/json").await); |
| } |
| |
| #[crate::test] |
| async fn invalid_json_syntax() { |
| let app = Router::new().route("/", post(|_: Json<serde_json::Value>| async {})); |
| |
| let client = TestClient::new(app); |
| let res = client |
| .post("/") |
| .body("{") |
| .header("content-type", "application/json") |
| .send() |
| .await; |
| |
| assert_eq!(res.status(), StatusCode::BAD_REQUEST); |
| } |
| |
| #[derive(Deserialize)] |
| struct Foo { |
| #[allow(dead_code)] |
| a: i32, |
| #[allow(dead_code)] |
| b: Vec<Bar>, |
| } |
| |
| #[derive(Deserialize)] |
| struct Bar { |
| #[allow(dead_code)] |
| x: i32, |
| #[allow(dead_code)] |
| y: i32, |
| } |
| |
| #[crate::test] |
| async fn invalid_json_data() { |
| let app = Router::new().route("/", post(|_: Json<Foo>| async {})); |
| |
| let client = TestClient::new(app); |
| let res = client |
| .post("/") |
| .body("{\"a\": 1, \"b\": [{\"x\": 2}]}") |
| .header("content-type", "application/json") |
| .send() |
| .await; |
| |
| assert_eq!(res.status(), StatusCode::UNPROCESSABLE_ENTITY); |
| let body_text = res.text().await; |
| assert_eq!( |
| body_text, |
| "Failed to deserialize the JSON body into the target type: b[0]: missing field `y` at line 1 column 23" |
| ); |
| } |
| } |