tork_core/response/
json.rs1use 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
11pub 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
25pub 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}