actix_http/body/
sized_stream.rs1use std::{
2 error::Error as StdError,
3 pin::Pin,
4 task::{Context, Poll},
5};
6
7use bytes::Bytes;
8use futures_core::{ready, Stream};
9use pin_project_lite::pin_project;
10
11use super::{BodySize, InfallibleStream, MessageBody};
12
13pin_project! {
14 pub struct SizedStream<S> {
19 size: u64,
20 #[pin]
21 stream: S,
22 }
23}
24
25impl<S, E> SizedStream<S>
26where
27 S: Stream<Item = Result<Bytes, E>>,
28 E: Into<Box<dyn StdError>> + 'static,
29{
30 #[inline]
31 #[must_use]
32 pub fn new(size: u64, stream: S) -> Self {
33 SizedStream { size, stream }
34 }
35}
36
37impl<S> SizedStream<InfallibleStream<S>>
38where
39 S: Stream<Item = Bytes>,
40{
41 #[inline]
58 #[must_use]
59 pub fn from_infallible(size: u64, stream: S) -> Self {
60 SizedStream::new(size, InfallibleStream { stream })
61 }
62}
63
64impl<S, E> MessageBody for SizedStream<S>
65where
66 S: Stream<Item = Result<Bytes, E>>,
67 E: Into<Box<dyn StdError>> + 'static,
68{
69 type Error = E;
70
71 #[inline]
72 fn size(&self) -> BodySize {
73 BodySize::Sized(self.size)
74 }
75
76 fn poll_next(
82 mut self: Pin<&mut Self>,
83 cx: &mut Context<'_>,
84 ) -> Poll<Option<Result<Bytes, Self::Error>>> {
85 loop {
86 let stream = self.as_mut().project().stream;
87
88 let chunk = match ready!(stream.poll_next(cx)) {
89 Some(Ok(ref bytes)) if bytes.is_empty() => continue,
90 val => val,
91 };
92
93 return Poll::Ready(chunk);
94 }
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use std::{convert::Infallible, pin::pin};
101
102 use actix_utils::future::poll_fn;
103 use futures_util::stream;
104 use static_assertions::{assert_impl_all, assert_not_impl_any};
105
106 use super::*;
107 use crate::body::to_bytes;
108
109 assert_impl_all!(SizedStream<stream::Empty<Result<Bytes, crate::Error>>>: MessageBody);
110 assert_impl_all!(SizedStream<stream::Empty<Result<Bytes, &'static str>>>: MessageBody);
111 assert_impl_all!(SizedStream<stream::Repeat<Result<Bytes, &'static str>>>: MessageBody);
112 assert_impl_all!(SizedStream<stream::Empty<Result<Bytes, Infallible>>>: MessageBody);
113 assert_impl_all!(SizedStream<stream::Repeat<Result<Bytes, Infallible>>>: MessageBody);
114
115 assert_not_impl_any!(SizedStream<stream::Empty<Bytes>>: MessageBody);
116 assert_not_impl_any!(SizedStream<stream::Repeat<Bytes>>: MessageBody);
117 assert_not_impl_any!(SizedStream<stream::Repeat<Result<Bytes, crate::Error>>>: MessageBody);
119
120 #[actix_rt::test]
121 async fn infallible_stream() {
122 let stream = async_stream::stream! {
123 yield Bytes::new();
124 yield Bytes::from_static(b"1");
125 tokio::task::yield_now().await;
126 yield Bytes::new();
127 yield Bytes::from_static(b"2");
128 yield Bytes::new();
129 };
130 let body = SizedStream::from_infallible(2, stream);
131
132 assert_eq!(body.size(), BodySize::Sized(2));
133 assert_eq!(
134 to_bytes(body).await,
135 Ok::<_, Infallible>(Bytes::from_static(b"12"))
136 );
137 }
138
139 #[actix_rt::test]
140 async fn infallible_empty_stream() {
141 let body = SizedStream::from_infallible(0, stream::empty::<Bytes>());
142
143 assert_eq!(to_bytes(body).await, Ok::<_, Infallible>(Bytes::new()));
144 }
145
146 #[actix_rt::test]
147 async fn skips_empty_chunks() {
148 let body = SizedStream::new(
149 2,
150 stream::iter(
151 ["1", "", "2"]
152 .iter()
153 .map(|&v| Ok::<_, Infallible>(Bytes::from(v))),
154 ),
155 );
156
157 let mut body = pin!(body);
158
159 assert_eq!(
160 poll_fn(|cx| body.as_mut().poll_next(cx))
161 .await
162 .unwrap()
163 .ok(),
164 Some(Bytes::from("1")),
165 );
166
167 assert_eq!(
168 poll_fn(|cx| body.as_mut().poll_next(cx))
169 .await
170 .unwrap()
171 .ok(),
172 Some(Bytes::from("2")),
173 );
174 }
175
176 #[actix_rt::test]
177 async fn read_to_bytes() {
178 let body = SizedStream::new(
179 2,
180 stream::iter(
181 ["1", "", "2"]
182 .iter()
183 .map(|&v| Ok::<_, Infallible>(Bytes::from(v))),
184 ),
185 );
186
187 assert_eq!(to_bytes(body).await.ok(), Some(Bytes::from("12")));
188 }
189
190 #[actix_rt::test]
191 async fn stream_string_error() {
192 let body = SizedStream::new(0, stream::once(async { Err("stringy error") }));
196 assert_eq!(to_bytes(body).await, Ok(Bytes::new()));
197
198 let body = SizedStream::new(1, stream::once(async { Err("stringy error") }));
199 assert!(matches!(to_bytes(body).await, Err("stringy error")));
200 }
201
202 #[actix_rt::test]
203 async fn stream_boxed_error() {
204 let body = SizedStream::new(
208 0,
209 stream::once(async { Err(Box::<dyn StdError>::from("stringy error")) }),
210 );
211 assert_eq!(to_bytes(body).await.unwrap(), Bytes::new());
212
213 let body = SizedStream::new(
214 1,
215 stream::once(async { Err(Box::<dyn StdError>::from("stringy error")) }),
216 );
217 assert_eq!(
218 to_bytes(body).await.unwrap_err().to_string(),
219 "stringy error"
220 );
221 }
222}