1use std::error::Error;
13use std::fmt;
14use std::io;
15use std::sync::Arc;
16use std::sync::atomic::{AtomicU64, Ordering};
17
18use bytes::{Bytes, BytesMut};
19use futures_util::stream::BoxStream;
20use futures_util::{Stream, StreamExt, TryStreamExt};
21
22pub type PayloadStream = BoxStream<'static, io::Result<Bytes>>;
26
27#[derive(Clone, Debug)]
34pub struct ClientError(Arc<dyn Error + Send + Sync + 'static>);
35
36impl ClientError {
37 pub fn new<E>(err: E) -> Self
39 where
40 E: Error + Send + Sync + 'static,
41 {
42 Self(Arc::new(err))
43 }
44}
45
46impl fmt::Display for ClientError {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 self.0.fmt(f)
49 }
50}
51
52impl Error for ClientError {
53 fn source(&self) -> Option<&(dyn Error + 'static)> {
54 self.0.source()
55 }
56}
57
58impl From<ClientError> for io::Error {
60 fn from(err: ClientError) -> Self {
61 io::Error::other(err)
62 }
63}
64
65pub type ClientStream = BoxStream<'static, Result<Bytes, ClientError>>;
75
76pub fn unpack_client_error<E>(err: &E) -> Option<ClientError>
88where
89 E: Error + 'static,
90{
91 let mut source = Some(err as &(dyn Error + 'static));
92
93 while let Some(s) = source {
94 let target = match s.downcast_ref::<io::Error>().and_then(|e| e.get_ref()) {
97 Some(inner) => inner,
98 None => s,
99 };
100
101 if let Some(client_error) = target.downcast_ref::<ClientError>() {
102 return Some(client_error.clone());
103 }
104
105 source = s.source();
106 }
107 None
108}
109
110#[derive(Debug, Default)]
111enum ChunkedBytesState {
112 #[default]
113 Empty,
114 Single(Bytes),
115 Multi(BytesMut),
116}
117
118#[derive(Debug)]
125pub(crate) struct ChunkedBytes {
126 state: ChunkedBytesState,
127 capacity: usize,
128}
129
130impl ChunkedBytes {
131 pub fn new(capacity: usize) -> Self {
136 Self {
137 state: ChunkedBytesState::Empty,
138 capacity,
139 }
140 }
141
142 pub fn push(&mut self, chunk: Bytes) {
147 if chunk.is_empty() {
148 return;
149 }
150 self.state = match std::mem::take(&mut self.state) {
151 ChunkedBytesState::Empty => ChunkedBytesState::Single(chunk),
152 ChunkedBytesState::Single(first) => {
153 let capacity = self.capacity.max(first.len() + chunk.len());
154 let mut buf = BytesMut::with_capacity(capacity);
155 buf.extend_from_slice(&first);
156 buf.extend_from_slice(&chunk);
157 ChunkedBytesState::Multi(buf)
158 }
159 ChunkedBytesState::Multi(mut buf) => {
160 buf.extend_from_slice(&chunk);
161 ChunkedBytesState::Multi(buf)
162 }
163 };
164 }
165
166 pub fn capacity(&self) -> usize {
168 self.capacity
169 }
170
171 pub fn len(&self) -> usize {
173 match &self.state {
174 ChunkedBytesState::Empty => 0,
175 ChunkedBytesState::Single(b) => b.len(),
176 ChunkedBytesState::Multi(b) => b.len(),
177 }
178 }
179
180 #[cfg(test)]
182 pub fn is_empty(&self) -> bool {
183 self.len() == 0
184 }
185
186 pub fn into_bytes(self) -> Bytes {
191 match self.state {
192 ChunkedBytesState::Empty => Bytes::new(),
193 ChunkedBytesState::Single(b) => b,
194 ChunkedBytesState::Multi(b) => b.freeze(),
195 }
196 }
197}
198
199pub(crate) struct SizedPeek<S> {
209 buffer: ChunkedBytes,
210 pending: Option<Bytes>,
212 stream: Option<S>,
214}
215
216impl<S> SizedPeek<S> {
217 pub fn is_exhausted(&self) -> bool {
219 self.pending.is_none()
220 }
221
222 #[cfg(test)]
224 pub fn len(&self) -> usize {
225 self.buffer.len()
226 }
227}
228
229impl<S, E> SizedPeek<S>
230where
231 S: Stream<Item = Result<Bytes, E>> + Unpin,
232{
233 pub async fn new(mut stream: S, limit: usize) -> Result<Self, E> {
239 let mut buffer = ChunkedBytes::new(limit);
240
241 while let Some(chunk) = stream.try_next().await? {
242 if buffer.len() + chunk.len() > buffer.capacity() {
243 return Ok(Self {
244 buffer,
245 pending: Some(chunk),
246 stream: Some(stream),
247 });
248 }
249 buffer.push(chunk);
250 }
251
252 Ok(Self {
253 buffer,
254 pending: None,
255 stream: None,
256 })
257 }
258
259 pub async fn into_bytes(mut self) -> Result<Bytes, E> {
264 if let Some(pending) = self.pending.take() {
265 self.buffer.push(pending);
266 }
267
268 if let Some(mut stream) = self.stream.take() {
269 while let Some(chunk) = stream.try_next().await? {
270 self.buffer.push(chunk);
271 }
272 }
273
274 Ok(self.buffer.into_bytes())
275 }
276}
277
278impl<S, E> SizedPeek<S>
279where
280 S: Stream<Item = Result<Bytes, E>>,
281{
282 pub fn into_stream(self) -> impl Stream<Item = Result<Bytes, E>> {
285 let leading = [self.buffer.into_bytes(), self.pending.unwrap_or_default()]
286 .into_iter()
287 .filter(|b| !b.is_empty())
288 .map(Ok);
289
290 let tail = futures_util::stream::iter(self.stream).flatten();
291 futures_util::stream::iter(leading).chain(tail)
292 }
293}
294
295pub fn single<E: Send + 'static>(
297 contents: impl Into<Bytes>,
298) -> BoxStream<'static, Result<Bytes, E>> {
299 futures_util::stream::once(std::future::ready(Ok(contents.into()))).boxed()
300}
301
302pub fn counting_stream<S, E>(stream: S) -> (Arc<AtomicU64>, impl Stream<Item = Result<Bytes, E>>)
307where
308 S: Stream<Item = Result<Bytes, E>>,
309{
310 let counter = Arc::new(AtomicU64::new(0));
311
312 (
313 counter.clone(),
314 stream.inspect(move |res| {
315 if let Ok(chunk) = res {
316 counter.fetch_add(chunk.len() as u64, Ordering::Relaxed);
317 }
318 }),
319 )
320}
321
322#[cfg(test)]
324pub(crate) async fn read_to_vec<S, E>(mut stream: S) -> crate::error::Result<Vec<u8>>
325where
326 S: Stream<Item = Result<Bytes, E>> + Unpin,
327 E: Into<crate::error::Error>,
328{
329 let mut payload = Vec::new();
330 while let Some(result) = stream.next().await {
331 let chunk = result.map_err(Into::into)?;
332 payload.extend(&chunk);
333 }
334 Ok(payload)
335}
336
337#[cfg(test)]
338mod tests {
339 use futures_util::stream;
340
341 use super::*;
342
343 #[test]
346 fn empty_into_bytes() {
347 let buf = ChunkedBytes::new(1024);
348 assert!(buf.is_empty());
349 assert_eq!(buf.len(), 0);
350 assert_eq!(buf.into_bytes().len(), 0);
351 }
352
353 #[test]
354 fn single_chunk_zero_copy() {
355 let original = Bytes::from_static(b"hello world");
356 let ptr = original.as_ptr();
357
358 let mut buf = ChunkedBytes::new(1024);
359 buf.push(original);
360
361 assert_eq!(buf.len(), 11);
362 assert!(!buf.is_empty());
363
364 let result = buf.into_bytes();
365 assert_eq!(result.as_ref(), b"hello world");
366 assert_eq!(result.as_ptr(), ptr);
368 }
369
370 #[test]
371 fn multi_chunk_coalesces() {
372 let mut buf = ChunkedBytes::new(1024);
373 buf.push(Bytes::from_static(b"hello "));
374 buf.push(Bytes::from_static(b"world"));
375
376 assert_eq!(buf.len(), 11);
377 assert_eq!(buf.into_bytes().as_ref(), b"hello world");
378 }
379
380 #[test]
381 fn push_empty_chunk_is_noop() {
382 let original = Bytes::from_static(b"data");
383 let ptr = original.as_ptr();
384
385 let mut buf = ChunkedBytes::new(1024);
386 buf.push(Bytes::new());
387 assert!(buf.is_empty());
388
389 buf.push(original);
390 buf.push(Bytes::new());
391 let result = buf.into_bytes();
393 assert_eq!(result.as_ptr(), ptr);
394 }
395
396 #[test]
397 fn capacity_is_preserved() {
398 let buf = ChunkedBytes::new(42);
399 assert_eq!(buf.capacity(), 42);
400 }
401
402 #[tokio::test]
405 async fn exhausted_when_stream_fits() {
406 let s = stream::once(std::future::ready(Ok::<_, io::Error>(Bytes::from_static(
407 b"small payload",
408 ))));
409
410 let peeked = SizedPeek::new(s, 1024).await.unwrap();
411
412 assert!(peeked.is_exhausted());
413 assert_eq!(peeked.len(), 13);
414 }
415
416 #[tokio::test]
417 async fn remaining_when_stream_exceeds_limit() {
418 let chunks: Vec<io::Result<Bytes>> = vec![
419 Ok(Bytes::from(vec![0u8; 600])),
420 Ok(Bytes::from(vec![1u8; 600])),
421 ];
422
423 let peeked = SizedPeek::new(stream::iter(chunks), 1000).await.unwrap();
424
425 assert!(!peeked.is_exhausted());
426 assert_eq!(peeked.len(), 600);
428 }
429
430 #[tokio::test]
431 async fn into_stream_reassembles_data() {
432 let chunks: Vec<io::Result<Bytes>> = vec![
434 Ok(Bytes::from_static(b"aaa")),
435 Ok(Bytes::from_static(b"bbb")),
436 Ok(Bytes::from_static(b"ccc")),
437 ];
438
439 let peeked = SizedPeek::new(stream::iter(chunks), 5).await.unwrap();
440 assert!(!peeked.is_exhausted());
441
442 let output = read_to_vec(peeked.into_stream()).await.unwrap();
443 assert_eq!(output, b"aaabbbccc");
444 }
445
446 #[tokio::test]
447 async fn into_stream_single_chunk_zero_copy() {
448 let original = Bytes::from_static(b"zero-copy roundtrip");
449 let ptr = original.as_ptr();
450 let s = stream::once(std::future::ready(Ok::<_, io::Error>(original)));
451
452 let peeked = SizedPeek::new(s, 1024).await.unwrap();
453 let out = peeked.into_stream();
454 futures_util::pin_mut!(out);
455 let first = out.try_next().await.unwrap().unwrap();
456 assert_eq!(first.as_ptr(), ptr);
457 }
458
459 #[tokio::test]
460 async fn oversized_single_chunk_goes_to_pending() {
461 let big = Bytes::from(vec![0u8; 2000]);
463 let ptr = big.as_ptr();
464 let s = stream::once(std::future::ready(Ok::<_, io::Error>(big)));
465
466 let peeked = SizedPeek::new(s, 1000).await.unwrap();
467 assert!(!peeked.is_exhausted());
468 assert_eq!(peeked.len(), 0); let out = peeked.into_stream();
471 futures_util::pin_mut!(out);
472 let first = out.try_next().await.unwrap().unwrap();
473 assert_eq!(first.as_ptr(), ptr);
475 assert_eq!(first.len(), 2000);
476 }
477
478 #[tokio::test]
479 async fn error_propagation() {
480 let chunks: Vec<io::Result<Bytes>> = vec![
481 Ok(Bytes::from_static(b"ok")),
482 Err(io::Error::new(io::ErrorKind::BrokenPipe, "broken")),
483 ];
484
485 let result = SizedPeek::new(stream::iter(chunks), 1024).await;
486 assert!(result.is_err());
487 }
488}