use std::collections::VecDeque;
use std::sync::{Arc, Mutex, MutexGuard};
use crate::client::transport::{Method, Request, Response, Transport, TransportError};
#[derive(Clone, PartialEq, Eq)]
pub struct Recorded {
pub method: Method,
pub path_and_query: String,
pub if_match: Option<String>,
pub if_none_match: Option<String>,
pub authorization: Option<String>,
pub body: Option<Vec<u8>>,
}
impl std::fmt::Debug for Recorded {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Recorded")
.field("method", &self.method)
.field("path_and_query", &self.path_and_query)
.field("if_match", &self.if_match)
.field("if_none_match", &self.if_none_match)
.field("authorized", &self.authorization.is_some())
.field("body_len", &self.body.as_ref().map(Vec::len))
.finish()
}
}
impl From<&Request<'_>> for Recorded {
fn from(r: &Request<'_>) -> Recorded {
Recorded {
method: r.method,
path_and_query: r.path_and_query.to_owned(),
if_match: r.if_match.map(str::to_owned),
if_none_match: r.if_none_match.map(str::to_owned),
authorization: r.authorization.map(str::to_owned),
body: r.body.map(<[u8]>::to_vec),
}
}
}
type Responder = dyn Fn(&Recorded) -> Result<Response, TransportError> + Send + Sync;
enum Source {
Canned,
Closure(Box<Responder>),
Forward(Arc<dyn Transport>),
}
pub struct RecordingTransport {
log: Mutex<Vec<Recorded>>,
canned: Mutex<VecDeque<Result<Response, TransportError>>>,
source: Source,
}
impl std::fmt::Debug for RecordingTransport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RecordingTransport")
.field("recorded", &lock(&self.log).len())
.finish_non_exhaustive()
}
}
fn lock<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {
m.lock().unwrap_or_else(|p| p.into_inner())
}
impl Default for RecordingTransport {
fn default() -> RecordingTransport {
RecordingTransport::new()
}
}
impl RecordingTransport {
pub fn new() -> RecordingTransport {
RecordingTransport {
log: Mutex::new(Vec::new()),
canned: Mutex::new(VecDeque::new()),
source: Source::Canned,
}
}
pub fn responding(
respond: impl Fn(&Recorded) -> Result<Response, TransportError> + Send + Sync + 'static,
) -> RecordingTransport {
RecordingTransport {
source: Source::Closure(Box::new(respond)),
..RecordingTransport::new()
}
}
pub fn forwarding(inner: Arc<dyn Transport>) -> RecordingTransport {
RecordingTransport {
source: Source::Forward(inner),
..RecordingTransport::new()
}
}
pub fn replay(&self, response: Response) {
lock(&self.canned).push_back(Ok(response));
}
pub fn fail_next(&self, error: TransportError) {
lock(&self.canned).push_back(Err(error));
}
pub fn requests(&self) -> Vec<Recorded> {
lock(&self.log).clone()
}
pub fn clear(&self) {
lock(&self.log).clear();
}
}
impl Transport for RecordingTransport {
fn send(&self, request: Request<'_>) -> Result<Response, TransportError> {
let recorded = Recorded::from(&request);
lock(&self.log).push(recorded.clone());
match &self.source {
Source::Canned => lock(&self.canned).pop_front().unwrap_or_else(|| {
Ok(Response::new(
404,
br#"{"error":"not_found","message":"nothing recorded to replay"}"#.to_vec(),
))
}),
Source::Closure(f) => f(&recorded),
Source::Forward(inner) => inner.send(request),
}
}
}