mockforge_http/handlers/
snapshots.rs

1//! Snapshot management API handlers
2//!
3//! This module provides HTTP handlers for managing snapshots via REST API.
4
5use 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/// State for snapshot handlers
18#[derive(Clone)]
19pub struct SnapshotState {
20    /// Snapshot manager
21    pub manager: Arc<SnapshotManager>,
22    /// Consistency engine (optional, for saving/loading unified state)
23    pub consistency_engine: Option<Arc<ConsistencyEngine>>,
24}
25
26/// Request to save a snapshot
27#[derive(Debug, Deserialize)]
28pub struct SaveSnapshotRequest {
29    /// Snapshot name
30    pub name: String,
31    /// Optional description
32    pub description: Option<String>,
33    /// Components to include
34    pub components: Option<SnapshotComponents>,
35}
36
37/// Request to load a snapshot
38#[derive(Debug, Deserialize)]
39pub struct LoadSnapshotRequest {
40    /// Components to restore (optional, defaults to all)
41    pub components: Option<SnapshotComponents>,
42}
43
44/// Query parameters for snapshot operations
45#[derive(Debug, Deserialize)]
46pub struct SnapshotQuery {
47    /// Workspace ID (defaults to "default" if not provided)
48    #[serde(default = "default_workspace")]
49    pub workspace: String,
50}
51
52fn default_workspace() -> String {
53    "default".to_string()
54}
55
56/// Save a snapshot
57///
58/// POST /api/v1/snapshots?workspace={workspace_id}
59pub 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
89/// Load a snapshot
90///
91/// POST /api/v1/snapshots/{name}/load?workspace={workspace_id}
92pub 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
120/// List all snapshots
121///
122/// GET /api/v1/snapshots?workspace={workspace_id}
123pub 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(&params.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
139/// Get snapshot information
140///
141/// GET /api/v1/snapshots/{name}?workspace={workspace_id}
142pub 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
162/// Delete a snapshot
163///
164/// DELETE /api/v1/snapshots/{name}?workspace={workspace_id}
165pub 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
186/// Validate snapshot integrity
187///
188/// GET /api/v1/snapshots/{name}/validate?workspace={workspace_id}
189pub 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
211/// Create snapshot router
212pub 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}