Skip to main content

rama_http/layer/json_rewrite/
body.rs

1//! Streaming request or response [`Body`](crate::Body)
2//! that rewrites JSON on the fly.
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::path::JsonPath;
12use rama_json::rewrite::{JsonRewriter, JsonValueHandler};
13use rama_json::tokenizer::DEFAULT_MAX_BUFFERED_BYTES;
14
15use crate::HeaderMap;
16use crate::body::{Frame, SizeHint, StreamingBody};
17
18/// Completion hook, handed the finalized handler once the rewrite ends.
19/// `Send + Sync` so the body keeps satisfying [`Body::new`](crate::Body::new).
20type OnEnd<H> = Box<dyn FnOnce(H) + Send + Sync>;
21
22pin_project! {
23    /// A body that feeds the inner body's bytes through a
24    /// [`JsonRewriter`], emitting rewritten chunks as they become available.
25    ///
26    /// Build it directly with [`new`](Self::new) (rewriting) or
27    /// [`passthrough`](Self::passthrough) (forward unchanged), or let
28    /// [`JsonRewriteLayer`](super::JsonRewriteLayer) and
29    /// [`JsonRequestRewriteLayer`](super::JsonRequestRewriteLayer) construct
30    /// one per body. Attach [`on_end`](Self::on_end) to recover the handler
31    /// (and any state it accumulated) once the rewrite finishes.
32    pub struct JsonRewriteBody<B, H> {
33        #[pin]
34        inner: B,
35        // `None` => passthrough; `Some` => actively rewriting.
36        rewriter: Option<JsonRewriter<H>>,
37        // Fired once after `end()` on clean termination; `None` => no hook.
38        on_end: Option<OnEnd<H>>,
39        pending_trailers: Option<HeaderMap>,
40        // Set once the inner body has ended and the rewriter is flushed.
41        done: bool,
42    }
43}
44
45impl<B, H> JsonRewriteBody<B, H>
46where
47    H: JsonValueHandler,
48{
49    /// Wraps `inner`, rewriting values matching `selectors` with `handler`.
50    pub fn new(inner: B, selectors: impl IntoIterator<Item = JsonPath>, handler: H) -> Self {
51        Self::with_max_buffered_bytes(inner, selectors, handler, DEFAULT_MAX_BUFFERED_BYTES)
52    }
53
54    /// Wraps `inner` with a custom tokenizer buffered-input limit.
55    pub fn with_max_buffered_bytes(
56        inner: B,
57        selectors: impl IntoIterator<Item = JsonPath>,
58        handler: H,
59        max_buffered_bytes: usize,
60    ) -> Self {
61        Self {
62            inner,
63            rewriter: Some(JsonRewriter::with_max_buffered_bytes(
64                selectors,
65                handler,
66                max_buffered_bytes,
67            )),
68            on_end: None,
69            pending_trailers: None,
70            done: false,
71        }
72    }
73}
74
75impl<B, H> JsonRewriteBody<B, H> {
76    /// Wraps `inner` without rewriting - frames pass through unchanged (their
77    /// data type normalized to [`Bytes`]).
78    ///
79    /// Lets a layer keep one body type for responses it must not rewrite
80    /// (e.g. a non-JSON content type).
81    pub fn passthrough(inner: B) -> Self {
82        Self {
83            inner,
84            rewriter: None,
85            on_end: None,
86            pending_trailers: None,
87            done: false,
88        }
89    }
90
91    /// Installs a completion hook, handed the finalized handler by value
92    /// after the rewrite ends - for reading state it accumulated.
93    ///
94    /// Fires once after [`JsonRewriter::end`] on clean termination (inner EOF
95    /// or trailers); not on the error path, nor in
96    /// [`passthrough`](Self::passthrough) mode (no handler). A later call
97    /// replaces an earlier hook.
98    #[must_use]
99    pub fn on_end<F>(mut self, on_end: F) -> Self
100    where
101        F: FnOnce(H) + Send + Sync + 'static,
102    {
103        self.on_end = Some(Box::new(on_end));
104        self
105    }
106}
107
108/// Hands the spent rewriter's handler to the hook, if one is installed.
109fn fire_on_end<H: JsonValueHandler>(
110    rewriter: &mut Option<JsonRewriter<H>>,
111    on_end: &mut Option<OnEnd<H>>,
112) {
113    if let (Some(rewriter), Some(on_end)) = (rewriter.take(), on_end.take()) {
114        on_end(rewriter.into_handler());
115    }
116}
117
118impl<B, H> StreamingBody for JsonRewriteBody<B, H>
119where
120    B: StreamingBody<Error: Into<BoxError>>,
121    H: JsonValueHandler,
122{
123    type Data = Bytes;
124    type Error = BoxError;
125
126    fn poll_frame(
127        self: Pin<&mut Self>,
128        cx: &mut Context<'_>,
129    ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
130        let mut this = self.project();
131
132        if let Some(trailers) = this.pending_trailers.take() {
133            *this.done = true;
134            return Poll::Ready(Some(Ok(Frame::trailers(trailers))));
135        }
136
137        if *this.done {
138            return Poll::Ready(None);
139        }
140
141        let Some(rewriter) = this.rewriter.as_mut() else {
142            // Passthrough: forward frames, normalizing the data type to `Bytes`.
143            return match ready!(this.inner.as_mut().poll_frame(cx)) {
144                Some(Ok(frame)) => Poll::Ready(Some(Ok(normalize_frame(frame)))),
145                Some(Err(err)) => Poll::Ready(Some(Err(err.into()))),
146                None => {
147                    *this.done = true;
148                    Poll::Ready(None)
149                }
150            };
151        };
152
153        loop {
154            match ready!(this.inner.as_mut().poll_frame(cx)) {
155                Some(Ok(frame)) => match frame.into_data() {
156                    Ok(mut data) => {
157                        // Feed the rewriter straight from the buffer's chunks:
158                        // the tokenizer copies what it needs into its own
159                        // buffer, so there is no intermediate `Bytes` copy.
160                        while data.has_remaining() {
161                            let chunk = data.chunk();
162                            let len = chunk.len();
163                            if let Err(err) = rewriter.write(chunk) {
164                                return Poll::Ready(Some(Err(err.into())));
165                            }
166                            data.advance(len);
167                        }
168                        let out = rewriter.take_output();
169                        if !out.is_empty() {
170                            return Poll::Ready(Some(Ok(Frame::data(Bytes::from(out)))));
171                        }
172                        // The rewriter buffered an incomplete token; keep
173                        // polling for more input.
174                    }
175                    // A trailers frame terminates the body. Flush the
176                    // rewriter first so data never appears after trailers.
177                    Err(frame) => {
178                        if let Ok(trailers) = frame.into_trailers() {
179                            if let Err(err) = rewriter.end() {
180                                return Poll::Ready(Some(Err(err.into())));
181                            }
182                            let out = rewriter.take_output();
183                            fire_on_end(this.rewriter, this.on_end);
184                            if out.is_empty() {
185                                *this.done = true;
186                                return Poll::Ready(Some(Ok(Frame::trailers(trailers))));
187                            }
188                            *this.pending_trailers = Some(trailers);
189                            return Poll::Ready(Some(Ok(Frame::data(Bytes::from(out)))));
190                        }
191                    }
192                },
193                Some(Err(err)) => return Poll::Ready(Some(Err(err.into()))),
194                None => {
195                    *this.done = true;
196                    if let Err(err) = rewriter.end() {
197                        return Poll::Ready(Some(Err(err.into())));
198                    }
199                    let out = rewriter.take_output();
200                    fire_on_end(this.rewriter, this.on_end);
201                    return if out.is_empty() {
202                        Poll::Ready(None)
203                    } else {
204                        Poll::Ready(Some(Ok(Frame::data(Bytes::from(out)))))
205                    };
206                }
207            }
208        }
209    }
210
211    fn size_hint(&self) -> SizeHint {
212        if self.rewriter.is_some() {
213            // Rewriting changes the body length unpredictably.
214            SizeHint::default()
215        } else {
216            self.inner.size_hint()
217        }
218    }
219}
220
221/// Normalizes a frame's data type to [`Bytes`], preserving trailers.
222fn normalize_frame<D: Buf>(frame: Frame<D>) -> Frame<Bytes> {
223    match frame.into_data() {
224        Ok(mut data) => Frame::data(data.copy_to_bytes(data.remaining())),
225        Err(frame) => match frame.into_trailers() {
226            Ok(trailers) => Frame::trailers(trailers),
227            // `Frame` is data-or-trailers, so this is unreachable; emit an
228            // empty data frame rather than panic.
229            Err(_) => Frame::data(Bytes::new()),
230        },
231    }
232}