Skip to main content

rama_http/layer/json_capture/
body.rs

1//! Streaming request or response [`Body`](crate::Body)
2//! that captures selected JSON values while forwarding frames.
3
4use std::pin::Pin;
5use std::task::{Context, Poll};
6
7use pin_project_lite::pin_project;
8use rama_core::bytes::{Buf, Bytes};
9use rama_core::error::BoxError;
10use rama_core::futures::ready;
11use rama_json::capture::{CaptureHandler, JsonCapturer};
12use rama_json::path::JsonPath;
13use rama_json::tokenizer::DEFAULT_MAX_BUFFERED_BYTES;
14
15use crate::body::{Frame, SizeHint, StreamingBody};
16
17/// Completion hook, handed the finalized handler once capture ends.
18/// `Send + Sync` so the body keeps satisfying [`Body::new`](crate::Body::new).
19type OnEnd<H> = Box<dyn FnOnce(H) + Send + Sync>;
20
21pin_project! {
22    /// A body that feeds the inner body's bytes through a
23    /// [`JsonCapturer`], forwarding the body unchanged as [`Bytes`].
24    ///
25    /// Build it directly with [`new`](Self::new). Attach
26    /// [`on_end`](Self::on_end) to recover the handler and any state it
27    /// accumulated once the body finishes cleanly.
28    pub struct JsonCaptureBody<B, H> {
29        #[pin]
30        inner: B,
31        // `None` => passthrough; `Some` => actively capturing.
32        capturer: Option<JsonCapturer<H>>,
33        // Fired once after `end()` on clean termination; `None` => no hook.
34        on_end: Option<OnEnd<H>>,
35        done: bool,
36    }
37}
38
39impl<B, H> JsonCaptureBody<B, H>
40where
41    H: CaptureHandler,
42{
43    /// Wraps `inner`, capturing values matching `selectors` with `handler`.
44    pub fn new(
45        inner: B,
46        selectors: impl IntoIterator<Item = JsonPath>,
47        max_capture_bytes: usize,
48        handler: H,
49    ) -> Self {
50        Self::with_max_buffered_bytes(
51            inner,
52            selectors,
53            max_capture_bytes,
54            DEFAULT_MAX_BUFFERED_BYTES,
55            handler,
56        )
57    }
58
59    /// Wraps `inner` with a custom tokenizer buffered-input limit.
60    pub fn with_max_buffered_bytes(
61        inner: B,
62        selectors: impl IntoIterator<Item = JsonPath>,
63        max_capture_bytes: usize,
64        max_buffered_bytes: usize,
65        handler: H,
66    ) -> Self {
67        Self {
68            inner,
69            capturer: Some(JsonCapturer::with_max_buffered_bytes(
70                selectors,
71                max_capture_bytes,
72                max_buffered_bytes,
73                handler,
74            )),
75            on_end: None,
76            done: false,
77        }
78    }
79}
80
81impl<B, H> JsonCaptureBody<B, H> {
82    /// Wraps `inner` without capturing - frames pass through unchanged (their
83    /// data type normalized to [`Bytes`]).
84    pub fn passthrough(inner: B) -> Self {
85        Self {
86            inner,
87            capturer: None,
88            on_end: None,
89            done: false,
90        }
91    }
92
93    /// Installs a completion hook, handed the finalized handler by value
94    /// after capture ends - for reading state it accumulated.
95    ///
96    /// Fires once after [`JsonCapturer::end`] on clean termination (inner EOF
97    /// or trailers); not on the error path, nor in
98    /// [`passthrough`](Self::passthrough) mode (no handler). A later call
99    /// replaces an earlier hook.
100    #[must_use]
101    pub fn on_end<F>(mut self, on_end: F) -> Self
102    where
103        F: FnOnce(H) + Send + Sync + 'static,
104    {
105        self.on_end = Some(Box::new(on_end));
106        self
107    }
108}
109
110/// Hands the spent capturer's handler to the hook, if one is installed.
111fn fire_on_end<H: CaptureHandler>(
112    capturer: &mut Option<JsonCapturer<H>>,
113    on_end: &mut Option<OnEnd<H>>,
114) {
115    if let (Some(capturer), Some(on_end)) = (capturer.take(), on_end.take()) {
116        on_end(capturer.into_handler());
117    }
118}
119
120impl<B, H> StreamingBody for JsonCaptureBody<B, H>
121where
122    B: StreamingBody<Error: Into<BoxError>>,
123    H: CaptureHandler,
124{
125    type Data = Bytes;
126    type Error = BoxError;
127
128    fn poll_frame(
129        self: Pin<&mut Self>,
130        cx: &mut Context<'_>,
131    ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
132        let mut this = self.project();
133
134        if *this.done {
135            return Poll::Ready(None);
136        }
137
138        let Some(capturer) = this.capturer.as_mut() else {
139            return match ready!(this.inner.as_mut().poll_frame(cx)) {
140                Some(Ok(frame)) => Poll::Ready(Some(Ok(normalize_frame(frame)))),
141                Some(Err(err)) => Poll::Ready(Some(Err(err.into()))),
142                None => {
143                    *this.done = true;
144                    Poll::Ready(None)
145                }
146            };
147        };
148
149        match ready!(this.inner.as_mut().poll_frame(cx)) {
150            Some(Ok(frame)) => match frame.into_data() {
151                Ok(mut data) => {
152                    let bytes = data.copy_to_bytes(data.remaining());
153                    if let Err(err) = capturer.write(&bytes) {
154                        return Poll::Ready(Some(Err(err.into())));
155                    }
156                    Poll::Ready(Some(Ok(Frame::data(bytes))))
157                }
158                Err(frame) => match frame.into_trailers() {
159                    Ok(trailers) => {
160                        if let Err(err) = capturer.end() {
161                            return Poll::Ready(Some(Err(err.into())));
162                        }
163                        fire_on_end(this.capturer, this.on_end);
164                        *this.done = true;
165                        Poll::Ready(Some(Ok(Frame::trailers(trailers))))
166                    }
167                    Err(_) => Poll::Ready(Some(Ok(Frame::data(Bytes::new())))),
168                },
169            },
170            Some(Err(err)) => Poll::Ready(Some(Err(err.into()))),
171            None => {
172                *this.done = true;
173                if let Err(err) = capturer.end() {
174                    return Poll::Ready(Some(Err(err.into())));
175                }
176                fire_on_end(this.capturer, this.on_end);
177                Poll::Ready(None)
178            }
179        }
180    }
181
182    fn size_hint(&self) -> SizeHint {
183        self.inner.size_hint()
184    }
185}
186
187/// Normalizes a frame's data type to [`Bytes`], preserving trailers.
188fn normalize_frame<D: Buf>(frame: Frame<D>) -> Frame<Bytes> {
189    match frame.into_data() {
190        Ok(mut data) => Frame::data(data.copy_to_bytes(data.remaining())),
191        Err(frame) => match frame.into_trailers() {
192            Ok(trailers) => Frame::trailers(trailers),
193            Err(_) => Frame::data(Bytes::new()),
194        },
195    }
196}