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(vbr_engine: &Option<Arc<dyn std::any::Any + Send + Sync>>) -> Option<serde_json::Value> {
65 if let Some(engine) = vbr_engine {
66 debug!("VBR engine available, but state extraction not yet implemented");
71 None
72 } else {
73 None
74 }
75}
76
77async fn extract_recorder_state(recorder: &Option<Arc<dyn std::any::Any + Send + Sync>>) -> Option<serde_json::Value> {
79 if let Some(rec) = recorder {
80 debug!("Recorder available, but state extraction not yet implemented");
85 None
86 } else {
87 None
88 }
89}
90
91pub async fn save_snapshot(
95 State(state): State<SnapshotState>,
96 Query(params): Query<SnapshotQuery>,
97 Json(request): Json<SaveSnapshotRequest>,
98) -> Result<Json<Value>, StatusCode> {
99 let components = request.components.unwrap_or_else(SnapshotComponents::all);
100
101 let consistency_engine = state.consistency_engine.as_deref();
102 let workspace_persistence = state.workspace_persistence.as_deref();
103
104 let vbr_state = if components.vbr_state {
106 extract_vbr_state(&state.vbr_engine).await
107 } else {
108 None
109 };
110
111 let recorder_state = if components.recorder_state {
113 extract_recorder_state(&state.recorder).await
114 } else {
115 None
116 };
117 let manifest = state
118 .manager
119 .save_snapshot(
120 request.name.clone(),
121 request.description,
122 params.workspace.clone(),
123 components,
124 consistency_engine,
125 workspace_persistence,
126 vbr_state,
127 recorder_state,
128 )
129 .await
130 .map_err(|e| {
131 error!("Failed to save snapshot: {}", e);
132 StatusCode::INTERNAL_SERVER_ERROR
133 })?;
134
135 info!("Saved snapshot '{}' for workspace '{}'", request.name, params.workspace);
136 Ok(Json(serde_json::json!({
137 "success": true,
138 "manifest": manifest,
139 })))
140}
141
142pub async fn load_snapshot(
146 State(state): State<SnapshotState>,
147 Path(name): Path<String>,
148 Query(params): Query<SnapshotQuery>,
149 Json(request): Json<LoadSnapshotRequest>,
150) -> Result<Json<Value>, StatusCode> {
151 let consistency_engine = state.consistency_engine.as_deref();
152 let workspace_persistence = state.workspace_persistence.as_deref();
153 let (manifest, vbr_state, recorder_state) = state
154 .manager
155 .load_snapshot(
156 name.clone(),
157 params.workspace.clone(),
158 request.components,
159 consistency_engine,
160 workspace_persistence,
161 )
162 .await
163 .map_err(|e| {
164 error!("Failed to load snapshot: {}", e);
165 StatusCode::INTERNAL_SERVER_ERROR
166 })?;
167
168 info!("Loaded snapshot '{}' for workspace '{}'", name, params.workspace);
169 Ok(Json(serde_json::json!({
170 "success": true,
171 "manifest": manifest,
172 "vbr_state": vbr_state,
173 "recorder_state": recorder_state,
174 })))
175}
176
177pub async fn list_snapshots(
181 State(state): State<SnapshotState>,
182 Query(params): Query<SnapshotQuery>,
183) -> Result<Json<Value>, StatusCode> {
184 let snapshots = state.manager.list_snapshots(¶ms.workspace).await.map_err(|e| {
185 error!("Failed to list snapshots: {}", e);
186 StatusCode::INTERNAL_SERVER_ERROR
187 })?;
188
189 Ok(Json(serde_json::json!({
190 "workspace": params.workspace,
191 "snapshots": snapshots,
192 "count": snapshots.len(),
193 })))
194}
195
196pub async fn get_snapshot_info(
200 State(state): State<SnapshotState>,
201 Path(name): Path<String>,
202 Query(params): Query<SnapshotQuery>,
203) -> Result<Json<Value>, StatusCode> {
204 let manifest = state
205 .manager
206 .get_snapshot_info(name.clone(), params.workspace.clone())
207 .await
208 .map_err(|e| {
209 error!("Failed to get snapshot info: {}", e);
210 StatusCode::NOT_FOUND
211 })?;
212
213 Ok(Json(serde_json::json!({
214 "success": true,
215 "manifest": manifest,
216 })))
217}
218
219pub async fn delete_snapshot(
223 State(state): State<SnapshotState>,
224 Path(name): Path<String>,
225 Query(params): Query<SnapshotQuery>,
226) -> Result<Json<Value>, StatusCode> {
227 state
228 .manager
229 .delete_snapshot(name.clone(), params.workspace.clone())
230 .await
231 .map_err(|e| {
232 error!("Failed to delete snapshot: {}", e);
233 StatusCode::INTERNAL_SERVER_ERROR
234 })?;
235
236 info!("Deleted snapshot '{}' for workspace '{}'", name, params.workspace);
237 Ok(Json(serde_json::json!({
238 "success": true,
239 "message": format!("Snapshot '{}' deleted successfully", name),
240 })))
241}
242
243pub async fn validate_snapshot(
247 State(state): State<SnapshotState>,
248 Path(name): Path<String>,
249 Query(params): Query<SnapshotQuery>,
250) -> Result<Json<Value>, StatusCode> {
251 let is_valid = state
252 .manager
253 .validate_snapshot(name.clone(), params.workspace.clone())
254 .await
255 .map_err(|e| {
256 error!("Failed to validate snapshot: {}", e);
257 StatusCode::INTERNAL_SERVER_ERROR
258 })?;
259
260 Ok(Json(serde_json::json!({
261 "success": true,
262 "valid": is_valid,
263 "snapshot": name,
264 "workspace": params.workspace,
265 })))
266}
267
268pub fn snapshot_router(state: SnapshotState) -> axum::Router {
270 use axum::routing::{get, post};
271
272 axum::Router::new()
273 .route("/api/v1/snapshots", get(list_snapshots).post(save_snapshot))
274 .route("/api/v1/snapshots/{name}", get(get_snapshot_info).delete(delete_snapshot))
275 .route("/api/v1/snapshots/{name}/load", post(load_snapshot))
276 .route("/api/v1/snapshots/{name}/validate", get(validate_snapshot))
277 .with_state(state)
278}