better_fetch/backend/
recording.rs1use std::sync::atomic::{AtomicU32, Ordering};
4use std::sync::{Arc, Mutex};
5
6use async_trait::async_trait;
7use bytes::Bytes;
8use http::Method;
9
10use super::{HttpBackend, HttpBody, HttpRequest, HttpResponse, HttpStreamingResponse};
11use crate::Result;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum RecordedBodyKind {
16 Empty,
18 Bytes(Bytes),
20 Stream,
22}
23
24#[derive(Debug, Clone)]
26pub struct RecordedRequest {
27 pub method: Method,
29 pub url: url::Url,
31 pub body: RecordedBodyKind,
33}
34
35#[derive(Clone)]
37pub struct RecordingBackend {
38 inner: Arc<dyn HttpBackend>,
39 state: Arc<RecordingState>,
40}
41
42#[derive(Default)]
43struct RecordingState {
44 last_recorded: Mutex<Option<RecordedRequest>>,
45 execute_count: AtomicU32,
46 execute_stream_count: AtomicU32,
47}
48
49fn snapshot_request(request: &HttpRequest) -> RecordedRequest {
50 let body = match &request.body {
51 HttpBody::Empty => RecordedBodyKind::Empty,
52 HttpBody::Bytes(bytes) => RecordedBodyKind::Bytes(bytes.clone()),
53 HttpBody::Stream(_) => RecordedBodyKind::Stream,
54 };
55 RecordedRequest {
56 method: request.method.clone(),
57 url: request.url.clone(),
58 body,
59 }
60}
61
62impl RecordingBackend {
63 pub fn new(inner: Arc<dyn HttpBackend>) -> Self {
65 Self {
66 inner,
67 state: Arc::new(RecordingState::default()),
68 }
69 }
70
71 pub fn last_recorded(&self) -> Option<RecordedRequest> {
73 self.state.last_recorded.lock().ok()?.clone()
74 }
75
76 pub fn last_request(&self) -> Option<HttpRequest> {
81 self.last_recorded().map(|recorded| HttpRequest {
82 method: recorded.method,
83 url: recorded.url,
84 body: match recorded.body {
85 RecordedBodyKind::Empty => HttpBody::Empty,
86 RecordedBodyKind::Bytes(bytes) => HttpBody::Bytes(bytes),
87 RecordedBodyKind::Stream => HttpBody::Empty,
88 },
89 headers: Default::default(),
90 timeout: None,
91 cancellation: None,
92 #[cfg(feature = "multipart")]
93 multipart: None,
94 })
95 }
96
97 pub fn take_last_recorded(&self) -> Option<RecordedRequest> {
99 self.state.last_recorded.lock().ok()?.take()
100 }
101
102 pub fn execute_count(&self) -> u32 {
104 self.state.execute_count.load(Ordering::SeqCst)
105 }
106
107 pub fn execute_stream_count(&self) -> u32 {
109 self.state.execute_stream_count.load(Ordering::SeqCst)
110 }
111
112 pub fn total_calls(&self) -> u32 {
114 self.execute_count() + self.execute_stream_count()
115 }
116
117 fn record(&self, request: &HttpRequest) {
118 if let Ok(mut slot) = self.state.last_recorded.lock() {
119 *slot = Some(snapshot_request(request));
120 }
121 }
122}
123
124#[async_trait]
125impl HttpBackend for RecordingBackend {
126 async fn execute(&self, request: HttpRequest) -> Result<HttpResponse> {
127 self.state.execute_count.fetch_add(1, Ordering::SeqCst);
128 self.record(&request);
129 self.inner.execute(request).await
130 }
131
132 async fn execute_stream(&self, request: HttpRequest) -> Result<HttpStreamingResponse> {
133 self.state
134 .execute_stream_count
135 .fetch_add(1, Ordering::SeqCst);
136 self.record(&request);
137 self.inner.execute_stream(request).await
138 }
139}