Skip to main content

topcoat_router/
response.rs

1mod headers;
2
3use std::{borrow::Cow, convert::Infallible, future::ready};
4
5use bytes::{Bytes, BytesMut};
6pub use headers::*;
7use http::{
8    Extensions, HeaderMap, StatusCode,
9    header::{CONTENT_TYPE, HeaderName, HeaderValue},
10    response::Parts,
11};
12use topcoat_core::{
13    context::Cx,
14    error::{Error, Result},
15};
16
17use crate::{Body, BoxError};
18
19pub type Response<T = Body> = http::Response<T>;
20
21const TEXT_PLAIN: HeaderValue = HeaderValue::from_static("text/plain; charset=utf-8");
22const APPLICATION_OCTET_STREAM: HeaderValue = HeaderValue::from_static("application/octet-stream");
23
24/// Converts a value into an HTTP [`Response`].
25///
26/// Return an implementing type from a route handler to build its response.
27/// The conversion receives [`Cx`] for access to request context. Every
28/// implementation also supports [`AsyncIntoResponse`].
29///
30/// A response can also be assembled from a tuple. The last element is converted
31/// with `IntoResponse` and becomes the body, while the earlier elements modify
32/// the response: a leading [`StatusCode`], [`Parts`], or [`Response<()>`] sets
33/// the status line, and every other element is applied with
34/// [`IntoResponseParts`]. For example `(StatusCode::CREATED, headers, body)`
35/// builds a `201` response carrying `headers` and `body`.
36///
37/// # Examples
38///
39/// Implement it for a domain type that should control its own status, headers,
40/// or body:
41///
42/// ```rust
43/// // src/app/api.rs
44/// use topcoat::{
45///     Result,
46///     context::Cx,
47///     router::{
48///         Body,
49///         response::{IntoResponse, Response},
50///         route,
51///     },
52/// };
53///
54/// struct Csv(String);
55///
56/// impl IntoResponse for Csv {
57///     fn into_response(self, _cx: &Cx) -> Result<Response> {
58///         Ok(Response::builder()
59///             .header("Content-Type", "text/csv; charset=utf-8")
60///             .body(Body::from(self.0))?)
61///     }
62/// }
63///
64/// #[route(GET "./report.csv")]
65/// async fn report() -> Result<Csv> {
66///     Ok(Csv("name,total\nAda,42\n".to_string()))
67/// }
68/// ```
69pub trait IntoResponse {
70    /// Converts `self` into an HTTP [`Response`], using the request [`Cx`] for any
71    /// request-scoped data.
72    ///
73    /// # Errors
74    ///
75    /// Returns an error if the response cannot be assembled (for example, a
76    /// header value is invalid).
77    fn into_response(self, cx: &Cx) -> Result<Response>;
78}
79
80/// Converts a value into an HTTP [`Response`], awaiting whatever the
81/// conversion needs.
82///
83/// Route and layer handlers return any type that implements this trait.
84/// Every [`IntoResponse`] type implements it, so a handler only needs it by
85/// name when its response cannot be built without awaiting, such as a view
86/// that resolves its content first.
87pub trait AsyncIntoResponse {
88    /// Converts `self` into an HTTP [`Response`], using the request [`Cx`] for any
89    /// request-scoped data.
90    ///
91    /// # Errors
92    ///
93    /// Returns an error if the response cannot be assembled.
94    fn async_into_response(self, cx: &Cx) -> impl Future<Output = Result<Response>> + Send;
95}
96
97impl<T: IntoResponse> AsyncIntoResponse for T {
98    fn async_into_response(self, cx: &Cx) -> impl Future<Output = Result<Response>> + Send {
99        ready(self.into_response(cx))
100    }
101}
102
103/// Modifies a [`Response`]'s [`Parts`] without supplying a body.
104///
105/// Place an implementing value before the body in an [`IntoResponse`] tuple
106/// to modify its headers or extensions. The conversion receives [`Cx`] for
107/// access to request context.
108pub trait IntoResponseParts {
109    /// Applies `self` to the response `parts`, using the request [`Cx`] for any
110    /// request-scoped data.
111    ///
112    /// # Errors
113    ///
114    /// Returns an error if a part cannot be applied (for example, a header
115    /// value is invalid).
116    fn into_response_parts(self, cx: &Cx, parts: &mut Parts) -> Result<()>;
117}
118
119/// Builds a response carrying `body` with the given `Content-Type`.
120pub(crate) fn content_response(content_type: HeaderValue, body: Body) -> Response {
121    let mut response = Response::new(body);
122    response.headers_mut().insert(CONTENT_TYPE, content_type);
123    response
124}
125
126/// Copies the status, version, headers, and extensions of `from` onto `parts`,
127/// used when a tuple leads with a [`Parts`] or [`Response<()>`] template.
128fn merge_parts(parts: &mut Parts, from: Parts) {
129    parts.status = from.status;
130    parts.version = from.version;
131    parts.headers.extend(from.headers);
132    parts.extensions.extend(from.extensions);
133}
134
135// -- Leaf IntoResponse impls --
136
137impl IntoResponse for Infallible {
138    fn into_response(self, _cx: &Cx) -> Result<Response> {
139        match self {}
140    }
141}
142
143impl IntoResponse for () {
144    fn into_response(self, _cx: &Cx) -> Result<Response> {
145        Ok(Response::new(Body::empty()))
146    }
147}
148
149impl IntoResponse for StatusCode {
150    fn into_response(self, cx: &Cx) -> Result<Response> {
151        (self, ()).into_response(cx)
152    }
153}
154
155impl IntoResponse for &'static str {
156    fn into_response(self, _cx: &Cx) -> Result<Response> {
157        Ok(content_response(TEXT_PLAIN, Body::from(self)))
158    }
159}
160
161impl IntoResponse for String {
162    fn into_response(self, _cx: &Cx) -> Result<Response> {
163        Ok(content_response(TEXT_PLAIN, Body::from(self)))
164    }
165}
166
167impl IntoResponse for Box<str> {
168    fn into_response(self, cx: &Cx) -> Result<Response> {
169        String::from(self).into_response(cx)
170    }
171}
172
173impl IntoResponse for Cow<'static, str> {
174    fn into_response(self, cx: &Cx) -> Result<Response> {
175        match self {
176            Cow::Borrowed(value) => value.into_response(cx),
177            Cow::Owned(value) => value.into_response(cx),
178        }
179    }
180}
181
182impl IntoResponse for &'static [u8] {
183    fn into_response(self, _cx: &Cx) -> Result<Response> {
184        Ok(content_response(APPLICATION_OCTET_STREAM, Body::from(self)))
185    }
186}
187
188impl<const N: usize> IntoResponse for &'static [u8; N] {
189    fn into_response(self, cx: &Cx) -> Result<Response> {
190        let bytes: &'static [u8] = self;
191        bytes.into_response(cx)
192    }
193}
194
195impl<const N: usize> IntoResponse for [u8; N] {
196    fn into_response(self, cx: &Cx) -> Result<Response> {
197        self.to_vec().into_response(cx)
198    }
199}
200
201impl IntoResponse for Vec<u8> {
202    fn into_response(self, _cx: &Cx) -> Result<Response> {
203        Ok(content_response(APPLICATION_OCTET_STREAM, Body::from(self)))
204    }
205}
206
207impl IntoResponse for Box<[u8]> {
208    fn into_response(self, cx: &Cx) -> Result<Response> {
209        Vec::from(self).into_response(cx)
210    }
211}
212
213impl IntoResponse for Cow<'static, [u8]> {
214    fn into_response(self, cx: &Cx) -> Result<Response> {
215        match self {
216            Cow::Borrowed(value) => value.into_response(cx),
217            Cow::Owned(value) => value.into_response(cx),
218        }
219    }
220}
221
222impl IntoResponse for Bytes {
223    fn into_response(self, _cx: &Cx) -> Result<Response> {
224        Ok(content_response(APPLICATION_OCTET_STREAM, Body::from(self)))
225    }
226}
227
228impl IntoResponse for BytesMut {
229    fn into_response(self, cx: &Cx) -> Result<Response> {
230        self.freeze().into_response(cx)
231    }
232}
233
234impl IntoResponse for Body {
235    fn into_response(self, _cx: &Cx) -> Result<Response> {
236        Ok(Response::new(self))
237    }
238}
239
240impl IntoResponse for HeaderMap {
241    fn into_response(self, cx: &Cx) -> Result<Response> {
242        (self, ()).into_response(cx)
243    }
244}
245
246impl IntoResponse for Extensions {
247    fn into_response(self, cx: &Cx) -> Result<Response> {
248        (self, ()).into_response(cx)
249    }
250}
251
252impl IntoResponse for Parts {
253    fn into_response(self, cx: &Cx) -> Result<Response> {
254        (self, ()).into_response(cx)
255    }
256}
257
258/// Replies with an empty body carrying each `(name, value)` pair as a header.
259impl<K, V, const N: usize> IntoResponse for [(K, V); N]
260where
261    K: TryInto<HeaderName>,
262    K::Error: std::error::Error + Send + Sync + 'static,
263    V: TryInto<HeaderValue>,
264    V::Error: std::error::Error + Send + Sync + 'static,
265{
266    fn into_response(self, cx: &Cx) -> Result<Response> {
267        (self, ()).into_response(cx)
268    }
269}
270
271/// Re-bodies any [`http::Response`] whose body is a [`Bytes`] stream into the
272/// framework's [`Body`], leaving the parts untouched.
273impl<B> IntoResponse for http::Response<B>
274where
275    B: http_body::Body<Data = Bytes> + Send + 'static,
276    B::Error: Into<BoxError>,
277{
278    fn into_response(self, _cx: &Cx) -> Result<Response> {
279        let (parts, body) = self.into_parts();
280        Ok(Response::from_parts(parts, Body::new(body)))
281    }
282}
283
284// -- IntoResponseParts impls --
285
286impl IntoResponseParts for () {
287    fn into_response_parts(self, _cx: &Cx, _parts: &mut Parts) -> Result<()> {
288        Ok(())
289    }
290}
291
292impl<T> IntoResponseParts for Option<T>
293where
294    T: IntoResponseParts,
295{
296    fn into_response_parts(self, cx: &Cx, parts: &mut Parts) -> Result<()> {
297        if let Some(value) = self {
298            value.into_response_parts(cx, parts)?;
299        }
300        Ok(())
301    }
302}
303
304impl IntoResponseParts for HeaderMap {
305    fn into_response_parts(self, _cx: &Cx, parts: &mut Parts) -> Result<()> {
306        parts.headers.extend(self);
307        Ok(())
308    }
309}
310
311impl IntoResponseParts for Extensions {
312    fn into_response_parts(self, _cx: &Cx, parts: &mut Parts) -> Result<()> {
313        parts.extensions.extend(self);
314        Ok(())
315    }
316}
317
318/// Inserts each `(name, value)` pair as a response header, failing if a name or
319/// value is not a valid header.
320impl<K, V, const N: usize> IntoResponseParts for [(K, V); N]
321where
322    K: TryInto<HeaderName>,
323    K::Error: std::error::Error + Send + Sync + 'static,
324    V: TryInto<HeaderValue>,
325    V::Error: std::error::Error + Send + Sync + 'static,
326{
327    fn into_response_parts(self, _cx: &Cx, parts: &mut Parts) -> Result<()> {
328        for (name, value) in self {
329            let name = name.try_into().map_err(Error::from)?;
330            let value = value.try_into().map_err(Error::from)?;
331            parts.headers.insert(name, value);
332        }
333        Ok(())
334    }
335}
336
337// -- Tuple IntoResponse impls --
338
339/// Generates the four `IntoResponse` tuple families for a fixed number of
340/// [`IntoResponseParts`] members `T1..Tn`: a bare tuple, and tuples led by a
341/// [`StatusCode`], [`Parts`], or [`Response<()>`] that seeds the response.
342///
343/// In every family the final element is the body (any [`IntoResponse`]) and the
344/// `Ti` are applied to the resulting [`Parts`] in order.
345macro_rules! impl_into_response_tuples {
346    ( $($ty:ident),* ) => {
347        #[allow(non_snake_case, unused_mut, unused_variables)]
348        impl<R, $($ty,)*> IntoResponse for ($($ty,)* R,)
349        where
350            R: IntoResponse,
351            $($ty: IntoResponseParts,)*
352        {
353            fn into_response(self, cx: &Cx) -> Result<Response> {
354                let ($($ty,)* r,) = self;
355                let (mut parts, body) = r.into_response(cx)?.into_parts();
356                $( $ty.into_response_parts(cx, &mut parts)?; )*
357                Ok(Response::from_parts(parts, body))
358            }
359        }
360
361        #[allow(non_snake_case, unused_mut, unused_variables)]
362        impl<R, $($ty,)*> IntoResponse for (StatusCode, $($ty,)* R,)
363        where
364            R: IntoResponse,
365            $($ty: IntoResponseParts,)*
366        {
367            fn into_response(self, cx: &Cx) -> Result<Response> {
368                let (status, $($ty,)* r,) = self;
369                let (mut parts, body) = r.into_response(cx)?.into_parts();
370                parts.status = status;
371                $( $ty.into_response_parts(cx, &mut parts)?; )*
372                Ok(Response::from_parts(parts, body))
373            }
374        }
375
376        #[allow(non_snake_case, unused_mut, unused_variables)]
377        impl<R, $($ty,)*> IntoResponse for (Parts, $($ty,)* R,)
378        where
379            R: IntoResponse,
380            $($ty: IntoResponseParts,)*
381        {
382            fn into_response(self, cx: &Cx) -> Result<Response> {
383                let (template, $($ty,)* r,) = self;
384                let (mut parts, body) = r.into_response(cx)?.into_parts();
385                merge_parts(&mut parts, template);
386                $( $ty.into_response_parts(cx, &mut parts)?; )*
387                Ok(Response::from_parts(parts, body))
388            }
389        }
390
391        #[allow(non_snake_case, unused_mut, unused_variables)]
392        impl<R, $($ty,)*> IntoResponse for (Response<()>, $($ty,)* R,)
393        where
394            R: IntoResponse,
395            $($ty: IntoResponseParts,)*
396        {
397            fn into_response(self, cx: &Cx) -> Result<Response> {
398                let (template, $($ty,)* r,) = self;
399                let (template, ()) = template.into_parts();
400                let (mut parts, body) = r.into_response(cx)?.into_parts();
401                merge_parts(&mut parts, template);
402                $( $ty.into_response_parts(cx, &mut parts)?; )*
403                Ok(Response::from_parts(parts, body))
404            }
405        }
406    };
407}
408
409impl_into_response_tuples!();
410impl_into_response_tuples!(T1);
411impl_into_response_tuples!(T1, T2);
412impl_into_response_tuples!(T1, T2, T3);
413impl_into_response_tuples!(T1, T2, T3, T4);
414impl_into_response_tuples!(T1, T2, T3, T4, T5);
415impl_into_response_tuples!(T1, T2, T3, T4, T5, T6);
416impl_into_response_tuples!(T1, T2, T3, T4, T5, T6, T7);
417impl_into_response_tuples!(T1, T2, T3, T4, T5, T6, T7, T8);
418impl_into_response_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9);
419impl_into_response_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
420impl_into_response_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
421impl_into_response_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
422impl_into_response_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
423impl_into_response_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14);
424impl_into_response_tuples!(
425    T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15
426);
427impl_into_response_tuples!(
428    T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16
429);
430
431#[cfg(test)]
432mod tests {
433    use http_body_util::Full;
434
435    use super::*;
436    use crate::to_bytes;
437
438    fn block_on<F: Future>(future: F) -> F::Output {
439        tokio::runtime::Builder::new_current_thread()
440            .build()
441            .unwrap()
442            .block_on(future)
443    }
444
445    /// Renders a value into a response and reads the body fully into memory.
446    fn run(value: impl IntoResponse) -> (Parts, Bytes) {
447        let cx = Cx::default();
448        let (parts, body) = value.into_response(&cx).unwrap().into_parts();
449        let bytes = block_on(to_bytes(body, usize::MAX)).unwrap();
450        (parts, bytes)
451    }
452
453    fn header(parts: &Parts, name: &'static str) -> String {
454        parts
455            .headers
456            .get(name)
457            .unwrap()
458            .to_str()
459            .unwrap()
460            .to_owned()
461    }
462
463    // -- leaf bodies --
464
465    #[test]
466    fn str_is_text_plain() {
467        let (parts, body) = run("hi");
468        assert_eq!(parts.status, StatusCode::OK);
469        assert_eq!(header(&parts, "content-type"), "text/plain; charset=utf-8");
470        assert_eq!(&body[..], b"hi");
471    }
472
473    #[test]
474    fn owned_and_borrowed_text_match() {
475        for (parts, body) in [
476            run(String::from("hi")),
477            run(Box::<str>::from("hi")),
478            run(Cow::Borrowed("hi")),
479            run(Cow::<str>::Owned("hi".to_owned())),
480        ] {
481            assert_eq!(header(&parts, "content-type"), "text/plain; charset=utf-8");
482            assert_eq!(&body[..], b"hi");
483        }
484    }
485
486    #[test]
487    fn byte_bodies_are_octet_stream() {
488        for (parts, body) in [
489            run(b"hi".to_vec()),
490            run(Bytes::from_static(b"hi")),
491            run(BytesMut::from(&b"hi"[..])),
492            run(*b"hi"),
493            run(b"hi"),
494            run(Box::<[u8]>::from(&b"hi"[..])),
495        ] {
496            assert_eq!(header(&parts, "content-type"), "application/octet-stream");
497            assert_eq!(&body[..], b"hi");
498        }
499    }
500
501    #[test]
502    fn unit_is_empty_ok() {
503        let (parts, body) = run(());
504        assert_eq!(parts.status, StatusCode::OK);
505        assert!(body.is_empty());
506        assert!(parts.headers.is_empty());
507    }
508
509    #[test]
510    fn status_code_sets_status_with_empty_body() {
511        let (parts, body) = run(StatusCode::NO_CONTENT);
512        assert_eq!(parts.status, StatusCode::NO_CONTENT);
513        assert!(body.is_empty());
514    }
515
516    #[test]
517    fn header_map_applies_headers() {
518        let mut headers = HeaderMap::new();
519        headers.insert(
520            HeaderName::from_static("x-test"),
521            HeaderValue::from_static("1"),
522        );
523        let (parts, body) = run(headers);
524        assert_eq!(header(&parts, "x-test"), "1");
525        assert!(body.is_empty());
526    }
527
528    #[test]
529    fn extensions_are_applied() {
530        #[derive(Clone, Debug, PartialEq)]
531        struct Marker(u32);
532
533        let mut extensions = Extensions::new();
534        extensions.insert(Marker(7));
535        let (parts, _) = run(extensions);
536        assert_eq!(parts.extensions.get::<Marker>(), Some(&Marker(7)));
537    }
538
539    #[test]
540    fn response_passes_through_with_rebodied_stream() {
541        let response = http::Response::builder()
542            .status(StatusCode::ACCEPTED)
543            .header("x-test", "1")
544            .body(Full::new(Bytes::from_static(b"yo")))
545            .unwrap();
546        let (parts, body) = run(response);
547        assert_eq!(parts.status, StatusCode::ACCEPTED);
548        assert_eq!(header(&parts, "x-test"), "1");
549        assert_eq!(&body[..], b"yo");
550    }
551
552    // -- header arrays --
553
554    #[test]
555    fn header_array_is_into_response() {
556        let (parts, body) = run([("x-test", "1"), ("x-other", "2")]);
557        assert_eq!(header(&parts, "x-test"), "1");
558        assert_eq!(header(&parts, "x-other"), "2");
559        assert!(body.is_empty());
560    }
561
562    #[test]
563    fn invalid_header_name_is_an_error() {
564        // A space is not allowed in a header name.
565        let cx = Cx::default();
566        assert!([("inva lid", "1")].into_response(&cx).is_err());
567    }
568
569    // -- tuples --
570
571    #[test]
572    fn one_tuple_is_just_the_body() {
573        let (parts, body) = run(("hi",));
574        assert_eq!(parts.status, StatusCode::OK);
575        assert_eq!(&body[..], b"hi");
576    }
577
578    #[test]
579    fn status_then_body() {
580        let (parts, body) = run((StatusCode::CREATED, "hi"));
581        assert_eq!(parts.status, StatusCode::CREATED);
582        assert_eq!(header(&parts, "content-type"), "text/plain; charset=utf-8");
583        assert_eq!(&body[..], b"hi");
584    }
585
586    #[test]
587    fn headers_then_body() {
588        let (parts, body) = run(([("x-test", "1")], "hi"));
589        assert_eq!(header(&parts, "x-test"), "1");
590        assert_eq!(&body[..], b"hi");
591    }
592
593    #[test]
594    fn status_headers_and_body() {
595        let (parts, body) = run((StatusCode::CREATED, [("x-test", "1")], "hi"));
596        assert_eq!(parts.status, StatusCode::CREATED);
597        assert_eq!(header(&parts, "x-test"), "1");
598        assert_eq!(&body[..], b"hi");
599    }
600
601    #[test]
602    fn multiple_response_parts_are_all_applied() {
603        let (parts, body) = run((
604            StatusCode::CREATED,
605            [("x-one", "1")],
606            [("x-two", "2")],
607            "hi",
608        ));
609        assert_eq!(parts.status, StatusCode::CREATED);
610        assert_eq!(header(&parts, "x-one"), "1");
611        assert_eq!(header(&parts, "x-two"), "2");
612        assert_eq!(&body[..], b"hi");
613    }
614
615    #[test]
616    fn optional_parts_are_applied_when_present() {
617        let (present, _) = run((Some([("x-test", "1")]), "hi"));
618        assert_eq!(header(&present, "x-test"), "1");
619
620        let (absent, body) = run((Option::<[(&str, &str); 1]>::None, "hi"));
621        assert!(absent.headers.get("x-test").is_none());
622        assert_eq!(&body[..], b"hi");
623    }
624
625    #[test]
626    fn parts_template_seeds_status_and_headers() {
627        let (mut template, _) = Response::new(Body::empty()).into_parts();
628        template.status = StatusCode::IM_A_TEAPOT;
629        template.headers.insert(
630            HeaderName::from_static("x-test"),
631            HeaderValue::from_static("1"),
632        );
633
634        let (parts, body) = run((template, "hi"));
635        assert_eq!(parts.status, StatusCode::IM_A_TEAPOT);
636        assert_eq!(header(&parts, "x-test"), "1");
637        assert_eq!(&body[..], b"hi");
638    }
639
640    #[test]
641    fn response_unit_template_seeds_status_and_headers() {
642        let template = http::Response::builder()
643            .status(StatusCode::IM_A_TEAPOT)
644            .header("x-test", "1")
645            .body(())
646            .unwrap();
647
648        let (parts, body) = run((template, "hi"));
649        assert_eq!(parts.status, StatusCode::IM_A_TEAPOT);
650        assert_eq!(header(&parts, "x-test"), "1");
651        assert_eq!(&body[..], b"hi");
652    }
653}