use crate::body::{Frame, StreamingBody};
use pin_project_lite::pin_project;
use rama_core::error::{BoxError, ErrorContext as _};
use std::{
pin::Pin,
task::{Context, Poll, ready},
time::Duration,
};
use tokio::time::{Sleep, sleep};
pin_project! {
pub struct TimeoutBody<B> {
timeout: Duration,
#[pin]
sleep: Option<Sleep>,
#[pin]
body: B,
}
}
impl<B> TimeoutBody<B> {
pub fn new(timeout: Duration, body: B) -> Self {
Self {
timeout,
sleep: None,
body,
}
}
}
impl<B> StreamingBody for TimeoutBody<B>
where
B: StreamingBody,
B::Error: Into<BoxError>,
{
type Data = B::Data;
type Error = BoxError;
fn poll_frame(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
let mut this = self.project();
let sleep_pinned = if let Some(some) = this.sleep.as_mut().as_pin_mut() {
some
} else {
this.sleep.set(Some(sleep(*this.timeout)));
#[expect(clippy::expect_used)]
this.sleep
.as_mut()
.as_pin_mut()
.expect("Some value to be set in previous statement (line)")
};
if sleep_pinned.poll(cx) == Poll::Ready(()) {
return Poll::Ready(Some(Err(Box::new(TimeoutError(())))));
}
let frame = ready!(this.body.poll_frame(cx));
this.sleep.set(None);
Poll::Ready(frame.transpose().into_box_error().transpose())
}
}
#[derive(Debug)]
pub struct TimeoutError(pub(super) ());
impl std::error::Error for TimeoutError {}
impl std::fmt::Display for TimeoutError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "data was not received within the designated timeout")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::body::util::BodyExt;
use pin_project_lite::pin_project;
use rama_core::bytes::Bytes;
use std::{error::Error, fmt::Display};
#[derive(Debug)]
struct MockError;
impl Error for MockError {}
impl Display for MockError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "mock error")
}
}
pin_project! {
struct MockBody {
#[pin]
sleep: Sleep
}
}
impl StreamingBody for MockBody {
type Data = Bytes;
type Error = MockError;
fn poll_frame(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
let this = self.project();
this.sleep
.poll(cx)
.map(|_| Some(Ok(Frame::data(vec![].into()))))
}
}
#[tokio::test]
async fn test_body_available_within_timeout() {
let mock_sleep = Duration::from_secs(1);
let timeout_sleep = Duration::from_secs(2);
let mock_body = MockBody {
sleep: sleep(mock_sleep),
};
let timeout_body = TimeoutBody::new(timeout_sleep, mock_body);
timeout_body
.boxed()
.frame()
.await
.expect("no frame")
.unwrap();
}
#[tokio::test]
async fn test_body_unavailable_within_timeout_error() {
let mock_sleep = Duration::from_secs(2);
let timeout_sleep = Duration::from_secs(1);
let mock_body = MockBody {
sleep: sleep(mock_sleep),
};
let timeout_body = TimeoutBody::new(timeout_sleep, mock_body);
timeout_body.boxed().frame().await.unwrap().unwrap_err();
}
}