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 IntoResponse for rama_core::layer::limit::policy::RateLimitReached {
101    fn into_response(self) -> Response {
102        let mut response = StatusCode::TOO_MANY_REQUESTS.into_response();
103        let secs = self.retry_after.as_secs() + u64::from(self.retry_after.subsec_nanos() > 0);
104        response
105            .headers_mut()
106            .typed_insert(rama_http_headers::RetryAfter::delay(
107                rama_http_headers::util::Seconds::new(secs),
108            ));
109        response
110    }
111}
112
113impl<B> IntoResponse for Response<B>
114where
115    B: StreamingBody<Data = Bytes, Error: Into<BoxError>> + Send + Sync + 'static,
116{
117    fn into_response(self) -> Response {
118        self.map(Body::new)
119    }
120}
121
122impl IntoResponse for crate::response::Parts {
123    fn into_response(self) -> Response {
124        Response::from_parts(self, Body::empty())
125    }
126}
127
128impl IntoResponse for Body {
129    fn into_response(self) -> Response {
130        Response::new(self)
131    }
132}
133
134impl IntoResponse for &'static str {
135    #[inline(always)]
136    fn into_response(self) -> Response {
137        Cow::Borrowed(self).into_response()
138    }
139}
140
141impl IntoResponse for String {
142    #[inline(always)]
143    fn into_response(self) -> Response {
144        Cow::<'static, str>::Owned(self).into_response()
145    }
146}
147
148impl IntoResponse for Box<str> {
149    #[inline(always)]
150    fn into_response(self) -> Response {
151        String::from(self).into_response()
152    }
153}
154
155impl_into_response_sized_body!(Cow<'static, str>, ContentType::text_utf8());
156impl_into_response_sized_body!(ArcStr, ContentType::text_utf8());
157impl_into_response_sized_body!(&ArcStr, ContentType::text_utf8());
158impl_into_response_sized_body!(Bytes, ContentType::octet_stream());
159
160impl IntoResponse for BytesMut {
161    fn into_response(self) -> Response {
162        self.freeze().into_response()
163    }
164}
165
166impl IntoResponse for InfiniteReader {
167    fn into_response(self) -> Response {
168        (
169            Headers((ContentDisposition::inline(), ContentType::octet_stream())),
170            self.into_body(),
171        )
172            .into_response()
173    }
174}
175
176impl<T, U> IntoResponse for Chain<T, U>
177where
178    T: Buf + Unpin + Send + Sync + 'static,
179    U: Buf + Unpin + Send + Sync + 'static,
180{
181    fn into_response(self) -> Response {
182        let (first, second) = self.into_inner();
183        let mut res = Response::new(Body::new(BytesChainBody {
184            first: Some(first),
185            second: Some(second),
186        }));
187        res.headers_mut().insert(
188            header::CONTENT_TYPE,
189            HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()),
190        );
191        res
192    }
193}
194
195struct BytesChainBody<T, U> {
196    first: Option<T>,
197    second: Option<U>,
198}
199
200impl<T, U> StreamingBody for BytesChainBody<T, U>
201where
202    T: Buf + Unpin,
203    U: Buf + Unpin,
204{
205    type Data = Bytes;
206    type Error = Infallible;
207
208    fn poll_frame(
209        mut self: Pin<&mut Self>,
210        _cx: &mut Context<'_>,
211    ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
212        if let Some(mut buf) = self.first.take() {
213            let bytes = buf.copy_to_bytes(buf.remaining());
214            return Poll::Ready(Some(Ok(Frame::data(bytes))));
215        }
216
217        if let Some(mut buf) = self.second.take() {
218            let bytes = buf.copy_to_bytes(buf.remaining());
219            return Poll::Ready(Some(Ok(Frame::data(bytes))));
220        }
221
222        Poll::Ready(None)
223    }
224
225    fn is_end_stream(&self) -> bool {
226        self.first.is_none() && self.second.is_none()
227    }
228
229    fn size_hint(&self) -> SizeHint {
230        match (self.first.as_ref(), self.second.as_ref()) {
231            (Some(first), Some(second)) => {
232                let total_size = first.remaining() + second.remaining();
233                SizeHint::with_exact(total_size as u64)
234            }
235            (Some(buf), None) => SizeHint::with_exact(buf.remaining() as u64),
236            (None, Some(buf)) => SizeHint::with_exact(buf.remaining() as u64),
237            (None, None) => SizeHint::with_exact(0),
238        }
239    }
240}
241
242impl IntoResponse for &'static [u8] {
243    fn into_response(self) -> Response {
244        Cow::Borrowed(self).into_response()
245    }
246}
247
248impl<const N: usize> IntoResponse for &'static [u8; N] {
249    #[inline(always)]
250    fn into_response(self) -> Response {
251        self.as_slice().into_response()
252    }
253}
254
255impl<const N: usize> IntoResponse for [u8; N] {
256    #[inline(always)]
257    fn into_response(self) -> Response {
258        self.to_vec().into_response()
259    }
260}
261
262impl IntoResponse for Vec<u8> {
263    fn into_response(self) -> Response {
264        Cow::<'static, [u8]>::Owned(self).into_response()
265    }
266}
267
268impl IntoResponse for Box<[u8]> {
269    fn into_response(self) -> Response {
270        Vec::from(self).into_response()
271    }
272}
273
274impl_into_response_sized_body!(Cow<'static, [u8]>, ContentType::octet_stream());
275
276impl<R> IntoResponse for (StatusCode, R)
277where
278    R: IntoResponse,
279{
280    fn into_response(self) -> Response {
281        let mut res = self.1.into_response();
282        *res.status_mut() = self.0;
283        res
284    }
285}
286
287impl IntoResponse for HeaderMap {
288    fn into_response(self) -> Response {
289        let mut res = ().into_response();
290        *res.headers_mut() = self;
291        res
292    }
293}
294
295impl IntoResponse for Extensions {
296    fn into_response(self) -> Response {
297        let res = ().into_response();
298        res.extensions().extend(&self);
299        res
300    }
301}
302
303impl<K, V, const N: usize> IntoResponse for [(K, V); N]
304where
305    K: TryInto<HeaderName, Error: fmt::Display>,
306    V: TryInto<HeaderValue, Error: fmt::Display>,
307{
308    fn into_response(self) -> Response {
309        (self, ()).into_response()
310    }
311}
312
313impl<R> IntoResponse for (crate::response::Parts, R)
314where
315    R: IntoResponse,
316{
317    fn into_response(self) -> Response {
318        let (parts, res) = self;
319        (parts.status, parts.headers, parts.extensions, res).into_response()
320    }
321}
322
323impl<R> IntoResponse for (crate::response::Response<()>, R)
324where
325    R: IntoResponse,
326{
327    fn into_response(self) -> Response {
328        let (template, res) = self;
329        let (parts, ()) = template.into_parts();
330        (parts, res).into_response()
331    }
332}
333
334impl<R> IntoResponse for (R,)
335where
336    R: IntoResponse,
337{
338    fn into_response(self) -> Response {
339        let (res,) = self;
340        res.into_response()
341    }
342}
343
344macro_rules! impl_into_response {
345    ( $($ty:ident),* $(,)? ) => {
346        #[allow(non_snake_case)]
347        impl<R, $($ty,)*> IntoResponse for ($($ty),*, R)
348        where
349            $( $ty: IntoResponseParts, )*
350            R: IntoResponse,
351        {
352            fn into_response(self) -> Response {
353                let ($($ty),*, res) = self;
354
355                let res = res.into_response();
356                let parts = ResponseParts { res };
357
358                $(
359                    let parts = match $ty.into_response_parts(parts) {
360                        Ok(parts) => parts,
361                        Err(err) => {
362                            return err.into_response();
363                        }
364                    };
365                )*
366
367                parts.res
368            }
369        }
370
371        #[allow(non_snake_case)]
372        impl<R, $($ty,)*> IntoResponse for (StatusCode, $($ty),*, R)
373        where
374            $( $ty: IntoResponseParts, )*
375            R: IntoResponse,
376        {
377            fn into_response(self) -> Response {
378                let (status, $($ty),*, res) = self;
379
380                let res = res.into_response();
381                let parts = ResponseParts { res };
382
383                $(
384                    let parts = match $ty.into_response_parts(parts) {
385                        Ok(parts) => parts,
386                        Err(err) => {
387                            return err.into_response();
388                        }
389                    };
390                )*
391
392                (status, parts.res).into_response()
393            }
394        }
395
396        #[allow(non_snake_case)]
397        impl<R, $($ty,)*> IntoResponse for (crate::response::Parts, $($ty),*, R)
398        where
399            $( $ty: IntoResponseParts, )*
400            R: IntoResponse,
401        {
402            fn into_response(self) -> Response {
403                let (outer_parts, $($ty),*, res) = self;
404
405                let res = res.into_response();
406                let parts = ResponseParts { res };
407                $(
408                    let parts = match $ty.into_response_parts(parts) {
409                        Ok(parts) => parts,
410                        Err(err) => {
411                            return err.into_response();
412                        }
413                    };
414                )*
415
416                (outer_parts, parts.res).into_response()
417            }
418        }
419
420        #[allow(non_snake_case)]
421        impl<R, $($ty,)*> IntoResponse for (crate::response::Response<()>, $($ty),*, R)
422        where
423            $( $ty: IntoResponseParts, )*
424            R: IntoResponse,
425        {
426            fn into_response(self) -> Response {
427                let (template, $($ty),*, res) = self;
428                let (parts, ()) = template.into_parts();
429                (parts, $($ty),*, res).into_response()
430            }
431        }
432    }
433}
434
435all_the_tuples_no_last_special_case!(impl_into_response);
436
437macro_rules! impl_into_response_either {
438    ($id:ident, $($param:ident),+ $(,)?) => {
439        impl<$($param),+> IntoResponse for rama_core::combinators::$id<$($param),+>
440        where
441            $($param: IntoResponse),+
442        {
443            fn into_response(self) -> Response {
444                match self {
445                    $(
446                        rama_core::combinators::$id::$param(val) => val.into_response(),
447                    )+
448                }
449            }
450        }
451    };
452}
453
454rama_core::combinators::impl_either!(impl_into_response_either);
455
456#[cfg(test)]
457mod tests {
458    use super::*;
459    use rama_core::combinators::Either;
460    use rama_http_types::body::util::BodyExt as _;
461    use rama_utils::str::arcstr::arcstr;
462
463    #[test]
464    fn test_either_into_response() {
465        let left: Either<&'static str, Vec<u8>> = Either::A("hello");
466        let right: Either<&'static str, Vec<u8>> = Either::B(vec![1, 2, 3]);
467
468        let left_res = left.into_response();
469        assert_eq!(
470            left_res.headers().get(header::CONTENT_TYPE).unwrap(),
471            mime::TEXT_PLAIN_UTF_8.as_ref()
472        );
473
474        let right_res = right.into_response();
475        assert_eq!(
476            right_res.headers().get(header::CONTENT_TYPE).unwrap(),
477            mime::APPLICATION_OCTET_STREAM.as_ref()
478        );
479    }
480
481    #[test]
482    fn test_either3_into_response() {
483        use rama_core::combinators::Either3;
484
485        let a: Either3<&'static str, Vec<u8>, StatusCode> = Either3::A("hello");
486        let b: Either3<&'static str, Vec<u8>, StatusCode> = Either3::B(vec![1, 2, 3]);
487        let c: Either3<&'static str, Vec<u8>, StatusCode> = Either3::C(StatusCode::NOT_FOUND);
488
489        let a_res = a.into_response();
490        assert_eq!(
491            a_res.headers().get(header::CONTENT_TYPE).unwrap(),
492            mime::TEXT_PLAIN_UTF_8.as_ref()
493        );
494
495        let b_res = b.into_response();
496        assert_eq!(
497            b_res.headers().get(header::CONTENT_TYPE).unwrap(),
498            mime::APPLICATION_OCTET_STREAM.as_ref()
499        );
500
501        let c_res = c.into_response();
502        assert_eq!(c_res.status(), StatusCode::NOT_FOUND);
503    }
504
505    macro_rules! test_content_length_content_type {
506        ($val:expr, $len:expr, $ct:expr) => {{
507            let n = $len;
508            let resp = $val.into_response();
509            let content_length: usize = resp
510                .headers()
511                .get("content-length")
512                .unwrap()
513                .to_str()
514                .unwrap()
515                .parse()
516                .unwrap();
517            assert_eq!(n, content_length);
518            let ct: ContentType = resp.headers().typed_get().unwrap();
519            assert_eq!($ct, ct);
520            let bytes = resp.into_body().collect().await.unwrap().to_bytes();
521            assert_eq!(n, bytes.len());
522        }};
523    }
524
525    #[tokio::test]
526    async fn test_content_length_types_into_response() {
527        test_content_length_content_type!("str", 3, ContentType::text_utf8());
528        test_content_length_content_type!("string".to_owned(), 6, ContentType::text_utf8());
529        test_content_length_content_type!(
530            Cow::Borrowed("Cow::Borrowed"),
531            13,
532            ContentType::text_utf8()
533        );
534        test_content_length_content_type!(
535            Cow::Borrowed("Cow::Owned").into_owned(),
536            10,
537            ContentType::text_utf8()
538        );
539        test_content_length_content_type!(
540            Bytes::from_static(b"Bytes::from_static"),
541            18,
542            ContentType::octet_stream()
543        );
544        test_content_length_content_type!(
545            Bytes::from("Bytes::from"),
546            11,
547            ContentType::octet_stream()
548        );
549        test_content_length_content_type!(b"&[u8]", 5, ContentType::octet_stream());
550        test_content_length_content_type!(*b"[u8]", 4, ContentType::octet_stream());
551        test_content_length_content_type!(b"Vec<u8>".to_vec(), 7, ContentType::octet_stream());
552        test_content_length_content_type!(
553            Cow::Borrowed(b"Cow::Borrowed::<u8>"),
554            19,
555            ContentType::octet_stream()
556        );
557        test_content_length_content_type!(
558            Cow::Borrowed(b"Cow::Owned::<u8>").into_owned(),
559            16,
560            ContentType::octet_stream()
561        );
562        test_content_length_content_type!(arcstr!("ArcStr"), 6, ContentType::text_utf8());
563    }
564}