Skip to main content

tork_core/response/
json.rs

1//! JSON response support.
2
3use bytes::Bytes;
4use http::StatusCode;
5use serde::Serialize;
6
7use crate::constants::APPLICATION_JSON;
8use crate::error::Error;
9use crate::response::{with_body, IntoResponse, Response};
10
11/// A JSON response wrapper.
12///
13/// Wrapping a serializable value in `Json` renders it as an `application/json`
14/// response body with a `200 OK` status. Handlers usually return the bare model
15/// and let the generated route glue serialize it; `Json` is available for when a
16/// response body needs to be built explicitly.
17pub struct Json<T>(pub T);
18
19impl<T: Serialize> IntoResponse for Json<T> {
20    fn into_response(self) -> Response {
21        json_response(StatusCode::OK, &self.0)
22    }
23}
24
25/// Serializes `value` to JSON and builds a response with the given status.
26///
27/// # Errors
28///
29/// If serialization fails, a redacted `500 Internal Server Error` is returned so
30/// that a partially written or malformed body is never sent to the client.
31pub fn json_response<T: Serialize + ?Sized>(status: StatusCode, value: &T) -> Response {
32    match serde_json::to_vec(value) {
33        Ok(buffer) => with_body(status, APPLICATION_JSON, Bytes::from(buffer)),
34        Err(error) => Error::internal("response body serialization failed")
35            .with_source(error)
36            .into_response(),
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43    use crate::response::into_body_bytes;
44    use serde::Serialize;
45
46    #[derive(Serialize)]
47    struct Payload {
48        ok: bool,
49    }
50
51    struct BrokenSerialize;
52
53    impl Serialize for BrokenSerialize {
54        fn serialize<S>(&self, _serializer: S) -> std::result::Result<S::Ok, S::Error>
55        where
56            S: serde::Serializer,
57        {
58            Err(serde::ser::Error::custom("boom"))
59        }
60    }
61
62    #[tokio::test]
63    async fn json_response_serializes_payload() {
64        let response = json_response(StatusCode::CREATED, &Payload { ok: true });
65        let (parts, body) = into_body_bytes(response).await;
66
67        assert_eq!(parts.status, StatusCode::CREATED);
68        assert_eq!(parts.headers["content-type"], APPLICATION_JSON);
69        assert_eq!(body, Bytes::from_static(br#"{"ok":true}"#));
70    }
71
72    #[tokio::test]
73    async fn json_response_redacts_serialize_failures() {
74        let response = json_response(StatusCode::OK, &BrokenSerialize);
75        let (parts, body) = into_body_bytes(response).await;
76
77        assert_eq!(parts.status, StatusCode::INTERNAL_SERVER_ERROR);
78        assert_eq!(parts.headers["content-type"], APPLICATION_JSON);
79        assert!(String::from_utf8(body.to_vec())
80            .unwrap()
81            .contains("INTERNAL_SERVER_ERROR"));
82    }
83
84    #[tokio::test]
85    async fn json_response_does_not_leak_serialize_error_message() {
86        let response = json_response(StatusCode::OK, &BrokenSerialize);
87        let (_, body) = into_body_bytes(response).await;
88        let body_str = String::from_utf8(body.to_vec()).unwrap();
89        assert!(
90            !body_str.contains("boom"),
91            "serialization error message leaked into response body: {body_str}"
92        );
93    }
94
95    #[tokio::test]
96    async fn json_wrapper_serializes_payload_with_default_ok_status() {
97        let response = Json(Payload { ok: true }).into_response();
98        let (parts, body) = into_body_bytes(response).await;
99
100        assert_eq!(parts.status, StatusCode::OK);
101        assert_eq!(parts.headers["content-type"], APPLICATION_JSON);
102        assert_eq!(body, Bytes::from_static(br#"{"ok":true}"#));
103    }
104
105    #[tokio::test]
106    async fn json_wrapper_accepts_dynamic_json_value() {
107        let value = serde_json::json!({ "name": "alice", "age": 30 });
108        let response = Json(value).into_response();
109        let (parts, body) = into_body_bytes(response).await;
110
111        assert_eq!(parts.status, StatusCode::OK);
112        assert_eq!(parts.headers["content-type"], APPLICATION_JSON);
113        let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
114        assert_eq!(parsed["name"], "alice");
115        assert_eq!(parsed["age"], 30);
116    }
117
118    #[tokio::test]
119    async fn json_response_preserves_custom_status_code() {
120        let response = json_response(StatusCode::ACCEPTED, &Payload { ok: false });
121        let (parts, body) = into_body_bytes(response).await;
122
123        assert_eq!(parts.status, StatusCode::ACCEPTED);
124        assert_eq!(parts.headers["content-type"], APPLICATION_JSON);
125        assert_eq!(body, Bytes::from_static(br#"{"ok":false}"#));
126    }
127}