http_streams_core/
buffer.rs1use bytes::{Bytes, BytesMut};
12use futures::stream::{Stream, StreamExt};
13
14pub fn buffer_ready_items<'b, S, E>(
19 stream: S,
20 count: usize,
21) -> impl Stream<Item = Result<Bytes, E>> + Send + 'b
22where
23 S: Stream<Item = Result<Bytes, E>> + Send + 'b,
24 E: 'b,
25{
26 stream.ready_chunks(count).map(|chunks| {
27 let mut buf = BytesMut::new();
28 for chunk in chunks {
29 buf.extend_from_slice(&chunk?);
30 }
31 Ok(buf.freeze())
32 })
33}
34
35pub fn buffer_bytes<'b, S, E>(
39 stream: S,
40 size: usize,
41) -> impl Stream<Item = Result<Bytes, E>> + Send + 'b
42where
43 S: Stream<Item = Result<Bytes, E>> + Send + 'b,
44 E: Send + 'b,
47{
48 let size = size.max(1);
52
53 let stream = stream.chain(futures::stream::once(futures::future::ready(Ok(
56 Bytes::new(),
57 ))));
58
59 stream
60 .scan(
61 (BytesMut::with_capacity(size), false),
62 move |(current_buffer, errored), maybe_bytes| {
63 futures::future::ready(if *errored {
64 None
65 } else {
66 match maybe_bytes {
67 Ok(bytes) if bytes.is_empty() => {
70 if current_buffer.is_empty() {
71 Some(Vec::new())
72 } else {
73 Some(vec![Ok(current_buffer.split().freeze())])
74 }
75 }
76 Ok(bytes) => {
77 let mut chunks = Vec::new();
78 current_buffer.extend_from_slice(&bytes);
79 while current_buffer.len() >= size {
80 chunks.push(Ok(current_buffer.split_to(size).freeze()));
81 }
82 Some(chunks)
83 }
84 Err(e) => {
90 *errored = true;
91 current_buffer.clear();
92 Some(vec![Err(e)])
93 }
94 }
95 })
96 },
97 )
98 .flat_map(futures::stream::iter)
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use crate::error::{StreamError, StreamErrorKind};
105
106 async fn collect(s: impl Stream<Item = Result<Bytes, StreamError>>) -> (Vec<Vec<u8>>, usize) {
107 let items: Vec<_> = Box::pin(s).collect().await;
108 let errors = items.iter().filter(|i| i.is_err()).count();
109 let chunks = items.into_iter().flatten().map(|b| b.to_vec()).collect();
110 (chunks, errors)
111 }
112
113 fn source(parts: Vec<&'static str>) -> impl Stream<Item = Result<Bytes, StreamError>> {
114 futures::stream::iter(
115 parts
116 .into_iter()
117 .map(|p| Ok(Bytes::from_static(p.as_bytes()))),
118 )
119 }
120
121 #[tokio::test]
122 async fn buffers_to_the_requested_size() {
123 let (chunks, errors) = collect(buffer_bytes(source(vec!["ab", "cd", "ef", "g"]), 3)).await;
124 assert_eq!(errors, 0);
125 assert_eq!(
126 chunks,
127 vec![b"abc".to_vec(), b"def".to_vec(), b"g".to_vec()]
128 );
129 }
130
131 #[tokio::test]
132 async fn flushes_a_short_tail() {
133 let (chunks, _) = collect(buffer_bytes(source(vec!["ab"]), 100)).await;
134 assert_eq!(chunks, vec![b"ab".to_vec()]);
135 }
136
137 #[tokio::test]
139 async fn an_error_stops_the_stream_and_is_visible() {
140 let parts = vec![
141 Ok(Bytes::from_static(b"ab")),
142 Err(StreamError::new(StreamErrorKind::CodecError, None, None)),
143 Ok(Bytes::from_static(b"cd")),
144 ];
145 let (chunks, errors) = collect(buffer_bytes(futures::stream::iter(parts), 100)).await;
146
147 assert_eq!(errors, 1, "the error must be yielded, not swallowed");
148 assert!(
149 chunks.is_empty(),
150 "buffered bytes are dropped, so no data can follow the error"
151 );
152 }
153
154 #[tokio::test]
155 async fn ready_items_coalesce() {
156 let (chunks, errors) =
157 collect(buffer_ready_items(source(vec!["a", "b", "c", "d", "e"]), 2)).await;
158 assert_eq!(errors, 0);
159 assert_eq!(
160 chunks.concat(),
161 b"abcde".to_vec(),
162 "coalescing must not change the bytes"
163 );
164 assert!(chunks.len() < 5, "chunks must actually be coalesced");
165 }
166
167 #[tokio::test]
170 async fn an_exact_multiple_emits_no_empty_tail() {
171 let (chunks, errors) = collect(buffer_bytes(source(vec!["abc", "def"]), 3)).await;
172 assert_eq!(errors, 0);
173 assert_eq!(chunks, vec![b"abc".to_vec(), b"def".to_vec()]);
174 assert!(
175 chunks.iter().all(|c| !c.is_empty()),
176 "no zero-length chunk may be emitted"
177 );
178 }
179
180 #[tokio::test]
181 async fn an_empty_source_emits_nothing() {
182 let empty = futures::stream::iter(Vec::<Result<Bytes, StreamError>>::new());
183 let (chunks, errors) = collect(buffer_bytes(empty, 8)).await;
184 assert_eq!(errors, 0);
185 assert!(chunks.is_empty(), "an empty body must produce no chunks");
186 }
187
188 #[tokio::test]
191 async fn a_zero_size_does_not_hang() {
192 let buffered = buffer_bytes(source(vec!["ab", "cd"]), 0);
193 let (chunks, errors) =
194 tokio::time::timeout(std::time::Duration::from_secs(5), collect(buffered))
195 .await
196 .expect("a zero size must not loop forever");
197
198 assert_eq!(errors, 0);
199 assert_eq!(chunks.concat(), b"abcd".to_vec());
200 }
201}