rama_http/layer/timeout/
body.rs1use crate::body::{Frame, StreamingBody};
2use pin_project_lite::pin_project;
3use rama_core::error::{BoxError, ErrorContext as _};
4use std::{
5 pin::Pin,
6 task::{Context, Poll, ready},
7 time::Duration,
8};
9use tokio::time::{Sleep, sleep};
10
11pin_project! {
12 pub struct TimeoutBody<B> {
53 timeout: Duration,
54 #[pin]
55 sleep: Option<Sleep>,
56 #[pin]
57 body: B,
58 }
59}
60
61impl<B> TimeoutBody<B> {
62 pub fn new(timeout: Duration, body: B) -> Self {
64 Self {
65 timeout,
66 sleep: None,
67 body,
68 }
69 }
70}
71
72impl<B> StreamingBody for TimeoutBody<B>
73where
74 B: StreamingBody,
75 B::Error: Into<BoxError>,
76{
77 type Data = B::Data;
78 type Error = BoxError;
79
80 fn poll_frame(
81 self: Pin<&mut Self>,
82 cx: &mut Context<'_>,
83 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
84 let mut this = self.project();
85
86 let sleep_pinned = if let Some(some) = this.sleep.as_mut().as_pin_mut() {
88 some
89 } else {
90 this.sleep.set(Some(sleep(*this.timeout)));
91 #[expect(clippy::expect_used)]
92 this.sleep
93 .as_mut()
94 .as_pin_mut()
95 .expect("Some value to be set in previous statement (line)")
96 };
97
98 if sleep_pinned.poll(cx) == Poll::Ready(()) {
100 return Poll::Ready(Some(Err(Box::new(TimeoutError(())))));
101 }
102
103 let frame = ready!(this.body.poll_frame(cx));
105 this.sleep.set(None);
107
108 Poll::Ready(frame.transpose().into_box_error().transpose())
109 }
110}
111
112#[derive(Debug)]
114pub struct TimeoutError(pub(super) ());
115
116impl std::error::Error for TimeoutError {}
117
118impl std::fmt::Display for TimeoutError {
119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120 write!(f, "data was not received within the designated timeout")
121 }
122}
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 use crate::body::util::BodyExt;
128 use pin_project_lite::pin_project;
129 use rama_core::bytes::Bytes;
130 use std::{error::Error, fmt::Display};
131
132 #[derive(Debug)]
133 struct MockError;
134
135 impl Error for MockError {}
136
137 impl Display for MockError {
138 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 write!(f, "mock error")
140 }
141 }
142
143 pin_project! {
144 struct MockBody {
145 #[pin]
146 sleep: Sleep
147 }
148 }
149
150 impl StreamingBody for MockBody {
151 type Data = Bytes;
152 type Error = MockError;
153
154 fn poll_frame(
155 self: Pin<&mut Self>,
156 cx: &mut Context<'_>,
157 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
158 let this = self.project();
159 this.sleep
160 .poll(cx)
161 .map(|_| Some(Ok(Frame::data(vec![].into()))))
162 }
163 }
164
165 #[tokio::test]
166 async fn test_body_available_within_timeout() {
167 let mock_sleep = Duration::from_secs(1);
168 let timeout_sleep = Duration::from_secs(2);
169
170 let mock_body = MockBody {
171 sleep: sleep(mock_sleep),
172 };
173 let timeout_body = TimeoutBody::new(timeout_sleep, mock_body);
174
175 timeout_body
176 .boxed()
177 .frame()
178 .await
179 .expect("no frame")
180 .unwrap();
181 }
182
183 #[tokio::test]
184 async fn test_body_unavailable_within_timeout_error() {
185 let mock_sleep = Duration::from_secs(2);
186 let timeout_sleep = Duration::from_secs(1);
187
188 let mock_body = MockBody {
189 sleep: sleep(mock_sleep),
190 };
191 let timeout_body = TimeoutBody::new(timeout_sleep, mock_body);
192
193 timeout_body.boxed().frame().await.unwrap().unwrap_err();
194 }
195}