1use axum::{
6 extract::{Path, Query, State},
7 http::StatusCode,
8 response::Json,
9};
10use mockforge_core::consistency::ConsistencyEngine;
11use mockforge_core::snapshots::{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 std::any::Any + Send + Sync>>,
29 pub recorder: Option<Arc<dyn std::any::Any + Send + Sync>>,
31}
32
33#[derive(Debug, Deserialize)]
35pub struct SaveSnapshotRequest {
36 pub name: String,
38 pub description: Option<String>,
40 pub components: Option<SnapshotComponents>,
42}
43
44#[derive(Debug, Deserialize)]
46pub struct LoadSnapshotRequest {
47 pub components: Option<SnapshotComponents>,
49}
50
51#[derive(Debug, Deserialize)]
53pub struct SnapshotQuery {
54 #[serde(default = "default_workspace")]
56 pub workspace: String,
57}
58
59fn default_workspace() -> String {
60 "default".to_string()
61}
62
63async fn extract_vbr_state(
65 vbr_engine: &Option<Arc<dyn std::any::Any + Send + Sync>>,
66) -> Option<serde_json::Value> {
67 if let Some(engine) = vbr_engine {
68 debug!("VBR engine available, but state extraction not yet implemented");
73 None
74 } else {
75 None
76 }
77}
78
79async fn extract_recorder_state(
81 recorder: &Option<Arc<dyn std::any::Any + Send + Sync>>,
82) -> Option<serde_json::Value> {
83 if let Some(rec) = recorder {
84 debug!("Recorder available, but state extraction not yet implemented");
89 None
90 } else {
91 None
92 }
93}
94
95pub async fn save_snapshot(
99 State(state): State<SnapshotState>,
100 Query(params): Query<SnapshotQuery>,
101 Json(request): Json<SaveSnapshotRequest>,
102) -> Result<Json<Value>, StatusCode> {
103 let components = request.components.unwrap_or_else(SnapshotComponents::all);
104
105 let consistency_engine = state.consistency_engine.as_deref();
106 let workspace_persistence = state.workspace_persistence.as_deref();
107
108 let vbr_state = if components.vbr_state {
110 extract_vbr_state(&state.vbr_engine).await
111 } else {
112 None
113 };
114
115 let recorder_state = if components.recorder_state {
117 extract_recorder_state(&state.recorder).await
118 } else {
119 None
120 };
121 let manifest = state
122 .manager
123 .save_snapshot(
124 request.name.clone(),
125 request.description,
126 params.workspace.clone(),
127 components,
128 consistency_engine,
129 workspace_persistence,
130 vbr_state,
131 recorder_state,
132 )
133 .await
134 .map_err(|e| {
135 error!("Failed to save snapshot: {}", e);
136 StatusCode::INTERNAL_SERVER_ERROR
137 })?;
138
139 info!("Saved snapshot '{}' for workspace '{}'", request.name, params.workspace);
140 Ok(Json(serde_json::json!({
141 "success": true,
142 "manifest": manifest,
143 })))
144}
145
146pub async fn load_snapshot(
150 State(state): State<SnapshotState>,
151 Path(name): Path<String>,
152 Query(params): Query<SnapshotQuery>,
153 Json(request): Json<LoadSnapshotRequest>,
154) -> Result<Json<Value>, StatusCode> {
155 let consistency_engine = state.consistency_engine.as_deref();
156 let workspace_persistence = state.workspace_persistence.as_deref();
157 let (manifest, vbr_state, recorder_state) = state
158 .manager
159 .load_snapshot(
160 name.clone(),
161 params.workspace.clone(),
162 request.components,
163 consistency_engine,
164 workspace_persistence,
165 )
166 .await
167 .map_err(|e| {
168 error!("Failed to load snapshot: {}", e);
169 StatusCode::INTERNAL_SERVER_ERROR
170 })?;
171
172 info!("Loaded snapshot '{}' for workspace '{}'", name, params.workspace);
173 Ok(Json(serde_json::json!({
174 "success": true,
175 "manifest": manifest,
176 "vbr_state": vbr_state,
177 "recorder_state": recorder_state,
178 })))
179}
180
181pub async fn list_snapshots(
185 State(state): State<SnapshotState>,
186 Query(params): Query<SnapshotQuery>,
187) -> Result<Json<Value>, StatusCode> {
188 let snapshots = state.manager.list_snapshots(¶ms.workspace).await.map_err(|e| {
189 error!("Failed to list snapshots: {}", e);
190 StatusCode::INTERNAL_SERVER_ERROR
191 })?;
192
193 Ok(Json(serde_json::json!({
194 "workspace": params.workspace,
195 "snapshots": snapshots,
196 "count": snapshots.len(),
197 })))
198}
199
200pub async fn get_snapshot_info(
204 State(state): State<SnapshotState>,
205 Path(name): Path<String>,
206 Query(params): Query<SnapshotQuery>,
207) -> Result<Json<Value>, StatusCode> {
208 let manifest = state
209 .manager
210 .get_snapshot_info(name.clone(), params.workspace.clone())
211 .await
212 .map_err(|e| {
213 error!("Failed to get snapshot info: {}", e);
214 StatusCode::NOT_FOUND
215 })?;
216
217 Ok(Json(serde_json::json!({
218 "success": true,
219 "manifest": manifest,
220 })))
221}
222
223pub async fn delete_snapshot(
227 State(state): State<SnapshotState>,
228 Path(name): Path<String>,
229 Query(params): Query<SnapshotQuery>,
230) -> Result<Json<Value>, StatusCode> {
231 state
232 .manager
233 .delete_snapshot(name.clone(), params.workspace.clone())
234 .await
235 .map_err(|e| {
236 error!("Failed to delete snapshot: {}", e);
237 StatusCode::INTERNAL_SERVER_ERROR
238 })?;
239
240 info!("Deleted snapshot '{}' for workspace '{}'", name, params.workspace);
241 Ok(Json(serde_json::json!({
242 "success": true,
243 "message": format!("Snapshot '{}' deleted successfully", name),
244 })))
245}
246
247pub async fn validate_snapshot(
251 State(state): State<SnapshotState>,
252 Path(name): Path<String>,
253 Query(params): Query<SnapshotQuery>,
254) -> Result<Json<Value>, StatusCode> {
255 let is_valid = state
256 .manager
257 .validate_snapshot(name.clone(), params.workspace.clone())
258 .await
259 .map_err(|e| {
260 error!("Failed to validate snapshot: {}", e);
261 StatusCode::INTERNAL_SERVER_ERROR
262 })?;
263
264 Ok(Json(serde_json::json!({
265 "success": true,
266 "valid": is_valid,
267 "snapshot": name,
268 "workspace": params.workspace,
269 })))
270}
271
272pub fn snapshot_router(state: SnapshotState) -> axum::Router {
274 use axum::routing::{get, post};
275
276 axum::Router::new()
277 .route("/api/v1/snapshots", get(list_snapshots).post(save_snapshot))
278 .route("/api/v1/snapshots/{name}", get(get_snapshot_info).delete(delete_snapshot))
279 .route("/api/v1/snapshots/{name}/load", post(load_snapshot))
280 .route("/api/v1/snapshots/{name}/validate", get(validate_snapshot))
281 .with_state(state)
282}