Skip to main content

tork_core/response/
mod.rs

1//! Response types and the [`IntoResponse`] conversion trait.
2
3use bytes::Bytes;
4use http::header::{HeaderValue, CONTENT_TYPE};
5use http::StatusCode;
6
7use crate::body::RespBody;
8
9pub mod json;
10
11pub use json::{json_response, Json};
12
13/// The concrete HTTP response type used throughout the framework.
14pub type Response = http::Response<RespBody>;
15
16/// Converts a value into an HTTP [`Response`].
17///
18/// Implemented by the response building blocks ([`Json`], [`Error`], status
19/// codes) and by [`Result`], so handlers can return high-level values and have
20/// them rendered into a proper HTTP response by the generated route glue.
21///
22/// [`Error`]: crate::Error
23/// [`Result`]: core::result::Result
24pub trait IntoResponse {
25    /// Consumes `self` and produces the HTTP response to send to the client.
26    fn into_response(self) -> Response;
27}
28
29/// Builds a response with the given status, `Content-Type`, and body.
30pub(crate) fn with_body(status: StatusCode, content_type: &'static str, body: Bytes) -> Response {
31    let mut response = http::Response::new(RespBody::new(body));
32    *response.status_mut() = status;
33    response
34        .headers_mut()
35        .insert(CONTENT_TYPE, HeaderValue::from_static(content_type));
36    response
37}
38
39/// Builds a response from a pre-serialized body and an explicit content type.
40///
41/// Useful for serving content that is generated once and cached, such as the
42/// OpenAPI document or a static documentation asset.
43pub fn bytes_response(status: StatusCode, content_type: &'static str, body: Bytes) -> Response {
44    with_body(status, content_type, body)
45}
46
47/// Splits a response into its head and fully-buffered body bytes.
48///
49/// Used by middleware that needs to inspect or rewrite a response body (such as
50/// compression). The body is `Full<Bytes>`, so collecting it is immediate.
51pub(crate) async fn into_body_bytes(response: Response) -> (http::response::Parts, Bytes) {
52    use http_body_util::BodyExt;
53    let (parts, body) = response.into_parts();
54    let bytes = body
55        .collect()
56        .await
57        .map(|collected| collected.to_bytes())
58        .unwrap_or_default();
59    (parts, bytes)
60}
61
62/// Builds an empty-bodied response carrying only a status code.
63pub(crate) fn empty(status: StatusCode) -> Response {
64    let mut response = http::Response::new(RespBody::new(Bytes::new()));
65    *response.status_mut() = status;
66    response
67}
68
69impl IntoResponse for Response {
70    fn into_response(self) -> Response {
71        self
72    }
73}
74
75impl IntoResponse for StatusCode {
76    fn into_response(self) -> Response {
77        empty(self)
78    }
79}
80
81impl IntoResponse for () {
82    fn into_response(self) -> Response {
83        empty(StatusCode::OK)
84    }
85}
86
87impl<T, E> IntoResponse for core::result::Result<T, E>
88where
89    T: IntoResponse,
90    E: Into<crate::error::Error>,
91{
92    fn into_response(self) -> Response {
93        match self {
94            Ok(value) => value.into_response(),
95            Err(error) => error.into().into_response(),
96        }
97    }
98}
99
100/// Renders a handler's result into a response, serializing the success value to
101/// JSON with the given status code.
102///
103/// This is generated-code support, not part of the user-facing API. Handlers
104/// return `Result<T>` where `T` is serializable; the route macro feeds that
105/// result and the declared success status here.
106///
107/// A handler error is returned as `Err` rather than rendered here, so that the
108/// dispatch boundary can run lifecycle hooks and exception handlers against the
109/// error value before it becomes a response.
110#[doc(hidden)]
111pub fn __finish<T, E>(
112    result: core::result::Result<T, E>,
113    status: StatusCode,
114) -> crate::error::Result<Response>
115where
116    T: serde::Serialize,
117    E: Into<crate::error::Error>,
118{
119    match result {
120        Ok(value) => Ok(json::json_response(status, &value)),
121        Err(error) => Err(error.into()),
122    }
123}
124
125/// Like [`__finish`], but converts the success value into the declared response
126/// model `U` (via `From`) before serializing.
127///
128/// Generated by the route macro when `response_model` differs from the handler's
129/// return type. With `response_model` equal to the return type the conversion is
130/// the free identity `From<T> for T`.
131#[doc(hidden)]
132pub fn __finish_into<T, U, E>(
133    result: core::result::Result<T, E>,
134    status: StatusCode,
135) -> crate::error::Result<Response>
136where
137    U: From<T> + serde::Serialize,
138    E: Into<crate::error::Error>,
139{
140    match result {
141        Ok(value) => Ok(json::json_response(status, &U::from(value))),
142        Err(error) => Err(error.into()),
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149    use crate::error::Error;
150    use bytes::Bytes;
151    use serde::Serialize;
152
153    #[derive(Serialize)]
154    struct Payload {
155        id: i64,
156    }
157
158    #[derive(Serialize)]
159    struct RawPayload {
160        id: i64,
161    }
162
163    impl From<RawPayload> for Payload {
164        fn from(value: RawPayload) -> Self {
165            Self { id: value.id }
166        }
167    }
168
169    #[tokio::test]
170    async fn status_code_and_unit_into_response_are_empty() {
171        let status = StatusCode::NO_CONTENT.into_response();
172        let unit = ().into_response();
173
174        let (status_parts, status_body) = into_body_bytes(status).await;
175        let (unit_parts, unit_body) = into_body_bytes(unit).await;
176
177        assert_eq!(status_parts.status, StatusCode::NO_CONTENT);
178        assert_eq!(status_body, Bytes::new());
179        assert_eq!(unit_parts.status, StatusCode::OK);
180        assert_eq!(unit_body, Bytes::new());
181    }
182
183    #[tokio::test]
184    async fn finish_helpers_serialize_success_values() {
185        let direct = __finish::<_, Error>(Ok(Payload { id: 7 }), StatusCode::CREATED).unwrap();
186        let mapped =
187            __finish_into::<_, Payload, Error>(Ok(RawPayload { id: 9 }), StatusCode::ACCEPTED)
188                .unwrap();
189
190        let (direct_parts, direct_body) = into_body_bytes(direct).await;
191        let (mapped_parts, mapped_body) = into_body_bytes(mapped).await;
192
193        assert_eq!(direct_parts.status, StatusCode::CREATED);
194        assert_eq!(mapped_parts.status, StatusCode::ACCEPTED);
195        assert_eq!(direct_body, Bytes::from_static(br#"{"id":7}"#));
196        assert_eq!(mapped_body, Bytes::from_static(br#"{"id":9}"#));
197    }
198
199    #[test]
200    fn result_into_response_and_finish_helpers_propagate_errors() {
201        let error = Error::bad_request("bad");
202
203        let response =
204            core::result::Result::<StatusCode, _>::Err(Error::bad_request("bad")).into_response();
205        assert_eq!(response.status(), StatusCode::BAD_REQUEST);
206
207        let finish = __finish::<Payload, _>(Err(error), StatusCode::OK)
208            .err()
209            .expect("expected handler error");
210        assert_eq!(finish.message(), "bad");
211    }
212}