Skip to main content

rama_http/service/web/endpoint/response/
into_response.rs

1#![expect(
2    clippy::allow_attributes,
3    reason = "macro-generated `#[allow]` attributes whose underlying lints fire only for some expansions"
4)]
5
6use super::{IntoResponseParts, ResponseParts};
7use crate::Response;
8use crate::body::{Body, Frame, SizeHint, StreamingBody};
9use crate::service::web::response::Headers;
10use crate::{
11    StatusCode,
12    header::{self, HeaderMap, HeaderName, HeaderValue},
13};
14use rama_core::bytes::{Buf, Bytes, BytesMut, buf::Chain};
15use rama_core::error::BoxError;
16use rama_core::extensions::{Extensions, ExtensionsRef};
17use rama_core::telemetry::tracing;
18use rama_http_headers::{ContentDisposition, ContentLength, ContentType, HeaderMapExt};
19use rama_http_types::InfiniteReader;
20use rama_http_types::mime;
21use rama_utils::macros::all_the_tuples_no_last_special_case;
22use rama_utils::str::arcstr::ArcStr;
23use std::{
24    borrow::Cow,
25    convert::Infallible,
26    fmt,
27    pin::Pin,
28    task::{Context, Poll},
29};
30
31/// Implement [`IntoResponse`] for an owned, sized body type by streaming it
32/// through [`Body`] and tagging the response with the given `Content-Type` plus
33/// an exact `Content-Length` (from the value's `len()`).
34macro_rules! impl_into_response_sized_body {
35    ($t:ty, $content_type:expr) => {
36        impl IntoResponse for $t {
37            fn into_response(self) -> Response {
38                let len = self.len();
39                let mut res = Body::from(self).into_response();
40                res.headers_mut().typed_insert($content_type);
41                res.headers_mut().typed_insert(ContentLength(len as u64));
42                res
43            }
44        }
45    };
46}
47
48/// Trait for generating responses.
49///
50/// Types that implement `IntoResponse` can be returned from handlers.
51///
52/// # Implementing `IntoResponse`
53///
54/// You generally shouldn't have to implement `IntoResponse` manually, as rama
55/// provides implementations for many common types.
56pub trait IntoResponse {
57    /// Create a response.
58    fn into_response(self) -> Response;
59}
60
61/// Wrapper that can be used to turn an `IntoResponse` type into
62/// something that implements `Into<Response>`.
63#[derive(Debug, Clone)]
64pub struct StaticResponseFactory<T>(pub T);
65
66impl<T: IntoResponse> From<StaticResponseFactory<T>> for Response {
67    fn from(value: StaticResponseFactory<T>) -> Self {
68        value.0.into_response()
69    }
70}
71
72impl IntoResponse for StatusCode {
73    fn into_response(self) -> Response {
74        let mut res = ().into_response();
75        *res.status_mut() = self;
76        res
77    }
78}
79
80impl IntoResponse for () {
81    fn into_response(self) -> Response {
82        Body::empty().into_response()
83    }
84}
85
86impl IntoResponse for Infallible {
87    fn into_response(self) -> Response {
88        match self {}
89    }
90}
91
92impl IntoResponse for BoxError {
93    // do not expose error in response for security reasons
94    fn into_response(self) -> Response {
95        tracing::debug!("unexpected error in HTTP handler: {self}; return 500 status code");
96        StatusCode::INTERNAL_SERVER_ERROR.into_response()
97    }
98}
99
100impl<B> IntoResponse for Response<B>
101where
102    B: StreamingBody<Data = Bytes, Error: Into<BoxError>> + Send + Sync + 'static,
103{
104    fn into_response(self) -> Response {
105        self.map(Body::new)
106    }
107}
108
109impl IntoResponse for crate::response::Parts {
110    fn into_response(self) -> Response {
111        Response::from_parts(self, Body::empty())
112    }
113}
114
115impl IntoResponse for Body {
116    fn into_response(self) -> Response {
117        Response::new(self)
118    }
119}
120
121impl IntoResponse for &'static str {
122    #[inline(always)]
123    fn into_response(self) -> Response {
124        Cow::Borrowed(self).into_response()
125    }
126}
127
128impl IntoResponse for String {
129    #[inline(always)]
130    fn into_response(self) -> Response {
131        Cow::<'static, str>::Owned(self).into_response()
132    }
133}
134
135impl IntoResponse for Box<str> {
136    #[inline(always)]
137    fn into_response(self) -> Response {
138        String::from(self).into_response()
139    }
140}
141
142impl_into_response_sized_body!(Cow<'static, str>, ContentType::text_utf8());
143impl_into_response_sized_body!(ArcStr, ContentType::text_utf8());
144impl_into_response_sized_body!(&ArcStr, ContentType::text_utf8());
145impl_into_response_sized_body!(Bytes, ContentType::octet_stream());
146
147impl IntoResponse for BytesMut {
148    fn into_response(self) -> Response {
149        self.freeze().into_response()
150    }
151}
152
153impl IntoResponse for InfiniteReader {
154    fn into_response(self) -> Response {
155        (
156            Headers((ContentDisposition::inline(), ContentType::octet_stream())),
157            self.into_body(),
158        )
159            .into_response()
160    }
161}
162
163impl<T, U> IntoResponse for Chain<T, U>
164where
165    T: Buf + Unpin + Send + Sync + 'static,
166    U: Buf + Unpin + Send + Sync + 'static,
167{
168    fn into_response(self) -> Response {
169        let (first, second) = self.into_inner();
170        let mut res = Response::new(Body::new(BytesChainBody {
171            first: Some(first),
172            second: Some(second),
173        }));
174        res.headers_mut().insert(
175            header::CONTENT_TYPE,
176            HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()),
177        );
178        res
179    }
180}
181
182struct BytesChainBody<T, U> {
183    first: Option<T>,
184    second: Option<U>,
185}
186
187impl<T, U> StreamingBody for BytesChainBody<T, U>
188where
189    T: Buf + Unpin,
190    U: Buf + Unpin,
191{
192    type Data = Bytes;
193    type Error = Infallible;
194
195    fn poll_frame(
196        mut self: Pin<&mut Self>,
197        _cx: &mut Context<'_>,
198    ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
199        if let Some(mut buf) = self.first.take() {
200            let bytes = buf.copy_to_bytes(buf.remaining());
201            return Poll::Ready(Some(Ok(Frame::data(bytes))));
202        }
203
204        if let Some(mut buf) = self.second.take() {
205            let bytes = buf.copy_to_bytes(buf.remaining());
206            return Poll::Ready(Some(Ok(Frame::data(bytes))));
207        }
208
209        Poll::Ready(None)
210    }
211
212    fn is_end_stream(&self) -> bool {
213        self.first.is_none() && self.second.is_none()
214    }
215
216    fn size_hint(&self) -> SizeHint {
217        match (self.first.as_ref(), self.second.as_ref()) {
218            (Some(first), Some(second)) => {
219                let total_size = first.remaining() + second.remaining();
220                SizeHint::with_exact(total_size as u64)
221            }
222            (Some(buf), None) => SizeHint::with_exact(buf.remaining() as u64),
223            (None, Some(buf)) => SizeHint::with_exact(buf.remaining() as u64),
224            (None, None) => SizeHint::with_exact(0),
225        }
226    }
227}
228
229impl IntoResponse for &'static [u8] {
230    fn into_response(self) -> Response {
231        Cow::Borrowed(self).into_response()
232    }
233}
234
235impl<const N: usize> IntoResponse for &'static [u8; N] {
236    #[inline(always)]
237    fn into_response(self) -> Response {
238        self.as_slice().into_response()
239    }
240}
241
242impl<const N: usize> IntoResponse for [u8; N] {
243    #[inline(always)]
244    fn into_response(self) -> Response {
245        self.to_vec().into_response()
246    }
247}
248
249impl IntoResponse for Vec<u8> {
250    fn into_response(self) -> Response {
251        Cow::<'static, [u8]>::Owned(self).into_response()
252    }
253}
254
255impl IntoResponse for Box<[u8]> {
256    fn into_response(self) -> Response {
257        Vec::from(self).into_response()
258    }
259}
260
261impl_into_response_sized_body!(Cow<'static, [u8]>, ContentType::octet_stream());
262
263impl<R> IntoResponse for (StatusCode, R)
264where
265    R: IntoResponse,
266{
267    fn into_response(self) -> Response {
268        let mut res = self.1.into_response();
269        *res.status_mut() = self.0;
270        res
271    }
272}
273
274impl IntoResponse for HeaderMap {
275    fn into_response(self) -> Response {
276        let mut res = ().into_response();
277        *res.headers_mut() = self;
278        res
279    }
280}
281
282impl IntoResponse for Extensions {
283    fn into_response(self) -> Response {
284        let res = ().into_response();
285        res.extensions().extend(&self);
286        res
287    }
288}
289
290impl<K, V, const N: usize> IntoResponse for [(K, V); N]
291where
292    K: TryInto<HeaderName, Error: fmt::Display>,
293    V: TryInto<HeaderValue, Error: fmt::Display>,
294{
295    fn into_response(self) -> Response {
296        (self, ()).into_response()
297    }
298}
299
300impl<R> IntoResponse for (crate::response::Parts, R)
301where
302    R: IntoResponse,
303{
304    fn into_response(self) -> Response {
305        let (parts, res) = self;
306        (parts.status, parts.headers, parts.extensions, res).into_response()
307    }
308}
309
310impl<R> IntoResponse for (crate::response::Response<()>, R)
311where
312    R: IntoResponse,
313{
314    fn into_response(self) -> Response {
315        let (template, res) = self;
316        let (parts, ()) = template.into_parts();
317        (parts, res).into_response()
318    }
319}
320
321impl<R> IntoResponse for (R,)
322where
323    R: IntoResponse,
324{
325    fn into_response(self) -> Response {
326        let (res,) = self;
327        res.into_response()
328    }
329}
330
331macro_rules! impl_into_response {
332    ( $($ty:ident),* $(,)? ) => {
333        #[allow(non_snake_case)]
334        impl<R, $($ty,)*> IntoResponse for ($($ty),*, R)
335        where
336            $( $ty: IntoResponseParts, )*
337            R: IntoResponse,
338        {
339            fn into_response(self) -> Response {
340                let ($($ty),*, res) = self;
341
342                let res = res.into_response();
343                let parts = ResponseParts { res };
344
345                $(
346                    let parts = match $ty.into_response_parts(parts) {
347                        Ok(parts) => parts,
348                        Err(err) => {
349                            return err.into_response();
350                        }
351                    };
352                )*
353
354                parts.res
355            }
356        }
357
358        #[allow(non_snake_case)]
359        impl<R, $($ty,)*> IntoResponse for (StatusCode, $($ty),*, R)
360        where
361            $( $ty: IntoResponseParts, )*
362            R: IntoResponse,
363        {
364            fn into_response(self) -> Response {
365                let (status, $($ty),*, res) = self;
366
367                let res = res.into_response();
368                let parts = ResponseParts { res };
369
370                $(
371                    let parts = match $ty.into_response_parts(parts) {
372                        Ok(parts) => parts,
373                        Err(err) => {
374                            return err.into_response();
375                        }
376                    };
377                )*
378
379                (status, parts.res).into_response()
380            }
381        }
382
383        #[allow(non_snake_case)]
384        impl<R, $($ty,)*> IntoResponse for (crate::response::Parts, $($ty),*, R)
385        where
386            $( $ty: IntoResponseParts, )*
387            R: IntoResponse,
388        {
389            fn into_response(self) -> Response {
390                let (outer_parts, $($ty),*, res) = self;
391
392                let res = res.into_response();
393                let parts = ResponseParts { res };
394                $(
395                    let parts = match $ty.into_response_parts(parts) {
396                        Ok(parts) => parts,
397                        Err(err) => {
398                            return err.into_response();
399                        }
400                    };
401                )*
402
403                (outer_parts, parts.res).into_response()
404            }
405        }
406
407        #[allow(non_snake_case)]
408        impl<R, $($ty,)*> IntoResponse for (crate::response::Response<()>, $($ty),*, R)
409        where
410            $( $ty: IntoResponseParts, )*
411            R: IntoResponse,
412        {
413            fn into_response(self) -> Response {
414                let (template, $($ty),*, res) = self;
415                let (parts, ()) = template.into_parts();
416                (parts, $($ty),*, res).into_response()
417            }
418        }
419    }
420}
421
422all_the_tuples_no_last_special_case!(impl_into_response);
423
424macro_rules! impl_into_response_either {
425    ($id:ident, $($param:ident),+ $(,)?) => {
426        impl<$($param),+> IntoResponse for rama_core::combinators::$id<$($param),+>
427        where
428            $($param: IntoResponse),+
429        {
430            fn into_response(self) -> Response {
431                match self {
432                    $(
433                        rama_core::combinators::$id::$param(val) => val.into_response(),
434                    )+
435                }
436            }
437        }
438    };
439}
440
441rama_core::combinators::impl_either!(impl_into_response_either);
442
443#[cfg(test)]
444mod tests {
445    use super::*;
446    use rama_core::combinators::Either;
447    use rama_http_types::body::util::BodyExt as _;
448    use rama_utils::str::arcstr::arcstr;
449
450    #[test]
451    fn test_either_into_response() {
452        let left: Either<&'static str, Vec<u8>> = Either::A("hello");
453        let right: Either<&'static str, Vec<u8>> = Either::B(vec![1, 2, 3]);
454
455        let left_res = left.into_response();
456        assert_eq!(
457            left_res.headers().get(header::CONTENT_TYPE).unwrap(),
458            mime::TEXT_PLAIN_UTF_8.as_ref()
459        );
460
461        let right_res = right.into_response();
462        assert_eq!(
463            right_res.headers().get(header::CONTENT_TYPE).unwrap(),
464            mime::APPLICATION_OCTET_STREAM.as_ref()
465        );
466    }
467
468    #[test]
469    fn test_either3_into_response() {
470        use rama_core::combinators::Either3;
471
472        let a: Either3<&'static str, Vec<u8>, StatusCode> = Either3::A("hello");
473        let b: Either3<&'static str, Vec<u8>, StatusCode> = Either3::B(vec![1, 2, 3]);
474        let c: Either3<&'static str, Vec<u8>, StatusCode> = Either3::C(StatusCode::NOT_FOUND);
475
476        let a_res = a.into_response();
477        assert_eq!(
478            a_res.headers().get(header::CONTENT_TYPE).unwrap(),
479            mime::TEXT_PLAIN_UTF_8.as_ref()
480        );
481
482        let b_res = b.into_response();
483        assert_eq!(
484            b_res.headers().get(header::CONTENT_TYPE).unwrap(),
485            mime::APPLICATION_OCTET_STREAM.as_ref()
486        );
487
488        let c_res = c.into_response();
489        assert_eq!(c_res.status(), StatusCode::NOT_FOUND);
490    }
491
492    macro_rules! test_content_length_content_type {
493        ($val:expr, $len:expr, $ct:expr) => {{
494            let n = $len;
495            let resp = $val.into_response();
496            let content_length: usize = resp
497                .headers()
498                .get("content-length")
499                .unwrap()
500                .to_str()
501                .unwrap()
502                .parse()
503                .unwrap();
504            assert_eq!(n, content_length);
505            let ct: ContentType = resp.headers().typed_get().unwrap();
506            assert_eq!($ct, ct);
507            let bytes = resp.into_body().collect().await.unwrap().to_bytes();
508            assert_eq!(n, bytes.len());
509        }};
510    }
511
512    #[tokio::test]
513    async fn test_content_length_types_into_response() {
514        test_content_length_content_type!("str", 3, ContentType::text_utf8());
515        test_content_length_content_type!("string".to_owned(), 6, ContentType::text_utf8());
516        test_content_length_content_type!(
517            Cow::Borrowed("Cow::Borrowed"),
518            13,
519            ContentType::text_utf8()
520        );
521        test_content_length_content_type!(
522            Cow::Borrowed("Cow::Owned").into_owned(),
523            10,
524            ContentType::text_utf8()
525        );
526        test_content_length_content_type!(
527            Bytes::from_static(b"Bytes::from_static"),
528            18,
529            ContentType::octet_stream()
530        );
531        test_content_length_content_type!(
532            Bytes::from("Bytes::from"),
533            11,
534            ContentType::octet_stream()
535        );
536        test_content_length_content_type!(b"&[u8]", 5, ContentType::octet_stream());
537        test_content_length_content_type!(*b"[u8]", 4, ContentType::octet_stream());
538        test_content_length_content_type!(b"Vec<u8>".to_vec(), 7, ContentType::octet_stream());
539        test_content_length_content_type!(
540            Cow::Borrowed(b"Cow::Borrowed::<u8>"),
541            19,
542            ContentType::octet_stream()
543        );
544        test_content_length_content_type!(
545            Cow::Borrowed(b"Cow::Owned::<u8>").into_owned(),
546            16,
547            ContentType::octet_stream()
548        );
549        test_content_length_content_type!(arcstr!("ArcStr"), 6, ContentType::text_utf8());
550    }
551}