use chrono::Utc; use serde::{Deserialize, Serialize}; use std::path::Path; use uuid::Uuid; /// A captured HTTP request from the FIFA 23 client. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CapturedRequest { pub id: String, pub timestamp: String, pub method: String, pub path: String, pub query: Option, pub headers: Vec<(String, String)>, pub body: Option, pub response_status: Option, pub response_body: Option, pub mapped_to_core: Option, } impl CapturedRequest { pub fn new( method: &str, path: &str, query: Option<&str>, headers: Vec<(String, String)>, body: Option, ) -> Self { Self { id: Uuid::new_v4().to_string(), timestamp: Utc::now().to_rfc3339(), method: method.to_string(), path: path.to_string(), query: query.map(String::from), headers, body, response_status: None, response_body: None, mapped_to_core: None, } } pub fn with_response(mut self, status: u16, body: Option) -> Self { self.response_status = Some(status); self.response_body = body; self } } /// Persist a capture to disk as JSON. pub fn save_capture(captures_dir: &str, capture: &CapturedRequest) -> anyhow::Result<()> { let dir = Path::new(captures_dir); std::fs::create_dir_all(dir)?; let filename = format!( "{}_{}_{}.json", capture.timestamp.replace(':', "-"), capture.method, capture.id ); let path = dir.join(filename); let json = serde_json::to_string_pretty(capture)?; std::fs::write(&path, json)?; tracing::debug!("Capture saved: {:?}", path); Ok(()) } /// Load all captures from the captures directory. pub fn load_all_captures(captures_dir: &str) -> anyhow::Result> { let dir = Path::new(captures_dir); if !dir.exists() { return Ok(vec![]); } let mut captures = Vec::new(); for entry in std::fs::read_dir(dir)? { let entry = entry?; let path = entry.path(); if path.extension().map(|e| e == "json").unwrap_or(false) { let content = std::fs::read_to_string(&path)?; if let Ok(capture) = serde_json::from_str::(&content) { captures.push(capture); } } } captures.sort_by(|a, b| a.timestamp.cmp(&b.timestamp)); Ok(captures) }