Skip to main content

fraiseql_server/backup/
backup_provider.rs

1//! Backup provider trait.
2
3use serde::{Deserialize, Serialize};
4
5/// Result type for backup operations.
6pub type BackupResult<T> = Result<T, BackupError>;
7
8/// Backup operation errors.
9#[derive(Debug, Clone)]
10pub enum BackupError {
11    /// Connection failed
12    ConnectionFailed { store: String, message: String },
13    /// Backup failed
14    BackupFailed { store: String, message: String },
15    /// Restore failed
16    RestoreFailed { store: String, message: String },
17    /// Verification failed
18    VerificationFailed { store: String, message: String },
19    /// Storage error
20    StorageError { message: String },
21    /// Not found
22    NotFound {
23        store:     String,
24        backup_id: String,
25    },
26    /// Timeout
27    Timeout { store: String },
28}
29
30impl std::fmt::Display for BackupError {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        match self {
33            Self::ConnectionFailed { store, message } => {
34                write!(f, "Failed to connect to {}: {}", store, message)
35            },
36            Self::BackupFailed { store, message } => {
37                write!(f, "Backup failed for {}: {}", store, message)
38            },
39            Self::RestoreFailed { store, message } => {
40                write!(f, "Restore failed for {}: {}", store, message)
41            },
42            Self::VerificationFailed { store, message } => {
43                write!(f, "Verification failed for {}: {}", store, message)
44            },
45            Self::StorageError { message } => write!(f, "Storage error: {}", message),
46            Self::NotFound { store, backup_id } => {
47                write!(f, "Backup not found for {}: {}", store, backup_id)
48            },
49            Self::Timeout { store } => write!(f, "Backup timeout for {}", store),
50        }
51    }
52}
53
54impl std::error::Error for BackupError {}
55
56/// Backup information.
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct BackupInfo {
59    /// Unique backup identifier
60    pub backup_id: String,
61
62    /// Data store name (postgres, redis, etc.)
63    pub store_name: String,
64
65    /// Backup timestamp (Unix seconds)
66    pub timestamp: i64,
67
68    /// Backup size in bytes
69    pub size_bytes: u64,
70
71    /// Whether backup is verified
72    pub verified: bool,
73
74    /// Compression algorithm (if any)
75    pub compression: Option<String>,
76
77    /// Additional metadata
78    pub metadata: std::collections::HashMap<String, String>,
79}
80
81impl BackupInfo {
82    /// Get human-readable timestamp.
83    pub fn timestamp_display(&self) -> String {
84        let secs = self.timestamp;
85        let duration = std::time::UNIX_EPOCH + std::time::Duration::from_secs(secs as u64);
86        match duration.elapsed() {
87            Ok(_) => format_timestamp(secs),
88            Err(_) => format_timestamp(secs),
89        }
90    }
91
92    /// Get human-readable size.
93    pub fn size_display(&self) -> String {
94        format_size_bytes(self.size_bytes)
95    }
96}
97
98/// Backup provider trait for each data store.
99#[async_trait::async_trait]
100pub trait BackupProvider: Send + Sync {
101    /// Get the name of this provider (e.g., "postgres", "redis").
102    fn name(&self) -> &str;
103
104    /// Check if provider is healthy and connected.
105    async fn health_check(&self) -> BackupResult<()>;
106
107    /// Create a new backup.
108    ///
109    /// Returns backup info on success.
110    async fn backup(&self) -> BackupResult<BackupInfo>;
111
112    /// Restore from a backup.
113    ///
114    /// # Arguments
115    /// * `backup_id` - The backup to restore from
116    /// * `verify` - Whether to verify after restore
117    async fn restore(&self, backup_id: &str, verify: bool) -> BackupResult<()>;
118
119    /// List all available backups.
120    async fn list_backups(&self) -> BackupResult<Vec<BackupInfo>>;
121
122    /// Get a specific backup by ID.
123    async fn get_backup(&self, backup_id: &str) -> BackupResult<BackupInfo>;
124
125    /// Delete a backup.
126    async fn delete_backup(&self, backup_id: &str) -> BackupResult<()>;
127
128    /// Verify a backup is restorable.
129    async fn verify_backup(&self, backup_id: &str) -> BackupResult<()>;
130
131    /// Get storage usage.
132    async fn get_storage_usage(&self) -> BackupResult<StorageUsage>;
133}
134
135/// Storage usage statistics.
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct StorageUsage {
138    /// Total backup size in bytes
139    pub total_bytes: u64,
140
141    /// Number of backups
142    pub backup_count: u32,
143
144    /// Oldest backup timestamp (Unix seconds)
145    pub oldest_backup_timestamp: Option<i64>,
146
147    /// Newest backup timestamp (Unix seconds)
148    pub newest_backup_timestamp: Option<i64>,
149}
150
151// Helper functions
152
153fn format_timestamp(secs: i64) -> String {
154    // Simple formatting - in production would use chrono or similar
155    format!("{}", secs)
156}
157
158fn format_size_bytes(bytes: u64) -> String {
159    const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
160    let mut size = bytes as f64;
161    let mut unit_idx = 0;
162
163    while size >= 1024.0 && unit_idx < UNITS.len() - 1 {
164        size /= 1024.0;
165        unit_idx += 1;
166    }
167
168    format!("{:.2} {}", size, UNITS[unit_idx])
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    #[test]
176    fn test_format_size_bytes() {
177        assert_eq!(format_size_bytes(100), "100.00 B");
178        assert_eq!(format_size_bytes(1024), "1.00 KB");
179        assert_eq!(format_size_bytes(1024 * 1024), "1.00 MB");
180        assert_eq!(format_size_bytes(1024 * 1024 * 1024), "1.00 GB");
181    }
182
183    #[test]
184    fn test_backup_info_display() {
185        let info = BackupInfo {
186            backup_id:   "backup-123".to_string(),
187            store_name:  "postgres".to_string(),
188            timestamp:   1_000_000,
189            size_bytes:  1024 * 1024,
190            verified:    true,
191            compression: Some("gzip".to_string()),
192            metadata:    Default::default(),
193        };
194
195        assert_eq!(info.size_display(), "1.00 MB");
196        assert!(!info.timestamp_display().is_empty());
197    }
198
199    #[test]
200    fn test_backup_error_display() {
201        let err = BackupError::BackupFailed {
202            store:   "postgres".to_string(),
203            message: "Connection timeout".to_string(),
204        };
205        assert!(err.to_string().contains("postgres"));
206        assert!(err.to_string().contains("Connection timeout"));
207    }
208}