1#![allow(deprecated)]
6
7use axum::{
8 extract::{Path, Query, State},
9 http::StatusCode,
10 response::Json,
11};
12use mockforge_core::consistency::ConsistencyEngine;
13use mockforge_core::snapshots::{ProtocolStateExporter, SnapshotComponents, SnapshotManager};
14use mockforge_core::workspace_persistence::WorkspacePersistence;
15use serde::Deserialize;
16use serde_json::Value;
17use std::sync::Arc;
18use tracing::{debug, error, info, warn};
19
20#[derive(Clone)]
22pub struct SnapshotState {
23 pub manager: Arc<SnapshotManager>,
25 pub consistency_engine: Option<Arc<ConsistencyEngine>>,
27 pub workspace_persistence: Option<Arc<WorkspacePersistence>>,
29 pub vbr_engine: Option<Arc<dyn ProtocolStateExporter>>,
32 pub recorder: Option<Arc<dyn ProtocolStateExporter>>,
35}
36
37#[derive(Debug, Deserialize)]
39pub struct SaveSnapshotRequest {
40 pub name: String,
42 pub description: Option<String>,
44 pub components: Option<SnapshotComponents>,
46}
47
48#[derive(Debug, Deserialize)]
50pub struct LoadSnapshotRequest {
51 pub components: Option<SnapshotComponents>,
53}
54
55#[derive(Debug, Deserialize)]
57pub struct SnapshotQuery {
58 #[serde(default = "default_workspace")]
60 pub workspace: String,
61}
62
63fn default_workspace() -> String {
64 "default".to_string()
65}
66
67async fn extract_vbr_state(vbr_engine: &Option<Arc<dyn ProtocolStateExporter>>) -> Option<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<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}