1use std::pin::Pin;
18use std::task::{Context, Poll};
19
20use bytes::Bytes;
21use futures_core::Stream;
22use rainier_support::Error;
23
24pub type BodyStream = Pin<Box<dyn Stream<Item = Result<Bytes, Error>> + Send>>;
26
27#[derive(Default)]
29pub enum Body {
30 #[default]
33 Empty,
34 Bytes(Bytes),
37 Stream(BodyStream),
40}
41
42impl Body {
43 pub fn empty() -> Self {
45 Body::Empty
46 }
47
48 pub fn from_bytes(bytes: impl Into<Bytes>) -> Self {
50 Body::Bytes(bytes.into())
51 }
52
53 pub fn from_stream<S>(stream: S) -> Self
55 where
56 S: Stream<Item = Result<Bytes, Error>> + Send + 'static,
57 {
58 Body::Stream(Box::pin(stream))
59 }
60
61 pub fn size_hint(&self) -> Option<u64> {
64 match self {
65 Body::Empty => Some(0),
66 Body::Bytes(bytes) => Some(bytes.len() as u64),
67 Body::Stream(_) => None,
68 }
69 }
70
71 pub fn is_empty(&self) -> bool {
74 match self {
75 Body::Empty => true,
76 Body::Bytes(bytes) => bytes.is_empty(),
77 Body::Stream(_) => false,
78 }
79 }
80
81 pub async fn collect(self) -> Result<Bytes, Error> {
86 match self {
87 Body::Empty => Ok(Bytes::new()),
88 Body::Bytes(bytes) => Ok(bytes),
89 Body::Stream(mut stream) => {
90 let mut collected = Vec::new();
91 std::future::poll_fn(|cx| loop {
94 match stream.as_mut().poll_next(cx) {
95 Poll::Ready(Some(Ok(chunk))) => collected.extend_from_slice(&chunk),
96 Poll::Ready(Some(Err(e))) => return Poll::Ready(Err(e)),
97 Poll::Ready(None) => return Poll::Ready(Ok(())),
98 Poll::Pending => return Poll::Pending,
99 }
100 })
101 .await?;
102 Ok(Bytes::from(collected))
103 }
104 }
105 }
106}
107
108impl std::fmt::Debug for Body {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 match self {
111 Body::Empty => f.write_str("Body::Empty"),
112 Body::Bytes(bytes) => write!(f, "Body::Bytes({} bytes)", bytes.len()),
113 Body::Stream(_) => f.write_str("Body::Stream(..)"),
114 }
115 }
116}
117
118impl From<()> for Body {
119 fn from(_: ()) -> Self {
120 Body::Empty
121 }
122}
123
124impl From<Bytes> for Body {
125 fn from(bytes: Bytes) -> Self {
126 Body::Bytes(bytes)
127 }
128}
129
130impl From<Vec<u8>> for Body {
131 fn from(bytes: Vec<u8>) -> Self {
132 Body::Bytes(Bytes::from(bytes))
133 }
134}
135
136impl From<String> for Body {
137 fn from(text: String) -> Self {
138 Body::Bytes(Bytes::from(text))
139 }
140}
141
142impl From<&'static str> for Body {
143 fn from(text: &'static str) -> Self {
144 Body::Bytes(Bytes::from_static(text.as_bytes()))
145 }
146}
147
148impl http_body::Body for Body {
151 type Data = Bytes;
152 type Error = Error;
153
154 fn poll_frame(
155 self: Pin<&mut Self>,
156 cx: &mut Context<'_>,
157 ) -> Poll<Option<Result<http_body::Frame<Bytes>, Error>>> {
158 let this = self.get_mut();
162 match this {
163 Body::Empty => Poll::Ready(None),
164 Body::Bytes(bytes) => {
165 if bytes.is_empty() {
166 Poll::Ready(None)
167 } else {
168 let chunk = std::mem::take(bytes);
169 Poll::Ready(Some(Ok(http_body::Frame::data(chunk))))
170 }
171 }
172 Body::Stream(stream) => match stream.as_mut().poll_next(cx) {
173 Poll::Ready(Some(Ok(chunk))) => {
174 Poll::Ready(Some(Ok(http_body::Frame::data(chunk))))
175 }
176 Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
177 Poll::Ready(None) => Poll::Ready(None),
178 Poll::Pending => Poll::Pending,
179 },
180 }
181 }
182
183 fn is_end_stream(&self) -> bool {
184 match self {
185 Body::Empty => true,
186 Body::Bytes(bytes) => bytes.is_empty(),
187 Body::Stream(_) => false,
188 }
189 }
190
191 fn size_hint(&self) -> http_body::SizeHint {
192 match Body::size_hint(self) {
193 Some(len) => http_body::SizeHint::with_exact(len),
194 None => http_body::SizeHint::default(),
195 }
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use futures_util::stream;
203
204 #[tokio::test]
205 async fn collects_in_memory_bodies() {
206 assert_eq!(Body::empty().collect().await.unwrap(), Bytes::new());
207 assert_eq!(Body::from("hello").collect().await.unwrap(), Bytes::from("hello"));
208 }
209
210 #[tokio::test]
211 async fn collects_a_stream() {
212 let body =
213 Body::from_stream(stream::iter(vec![Ok(Bytes::from("ab")), Ok(Bytes::from("cd"))]));
214 assert_eq!(body.collect().await.unwrap(), Bytes::from("abcd"));
215 }
216
217 #[tokio::test]
218 async fn a_stream_error_surfaces_from_collect() {
219 let body = Body::from_stream(stream::iter(vec![
220 Ok(Bytes::from("ab")),
221 Err(Error::internal("disk died")),
222 ]));
223 assert_eq!(body.collect().await.unwrap_err().message(), "disk died");
224 }
225
226 #[test]
227 fn size_hints_are_known_except_for_streams() {
228 assert_eq!(Body::empty().size_hint(), Some(0));
229 assert_eq!(Body::from("abc").size_hint(), Some(3));
230 assert_eq!(Body::from_stream(stream::empty()).size_hint(), None);
231 }
232
233 #[test]
234 fn emptiness_is_only_claimed_when_certain() {
235 assert!(Body::empty().is_empty());
236 assert!(Body::from("").is_empty());
237 assert!(!Body::from("x").is_empty());
238 assert!(!Body::from_stream(stream::empty()).is_empty());
240 }
241
242 #[tokio::test]
243 async fn drives_as_an_http_body() {
244 use http_body_util::BodyExt;
245
246 let collected = Body::from("payload").collect().await.unwrap();
247 assert_eq!(collected, Bytes::from("payload"));
248
249 let body = Body::from("payload");
251 let aggregated = BodyExt::collect(body).await.unwrap().to_bytes();
252 assert_eq!(aggregated, Bytes::from("payload"));
253 }
254}