use std::sync::{Arc, Mutex, OnceLock};
use super::uploader::{HttpClient, Request, Response};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CapturedUpload {
pub headers: Vec<(String, String)>,
pub body: String,
}
#[derive(Debug)]
struct CaptureState {
status: u16,
uploads: Vec<CapturedUpload>,
}
#[derive(Debug, Clone)]
pub struct CapturingHttpClient {
inner: Arc<Mutex<CaptureState>>,
}
impl Default for CapturingHttpClient {
fn default() -> Self {
Self::new()
}
}
impl CapturingHttpClient {
pub fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(CaptureState {
status: 202,
uploads: Vec::new(),
})),
}
}
pub fn set_status(&self, status: u16) {
self.lock().status = status;
}
pub fn uploads(&self) -> Vec<CapturedUpload> {
self.lock().uploads.clone()
}
pub fn bodies(&self) -> Vec<String> {
self.lock().uploads.iter().map(|u| u.body.clone()).collect()
}
fn lock(&self) -> std::sync::MutexGuard<'_, CaptureState> {
self.inner.lock().unwrap_or_else(|e| e.into_inner())
}
}
impl HttpClient for CapturingHttpClient {
fn post(&mut self, request: &Request<'_>) -> Result<Response, String> {
let mut state = self.lock();
if state.status == 0 {
return Err("capturing client: no answer".into());
}
state.uploads.push(CapturedUpload {
headers: request
.headers
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect(),
body: request.body.to_string(),
});
let accepted = serde_json::from_str::<serde_json::Value>(request.body)
.ok()
.and_then(|v| v["events"].as_array().map(Vec::len))
.unwrap_or(0);
Ok(Response {
status: state.status,
retry_after: None,
body: format!(r#"{{"accepted":{accepted},"rejected":0,"rejected_reasons":[]}}"#),
})
}
}
static OVERRIDE: OnceLock<Mutex<Option<CapturingHttpClient>>> = OnceLock::new();
fn slot() -> &'static Mutex<Option<CapturingHttpClient>> {
OVERRIDE.get_or_init(|| Mutex::new(None))
}
pub fn capture_uploads() -> CapturingHttpClient {
let client = CapturingHttpClient::new();
*slot().lock().unwrap_or_else(|e| e.into_inner()) = Some(client.clone());
client
}
pub fn stop_capturing_uploads() {
*slot().lock().unwrap_or_else(|e| e.into_inner()) = None;
}
pub(crate) fn override_client() -> Option<Box<dyn HttpClient>> {
slot()
.lock()
.unwrap_or_else(|e| e.into_inner())
.clone()
.map(|client| Box::new(client) as Box<dyn HttpClient>)
}
pub fn start_pipe_for_bench(
config: &crate::telemetry::TelemetryConfig,
host: super::TelemetryHost,
) -> (
Arc<super::TelemetryPipe>,
Arc<dyn crate::telemetry::TelemetrySink>,
) {
let client = CapturingHttpClient::new();
let pipe = super::TelemetryPipe::start(
config,
super::PipeParts {
host,
backend: super::store::Backend::Memory,
device_id: None,
client: Box::new(client),
clock: Arc::new(|| chrono::Utc::now().timestamp_millis()),
jitter: Box::new(|| 0),
spawn_thread: true,
},
)
.expect("a pipe starts");
let sink = pipe.sink();
(pipe, sink)
}