Skip to main content

typeway_server/
response.rs

1//! The [`IntoResponse`] trait and implementations.
2//!
3//! Any type implementing `IntoResponse` can be returned from a handler.
4
5use bytes::Bytes;
6use http::StatusCode;
7use serde::Serialize;
8
9use crate::body::{body_from_bytes, body_from_string, empty_body, BoxBody};
10
11/// Trait for types that can be converted into an HTTP response.
12#[diagnostic::on_unimplemented(
13    message = "`{Self}` cannot be used as an HTTP response",
14    label = "does not implement `IntoResponse`",
15    note = "valid response types include: `&'static str`, `String`, `Json<T>`, `StatusCode`, `(StatusCode, T)`, `Result<T, E>`"
16)]
17pub trait IntoResponse {
18    /// Convert this value into an HTTP response.
19    fn into_response(self) -> http::Response<BoxBody>;
20}
21
22impl IntoResponse for http::Response<BoxBody> {
23    fn into_response(self) -> http::Response<BoxBody> {
24        self
25    }
26}
27
28impl IntoResponse for &'static str {
29    fn into_response(self) -> http::Response<BoxBody> {
30        let body = body_from_bytes(Bytes::from_static(self.as_bytes()));
31        let mut res = http::Response::new(body);
32        res.headers_mut().insert(
33            http::header::CONTENT_TYPE,
34            http::HeaderValue::from_static("text/plain; charset=utf-8"),
35        );
36        res
37    }
38}
39
40impl IntoResponse for String {
41    fn into_response(self) -> http::Response<BoxBody> {
42        let body = body_from_string(self);
43        let mut res = http::Response::new(body);
44        res.headers_mut().insert(
45            http::header::CONTENT_TYPE,
46            http::HeaderValue::from_static("text/plain; charset=utf-8"),
47        );
48        res
49    }
50}
51
52impl IntoResponse for StatusCode {
53    fn into_response(self) -> http::Response<BoxBody> {
54        let mut res = http::Response::new(empty_body());
55        *res.status_mut() = self;
56        res
57    }
58}
59
60impl<T: IntoResponse> IntoResponse for (StatusCode, T) {
61    fn into_response(self) -> http::Response<BoxBody> {
62        let mut res = self.1.into_response();
63        *res.status_mut() = self.0;
64        res
65    }
66}
67
68impl<T: IntoResponse, E: IntoResponse> IntoResponse for Result<T, E> {
69    fn into_response(self) -> http::Response<BoxBody> {
70        match self {
71            Ok(v) => v.into_response(),
72            Err(e) => e.into_response(),
73        }
74    }
75}
76
77impl IntoResponse for Bytes {
78    fn into_response(self) -> http::Response<BoxBody> {
79        let body = body_from_bytes(self);
80        let mut res = http::Response::new(body);
81        res.headers_mut().insert(
82            http::header::CONTENT_TYPE,
83            http::HeaderValue::from_static("application/octet-stream"),
84        );
85        res
86    }
87}
88
89/// A JSON response wrapper.
90///
91/// Serializes `T` as JSON and sets `Content-Type: application/json`.
92///
93/// # Example
94///
95/// ```
96/// use typeway_server::Json;
97///
98/// #[derive(serde::Serialize)]
99/// struct User { id: u32, name: String }
100///
101/// async fn get_user() -> Json<User> {
102///     Json(User { id: 1, name: "Alice".into() })
103/// }
104/// ```
105pub struct Json<T>(pub T);
106
107impl<T: Serialize> IntoResponse for Json<T> {
108    fn into_response(self) -> http::Response<BoxBody> {
109        match serde_json::to_vec(&self.0) {
110            Ok(bytes) => {
111                let body = body_from_bytes(Bytes::from(bytes));
112                let mut res = http::Response::new(body);
113                res.headers_mut().insert(
114                    http::header::CONTENT_TYPE,
115                    http::HeaderValue::from_static("application/json"),
116                );
117                res
118            }
119            Err(e) => {
120                let body = body_from_string(format!("JSON serialization error: {e}"));
121                let mut res = http::Response::new(body);
122                *res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
123                res
124            }
125        }
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn static_str_response() {
135        let res = "hello".into_response();
136        assert_eq!(res.status(), StatusCode::OK);
137        assert_eq!(
138            res.headers().get("content-type").unwrap(),
139            "text/plain; charset=utf-8"
140        );
141    }
142
143    #[test]
144    fn string_response() {
145        let res = "hello".to_string().into_response();
146        assert_eq!(res.status(), StatusCode::OK);
147    }
148
149    #[test]
150    fn status_code_response() {
151        let res = StatusCode::NOT_FOUND.into_response();
152        assert_eq!(res.status(), StatusCode::NOT_FOUND);
153    }
154
155    #[test]
156    fn tuple_status_body() {
157        let res = (StatusCode::CREATED, "done").into_response();
158        assert_eq!(res.status(), StatusCode::CREATED);
159    }
160
161    #[test]
162    fn result_ok() {
163        let res: Result<&str, StatusCode> = Ok("good");
164        let res = res.into_response();
165        assert_eq!(res.status(), StatusCode::OK);
166    }
167
168    #[test]
169    fn result_err() {
170        let res: Result<&str, StatusCode> = Err(StatusCode::BAD_REQUEST);
171        let res = res.into_response();
172        assert_eq!(res.status(), StatusCode::BAD_REQUEST);
173    }
174
175    #[test]
176    fn json_response() {
177        let res = Json(serde_json::json!({"id": 1})).into_response();
178        assert_eq!(res.status(), StatusCode::OK);
179        assert_eq!(
180            res.headers().get("content-type").unwrap(),
181            "application/json"
182        );
183    }
184}