1use parking_lot::Mutex;
2use pin_project_lite::pin_project;
3use rama_core::bytes::{Bytes, BytesMut};
4use std::{
5 fmt,
6 future::{Future, ready},
7 pin::Pin,
8 task::{Context, Poll, ready},
9};
10use sync_wrapper::SyncWrapper;
11use tokio::sync::oneshot;
12
13use super::{Body, Frame, SizeHint, StreamingBody};
14use crate::HeaderMap;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub enum CaptureOutcome {
19 Complete,
21 Error,
23 Aborted,
31}
32
33#[derive(Clone, Debug)]
39pub enum BodyCaptureEvent {
40 Frame(Frame<Bytes>),
42 End(CaptureOutcome),
47}
48
49pub trait BodyCaptureSink: Send + Sync + 'static {
56 fn capture(&self, event: BodyCaptureEvent) -> impl Future<Output = ()> + Send + 'static;
61
62 fn aborted(&self) {}
68}
69
70impl<F, Fut> BodyCaptureSink for F
71where
72 F: Fn(BodyCaptureEvent) -> Fut + Send + Sync + 'static,
73 Fut: Future<Output = ()> + Send + 'static,
74{
75 fn capture(&self, event: BodyCaptureEvent) -> impl Future<Output = ()> + Send + 'static {
76 self(event)
77 }
78}
79
80impl BodyCaptureSink for tokio::sync::mpsc::Sender<BodyCaptureEvent> {
81 fn capture(&self, event: BodyCaptureEvent) -> impl Future<Output = ()> + Send + 'static {
82 let sender = self.clone();
83 async move {
84 drop(sender.send(event).await);
85 }
86 }
87
88 fn aborted(&self) {
89 drop(self.try_send(BodyCaptureEvent::End(CaptureOutcome::Aborted)));
92 }
93}
94
95impl BodyCaptureSink for tokio::sync::mpsc::UnboundedSender<BodyCaptureEvent> {
96 fn capture(&self, event: BodyCaptureEvent) -> impl Future<Output = ()> + Send + 'static {
97 let result = self.send(event);
98 async move {
99 drop(result);
100 }
101 }
102
103 fn aborted(&self) {
104 drop(self.send(BodyCaptureEvent::End(CaptureOutcome::Aborted)));
105 }
106}
107
108type CaptureFuture = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
109
110enum PendingOutput<E> {
111 Frame(Frame<Bytes>),
112 Error(E),
113 End,
114}
115
116struct CaptureGuard<S: BodyCaptureSink> {
117 sink: S,
118 finished: bool,
119}
120
121impl<S: BodyCaptureSink> CaptureGuard<S> {
122 fn finish(&mut self) {
123 self.finished = true;
124 }
125}
126
127impl<S: BodyCaptureSink> Drop for CaptureGuard<S> {
128 fn drop(&mut self) {
129 if !self.finished {
130 self.sink.aborted();
131 }
132 }
133}
134
135pin_project! {
136 #[must_use = "a captured body does nothing unless polled"]
142 pub struct CaptureBody<B, S>
143 where
144 B: StreamingBody<Data = Bytes>,
145 S: BodyCaptureSink,
146 {
147 #[pin]
148 inner: B,
149 guard: CaptureGuard<S>,
150 pending: SyncWrapper<Option<CaptureFuture>>,
151 pending_output: Option<PendingOutput<B::Error>>,
152 }
153}
154
155impl<B, S> CaptureBody<B, S>
156where
157 B: StreamingBody<Data = Bytes>,
158 S: BodyCaptureSink,
159{
160 pub fn new(inner: B, sink: S) -> Self {
162 Self {
163 inner,
164 guard: CaptureGuard {
165 sink,
166 finished: false,
167 },
168 pending: SyncWrapper::new(None),
169 pending_output: None,
170 }
171 }
172
173 pub const fn get_ref(&self) -> &B {
175 &self.inner
176 }
177
178 pub fn get_mut(&mut self) -> &mut B {
180 &mut self.inner
181 }
182
183 pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut B> {
185 self.project().inner
186 }
187
188 pub fn into_inner(self) -> B {
192 self.inner
193 }
194}
195
196impl<B> CaptureBody<B, BufferedBodyCapture>
197where
198 B: StreamingBody<Data = Bytes>,
199{
200 pub fn buffered(inner: B, limit: CaptureLimit) -> (Self, CaptureHandle) {
202 let (sink, handle) = BufferedBodyCapture::new(limit);
203 (Self::new(inner, sink), handle)
204 }
205}
206
207impl<B, S> StreamingBody for CaptureBody<B, S>
208where
209 B: StreamingBody<Data = Bytes>,
210 S: BodyCaptureSink,
211{
212 type Data = Bytes;
213 type Error = B::Error;
214
215 fn poll_frame(
216 self: Pin<&mut Self>,
217 cx: &mut Context<'_>,
218 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
219 let mut this = self.project();
220
221 loop {
222 if let Some(future) = this.pending.get_mut().as_mut() {
223 ready!(future.as_mut().poll(cx));
224 *this.pending.get_mut() = None;
225
226 #[expect(
227 clippy::expect_used,
228 reason = "missing output means the CaptureBody state machine is invalid"
229 )]
230 let output = this
231 .pending_output
232 .take()
233 .expect("a capture future always has a pending output");
234 match output {
235 PendingOutput::Frame(frame) => return Poll::Ready(Some(Ok(frame))),
236 PendingOutput::Error(error) => {
237 this.guard.finish();
238 return Poll::Ready(Some(Err(error)));
239 }
240 PendingOutput::End => {
241 this.guard.finish();
242 return Poll::Ready(None);
243 }
244 }
245 }
246
247 if this.guard.finished {
248 return this.inner.poll_frame(cx);
249 }
250
251 match this.inner.as_mut().poll_frame(cx) {
252 Poll::Pending => return Poll::Pending,
253 Poll::Ready(Some(Ok(frame))) => {
254 let future = this
255 .guard
256 .sink
257 .capture(BodyCaptureEvent::Frame(frame.clone()));
258 *this.pending.get_mut() = Some(Box::pin(future));
259 *this.pending_output = Some(PendingOutput::Frame(frame));
260 }
261 Poll::Ready(Some(Err(error))) => {
262 let future = this
263 .guard
264 .sink
265 .capture(BodyCaptureEvent::End(CaptureOutcome::Error));
266 *this.pending.get_mut() = Some(Box::pin(future));
267 *this.pending_output = Some(PendingOutput::Error(error));
268 }
269 Poll::Ready(None) => {
270 let future = this
271 .guard
272 .sink
273 .capture(BodyCaptureEvent::End(CaptureOutcome::Complete));
274 *this.pending.get_mut() = Some(Box::pin(future));
275 *this.pending_output = Some(PendingOutput::End);
276 }
277 }
278 }
279 }
280
281 fn is_end_stream(&self) -> bool {
282 self.guard.finished && self.inner.is_end_stream()
283 }
284
285 fn size_hint(&self) -> SizeHint {
286 let pending = match &self.pending_output {
287 Some(PendingOutput::Frame(frame)) => frame
288 .data_ref()
289 .map_or(0, |data| u64::try_from(data.len()).unwrap_or(u64::MAX)),
290 _ => 0,
291 };
292 self.inner.size_hint() + SizeHint::with_exact(pending)
293 }
294}
295
296impl<B, S> fmt::Debug for CaptureBody<B, S>
297where
298 B: StreamingBody<Data = Bytes> + fmt::Debug,
299 S: BodyCaptureSink,
300{
301 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302 f.debug_struct("CaptureBody")
303 .field("inner", &self.inner)
304 .field("sink", &std::any::type_name::<S>())
305 .field("capture_pending", &self.pending_output.is_some())
306 .field("finished", &self.guard.finished)
307 .finish()
308 }
309}
310
311#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
313pub struct CaptureLimit(Option<usize>);
314
315impl CaptureLimit {
316 #[must_use]
318 pub const fn max_bytes(max_bytes: usize) -> Self {
319 Self(Some(max_bytes))
320 }
321
322 #[must_use]
324 pub const fn unlimited() -> Self {
325 Self(None)
326 }
327
328 #[must_use]
330 pub const fn get(self) -> Option<usize> {
331 self.0
332 }
333}
334
335impl From<usize> for CaptureLimit {
336 fn from(value: usize) -> Self {
337 Self::max_bytes(value)
338 }
339}
340
341#[derive(Debug)]
343pub struct CapturedBody {
344 bytes: Bytes,
345 trailers: Option<HeaderMap>,
346 outcome: CaptureOutcome,
347 total_bytes: u64,
348 truncated: bool,
349}
350
351impl CapturedBody {
352 #[must_use]
354 pub const fn bytes(&self) -> &Bytes {
355 &self.bytes
356 }
357
358 #[must_use]
364 pub const fn trailers(&self) -> Option<&HeaderMap> {
365 self.trailers.as_ref()
366 }
367
368 #[must_use]
370 pub const fn outcome(&self) -> CaptureOutcome {
371 self.outcome
372 }
373
374 #[must_use]
376 pub const fn total_bytes(&self) -> u64 {
377 self.total_bytes
378 }
379
380 #[must_use]
382 pub const fn is_truncated(&self) -> bool {
383 self.truncated
384 }
385
386 #[must_use]
388 pub fn into_parts(self) -> (Bytes, Option<HeaderMap>) {
389 (self.bytes, self.trailers)
390 }
391
392 pub fn into_body(self) -> Body {
394 let (bytes, trailers) = self.into_parts();
395 match trailers {
396 Some(trailers) => Body::from(bytes).with_trailer_headers(trailers),
397 None => Body::from(bytes),
398 }
399 }
400}
401
402#[derive(Debug, Clone, Copy, PartialEq, Eq)]
404pub struct CaptureCanceled;
405
406impl fmt::Display for CaptureCanceled {
407 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
408 f.write_str("body capture producer was canceled")
409 }
410}
411
412impl std::error::Error for CaptureCanceled {}
413
414#[derive(Debug)]
416#[must_use = "drop the handle explicitly if the buffered body is not needed"]
417pub struct CaptureHandle {
418 receiver: oneshot::Receiver<CapturedBody>,
419}
420
421impl CaptureHandle {
422 pub async fn wait(self) -> Result<CapturedBody, CaptureCanceled> {
424 self.receiver.await.map_err(|_error| CaptureCanceled)
425 }
426}
427
428struct BufferState {
429 bytes: BytesMut,
430 trailers: Option<HeaderMap>,
431 limit: CaptureLimit,
432 total_bytes: u64,
433 truncated: bool,
434 sender: Option<oneshot::Sender<CapturedBody>>,
435}
436
437impl BufferState {
438 fn is_observed(&mut self) -> bool {
439 if self.sender.as_ref().is_some_and(oneshot::Sender::is_closed) {
440 self.sender = None;
441 self.bytes.clear();
442 self.trailers = None;
443 }
444 self.sender.is_some()
445 }
446
447 fn observe_frame(&mut self, frame: Frame<Bytes>) {
448 if !self.is_observed() {
449 return;
450 }
451
452 match frame.into_data() {
453 Ok(data) => {
454 self.total_bytes = self
455 .total_bytes
456 .saturating_add(u64::try_from(data.len()).unwrap_or(u64::MAX));
457 let remaining = self
458 .limit
459 .get()
460 .map_or(data.len(), |limit| limit.saturating_sub(self.bytes.len()));
461 let take = remaining.min(data.len());
462 self.bytes.extend_from_slice(&data[..take]);
463 self.truncated |= take < data.len();
464 }
465 Err(frame) => {
466 if let Ok(trailers) = frame.into_trailers() {
467 if let Some(current) = &mut self.trailers {
468 current.extend(trailers);
469 } else {
470 self.trailers = Some(trailers);
471 }
472 }
473 }
474 }
475 }
476
477 fn finish(&mut self, outcome: CaptureOutcome) {
478 let Some(sender) = self.sender.take() else {
479 return;
480 };
481 let captured = CapturedBody {
482 bytes: std::mem::take(&mut self.bytes).freeze(),
483 trailers: self.trailers.take(),
484 outcome,
485 total_bytes: self.total_bytes,
486 truncated: self.truncated,
487 };
488 drop(sender.send(captured));
489 }
490}
491
492impl Drop for BufferState {
493 fn drop(&mut self) {
494 self.finish(CaptureOutcome::Aborted);
495 }
496}
497
498pub struct BufferedBodyCapture {
504 state: Mutex<BufferState>,
505}
506
507impl BufferedBodyCapture {
508 pub fn new(limit: CaptureLimit) -> (Self, CaptureHandle) {
510 let (sender, receiver) = oneshot::channel();
511 (
512 Self {
513 state: Mutex::new(BufferState {
514 bytes: BytesMut::new(),
515 trailers: None,
516 limit,
517 total_bytes: 0,
518 truncated: false,
519 sender: Some(sender),
520 }),
521 },
522 CaptureHandle { receiver },
523 )
524 }
525
526 fn state(&self) -> parking_lot::MutexGuard<'_, BufferState> {
527 self.state.lock()
528 }
529}
530
531impl BodyCaptureSink for BufferedBodyCapture {
532 fn capture(&self, event: BodyCaptureEvent) -> impl Future<Output = ()> + Send + 'static {
533 let mut state = self.state();
534 match event {
535 BodyCaptureEvent::Frame(frame) => state.observe_frame(frame),
536 BodyCaptureEvent::End(outcome) => state.finish(outcome),
537 }
538 ready(())
539 }
540
541 fn aborted(&self) {
542 self.state().finish(CaptureOutcome::Aborted);
543 }
544}
545
546impl fmt::Debug for BufferedBodyCapture {
547 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
548 let state = self.state();
549 f.debug_struct("BufferedBodyCapture")
550 .field("limit", &state.limit)
551 .field("captured_bytes", &state.bytes.len())
552 .field("total_bytes", &state.total_bytes)
553 .field("truncated", &state.truncated)
554 .finish_non_exhaustive()
555 }
556}
557
558#[cfg(test)]
559mod tests {
560 use std::{
561 convert::Infallible,
562 io,
563 sync::{
564 Arc,
565 atomic::{AtomicUsize, Ordering},
566 },
567 };
568
569 use rama_core::bytes::Bytes;
570
571 use super::*;
572 use crate::body::util::{BodyExt as _, StreamBody};
573
574 async fn wait_for_capture(handle: CaptureHandle) -> CapturedBody {
575 tokio::time::timeout(std::time::Duration::from_secs(1), handle.wait())
576 .await
577 .expect("capture should finish promptly")
578 .expect("capture producer should finalize")
579 }
580
581 #[tokio::test]
582 async fn sink_future_applies_backpressure_before_forwarding() {
583 let gate = Arc::new(tokio::sync::Notify::new());
584 let sink_gate = Arc::clone(&gate);
585 let sink = move |event: BodyCaptureEvent| {
586 let gate = Arc::clone(&sink_gate);
587 async move {
588 if matches!(event, BodyCaptureEvent::Frame(_)) {
589 gate.notified().await;
590 }
591 }
592 };
593 let mut body = CaptureBody::new(Body::from("held"), sink);
594 let mut frame = Box::pin(body.frame());
595
596 tokio::time::timeout(std::time::Duration::ZERO, &mut frame)
597 .await
598 .expect_err("the frame must wait for its capture future");
599 drop(frame);
600 assert_eq!(body.size_hint().exact(), Some(4));
601 assert!(!body.is_end_stream());
602 gate.notify_one();
603 assert_eq!(
604 body.frame().await.unwrap().unwrap().into_data().unwrap(),
605 Bytes::from_static(b"held")
606 );
607 }
608
609 #[tokio::test]
610 async fn channel_sinks_report_abort_when_capacity_is_available() {
611 let (bounded, mut bounded_events) = tokio::sync::mpsc::channel(1);
612 bounded.aborted();
613 assert!(matches!(
614 tokio::time::timeout(std::time::Duration::from_secs(1), bounded_events.recv())
615 .await
616 .expect("bounded abort should arrive promptly"),
617 Some(BodyCaptureEvent::End(CaptureOutcome::Aborted))
618 ));
619
620 let (unbounded, mut unbounded_events) = tokio::sync::mpsc::unbounded_channel();
621 unbounded.aborted();
622 assert!(matches!(
623 tokio::time::timeout(std::time::Duration::from_secs(1), unbounded_events.recv())
624 .await
625 .expect("unbounded abort should arrive promptly"),
626 Some(BodyCaptureEvent::End(CaptureOutcome::Aborted))
627 ));
628 }
629
630 #[tokio::test]
631 async fn bounded_channel_drops_abort_notification_when_full() {
632 let (bounded, mut events) = tokio::sync::mpsc::channel(1);
633 bounded
634 .send(BodyCaptureEvent::Frame(Frame::data(Bytes::from_static(
635 b"queued",
636 ))))
637 .await
638 .unwrap();
639
640 bounded.aborted();
641 drop(bounded);
642
643 assert!(matches!(
644 events.recv().await,
645 Some(BodyCaptureEvent::Frame(_))
646 ));
647 assert!(events.recv().await.is_none());
648 }
649
650 #[tokio::test]
651 async fn buffered_capture_forwards_frames_and_finishes_once() {
652 let mut trailers = HeaderMap::new();
653 trailers.insert("x-finished", "yes".parse().unwrap());
654 let source = StreamBody::new(rama_core::futures::stream::iter([
655 Ok::<_, Infallible>(Frame::data(Bytes::from_static(b"hello"))),
656 Ok(Frame::data(Bytes::from_static(b" world"))),
657 Ok(Frame::trailers(trailers.clone())),
658 ]));
659 let (mut body, handle) = CaptureBody::buffered(source, CaptureLimit::max_bytes(7));
660
661 let mut forwarded = Vec::new();
662 while let Some(frame) = body.frame().await {
663 let frame = frame.unwrap();
664 if let Some(data) = frame.data_ref() {
665 forwarded.extend_from_slice(data);
666 } else {
667 assert_eq!(frame.trailers_ref().unwrap(), &trailers);
668 }
669 }
670
671 let captured = wait_for_capture(handle).await;
672 assert_eq!(forwarded, b"hello world");
673 assert_eq!(captured.bytes(), &Bytes::from_static(b"hello w"));
674 assert_eq!(captured.trailers(), Some(&trailers));
675 assert_eq!(captured.outcome(), CaptureOutcome::Complete);
676 assert_eq!(captured.total_bytes(), 11);
677 assert!(captured.is_truncated());
678 }
679
680 #[tokio::test]
681 async fn zero_byte_limit_records_length_without_retaining_data() {
682 let (body, handle) =
683 CaptureBody::buffered(Body::from("not retained"), CaptureLimit::max_bytes(0));
684 body.collect().await.unwrap();
685
686 let captured = wait_for_capture(handle).await;
687 assert!(captured.bytes().is_empty());
688 assert_eq!(captured.total_bytes(), 12);
689 assert!(captured.is_truncated());
690 assert_eq!(captured.outcome(), CaptureOutcome::Complete);
691 }
692
693 #[tokio::test]
694 async fn buffered_capture_reports_error_and_partial_body() {
695 let source = StreamBody::new(rama_core::futures::stream::iter([
696 Ok(Frame::data(Bytes::from_static(b"before"))),
697 Err(io::Error::other("broken body")),
698 ]));
699 let (mut body, handle) = CaptureBody::buffered(source, CaptureLimit::unlimited());
700
701 assert_eq!(
702 body.frame().await.unwrap().unwrap().into_data().unwrap(),
703 Bytes::from_static(b"before")
704 );
705 assert_eq!(
706 body.frame().await.unwrap().unwrap_err().to_string(),
707 "broken body"
708 );
709
710 let captured = wait_for_capture(handle).await;
711 assert_eq!(captured.bytes(), &Bytes::from_static(b"before"));
712 assert_eq!(captured.outcome(), CaptureOutcome::Error);
713 }
714
715 #[tokio::test]
716 async fn unlimited_buffer_can_rebuild_data_and_trailers() {
717 let mut trailers = HeaderMap::new();
718 trailers.insert("x-finished", "yes".parse().unwrap());
719 let source = StreamBody::new(rama_core::futures::stream::iter([
720 Ok::<_, Infallible>(Frame::data(Bytes::from_static(b"complete"))),
721 Ok(Frame::trailers(trailers.clone())),
722 ]));
723 let (body, handle) = CaptureBody::buffered(source, CaptureLimit::unlimited());
724 body.collect().await.unwrap();
725
726 let captured = wait_for_capture(handle).await;
727 assert_eq!(captured.outcome(), CaptureOutcome::Complete);
728 assert!(!captured.is_truncated());
729 let rebuilt = captured.into_body().collect().await.unwrap();
730 assert_eq!(rebuilt.trailers(), Some(&trailers));
731 assert_eq!(rebuilt.to_bytes(), Bytes::from_static(b"complete"));
732 }
733
734 #[tokio::test]
735 async fn dropping_buffer_handle_stops_retaining_frames() {
736 let (sink, handle) = BufferedBodyCapture::new(CaptureLimit::unlimited());
737 drop(handle);
738 sink.capture(BodyCaptureEvent::Frame(Frame::data(Bytes::from_static(
739 b"ignored",
740 ))))
741 .await;
742
743 let state = sink.state();
744 assert!(state.sender.is_none());
745 assert!(state.bytes.is_empty());
746 assert_eq!(state.total_bytes, 0);
747 }
748
749 #[tokio::test]
750 async fn dropping_buffer_sink_reports_abort() {
751 let (sink, handle) = BufferedBodyCapture::new(CaptureLimit::unlimited());
752 drop(sink);
753
754 let captured = wait_for_capture(handle).await;
755 assert_eq!(captured.outcome(), CaptureOutcome::Aborted);
756 }
757
758 #[tokio::test]
759 async fn explicit_buffer_abort_finishes_before_the_sink_is_dropped() {
760 let (sink, handle) = BufferedBodyCapture::new(CaptureLimit::unlimited());
761 sink.aborted();
762
763 let captured = wait_for_capture(handle).await;
764 assert_eq!(captured.outcome(), CaptureOutcome::Aborted);
765 drop(sink);
766 }
767
768 #[tokio::test]
769 async fn buffered_capture_reports_abort_once() {
770 let source = StreamBody::new(rama_core::futures::stream::iter([
771 Ok::<_, Infallible>(Frame::data(Bytes::from_static(b"first"))),
772 Ok(Frame::data(Bytes::from_static(b"second"))),
773 ]));
774 let (mut body, handle) = CaptureBody::buffered(source, CaptureLimit::unlimited());
775 assert_eq!(
776 body.frame().await.unwrap().unwrap().into_data().unwrap(),
777 Bytes::from_static(b"first")
778 );
779 drop(body);
780
781 let captured = wait_for_capture(handle).await;
782 assert_eq!(captured.bytes(), &Bytes::from_static(b"first"));
783 assert_eq!(captured.outcome(), CaptureOutcome::Aborted);
784 }
785
786 struct PendingSink {
787 events: tokio::sync::mpsc::UnboundedSender<BodyCaptureEvent>,
788 aborts: Arc<AtomicUsize>,
789 }
790
791 impl BodyCaptureSink for PendingSink {
792 fn capture(&self, event: BodyCaptureEvent) -> impl Future<Output = ()> + Send + 'static {
793 self.events.send(event).unwrap();
794 std::future::pending()
795 }
796
797 fn aborted(&self) {
798 self.aborts.fetch_add(1, Ordering::Relaxed);
799 }
800 }
801
802 #[tokio::test]
803 async fn dropping_while_sink_is_pending_reports_abort_without_forwarding() {
804 let (events, mut captured) = tokio::sync::mpsc::unbounded_channel();
805 let aborts = Arc::new(AtomicUsize::new(0));
806 let mut body = CaptureBody::new(
807 Body::from("captured first"),
808 PendingSink {
809 events,
810 aborts: Arc::clone(&aborts),
811 },
812 );
813 let mut frame = Box::pin(body.frame());
814
815 tokio::time::timeout(std::time::Duration::ZERO, &mut frame)
816 .await
817 .expect_err("the sink future should remain pending");
818 drop(frame);
819 assert!(matches!(
820 captured.recv().await,
821 Some(BodyCaptureEvent::Frame(_))
822 ));
823 drop(body);
824 assert_eq!(aborts.load(Ordering::Relaxed), 1);
825 }
826
827 struct AbortSink(Arc<AtomicUsize>);
828
829 impl BodyCaptureSink for AbortSink {
830 fn capture(&self, _event: BodyCaptureEvent) -> impl Future<Output = ()> + Send + 'static {
831 ready(())
832 }
833
834 fn aborted(&self) {
835 self.0.fetch_add(1, Ordering::Relaxed);
836 }
837 }
838
839 #[test]
840 fn into_inner_notifies_sink_of_abort() {
841 let aborted = Arc::new(AtomicUsize::new(0));
842 let body = CaptureBody::new(Body::from("body"), AbortSink(Arc::clone(&aborted)));
843 let _inner = body.into_inner();
844 assert_eq!(aborted.load(Ordering::Relaxed), 1);
845 }
846
847 struct CompletionSink {
848 endings: Arc<AtomicUsize>,
849 aborts: Arc<AtomicUsize>,
850 }
851
852 impl BodyCaptureSink for CompletionSink {
853 fn capture(&self, event: BodyCaptureEvent) -> impl Future<Output = ()> + Send + 'static {
854 if matches!(event, BodyCaptureEvent::End(CaptureOutcome::Complete)) {
855 self.endings.fetch_add(1, Ordering::Relaxed);
856 }
857 ready(())
858 }
859
860 fn aborted(&self) {
861 self.aborts.fetch_add(1, Ordering::Relaxed);
862 }
863 }
864
865 #[tokio::test]
866 async fn normal_completion_finishes_once_without_abort() {
867 let endings = Arc::new(AtomicUsize::new(0));
868 let aborts = Arc::new(AtomicUsize::new(0));
869 let mut body = CaptureBody::new(
870 Body::empty(),
871 CompletionSink {
872 endings: Arc::clone(&endings),
873 aborts: Arc::clone(&aborts),
874 },
875 );
876
877 assert!(body.frame().await.is_none());
878 assert!(body.is_end_stream());
879 drop(body);
880
881 assert_eq!(endings.load(Ordering::Relaxed), 1);
882 assert_eq!(aborts.load(Ordering::Relaxed), 0);
883 }
884
885 #[test]
886 fn delegates_stream_metadata_without_hiding_pending_completion() {
887 let (body, handle) = CaptureBody::buffered(Body::from("abc"), CaptureLimit::unlimited());
888 assert!(!body.is_end_stream());
889 assert_eq!(body.size_hint().exact(), Some(3));
890 drop(handle);
891
892 let (body, handle) = CaptureBody::buffered(Body::empty(), CaptureLimit::unlimited());
893 assert!(!body.is_end_stream());
894 assert_eq!(body.size_hint().exact(), Some(0));
895 drop(handle);
896 }
897
898 #[test]
899 fn capture_debug_and_canceled_display_are_informative() {
900 let (sink, handle) = BufferedBodyCapture::new(CaptureLimit::max_bytes(8));
901 assert!(format!("{sink:?}").contains("BufferedBodyCapture"));
902 let body = CaptureBody::new(Body::empty(), sink);
903 assert!(format!("{body:?}").contains("CaptureBody"));
904 assert_eq!(
905 CaptureCanceled.to_string(),
906 "body capture producer was canceled"
907 );
908 drop(handle);
909 }
910}