1use crate::extract::Request;
2use crate::extract::{rejection::*, FromRequest};
3use axum_core::extract::OptionalFromRequest;
4use axum_core::response::{IntoResponse, Response};
5use bytes::{BufMut, Bytes, BytesMut};
6use http::{
7    header::{self, HeaderMap, HeaderValue},
8    StatusCode,
9};
10use serde::{de::DeserializeOwned, Serialize};
11
12#[derive(Debug, Clone, Copy, Default)]
95#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
96#[must_use]
97pub struct Json<T>(pub T);
98
99impl<T, S> FromRequest<S> for Json<T>
100where
101    T: DeserializeOwned,
102    S: Send + Sync,
103{
104    type Rejection = JsonRejection;
105
106    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
107        if !json_content_type(req.headers()) {
108            return Err(MissingJsonContentType.into());
109        }
110
111        let bytes = Bytes::from_request(req, state).await?;
112        Self::from_bytes(&bytes)
113    }
114}
115
116impl<T, S> OptionalFromRequest<S> for Json<T>
117where
118    T: DeserializeOwned,
119    S: Send + Sync,
120{
121    type Rejection = JsonRejection;
122
123    async fn from_request(req: Request, state: &S) -> Result<Option<Self>, Self::Rejection> {
124        let headers = req.headers();
125        if headers.get(header::CONTENT_TYPE).is_some() {
126            if json_content_type(headers) {
127                let bytes = Bytes::from_request(req, state).await?;
128                Ok(Some(Self::from_bytes(&bytes)?))
129            } else {
130                Err(MissingJsonContentType.into())
131            }
132        } else {
133            Ok(None)
134        }
135    }
136}
137
138fn json_content_type(headers: &HeaderMap) -> bool {
139    let Some(content_type) = headers.get(header::CONTENT_TYPE) else {
140        return false;
141    };
142
143    let Ok(content_type) = content_type.to_str() else {
144        return false;
145    };
146
147    let Ok(mime) = content_type.parse::<mime::Mime>() else {
148        return false;
149    };
150
151    let is_json_content_type = mime.type_() == "application"
152        && (mime.subtype() == "json" || mime.suffix().is_some_and(|name| name == "json"));
153
154    is_json_content_type
155}
156
157axum_core::__impl_deref!(Json);
158
159impl<T> From<T> for Json<T> {
160    fn from(inner: T) -> Self {
161        Self(inner)
162    }
163}
164
165impl<T> Json<T>
166where
167    T: DeserializeOwned,
168{
169    pub fn from_bytes(bytes: &[u8]) -> Result<Self, JsonRejection> {
173        fn make_rejection(err: serde_path_to_error::Error<serde_json::Error>) -> JsonRejection {
175            match err.inner().classify() {
176                serde_json::error::Category::Data => JsonDataError::from_err(err).into(),
177                serde_json::error::Category::Syntax | serde_json::error::Category::Eof => {
178                    JsonSyntaxError::from_err(err).into()
179                }
180                serde_json::error::Category::Io => {
181                    if cfg!(debug_assertions) {
182                        unreachable!()
185                    } else {
186                        JsonSyntaxError::from_err(err).into()
187                    }
188                }
189            }
190        }
191
192        let deserializer = &mut serde_json::Deserializer::from_slice(bytes);
193
194        match serde_path_to_error::deserialize(deserializer) {
195            Ok(value) => Ok(Json(value)),
196            Err(err) => Err(make_rejection(err)),
197        }
198    }
199}
200
201impl<T> IntoResponse for Json<T>
202where
203    T: Serialize,
204{
205    fn into_response(self) -> Response {
206        fn make_response(buf: BytesMut, ser_result: serde_json::Result<()>) -> Response {
208            match ser_result {
209                Ok(()) => (
210                    [(
211                        header::CONTENT_TYPE,
212                        HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
213                    )],
214                    buf.freeze(),
215                )
216                    .into_response(),
217                Err(err) => (
218                    StatusCode::INTERNAL_SERVER_ERROR,
219                    [(
220                        header::CONTENT_TYPE,
221                        HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
222                    )],
223                    err.to_string(),
224                )
225                    .into_response(),
226            }
227        }
228
229        let mut buf = BytesMut::with_capacity(128).writer();
232        let res = serde_json::to_writer(&mut buf, &self.0);
233        make_response(buf.into_inner(), res)
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240    use crate::{routing::post, test_helpers::*, Router};
241    use serde::Deserialize;
242    use serde_json::{json, Value};
243
244    #[crate::test]
245    async fn deserialize_body() {
246        #[derive(Debug, Deserialize)]
247        struct Input {
248            foo: String,
249        }
250
251        let app = Router::new().route("/", post(|input: Json<Input>| async { input.0.foo }));
252
253        let client = TestClient::new(app);
254        let res = client.post("/").json(&json!({ "foo": "bar" })).await;
255        let body = res.text().await;
256
257        assert_eq!(body, "bar");
258    }
259
260    #[crate::test]
261    async fn consume_body_to_json_requires_json_content_type() {
262        #[derive(Debug, Deserialize)]
263        struct Input {
264            foo: String,
265        }
266
267        let app = Router::new().route("/", post(|input: Json<Input>| async { input.0.foo }));
268
269        let client = TestClient::new(app);
270        let res = client.post("/").body(r#"{ "foo": "bar" }"#).await;
271
272        let status = res.status();
273
274        assert_eq!(status, StatusCode::UNSUPPORTED_MEDIA_TYPE);
275    }
276
277    #[crate::test]
278    async fn json_content_types() {
279        async fn valid_json_content_type(content_type: &str) -> bool {
280            println!("testing {content_type:?}");
281
282            let app = Router::new().route("/", post(|Json(_): Json<Value>| async {}));
283
284            let res = TestClient::new(app)
285                .post("/")
286                .header("content-type", content_type)
287                .body("{}")
288                .await;
289
290            res.status() == StatusCode::OK
291        }
292
293        assert!(valid_json_content_type("application/json").await);
294        assert!(valid_json_content_type("application/json; charset=utf-8").await);
295        assert!(valid_json_content_type("application/json;charset=utf-8").await);
296        assert!(valid_json_content_type("application/cloudevents+json").await);
297        assert!(!valid_json_content_type("text/json").await);
298    }
299
300    #[crate::test]
301    async fn invalid_json_syntax() {
302        let app = Router::new().route("/", post(|_: Json<serde_json::Value>| async {}));
303
304        let client = TestClient::new(app);
305        let res = client
306            .post("/")
307            .body("{")
308            .header("content-type", "application/json")
309            .await;
310
311        assert_eq!(res.status(), StatusCode::BAD_REQUEST);
312    }
313
314    #[derive(Deserialize)]
315    struct Foo {
316        #[allow(dead_code)]
317        a: i32,
318        #[allow(dead_code)]
319        b: Vec<Bar>,
320    }
321
322    #[derive(Deserialize)]
323    struct Bar {
324        #[allow(dead_code)]
325        x: i32,
326        #[allow(dead_code)]
327        y: i32,
328    }
329
330    #[crate::test]
331    async fn invalid_json_data() {
332        let app = Router::new().route("/", post(|_: Json<Foo>| async {}));
333
334        let client = TestClient::new(app);
335        let res = client
336            .post("/")
337            .body("{\"a\": 1, \"b\": [{\"x\": 2}]}")
338            .header("content-type", "application/json")
339            .await;
340
341        assert_eq!(res.status(), StatusCode::UNPROCESSABLE_ENTITY);
342        let body_text = res.text().await;
343        assert_eq!(
344            body_text,
345            "Failed to deserialize the JSON body into the target type: b[0]: missing field `y` at line 1 column 23"
346        );
347    }
348}