tower_embed/
response.rs

1use std::{
2    borrow::Cow,
3    convert::Infallible,
4    pin::Pin,
5    task::{Context, Poll},
6};
7
8use bytes::Bytes;
9use http_body_util::BodyExt;
10
11use crate::headers::{self, HeaderMapExt};
12
13type BoxBody = http_body_util::combinators::UnsyncBoxBody<Bytes, Infallible>;
14
15/// The body used in crate responses.
16#[derive(Debug)]
17pub struct ResponseBody(BoxBody);
18
19impl ResponseBody {
20    /// Create an empty response body.
21    pub(crate) fn empty() -> Self {
22        ResponseBody(http_body_util::Empty::new().boxed_unsync())
23    }
24
25    /// Create a new response body that contains a single chunk
26    pub(crate) fn full(data: Bytes) -> Self {
27        ResponseBody(http_body_util::Full::new(data).boxed_unsync())
28    }
29}
30
31impl http_body::Body for ResponseBody {
32    type Data = Bytes;
33    type Error = Infallible;
34
35    fn poll_frame(
36        mut self: Pin<&mut Self>,
37        cx: &mut Context<'_>,
38    ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
39        Pin::new(&mut self.0).poll_frame(cx)
40    }
41
42    fn is_end_stream(&self) -> bool {
43        self.0.is_end_stream()
44    }
45
46    fn size_hint(&self) -> http_body::SizeHint {
47        self.0.size_hint()
48    }
49}
50
51/// File information
52pub(crate) struct File {
53    pub content: Cow<'static, [u8]>,
54    pub content_type: headers::ContentType,
55    pub etag: headers::ETag,
56    pub last_modified: Option<headers::LastModified>,
57}
58
59/// Response future of [`ServeEmbed`]
60///
61/// [`ServeEmbed`]: crate::ServeEmbed
62pub struct ResponseFuture {
63    inner: ResponseFutureInner,
64}
65
66enum ResponseFutureInner {
67    Ready(Option<http::Response<ResponseBody>>),
68}
69
70impl ResponseFuture {
71    pub(crate) fn method_not_allowed() -> Self {
72        let response = http::Response::builder()
73            .header(
74                http::header::ALLOW,
75                http::HeaderValue::from_static("GET, HEAD"),
76            )
77            .status(http::StatusCode::METHOD_NOT_ALLOWED)
78            .body(ResponseBody::empty())
79            .unwrap();
80
81        Self {
82            inner: ResponseFutureInner::Ready(Some(response)),
83        }
84    }
85
86    pub(crate) fn file_not_found() -> Self {
87        let response = http::Response::builder()
88            .status(http::StatusCode::NOT_FOUND)
89            .body(ResponseBody::empty())
90            .unwrap();
91
92        Self {
93            inner: ResponseFutureInner::Ready(Some(response)),
94        }
95    }
96
97    pub(crate) fn not_modified() -> Self {
98        let response = http::Response::builder()
99            .status(http::StatusCode::NOT_MODIFIED)
100            .body(ResponseBody::empty())
101            .unwrap();
102
103        Self {
104            inner: ResponseFutureInner::Ready(Some(response)),
105        }
106    }
107
108    pub(crate) fn file(file: File) -> Self {
109        let mut response = http::Response::builder()
110            .status(http::StatusCode::OK)
111            .body(ResponseBody::full(match file.content {
112                Cow::Borrowed(bytes) => Bytes::from_static(bytes),
113                Cow::Owned(bytes) => Bytes::from_owner(bytes),
114            }))
115            .unwrap();
116
117        response.headers_mut().typed_insert(file.content_type);
118        response.headers_mut().typed_insert(file.etag);
119        if let Some(last_modified) = file.last_modified {
120            response.headers_mut().typed_insert(last_modified);
121        }
122
123        Self {
124            inner: ResponseFutureInner::Ready(Some(response)),
125        }
126    }
127}
128
129impl Future for ResponseFuture {
130    type Output = Result<http::Response<ResponseBody>, std::convert::Infallible>;
131
132    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
133        let response = match self.get_mut().inner {
134            ResponseFutureInner::Ready(ref mut response) => response
135                .take()
136                .expect("ResponseFuture polled after completion"),
137        };
138        Poll::Ready(Ok(response))
139    }
140}