use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use bytes::Bytes;
use http::Method;
use super::{HttpBackend, HttpBody, HttpRequest, HttpResponse, HttpStreamingResponse};
use crate::Result;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RecordedBodyKind {
Empty,
Bytes(Bytes),
Stream,
}
#[derive(Debug, Clone)]
pub struct RecordedRequest {
pub method: Method,
pub url: url::Url,
pub body: RecordedBodyKind,
}
#[derive(Clone)]
pub struct RecordingBackend {
inner: Arc<dyn HttpBackend>,
state: Arc<RecordingState>,
}
#[derive(Default)]
struct RecordingState {
last_recorded: Mutex<Option<RecordedRequest>>,
execute_count: AtomicU32,
execute_stream_count: AtomicU32,
}
fn snapshot_request(request: &HttpRequest) -> RecordedRequest {
let body = match &request.body {
HttpBody::Empty => RecordedBodyKind::Empty,
HttpBody::Bytes(bytes) => RecordedBodyKind::Bytes(bytes.clone()),
HttpBody::Stream(_) => RecordedBodyKind::Stream,
};
RecordedRequest {
method: request.method.clone(),
url: request.url.clone(),
body,
}
}
impl RecordingBackend {
pub fn new(inner: Arc<dyn HttpBackend>) -> Self {
Self {
inner,
state: Arc::new(RecordingState::default()),
}
}
pub fn last_recorded(&self) -> Option<RecordedRequest> {
self.state.last_recorded.lock().ok()?.clone()
}
pub fn last_request(&self) -> Option<HttpRequest> {
self.last_recorded().map(|recorded| HttpRequest {
method: recorded.method,
url: recorded.url,
body: match recorded.body {
RecordedBodyKind::Empty => HttpBody::Empty,
RecordedBodyKind::Bytes(bytes) => HttpBody::Bytes(bytes),
RecordedBodyKind::Stream => HttpBody::Empty,
},
headers: Default::default(),
timeout: None,
cancellation: None,
#[cfg(feature = "multipart")]
multipart: None,
})
}
pub fn take_last_recorded(&self) -> Option<RecordedRequest> {
self.state.last_recorded.lock().ok()?.take()
}
pub fn execute_count(&self) -> u32 {
self.state.execute_count.load(Ordering::SeqCst)
}
pub fn execute_stream_count(&self) -> u32 {
self.state.execute_stream_count.load(Ordering::SeqCst)
}
pub fn total_calls(&self) -> u32 {
self.execute_count() + self.execute_stream_count()
}
fn record(&self, request: &HttpRequest) {
if let Ok(mut slot) = self.state.last_recorded.lock() {
*slot = Some(snapshot_request(request));
}
}
}
#[async_trait]
impl HttpBackend for RecordingBackend {
async fn execute(&self, request: HttpRequest) -> Result<HttpResponse> {
self.state.execute_count.fetch_add(1, Ordering::SeqCst);
self.record(&request);
self.inner.execute(request).await
}
async fn execute_stream(&self, request: HttpRequest) -> Result<HttpStreamingResponse> {
self.state
.execute_stream_count
.fetch_add(1, Ordering::SeqCst);
self.record(&request);
self.inner.execute_stream(request).await
}
}