Skip to main content

timvw_rust_runtime/
body.rs

1use std::fmt;
2use std::pin::Pin;
3use std::task::{Context, Poll};
4
5use bytes::Bytes;
6use futures_core::TryStream;
7use futures_util::TryStreamExt;
8use http_body::{Frame, SizeHint};
9use http_body_util::combinators::BoxBody;
10use http_body_util::{BodyExt, StreamBody};
11use sync_wrapper::SyncStream;
12
13use crate::BoxError;
14
15/// The body of a request or a response: bytes held in memory, or a stream.
16///
17/// A body held in memory can be cloned ([`Body::try_clone`]), which is what lets a retry layer send a request again.
18/// A stream can be read once.
19pub struct Body {
20    kind: Kind,
21}
22
23enum Kind {
24    Full(Option<Bytes>),
25    Stream(BoxBody<Bytes, BoxError>),
26}
27
28impl Body {
29    /// An empty body.
30    pub fn empty() -> Self {
31        Self {
32            kind: Kind::Full(None),
33        }
34    }
35
36    /// A body that wraps another [`http_body::Body`], such as the body of a hyper or reqwest response.
37    pub fn wrap<B>(body: B) -> Self
38    where
39        B: http_body::Body + Send + Sync + 'static,
40        B::Data: Into<Bytes>,
41        B::Error: Into<BoxError>,
42    {
43        Self {
44            kind: Kind::Stream(
45                body.map_frame(|frame| frame.map_data(Into::into))
46                    .map_err(Into::into)
47                    .boxed(),
48            ),
49        }
50    }
51
52    /// A body that reads the chunks of a stream, such as a file read with `tokio_util::io::ReaderStream`.
53    pub fn wrap_stream<S>(stream: S) -> Self
54    where
55        S: TryStream + Send + 'static,
56        S::Ok: Into<Bytes>,
57        S::Error: Into<BoxError>,
58    {
59        let frames = SyncStream::new(
60            stream
61                .into_stream()
62                .map_ok(|chunk| Frame::data(chunk.into()))
63                .map_err(Into::into),
64        );
65        Self {
66            kind: Kind::Stream(StreamBody::new(frames).boxed()),
67        }
68    }
69
70    /// A copy of a body held in memory; `None` for a stream.
71    pub fn try_clone(&self) -> Option<Self> {
72        match &self.kind {
73            Kind::Full(bytes) => Some(Self {
74                kind: Kind::Full(bytes.clone()),
75            }),
76            Kind::Stream(_) => None,
77        }
78    }
79
80    /// The bytes of a body held in memory; `None` for a stream.
81    pub fn as_bytes(&self) -> Option<&[u8]> {
82        match &self.kind {
83            Kind::Full(bytes) => Some(bytes.as_deref().unwrap_or_default()),
84            Kind::Stream(_) => None,
85        }
86    }
87
88    /// Reads the whole body into memory.
89    pub async fn collect_bytes(self) -> Result<Bytes, BoxError> {
90        match self.kind {
91            Kind::Full(bytes) => Ok(bytes.unwrap_or_default()),
92            Kind::Stream(body) => Ok(body.collect().await?.to_bytes()),
93        }
94    }
95}
96
97impl Default for Body {
98    fn default() -> Self {
99        Self::empty()
100    }
101}
102
103impl fmt::Debug for Body {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        match &self.kind {
106            Kind::Full(bytes) => f
107                .debug_struct("Body")
108                .field("len", &bytes.as_ref().map_or(0, Bytes::len))
109                .finish(),
110            Kind::Stream(_) => f.debug_struct("Body").field("stream", &true).finish(),
111        }
112    }
113}
114
115impl http_body::Body for Body {
116    type Data = Bytes;
117    type Error = BoxError;
118
119    fn poll_frame(
120        self: Pin<&mut Self>,
121        cx: &mut Context<'_>,
122    ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
123        match &mut self.get_mut().kind {
124            Kind::Full(bytes) => Poll::Ready(
125                bytes
126                    .take()
127                    .filter(|b| !b.is_empty())
128                    .map(|b| Ok(Frame::data(b))),
129            ),
130            Kind::Stream(body) => Pin::new(body).poll_frame(cx),
131        }
132    }
133
134    fn is_end_stream(&self) -> bool {
135        match &self.kind {
136            Kind::Full(bytes) => bytes.as_ref().is_none_or(Bytes::is_empty),
137            Kind::Stream(body) => body.is_end_stream(),
138        }
139    }
140
141    fn size_hint(&self) -> SizeHint {
142        match &self.kind {
143            Kind::Full(bytes) => SizeHint::with_exact(bytes.as_ref().map_or(0, |b| b.len() as u64)),
144            Kind::Stream(body) => body.size_hint(),
145        }
146    }
147}
148
149impl From<Bytes> for Body {
150    fn from(bytes: Bytes) -> Self {
151        Self {
152            kind: Kind::Full(Some(bytes)),
153        }
154    }
155}
156
157impl From<Vec<u8>> for Body {
158    fn from(bytes: Vec<u8>) -> Self {
159        Bytes::from(bytes).into()
160    }
161}
162
163impl From<String> for Body {
164    fn from(text: String) -> Self {
165        Bytes::from(text).into()
166    }
167}
168
169impl From<&'static str> for Body {
170    fn from(text: &'static str) -> Self {
171        Bytes::from_static(text.as_bytes()).into()
172    }
173}
174
175impl From<&'static [u8]> for Body {
176    fn from(bytes: &'static [u8]) -> Self {
177        Bytes::from_static(bytes).into()
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[tokio::test]
186    async fn bytes_clone_and_read() {
187        let body = Body::from("hello");
188        let copy = body.try_clone().expect("bytes can be cloned");
189        assert_eq!(body.collect_bytes().await.unwrap(), "hello");
190        assert_eq!(copy.as_bytes(), Some(&b"hello"[..]));
191    }
192
193    #[tokio::test]
194    async fn streams_are_read_once() {
195        let chunks = futures_util::stream::iter(vec![
196            Ok::<_, std::io::Error>(Bytes::from("a")),
197            Ok(Bytes::from("b")),
198        ]);
199        let body = Body::wrap_stream(chunks);
200        assert!(body.try_clone().is_none());
201        assert_eq!(body.collect_bytes().await.unwrap(), "ab");
202    }
203
204    #[test]
205    fn empty_is_end_of_stream() {
206        assert!(http_body::Body::is_end_stream(&Body::empty()));
207        assert_eq!(
208            http_body::Body::size_hint(&Body::from("abc")).exact(),
209            Some(3)
210        );
211    }
212}