1use std::{fmt, future::Future};
8
9use rama_core::{Layer, Service, bytes::Bytes, error::BoxError};
10use rama_http_types::{
11 Body, BodyCaptureSink, CaptureBody, Request, Response, StreamingBody, body::util::BodyExt as _,
12 request, response,
13};
14use rama_utils::macros::define_inner_service_accessors;
15
16#[derive(Clone)]
18pub enum CapturedHead {
19 Request(request::Parts),
21 Response(response::Parts),
23}
24
25impl fmt::Debug for CapturedHead {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 match self {
28 Self::Request(parts) => f.debug_tuple("Request").field(parts).finish(),
29 Self::Response(parts) => f.debug_tuple("Response").field(parts).finish(),
30 }
31 }
32}
33
34pub trait MakeBodyCaptureSink: Send + Sync + 'static {
39 type Sink: BodyCaptureSink;
41
42 fn make_sink(&self, head: CapturedHead)
44 -> impl Future<Output = Option<Self::Sink>> + Send + '_;
45}
46
47impl<F, Fut, S> MakeBodyCaptureSink for F
48where
49 F: Fn(CapturedHead) -> Fut + Send + Sync + 'static,
50 Fut: Future<Output = Option<S>> + Send + 'static,
51 S: BodyCaptureSink,
52{
53 type Sink = S;
54
55 fn make_sink(
56 &self,
57 head: CapturedHead,
58 ) -> impl Future<Output = Option<Self::Sink>> + Send + '_ {
59 self(head)
60 }
61}
62
63#[derive(Clone)]
66pub struct BodyCaptureLayer<M> {
67 make_sink: M,
68 capture_request: bool,
69 capture_response: bool,
70}
71
72impl<M> BodyCaptureLayer<M> {
73 #[must_use]
75 pub const fn request(make_sink: M) -> Self {
76 Self {
77 make_sink,
78 capture_request: true,
79 capture_response: false,
80 }
81 }
82
83 #[must_use]
85 pub const fn response(make_sink: M) -> Self {
86 Self {
87 make_sink,
88 capture_request: false,
89 capture_response: true,
90 }
91 }
92
93 #[must_use]
95 pub const fn bidirectional(make_sink: M) -> Self {
96 Self {
97 make_sink,
98 capture_request: true,
99 capture_response: true,
100 }
101 }
102
103 #[must_use]
105 pub const fn make_sink(&self) -> &M {
106 &self.make_sink
107 }
108
109 #[must_use]
111 pub const fn captures_requests(&self) -> bool {
112 self.capture_request
113 }
114
115 #[must_use]
117 pub const fn captures_responses(&self) -> bool {
118 self.capture_response
119 }
120}
121
122impl<M: fmt::Debug> fmt::Debug for BodyCaptureLayer<M> {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 f.debug_struct("BodyCaptureLayer")
125 .field("make_sink", &self.make_sink)
126 .field("capture_request", &self.capture_request)
127 .field("capture_response", &self.capture_response)
128 .finish()
129 }
130}
131
132impl<M, S> Layer<S> for BodyCaptureLayer<M>
133where
134 M: Clone,
135{
136 type Service = BodyCaptureService<M, S>;
137
138 fn layer(&self, inner: S) -> Self::Service {
139 BodyCaptureService {
140 inner,
141 make_sink: self.make_sink.clone(),
142 capture_request: self.capture_request,
143 capture_response: self.capture_response,
144 }
145 }
146
147 fn into_layer(self, inner: S) -> Self::Service {
148 BodyCaptureService {
149 inner,
150 make_sink: self.make_sink,
151 capture_request: self.capture_request,
152 capture_response: self.capture_response,
153 }
154 }
155}
156
157#[derive(Clone)]
159pub struct BodyCaptureService<M, S> {
160 inner: S,
161 make_sink: M,
162 capture_request: bool,
163 capture_response: bool,
164}
165
166impl<M, S> BodyCaptureService<M, S> {
167 #[must_use]
169 pub const fn bidirectional(inner: S, make_sink: M) -> Self {
170 Self {
171 inner,
172 make_sink,
173 capture_request: true,
174 capture_response: true,
175 }
176 }
177
178 define_inner_service_accessors!();
179}
180
181impl<M: fmt::Debug, S: fmt::Debug> fmt::Debug for BodyCaptureService<M, S> {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 f.debug_struct("BodyCaptureService")
184 .field("inner", &self.inner)
185 .field("make_sink", &self.make_sink)
186 .field("capture_request", &self.capture_request)
187 .field("capture_response", &self.capture_response)
188 .finish()
189 }
190}
191
192impl<M, S, ReqBody, ResBody> Service<Request<ReqBody>> for BodyCaptureService<M, S>
193where
194 M: MakeBodyCaptureSink,
195 S: Service<Request<Body>, Output = Response<ResBody>>,
196 ReqBody: StreamingBody<Data = Bytes, Error: Into<BoxError>> + Send + Sync + 'static,
197 ResBody: StreamingBody<Data = Bytes, Error: Into<BoxError>> + Send + Sync + 'static,
198{
199 type Output = Response<Body>;
200 type Error = S::Error;
201
202 async fn serve(&self, request: Request<ReqBody>) -> Result<Self::Output, Self::Error> {
203 let request = if self.capture_request {
204 let (parts, body) = request.into_parts();
205 let body = match self
206 .make_sink
207 .make_sink(CapturedHead::Request(parts.clone()))
208 .await
209 {
210 Some(sink) => Body::new(CaptureBody::new(body.map_err(Into::into), sink)),
211 None => Body::new(body),
212 };
213 Request::from_parts(parts, body)
214 } else {
215 request.map(Body::new)
216 };
217
218 let response = self.inner.serve(request).await?;
219 Ok(if self.capture_response {
220 let (parts, body) = response.into_parts();
221 let body = match self
222 .make_sink
223 .make_sink(CapturedHead::Response(parts.clone()))
224 .await
225 {
226 Some(sink) => Body::new(CaptureBody::new(body.map_err(Into::into), sink)),
227 None => Body::new(body),
228 };
229 Response::from_parts(parts, body)
230 } else {
231 response.map(Body::new)
232 })
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use std::{
239 convert::Infallible,
240 sync::{
241 Arc,
242 atomic::{AtomicUsize, Ordering},
243 },
244 };
245
246 use rama_core::{
247 Layer as _, Service as _, bytes::Bytes, futures::StreamExt as _, service::service_fn,
248 };
249 use rama_http_types::{
250 Body, BodyCaptureEvent, CaptureOutcome, Request, Response, StatusCode, body::Frame,
251 body::util::BodyExt as _,
252 };
253
254 use super::{BodyCaptureLayer, BodyCaptureService, CapturedHead};
255
256 type Capture = (
257 CapturedHead,
258 tokio::sync::mpsc::UnboundedReceiver<BodyCaptureEvent>,
259 );
260
261 async fn recv<T>(receiver: &mut tokio::sync::mpsc::UnboundedReceiver<T>) -> T {
262 tokio::time::timeout(std::time::Duration::from_secs(1), receiver.recv())
263 .await
264 .expect("capture should arrive promptly")
265 .expect("capture channel should remain open")
266 }
267
268 #[tokio::test]
269 async fn bidirectional_capture_streams_both_messages() {
270 let request_polls = Arc::new(AtomicUsize::new(0));
271 let polled = Arc::clone(&request_polls);
272 let request_body = Body::from_stream(
273 rama_core::futures::stream::iter([
274 Ok::<_, Infallible>(Bytes::from_static(b"request ")),
275 Ok(Bytes::from_static(b"body")),
276 ])
277 .inspect(move |_| {
278 polled.fetch_add(1, Ordering::Relaxed);
279 }),
280 );
281
282 let (captures, mut captured) = tokio::sync::mpsc::unbounded_channel::<Capture>();
283 let make_sink = move |head| {
284 let captures = captures.clone();
285 async move {
286 let (events, receiver) = tokio::sync::mpsc::unbounded_channel();
287 captures.send((head, receiver)).unwrap();
288 Some(events)
289 }
290 };
291 let layer = BodyCaptureLayer::bidirectional(make_sink);
292 let service_polls = Arc::clone(&request_polls);
293 let service = layer.into_layer(service_fn(move |request: Request<Body>| {
294 let request_polls = Arc::clone(&service_polls);
295 async move {
296 assert_eq!(request_polls.load(Ordering::Relaxed), 0);
297 assert_eq!(
298 request.into_body().collect().await.unwrap().to_bytes(),
299 Bytes::from_static(b"request body")
300 );
301 Ok::<_, Infallible>(
302 Response::builder()
303 .status(StatusCode::CREATED)
304 .body(Body::from("response body"))
305 .unwrap(),
306 )
307 }
308 }));
309
310 let response = service
311 .serve(
312 Request::builder()
313 .method("POST")
314 .uri("https://example.com/")
315 .body(request_body)
316 .unwrap(),
317 )
318 .await
319 .unwrap();
320
321 let (head, mut request_events) = recv(&mut captured).await;
322 assert!(matches!(head, CapturedHead::Request(ref parts) if parts.method == "POST"));
323 let mut request_bytes = Vec::new();
324 loop {
325 let event = recv(&mut request_events).await;
326 match event {
327 BodyCaptureEvent::Frame(frame) => {
328 if let Ok(data) = frame.into_data() {
329 request_bytes.extend_from_slice(&data);
330 }
331 }
332 BodyCaptureEvent::End(outcome) => {
333 assert_eq!(outcome, CaptureOutcome::Complete);
334 break;
335 }
336 }
337 }
338 assert_eq!(request_bytes, b"request body");
339
340 let (head, mut response_events) = recv(&mut captured).await;
341 assert!(
342 matches!(head, CapturedHead::Response(ref parts) if parts.status == StatusCode::CREATED)
343 );
344 response_events.try_recv().unwrap_err();
345
346 assert_eq!(
347 response.into_body().collect().await.unwrap().to_bytes(),
348 Bytes::from_static(b"response body")
349 );
350 let BodyCaptureEvent::Frame(frame) = recv(&mut response_events).await else {
351 panic!("expected a captured response frame");
352 };
353 assert_eq!(
354 frame.into_data().unwrap(),
355 Bytes::from_static(b"response body")
356 );
357 assert!(matches!(
358 recv(&mut response_events).await,
359 BodyCaptureEvent::End(CaptureOutcome::Complete)
360 ));
361 }
362
363 #[tokio::test]
364 async fn factory_can_skip_capture_from_the_head() {
365 let make_sink = |head| async move {
366 match head {
367 CapturedHead::Request(parts) if parts.method == "GET" => None,
368 _ => Some(|_event: BodyCaptureEvent| async {}),
369 }
370 };
371 let service = BodyCaptureLayer::request(make_sink).into_layer(service_fn(
372 |request: Request<Body>| async move {
373 request.into_body().collect().await.unwrap();
374 Ok::<_, Infallible>(Response::new(Body::empty()))
375 },
376 ));
377
378 service
379 .serve(
380 Request::builder()
381 .method("GET")
382 .body(Body::empty())
383 .unwrap(),
384 )
385 .await
386 .unwrap();
387 }
388
389 #[test]
390 fn frame_is_cloneable_when_its_data_is_cloneable() {
391 let frame = Frame::data(Bytes::from_static(b"shared"));
392 assert_eq!(
393 frame.clone().into_data().unwrap(),
394 frame.into_data().unwrap()
395 );
396 }
397
398 #[test]
399 fn constructors_and_debug_report_capture_directions() {
400 let request = BodyCaptureLayer::request("factory");
401 assert_eq!(request.make_sink(), &"factory");
402 assert!(request.captures_requests());
403 assert!(!request.captures_responses());
404 assert!(format!("{request:?}").contains("BodyCaptureLayer"));
405
406 let response = BodyCaptureLayer::response("factory");
407 assert!(!response.captures_requests());
408 assert!(response.captures_responses());
409
410 let both = BodyCaptureLayer::bidirectional("factory");
411 assert!(both.captures_requests());
412 assert!(both.captures_responses());
413
414 let service = BodyCaptureService::bidirectional("inner", "factory");
415 assert!(format!("{service:?}").contains("BodyCaptureService"));
416
417 let (request_parts, ()) = Request::builder()
418 .method("POST")
419 .body(())
420 .unwrap()
421 .into_parts();
422 let request_head = format!("{:?}", CapturedHead::Request(request_parts));
423 assert!(request_head.starts_with("Request("));
424 assert!(request_head.contains("POST"));
425
426 let (response_parts, ()) = Response::builder()
427 .status(StatusCode::CREATED)
428 .body(())
429 .unwrap()
430 .into_parts();
431 let response_head = format!("{:?}", CapturedHead::Response(response_parts));
432 assert!(response_head.starts_with("Response("));
433 assert!(response_head.contains("201"));
434 }
435}