use std::{
io::ErrorKind,
path::{Path, PathBuf},
sync::{Arc, Mutex},
};
use bytes::Bytes;
use crate::{
When,
api::server::MockServer,
common::{
data::RecordingRuleConfig,
util::{Join, lock_mutex, take_from_mutex, update_mutex, write_file},
},
};
pub struct Recording<'a> {
pub id: usize,
pub(crate) server: &'a MockServer,
}
impl<'a> Recording<'a> {
pub fn new(id: usize, server: &'a MockServer) -> Self {
Self { id, server }
}
pub fn delete(&mut self) {
self.delete_async().join();
}
pub async fn delete_async(&self) {
self.server
.server_adapter
.as_ref()
.unwrap()
.delete_recording(self.id)
.await
.expect("could not delete mock from server");
}
pub fn export(&self) -> Result<Option<Bytes>, Box<dyn std::error::Error>> {
self.export_async().join()
}
pub async fn export_async(&self) -> Result<Option<Bytes>, Box<dyn std::error::Error>> {
let rec = self
.server
.server_adapter
.as_ref()
.unwrap()
.export_recording(self.id)
.await?;
Ok(rec)
}
pub fn save_to<PathRef: AsRef<Path>, IntoString: Into<String>>(
&self,
dir: PathRef,
scenario_name: IntoString,
) -> Result<PathBuf, Box<dyn std::error::Error>> {
self.save_to_async(dir, scenario_name).join()
}
pub async fn save_to_async<PathRef: AsRef<Path>, IntoString: Into<String>>(
&self,
dir: PathRef,
scenario: IntoString,
) -> Result<PathBuf, Box<dyn std::error::Error>> {
let rec = self.export_async().await?;
let scenario = scenario.into();
let dir = dir.as_ref();
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs();
let bytes = rec.ok_or("No recording data available")?;
for n in 0.. {
let filename = match n {
0 => format!("{}_{}.yaml", scenario, timestamp),
n => format!("{}_{}_{}.yaml", scenario, timestamp, n),
};
match write_file(dir.join(filename), &bytes, true).await {
Err(e)
if e.downcast_ref::<std::io::Error>()
.is_some_and(|e| e.kind() == ErrorKind::AlreadyExists) =>
{
continue;
}
result => return result,
}
}
unreachable!()
}
pub fn save<IntoString: Into<String>>(
&self,
scenario_name: IntoString,
) -> Result<PathBuf, Box<dyn std::error::Error>> {
self.save_async(scenario_name).join()
}
pub async fn save_async<IntoString: Into<String>>(
&self,
scenario: IntoString,
) -> Result<PathBuf, Box<dyn std::error::Error>> {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("target")
.join("httpmock")
.join("recordings");
self.save_to_async(path, scenario).await
}
}
pub struct RecordingRuleBuilder {
pub(crate) config: Arc<Mutex<RecordingRuleConfig>>,
}
impl RecordingRuleBuilder {
pub fn record_request_header<IntoString: Into<String>>(self, header: IntoString) -> Self {
update_mutex(&self.config, |config| config.record_headers.push(header.into()));
self
}
pub fn record_request_headers<IntoString: Into<String>>(self, headers: Vec<IntoString>) -> Self {
update_mutex(&self.config, |config| {
config.record_headers.extend(headers.into_iter().map(Into::into))
});
self
}
pub fn filter<WhenSpecFn>(self, when: WhenSpecFn) -> Self
where
WhenSpecFn: FnOnce(When),
{
let request_requirements = Arc::new(Mutex::new(std::mem::take(
&mut lock_mutex(&self.config).request_requirements,
)));
when(When {
expectations: request_requirements.clone(),
});
lock_mutex(&self.config).request_requirements = take_from_mutex(&request_requirements);
self
}
pub fn record_response_delays(self, record: bool) -> Self {
update_mutex(&self.config, |config| config.record_response_delays = record);
self
}
}