use anyhow::Result;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fs;
use chrono::Utc;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SnapshotMeta {
pub name: String,
pub recorded_at: String,
pub server_name: String,
pub server_version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolInfo {
pub name: String,
pub description: String,
#[serde(rename = "inputSchema")]
pub input_schema: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Exchange {
pub id: String,
pub tool: String,
pub input: Value,
pub match_mode: String,
pub expected: ExchangeResult,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExchangeResult {
pub is_error: bool,
pub content: Vec<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Snapshot {
pub format_version: u32,
pub meta: SnapshotMeta,
pub tool_catalog: Vec<ToolInfo>,
pub exchanges: Vec<Exchange>,
}
impl Snapshot {
pub fn new(server_name: String, server_version: String) -> Self {
Snapshot {
format_version: 1,
meta: SnapshotMeta {
name: format!("{}-baseline", server_name),
recorded_at: Utc::now().to_rfc3339(),
server_name,
server_version,
},
tool_catalog: Vec::new(),
exchanges: Vec::new(),
}
}
pub fn add_tool(&mut self, tool: ToolInfo) {
self.tool_catalog.push(tool);
}
pub fn add_exchange(&mut self, tool: String, input: Value, result: ExchangeResult) {
let id = format!("ex-{:03}", self.exchanges.len() + 1);
self.exchanges.push(Exchange {
id,
tool,
input,
match_mode: "structural".to_string(),
expected: result,
});
}
pub fn save(&self, path: &str) -> Result<()> {
let json = serde_json::to_string_pretty(&self)?;
fs::write(path, json)?;
println!("Snapshot saved to: {}", path);
Ok(())
}
pub fn load(path: &str) -> Result<Self> {
let content = fs::read_to_string(path)?;
let snapshot = serde_json::from_str(&content)?;
Ok(snapshot)
}
}