diff --git a/http-body-util/src/lib.rs b/http-body-util/src/lib.rs index ae2c369..994e9b7 100644 --- a/http-body-util/src/lib.rs +++ b/http-body-util/src/lib.rs @@ -15,6 +15,7 @@ mod empty; mod full; mod future; mod limited; +mod pending; mod stream; #[cfg(feature = "channel")] @@ -30,6 +31,7 @@ pub use self::empty::Empty; pub use self::full::Full; pub use self::future::TryFutureBody; pub use self::limited::{LengthLimitError, Limited}; +pub use self::pending::Pending; pub use self::stream::{BodyDataStream, BodyStream, StreamBody}; #[cfg(feature = "channel")] diff --git a/http-body-util/src/pending.rs b/http-body-util/src/pending.rs new file mode 100644 index 0000000..f32b281 --- /dev/null +++ b/http-body-util/src/pending.rs @@ -0,0 +1,119 @@ +use bytes::Buf; +use http_body::{Body, Frame, SizeHint}; +use std::{ + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, +}; + +/// A [`Body`] that always returns [`Poll::Pending`] when polled. +/// +/// A [`Pending`] is a body that will continue to yield [`Poll::Pending`] when +/// [`Body::poll_frame()`] is called. The `D` and `E` generics are used to specify [`Body::Data`] +/// and [`Body::Error`]. +/// +/// This is like [`std::future::Pending`], but for response bodies. It represents a +/// body that never resolves, which is often useful for writing test coverage. +#[derive(Debug, Default)] +pub struct Pending { + data: PhantomData, + error: PhantomData, +} + +// === impl Pending === + +impl Pending { + /// Returns a new [`Pending`] that will yield the provided error. + /// + /// # Examples + /// + /// ``` + /// # use bytes::Bytes; + /// # use http_body::Body; + /// # use http_body_util::Pending; + /// # use std::pin::Pin; + /// # use std::task::{Context, Poll}; + /// # + /// type Error = Box; + /// + /// let mut body = Pending::::new(); + /// + /// let waker = futures_util::task::noop_waker(); + /// let mut cx = Context::from_waker(&waker); + /// + /// // The body yields `Pending` when polled. + /// match Pin::new(&mut body).poll_frame(&mut cx) { + /// Poll::Pending => {} + /// other => panic!(), + /// } + /// ``` + pub fn new() -> Self { + Self { + data: PhantomData, + error: PhantomData, + } + } +} + +impl Body for Pending +where + E: Unpin, + D: Buf + Unpin, +{ + type Data = D; + type Error = E; + + fn poll_frame( + self: Pin<&mut Self>, + _: &mut Context<'_>, + ) -> Poll, Self::Error>>> { + Poll::Pending + } + + fn is_end_stream(&self) -> bool { + false + } + + fn size_hint(&self) -> SizeHint { + SizeHint::default() + } +} + +#[cfg(test)] +mod pending_body_tests { + use super::Pending; + use bytes::Bytes; + use http_body::Body; + use std::{ + ops::Not, + pin::Pin, + task::{Context, Poll}, + }; + + #[test] + fn yields_poll_pending() { + type Error = &'static str; + + let mut body = Pending::::new(); + + assert!( + body.is_end_stream().not(), + "body is not finished until polled" + ); + assert_eq!(body.size_hint().lower(), 0); + assert_eq!(body.size_hint().upper(), None); + + let waker = futures_util::task::noop_waker(); + let mut cx = Context::from_waker(&waker); + + match Pin::new(&mut body).poll_frame(&mut cx) { + Poll::Pending => {} + other => panic!("unexpected poll outcome: {:?}", other), + } + + match Pin::new(&mut body).poll_frame(&mut cx) { + Poll::Pending => {} + other => panic!("unexpected poll outcome: {:?}", other), + } + } +}