Skip to main content

prax_migrate/
error.rs

1//! Error types for the migration engine.
2
3use thiserror::Error;
4
5/// Result type alias for migration operations.
6pub type MigrateResult<T> = Result<T, MigrationError>;
7
8/// Errors that can occur during migration operations.
9#[derive(Debug, Error)]
10pub enum MigrationError {
11    /// File system error.
12    #[error("I/O error: {0}")]
13    Io(#[from] std::io::Error),
14
15    /// Database operation error.
16    #[error("Database error: {0}")]
17    Database(String),
18
19    /// Schema parsing error.
20    #[error("Schema error: {0}")]
21    Schema(String),
22
23    /// Invalid migration file or format.
24    #[error("Invalid migration: {0}")]
25    InvalidMigration(String),
26
27    /// Migration checksum mismatch.
28    #[error("Checksum mismatch for migration '{id}': expected {expected}, got {actual}")]
29    ChecksumMismatch {
30        /// Migration ID.
31        id: String,
32        /// Expected checksum.
33        expected: String,
34        /// Actual checksum.
35        actual: String,
36    },
37
38    /// Migration already applied.
39    #[error("Migration '{0}' has already been applied")]
40    AlreadyApplied(String),
41
42    /// Migration not found.
43    #[error("Migration '{0}' not found")]
44    NotFound(String),
45
46    /// Data loss would occur.
47    #[error("Data loss would occur: {0}")]
48    DataLoss(String),
49
50    /// Lock acquisition failed.
51    #[error("Failed to acquire migration lock: {0}")]
52    LockFailed(String),
53
54    /// No changes to migrate.
55    #[error("No schema changes detected")]
56    NoChanges,
57
58    /// Rollback not possible.
59    #[error("Cannot rollback: {0}")]
60    RollbackFailed(String),
61
62    /// Shadow database error.
63    #[error("Shadow database error: {0}")]
64    ShadowDatabaseError(String),
65
66    /// Resolution file error.
67    #[error("Resolution file error: {0}")]
68    ResolutionFile(String),
69
70    /// Resolution conflict.
71    #[error("Resolution conflict: {0}")]
72    ResolutionConflict(String),
73
74    /// Migration conflict detected.
75    #[error("Migration conflict: migrations '{0}' and '{1}' conflict")]
76    MigrationConflict(String, String),
77
78    /// General migration error.
79    #[error("Migration error: {0}")]
80    Other(String),
81}
82
83impl MigrationError {
84    /// Create a database error.
85    pub fn database(msg: impl Into<String>) -> Self {
86        Self::Database(msg.into())
87    }
88
89    /// Create a schema error.
90    pub fn schema(msg: impl Into<String>) -> Self {
91        Self::Schema(msg.into())
92    }
93
94    /// Create a data loss error.
95    pub fn data_loss(msg: impl Into<String>) -> Self {
96        Self::DataLoss(msg.into())
97    }
98
99    /// Create a lock failed error.
100    pub fn lock_failed(msg: impl Into<String>) -> Self {
101        Self::LockFailed(msg.into())
102    }
103
104    /// Create an other error.
105    pub fn other(msg: impl Into<String>) -> Self {
106        Self::Other(msg.into())
107    }
108
109    /// Create a shadow database error.
110    pub fn shadow_database(msg: impl Into<String>) -> Self {
111        Self::ShadowDatabaseError(msg.into())
112    }
113
114    /// Create a resolution file error.
115    pub fn resolution_file(msg: impl Into<String>) -> Self {
116        Self::ResolutionFile(msg.into())
117    }
118
119    /// Create a resolution conflict error.
120    pub fn resolution_conflict(msg: impl Into<String>) -> Self {
121        Self::ResolutionConflict(msg.into())
122    }
123
124    /// Create a migration conflict error.
125    pub fn migration_conflict(m1: impl Into<String>, m2: impl Into<String>) -> Self {
126        Self::MigrationConflict(m1.into(), m2.into())
127    }
128
129    /// Create a migration file error.
130    pub fn migration_file(msg: impl Into<String>) -> Self {
131        Self::InvalidMigration(msg.into())
132    }
133
134    /// Check if this is a recoverable error.
135    pub fn is_recoverable(&self) -> bool {
136        matches!(
137            self,
138            Self::LockFailed(_) | Self::AlreadyApplied(_) | Self::NoChanges
139        )
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn test_error_display() {
149        let err = MigrationError::NotFound("20231215_test".to_string());
150        assert!(err.to_string().contains("20231215_test"));
151    }
152
153    #[test]
154    fn test_checksum_mismatch_display() {
155        let err = MigrationError::ChecksumMismatch {
156            id: "test".to_string(),
157            expected: "abc".to_string(),
158            actual: "xyz".to_string(),
159        };
160        let msg = err.to_string();
161        assert!(msg.contains("abc"));
162        assert!(msg.contains("xyz"));
163    }
164
165    #[test]
166    fn test_is_recoverable() {
167        assert!(MigrationError::NoChanges.is_recoverable());
168        assert!(MigrationError::LockFailed("timeout".to_string()).is_recoverable());
169        assert!(!MigrationError::Database("connection".to_string()).is_recoverable());
170    }
171}