1use axum::{
6 extract::{Path, Query, State},
7 http::StatusCode,
8 response::Json,
9};
10use mockforge_core::consistency::ConsistencyEngine;
11use mockforge_core::snapshots::{ProtocolStateExporter, SnapshotComponents, SnapshotManager};
12use mockforge_core::workspace_persistence::WorkspacePersistence;
13use serde::Deserialize;
14use serde_json::Value;
15use std::sync::Arc;
16use tracing::{debug, error, info, warn};
17
18#[derive(Clone)]
20pub struct SnapshotState {
21 pub manager: Arc<SnapshotManager>,
23 pub consistency_engine: Option<Arc<ConsistencyEngine>>,
25 pub workspace_persistence: Option<Arc<WorkspacePersistence>>,
27 pub vbr_engine: Option<Arc<dyn ProtocolStateExporter>>,
30 pub recorder: Option<Arc<dyn ProtocolStateExporter>>,
33}
34
35#[derive(Debug, Deserialize)]
37pub struct SaveSnapshotRequest {
38 pub name: String,
40 pub description: Option<String>,
42 pub components: Option<SnapshotComponents>,
44}
45
46#[derive(Debug, Deserialize)]
48pub struct LoadSnapshotRequest {
49 pub components: Option<SnapshotComponents>,
51}
52
53#[derive(Debug, Deserialize)]
55pub struct SnapshotQuery {
56 #[serde(default = "default_workspace")]
58 pub workspace: String,
59}
60
61fn default_workspace() -> String {
62 "default".to_string()
63}
64
65async fn extract_vbr_state(
67 vbr_engine: &Option<Arc<dyn ProtocolStateExporter>>,
68) -> Option<serde_json::Value> {
69 if let Some(engine) = vbr_engine {
70 match engine.export_state().await {
71 Ok(state) => {
72 let summary = engine.state_summary().await;
73 info!("Extracted VBR state from {} engine: {}", engine.protocol_name(), summary);
74 Some(state)
75 }
76 Err(e) => {
77 warn!("Failed to extract VBR state: {}", e);
78 None
79 }
80 }
81 } else {
82 debug!("No VBR engine available for state extraction");
83 None
84 }
85}
86
87async fn extract_recorder_state(
89 recorder: &Option<Arc<dyn ProtocolStateExporter>>,
90) -> Option<serde_json::Value> {
91 if let Some(rec) = recorder {
92 match rec.export_state().await {
93 Ok(state) => {
94 let summary = rec.state_summary().await;
95 info!("Extracted Recorder state from {} engine: {}", rec.protocol_name(), summary);
96 Some(state)
97 }
98 Err(e) => {
99 warn!("Failed to extract Recorder state: {}", e);
100 None
101 }
102 }
103 } else {
104 debug!("No Recorder available for state extraction");
105 None
106 }
107}
108
109pub async fn save_snapshot(
113 State(state): State<SnapshotState>,
114 Query(params): Query<SnapshotQuery>,
115 Json(request): Json<SaveSnapshotRequest>,
116) -> Result<Json<Value>, StatusCode> {
117 let components = request.components.unwrap_or_else(SnapshotComponents::all);
118
119 let consistency_engine = state.consistency_engine.as_deref();
120 let workspace_persistence = state.workspace_persistence.as_deref();
121
122 let vbr_state = if components.vbr_state {
124 extract_vbr_state(&state.vbr_engine).await
125 } else {
126 None
127 };
128
129 let recorder_state = if components.recorder_state {
131 extract_recorder_state(&state.recorder).await
132 } else {
133 None
134 };
135 let manifest = state
136 .manager
137 .save_snapshot(
138 request.name.clone(),
139 request.description,
140 params.workspace.clone(),
141 components,
142 consistency_engine,
143 workspace_persistence,
144 vbr_state,
145 recorder_state,
146 )
147 .await
148 .map_err(|e| {
149 error!("Failed to save snapshot: {}", e);
150 StatusCode::INTERNAL_SERVER_ERROR
151 })?;
152
153 info!("Saved snapshot '{}' for workspace '{}'", request.name, params.workspace);
154 Ok(Json(serde_json::json!({
155 "success": true,
156 "manifest": manifest,
157 })))
158}
159
160pub async fn load_snapshot(
164 State(state): State<SnapshotState>,
165 Path(name): Path<String>,
166 Query(params): Query<SnapshotQuery>,
167 Json(request): Json<LoadSnapshotRequest>,
168) -> Result<Json<Value>, StatusCode> {
169 let consistency_engine = state.consistency_engine.as_deref();
170 let workspace_persistence = state.workspace_persistence.as_deref();
171 let (manifest, vbr_state, recorder_state) = state
172 .manager
173 .load_snapshot(
174 name.clone(),
175 params.workspace.clone(),
176 request.components,
177 consistency_engine,
178 workspace_persistence,
179 )
180 .await
181 .map_err(|e| {
182 error!("Failed to load snapshot: {}", e);
183 StatusCode::INTERNAL_SERVER_ERROR
184 })?;
185
186 info!("Loaded snapshot '{}' for workspace '{}'", name, params.workspace);
187 Ok(Json(serde_json::json!({
188 "success": true,
189 "manifest": manifest,
190 "vbr_state": vbr_state,
191 "recorder_state": recorder_state,
192 })))
193}
194
195pub async fn list_snapshots(
199 State(state): State<SnapshotState>,
200 Query(params): Query<SnapshotQuery>,
201) -> Result<Json<Value>, StatusCode> {
202 let snapshots = state.manager.list_snapshots(¶ms.workspace).await.map_err(|e| {
203 error!("Failed to list snapshots: {}", e);
204 StatusCode::INTERNAL_SERVER_ERROR
205 })?;
206
207 Ok(Json(serde_json::json!({
208 "workspace": params.workspace,
209 "snapshots": snapshots,
210 "count": snapshots.len(),
211 })))
212}
213
214pub async fn get_snapshot_info(
218 State(state): State<SnapshotState>,
219 Path(name): Path<String>,
220 Query(params): Query<SnapshotQuery>,
221) -> Result<Json<Value>, StatusCode> {
222 let manifest = state
223 .manager
224 .get_snapshot_info(name.clone(), params.workspace.clone())
225 .await
226 .map_err(|e| {
227 error!("Failed to get snapshot info: {}", e);
228 StatusCode::NOT_FOUND
229 })?;
230
231 Ok(Json(serde_json::json!({
232 "success": true,
233 "manifest": manifest,
234 })))
235}
236
237pub async fn delete_snapshot(
241 State(state): State<SnapshotState>,
242 Path(name): Path<String>,
243 Query(params): Query<SnapshotQuery>,
244) -> Result<Json<Value>, StatusCode> {
245 state
246 .manager
247 .delete_snapshot(name.clone(), params.workspace.clone())
248 .await
249 .map_err(|e| {
250 error!("Failed to delete snapshot: {}", e);
251 StatusCode::INTERNAL_SERVER_ERROR
252 })?;
253
254 info!("Deleted snapshot '{}' for workspace '{}'", name, params.workspace);
255 Ok(Json(serde_json::json!({
256 "success": true,
257 "message": format!("Snapshot '{}' deleted successfully", name),
258 })))
259}
260
261pub async fn validate_snapshot(
265 State(state): State<SnapshotState>,
266 Path(name): Path<String>,
267 Query(params): Query<SnapshotQuery>,
268) -> Result<Json<Value>, StatusCode> {
269 let is_valid = state
270 .manager
271 .validate_snapshot(name.clone(), params.workspace.clone())
272 .await
273 .map_err(|e| {
274 error!("Failed to validate snapshot: {}", e);
275 StatusCode::INTERNAL_SERVER_ERROR
276 })?;
277
278 Ok(Json(serde_json::json!({
279 "success": true,
280 "valid": is_valid,
281 "snapshot": name,
282 "workspace": params.workspace,
283 })))
284}
285
286pub fn snapshot_router(state: SnapshotState) -> axum::Router {
288 use axum::routing::{get, post};
289
290 axum::Router::new()
291 .route("/api/v1/snapshots", get(list_snapshots).post(save_snapshot))
292 .route("/api/v1/snapshots/{name}", get(get_snapshot_info).delete(delete_snapshot))
293 .route("/api/v1/snapshots/{name}/load", post(load_snapshot))
294 .route("/api/v1/snapshots/{name}/validate", get(validate_snapshot))
295 .with_state(state)
296}