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, SnapshotMetadata};
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14use std::sync::Arc;
15use tracing::{error, info};
16
17#[derive(Clone)]
19pub struct SnapshotState {
20 pub manager: Arc<SnapshotManager>,
22 pub consistency_engine: Option<Arc<ConsistencyEngine>>,
24}
25
26#[derive(Debug, Deserialize)]
28pub struct SaveSnapshotRequest {
29 pub name: String,
31 pub description: Option<String>,
33 pub components: Option<SnapshotComponents>,
35}
36
37#[derive(Debug, Deserialize)]
39pub struct LoadSnapshotRequest {
40 pub components: Option<SnapshotComponents>,
42}
43
44#[derive(Debug, Deserialize)]
46pub struct SnapshotQuery {
47 #[serde(default = "default_workspace")]
49 pub workspace: String,
50}
51
52fn default_workspace() -> String {
53 "default".to_string()
54}
55
56pub async fn save_snapshot(
60 State(state): State<SnapshotState>,
61 Query(params): Query<SnapshotQuery>,
62 Json(request): Json<SaveSnapshotRequest>,
63) -> Result<Json<Value>, StatusCode> {
64 let components = request.components.unwrap_or_else(SnapshotComponents::all);
65
66 let consistency_engine = state.consistency_engine.as_deref();
67 let manifest = state
68 .manager
69 .save_snapshot(
70 request.name.clone(),
71 request.description,
72 params.workspace.clone(),
73 components,
74 consistency_engine,
75 )
76 .await
77 .map_err(|e| {
78 error!("Failed to save snapshot: {}", e);
79 StatusCode::INTERNAL_SERVER_ERROR
80 })?;
81
82 info!("Saved snapshot '{}' for workspace '{}'", request.name, params.workspace);
83 Ok(Json(serde_json::json!({
84 "success": true,
85 "manifest": manifest,
86 })))
87}
88
89pub async fn load_snapshot(
93 State(state): State<SnapshotState>,
94 Path(name): Path<String>,
95 Query(params): Query<SnapshotQuery>,
96 Json(request): Json<LoadSnapshotRequest>,
97) -> Result<Json<Value>, StatusCode> {
98 let consistency_engine = state.consistency_engine.as_deref();
99 let manifest = state
100 .manager
101 .load_snapshot(
102 name.clone(),
103 params.workspace.clone(),
104 request.components,
105 consistency_engine,
106 )
107 .await
108 .map_err(|e| {
109 error!("Failed to load snapshot: {}", e);
110 StatusCode::INTERNAL_SERVER_ERROR
111 })?;
112
113 info!("Loaded snapshot '{}' for workspace '{}'", name, params.workspace);
114 Ok(Json(serde_json::json!({
115 "success": true,
116 "manifest": manifest,
117 })))
118}
119
120pub async fn list_snapshots(
124 State(state): State<SnapshotState>,
125 Query(params): Query<SnapshotQuery>,
126) -> Result<Json<Value>, StatusCode> {
127 let snapshots = state.manager.list_snapshots(¶ms.workspace).await.map_err(|e| {
128 error!("Failed to list snapshots: {}", e);
129 StatusCode::INTERNAL_SERVER_ERROR
130 })?;
131
132 Ok(Json(serde_json::json!({
133 "workspace": params.workspace,
134 "snapshots": snapshots,
135 "count": snapshots.len(),
136 })))
137}
138
139pub async fn get_snapshot_info(
143 State(state): State<SnapshotState>,
144 Path(name): Path<String>,
145 Query(params): Query<SnapshotQuery>,
146) -> Result<Json<Value>, StatusCode> {
147 let manifest = state
148 .manager
149 .get_snapshot_info(name.clone(), params.workspace.clone())
150 .await
151 .map_err(|e| {
152 error!("Failed to get snapshot info: {}", e);
153 StatusCode::NOT_FOUND
154 })?;
155
156 Ok(Json(serde_json::json!({
157 "success": true,
158 "manifest": manifest,
159 })))
160}
161
162pub async fn delete_snapshot(
166 State(state): State<SnapshotState>,
167 Path(name): Path<String>,
168 Query(params): Query<SnapshotQuery>,
169) -> Result<Json<Value>, StatusCode> {
170 state
171 .manager
172 .delete_snapshot(name.clone(), params.workspace.clone())
173 .await
174 .map_err(|e| {
175 error!("Failed to delete snapshot: {}", e);
176 StatusCode::INTERNAL_SERVER_ERROR
177 })?;
178
179 info!("Deleted snapshot '{}' for workspace '{}'", name, params.workspace);
180 Ok(Json(serde_json::json!({
181 "success": true,
182 "message": format!("Snapshot '{}' deleted successfully", name),
183 })))
184}
185
186pub async fn validate_snapshot(
190 State(state): State<SnapshotState>,
191 Path(name): Path<String>,
192 Query(params): Query<SnapshotQuery>,
193) -> Result<Json<Value>, StatusCode> {
194 let is_valid = state
195 .manager
196 .validate_snapshot(name.clone(), params.workspace.clone())
197 .await
198 .map_err(|e| {
199 error!("Failed to validate snapshot: {}", e);
200 StatusCode::INTERNAL_SERVER_ERROR
201 })?;
202
203 Ok(Json(serde_json::json!({
204 "success": true,
205 "valid": is_valid,
206 "snapshot": name,
207 "workspace": params.workspace,
208 })))
209}
210
211pub fn snapshot_router(state: SnapshotState) -> axum::Router {
213 use axum::routing::{delete, get, post};
214
215 axum::Router::new()
216 .route("/api/v1/snapshots", get(list_snapshots).post(save_snapshot))
217 .route("/api/v1/snapshots/{name}", get(get_snapshot_info).delete(delete_snapshot))
218 .route("/api/v1/snapshots/{name}/load", post(load_snapshot))
219 .route("/api/v1/snapshots/{name}/validate", get(validate_snapshot))
220 .with_state(state)
221}