1#![expect(
4 clippy::allow_attributes,
5 reason = "macro-generated `#[allow]` attributes whose underlying lints fire only for some expansions"
6)]
7#![expect(
8 clippy::unreachable,
9 reason = "SENTINEL_ERROR_CODE is only stored alongside an underlying body error, never observed at the unreachable branch"
10)]
11
12use crate::body::{Frame, SizeHint, StreamingBody};
13use pin_project_lite::pin_project;
14use rama_core::bytes::{Buf, Bytes, BytesMut};
15use rama_core::error::BoxError;
16use rama_core::futures::Stream;
17use rama_core::futures::ready;
18use rama_core::stream::io::StreamReader;
19use std::io::ErrorKind;
20use std::{
21 io,
22 pin::Pin,
23 task::{Context, Poll},
24};
25use tokio::io::AsyncRead;
26
27macro_rules! compressed_body_poll_frame {
34 ($self:expr, $cx:expr) => {
35 match $self.project().inner.project() {
36 BodyInnerProj::Gzip { inner } => inner.poll_frame($cx),
37 BodyInnerProj::Deflate { inner } => inner.poll_frame($cx),
38 BodyInnerProj::Brotli { inner } => inner.poll_frame($cx),
39 BodyInnerProj::Zstd { inner } => inner.poll_frame($cx),
40 BodyInnerProj::Identity { inner } => match ready!(inner.poll_frame($cx)) {
41 Some(Ok(frame)) => {
42 let frame = frame.map_data(|mut buf| buf.copy_to_bytes(buf.remaining()));
43 Poll::Ready(Some(Ok(frame)))
44 }
45 Some(Err(err)) => Poll::Ready(Some(Err(err.into()))),
46 None => Poll::Ready(None),
47 },
48 }
49 };
50}
51pub(crate) use compressed_body_poll_frame;
52
53macro_rules! impl_decorate_async_read {
58 ($codec:ident: |$input:pat_param, $quality:pat_param| $apply:block) => {
59 impl<B> DecorateAsyncRead for $codec<B>
60 where
61 B: StreamingBody,
62 {
63 type Input = AsyncReadBody<B>;
64 type Output = $codec<Self::Input>;
65
66 fn apply($input: Self::Input, $quality: CompressionLevel) -> Self::Output $apply
67
68 fn get_pin_mut(pinned: Pin<&mut Self::Output>) -> Pin<&mut Self::Input> {
69 pinned.get_pin_mut()
70 }
71 }
72 };
73}
74pub(crate) use impl_decorate_async_read;
75
76pub(crate) type AsyncReadBody<B> = StreamReader<
78 StreamErrorIntoIoError<BodyIntoStream<B>, <B as StreamingBody>::Error>,
79 <B as StreamingBody>::Data,
80>;
81
82pub(crate) trait DecorateAsyncRead {
84 type Input: AsyncRead;
85 type Output: AsyncRead;
86
87 fn apply(input: Self::Input, quality: CompressionLevel) -> Self::Output;
89
90 fn get_pin_mut(pinned: Pin<&mut Self::Output>) -> Pin<&mut Self::Input>;
94}
95
96pin_project! {
97 pub(crate) struct WrapBody<M: DecorateAsyncRead> {
99 #[pin]
100 pub read: M::Output,
103 buf: BytesMut,
106 read_all_data: bool,
107 tolerate_decode_errors: bool,
111 }
112}
113
114impl<M: DecorateAsyncRead> WrapBody<M> {
115 const INTERNAL_BUF_CAPACITY: usize = 8096;
116}
117
118impl<M: DecorateAsyncRead> WrapBody<M> {
119 #[allow(dead_code)]
120 pub(crate) fn new<B>(body: B, quality: CompressionLevel) -> Self
121 where
122 B: StreamingBody,
123 M: DecorateAsyncRead<Input = AsyncReadBody<B>>,
124 {
125 let stream = BodyIntoStream::new(body);
127
128 let stream = StreamErrorIntoIoError::<_, B::Error>::new(stream);
131
132 let read = StreamReader::new(stream);
134
135 let read = M::apply(read, quality);
137
138 Self {
139 read,
140 buf: BytesMut::with_capacity(Self::INTERNAL_BUF_CAPACITY),
141 read_all_data: false,
142 tolerate_decode_errors: false,
143 }
144 }
145
146 pub(crate) fn with_tolerate_decode_errors(mut self, tolerate: bool) -> Self {
155 self.tolerate_decode_errors = tolerate;
156 self
157 }
158}
159
160impl<B, M> StreamingBody for WrapBody<M>
161where
162 B: StreamingBody<Error: Into<BoxError>>,
163 M: DecorateAsyncRead<Input = AsyncReadBody<B>>,
164{
165 type Data = Bytes;
166 type Error = BoxError;
167
168 fn poll_frame(
169 self: Pin<&mut Self>,
170 cx: &mut Context<'_>,
171 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
172 let mut this = self.project();
173
174 if !*this.read_all_data {
175 if this.buf.capacity() == 0 {
176 this.buf.reserve(Self::INTERNAL_BUF_CAPACITY);
177 }
178
179 let result =
180 rama_core::stream::io::poll_read_buf(this.read.as_mut(), cx, &mut this.buf);
181
182 match ready!(result) {
183 Ok(0) => {
184 *this.read_all_data = true;
185 }
186 Ok(_) => {
187 let chunk = this.buf.split().freeze();
188 return Poll::Ready(Some(Ok(Frame::data(chunk))));
189 }
190 Err(err) => {
191 let body_error: Option<B::Error> = M::get_pin_mut(this.read.as_mut())
192 .get_pin_mut()
193 .project()
194 .error
195 .take();
196
197 let read_some_data = M::get_pin_mut(this.read.as_mut())
198 .get_pin_mut()
199 .project()
200 .read_some_data;
201
202 if let Some(body_error) = body_error {
203 return Poll::Ready(Some(Err(body_error.into())));
204 } else if err.raw_os_error() == Some(SENTINEL_ERROR_CODE) {
205 unreachable!()
208 } else if *read_some_data {
209 if err.kind() == ErrorKind::UnexpectedEof
210 && M::get_pin_mut(this.read.as_mut())
211 .get_pin_mut()
212 .inner
213 .yielded_all_data
214 {
215 *this.read_all_data = true;
216 } else if *this.tolerate_decode_errors {
217 rama_core::telemetry::tracing::debug!(
229 "decompression: tolerating mid-stream decode error ({err}); ending body cleanly"
230 );
231 return Poll::Ready(None);
232 } else {
233 return Poll::Ready(Some(Err(err.into())));
234 }
235 }
236 }
237 }
238 }
239
240 let body = M::get_pin_mut(this.read).get_pin_mut().get_pin_mut();
242 match ready!(body.poll_frame(cx)) {
243 Some(Ok(frame)) if frame.is_trailers() => Poll::Ready(Some(Ok(
244 frame.map_data(|mut data| data.copy_to_bytes(data.remaining()))
245 ))),
246 Some(Ok(frame)) => match frame.into_data() {
247 Ok(data) if data.has_remaining() => Poll::Ready(Some(Err(
251 "there are extra bytes after body has been decompressed".into(),
252 ))),
253 Ok(mut data) => {
258 Poll::Ready(Some(Ok(Frame::data(data.copy_to_bytes(data.remaining())))))
259 }
260 Err(frame) => Poll::Ready(Some(Ok(
261 frame.map_data(|mut data| data.copy_to_bytes(data.remaining()))
262 ))),
263 },
264 Some(Err(err)) => Poll::Ready(Some(Err(err.into()))),
265 None => Poll::Ready(None),
266 }
267 }
268}
269
270pin_project! {
271 pub(crate) struct BodyIntoStream<B>
272 where
273 B: StreamingBody,
274 {
275 #[pin]
276 body: B,
277 yielded_all_data: bool,
278 non_data_frame: Option<Frame<B::Data>>,
279 }
280}
281
282#[allow(dead_code)]
283impl<B> BodyIntoStream<B>
284where
285 B: StreamingBody,
286{
287 pub(crate) fn new(body: B) -> Self {
288 Self {
289 body,
290 yielded_all_data: false,
291 non_data_frame: None,
292 }
293 }
294
295 pub(crate) fn get_ref(&self) -> &B {
297 &self.body
298 }
299
300 pub(crate) fn get_mut(&mut self) -> &mut B {
302 &mut self.body
303 }
304
305 pub(crate) fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut B> {
307 self.project().body
308 }
309
310 pub(crate) fn into_inner(self) -> B {
312 self.body
313 }
314}
315
316impl<B> Stream for BodyIntoStream<B>
317where
318 B: StreamingBody,
319{
320 type Item = Result<B::Data, B::Error>;
321
322 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
323 loop {
324 let this = self.as_mut().project();
325
326 if *this.yielded_all_data {
327 return Poll::Ready(None);
328 }
329
330 match std::task::ready!(this.body.poll_frame(cx)) {
331 Some(Ok(frame)) => match frame.into_data() {
332 Ok(data) => return Poll::Ready(Some(Ok(data))),
333 Err(frame) => {
334 *this.yielded_all_data = true;
335 *this.non_data_frame = Some(frame);
336 }
337 },
338 Some(Err(err)) => return Poll::Ready(Some(Err(err))),
339 None => {
340 *this.yielded_all_data = true;
341 }
342 }
343 }
344 }
345}
346
347impl<B> StreamingBody for BodyIntoStream<B>
348where
349 B: StreamingBody,
350{
351 type Data = B::Data;
352 type Error = B::Error;
353
354 fn poll_frame(
355 mut self: Pin<&mut Self>,
356 cx: &mut Context<'_>,
357 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
358 if let Some(frame) = std::task::ready!(self.as_mut().poll_next(cx)) {
361 return Poll::Ready(Some(frame.map(Frame::data)));
362 }
363
364 let this = self.project();
365
366 if let Some(frame) = this.non_data_frame.take() {
368 return Poll::Ready(Some(Ok(frame)));
369 }
370
371 this.body.poll_frame(cx)
374 }
375
376 #[inline]
377 fn size_hint(&self) -> SizeHint {
378 self.body.size_hint()
379 }
380}
381
382pin_project! {
383 pub(crate) struct StreamErrorIntoIoError<S, E> {
384 #[pin]
385 inner: S,
386 error: Option<E>,
387 read_some_data: bool,
388 }
389}
390
391impl<S, E> StreamErrorIntoIoError<S, E> {
392 pub(crate) fn new(inner: S) -> Self {
393 Self {
394 inner,
395 error: None,
396 read_some_data: false,
397 }
398 }
399
400 pub(crate) fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut S> {
402 self.project().inner
403 }
404}
405
406impl<S, T, E> Stream for StreamErrorIntoIoError<S, E>
407where
408 S: Stream<Item = Result<T, E>>,
409{
410 type Item = Result<T, io::Error>;
411
412 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
413 let this = self.project();
414 match ready!(this.inner.poll_next(cx)) {
415 None => Poll::Ready(None),
416 Some(Ok(value)) => {
417 *this.read_some_data = true;
418 Poll::Ready(Some(Ok(value)))
419 }
420 Some(Err(err)) => {
421 *this.error = Some(err);
422 Poll::Ready(Some(Err(io::Error::from_raw_os_error(SENTINEL_ERROR_CODE))))
423 }
424 }
425 }
426}
427
428pub(crate) const SENTINEL_ERROR_CODE: i32 = -837459418;
429
430#[non_exhaustive]
432#[derive(Default, Clone, Copy, Debug, Eq, PartialEq, Hash)]
433pub enum CompressionLevel {
434 Fastest,
436 Best,
438 #[default]
441 Default,
442 Precise(u32),
450}
451
452use async_compression::Level as AsyncCompressionLevel;
453use compression_core::Level as CompressionCoreLevel;
454
455impl CompressionLevel {
456 #[allow(dead_code)]
457 pub(crate) fn into_async_compression(self) -> AsyncCompressionLevel {
458 match self {
459 Self::Fastest => AsyncCompressionLevel::Fastest,
460 Self::Best => AsyncCompressionLevel::Best,
461 Self::Default => AsyncCompressionLevel::Default,
462 Self::Precise(quality) => {
463 AsyncCompressionLevel::Precise(quality.try_into().unwrap_or(i32::MAX))
464 }
465 }
466 }
467}
468
469impl CompressionLevel {
470 #[allow(dead_code)]
471 pub(crate) fn into_compression_core(self) -> CompressionCoreLevel {
472 match self {
473 Self::Fastest => CompressionCoreLevel::Fastest,
474 Self::Best => CompressionCoreLevel::Best,
475 Self::Default => CompressionCoreLevel::Default,
476 Self::Precise(quality) => {
477 CompressionCoreLevel::Precise(quality.try_into().unwrap_or(i32::MAX))
478 }
479 }
480 }
481}