//! openfut-bridge-replay — replay a captured request file against the bridge. //! //! Usage: //! openfut-bridge-replay \ [bridge-url] //! openfut-bridge-replay captures/ (replay all captures in a dir) //! //! The bridge URL defaults to `http://127.0.0.1:8443`. //! Self-signed TLS certificates are accepted automatically. use openfut_bridge::capture::CapturedRequest; use std::process::ExitCode; #[tokio::main] async fn main() -> ExitCode { let args: Vec = std::env::args().collect(); if args.len() < 2 { eprintln!("Usage: openfut-bridge-replay [bridge-url]"); eprintln!(" bridge-url defaults to http://127.0.0.1:8443"); return ExitCode::FAILURE; } let path_arg = &args[1]; let bridge_url = args .get(2) .map(|s| s.as_str()) .unwrap_or("http://127.0.0.1:8443"); let client = match reqwest::Client::builder() .danger_accept_invalid_certs(true) .timeout(std::time::Duration::from_secs(10)) .build() { Ok(c) => c, Err(e) => { eprintln!("Failed to build HTTP client: {e}"); return ExitCode::FAILURE; } }; let captures = match collect_captures(path_arg) { Ok(c) => c, Err(e) => { eprintln!("Error loading captures: {e}"); return ExitCode::FAILURE; } }; if captures.is_empty() { eprintln!("No capture files found at {path_arg}"); return ExitCode::FAILURE; } println!( "Replaying {} capture(s) against {bridge_url}", captures.len() ); let mut failures = 0u32; for capture in &captures { let result = replay_one(&client, bridge_url, capture).await; match result { Ok(status) => println!(" [{}] {} {} → {status}", capture.id, capture.method, capture.path), Err(e) => { eprintln!(" [{}] {} {} → ERROR: {e}", capture.id, capture.method, capture.path); failures += 1; } } } if failures > 0 { eprintln!("{failures} replay(s) failed."); ExitCode::FAILURE } else { println!("All replays succeeded."); ExitCode::SUCCESS } } fn collect_captures(path_arg: &str) -> anyhow::Result> { let path = std::path::Path::new(path_arg); if path.is_dir() { openfut_bridge::capture::load_all_captures(path_arg) } else { let content = std::fs::read_to_string(path)?; let capture: CapturedRequest = serde_json::from_str(&content)?; Ok(vec![capture]) } } async fn replay_one( client: &reqwest::Client, bridge_url: &str, capture: &CapturedRequest, ) -> anyhow::Result { let url = format!("{}{}", bridge_url, capture.path); let builder = match capture.method.to_uppercase().as_str() { "POST" => client.post(&url), "PUT" => client.put(&url), "DELETE" => client.delete(&url), _ => client.get(&url), }; let builder = if let Some(body) = &capture.body { builder .header("content-type", "application/json") .body(body.clone()) } else { builder }; let resp = builder.send().await?; Ok(resp.status().as_u16()) }