Skip to main content

convergio_backup/
types.rs

1//! Core types for the backup module.
2
3use serde::{Deserialize, Serialize};
4
5/// Errors produced by the backup module.
6#[derive(Debug, thiserror::Error)]
7pub enum BackupError {
8    #[error("database error: {0}")]
9    Db(#[from] rusqlite::Error),
10
11    #[error("pool error: {0}")]
12    Pool(#[from] r2d2::Error),
13
14    #[error("io error: {0}")]
15    Io(#[from] std::io::Error),
16
17    #[error("json error: {0}")]
18    Json(#[from] serde_json::Error),
19
20    #[error("snapshot not found: {0}")]
21    SnapshotNotFound(String),
22
23    #[error("invalid config: {0}")]
24    InvalidConfig(String),
25
26    #[error("restore failed: {0}")]
27    RestoreFailed(String),
28}
29
30pub type BackupResult<T> = Result<T, BackupError>;
31
32/// Retention policy for a single table.
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct RetentionRule {
35    /// Table name to apply the policy to.
36    pub table: String,
37    /// Column containing the timestamp (e.g. "created_at").
38    pub timestamp_column: String,
39    /// Maximum age in days. Rows older than this are purged.
40    pub max_age_days: u32,
41}
42
43/// A completed snapshot record.
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct SnapshotRecord {
46    pub id: String,
47    pub path: String,
48    pub size_bytes: i64,
49    pub checksum: String,
50    pub created_at: String,
51    pub node: String,
52}
53
54/// Org export package metadata.
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct OrgExportMeta {
57    pub org_id: String,
58    pub org_name: String,
59    pub exported_at: String,
60    pub node: String,
61    pub tables: Vec<String>,
62    pub row_counts: Vec<(String, i64)>,
63    pub version: String,
64}
65
66/// Purge event — emitted after auto-purge runs.
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct PurgeEvent {
69    pub table: String,
70    pub rows_deleted: i64,
71    pub cutoff_date: String,
72    pub executed_at: String,
73}
74
75/// Default retention rules per spec.
76pub fn default_retention_rules() -> Vec<RetentionRule> {
77    vec![
78        RetentionRule {
79            table: "audit_log".into(),
80            timestamp_column: "created_at".into(),
81            max_age_days: 365,
82        },
83        RetentionRule {
84            table: "ipc_messages".into(),
85            timestamp_column: "created_at".into(),
86            max_age_days: 30,
87        },
88    ]
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn default_rules_cover_required_tables() {
97        let rules = default_retention_rules();
98        assert_eq!(rules.len(), 2);
99        assert_eq!(rules[0].table, "audit_log");
100        assert_eq!(rules[0].max_age_days, 365);
101        assert_eq!(rules[1].table, "ipc_messages");
102        assert_eq!(rules[1].max_age_days, 30);
103    }
104
105    #[test]
106    fn snapshot_record_serializes() {
107        let rec = SnapshotRecord {
108            id: "snap-001".into(),
109            path: "/tmp/backup.db".into(),
110            size_bytes: 1024,
111            checksum: "abc123".into(),
112            created_at: "2026-04-03T00:00:00Z".into(),
113            node: "m5max".into(),
114        };
115        let json = serde_json::to_string(&rec).unwrap();
116        assert!(json.contains("snap-001"));
117    }
118
119    #[test]
120    fn purge_event_serializes() {
121        let ev = PurgeEvent {
122            table: "audit_log".into(),
123            rows_deleted: 42,
124            cutoff_date: "2025-04-03".into(),
125            executed_at: "2026-04-03T00:00:00Z".into(),
126        };
127        let json = serde_json::to_string(&ev).unwrap();
128        assert!(json.contains("audit_log"));
129        assert!(json.contains("42"));
130    }
131}