version_migrate/
errors.rs

1//! Error types for migration operations.
2
3use thiserror::Error;
4
5/// Error types that can occur during migration operations.
6#[derive(Error, Debug)]
7pub enum MigrationError {
8    /// Failed to deserialize the data.
9    #[error("Failed to deserialize: {0}")]
10    DeserializationError(String),
11
12    /// Failed to serialize the data.
13    #[error("Failed to serialize: {0}")]
14    SerializationError(String),
15
16    /// The requested entity type was not found in the migrator.
17    #[error("Entity '{0}' not found")]
18    EntityNotFound(String),
19
20    /// No migration path is defined for the given entity and version.
21    #[error("No migration path defined for entity '{entity}' version '{version}'")]
22    MigrationPathNotDefined {
23        /// The entity name.
24        entity: String,
25        /// The version that has no migration path.
26        version: String,
27    },
28
29    /// A migration step failed during execution.
30    #[error("Migration failed from '{from}' to '{to}': {error}")]
31    MigrationStepFailed {
32        /// The source version.
33        from: String,
34        /// The target version.
35        to: String,
36        /// The error message.
37        error: String,
38    },
39
40    /// A circular migration path was detected.
41    #[error("Circular migration path detected in entity '{entity}': {path}")]
42    CircularMigrationPath {
43        /// The entity name.
44        entity: String,
45        /// The path that forms a cycle.
46        path: String,
47    },
48
49    /// Version ordering is invalid (not following semver rules).
50    #[error("Invalid version order in entity '{entity}': '{from}' -> '{to}' (versions must increase according to semver)")]
51    InvalidVersionOrder {
52        /// The entity name.
53        entity: String,
54        /// The source version.
55        from: String,
56        /// The target version.
57        to: String,
58    },
59
60    /// File I/O error.
61    #[error("File I/O error at '{path}': {error}")]
62    IoError {
63        /// The file path where the error occurred.
64        path: String,
65        /// The underlying I/O error.
66        error: String,
67    },
68
69    /// File locking error.
70    #[error("Failed to acquire file lock for '{path}': {error}")]
71    LockError {
72        /// The file path.
73        path: String,
74        /// The error message.
75        error: String,
76    },
77
78    /// TOML parsing error.
79    #[error("Failed to parse TOML: {0}")]
80    TomlParseError(String),
81
82    /// TOML serialization error.
83    #[error("Failed to serialize to TOML: {0}")]
84    TomlSerializeError(String),
85
86    /// Failed to find home directory.
87    #[error("Cannot determine home directory")]
88    HomeDirNotFound,
89
90    /// Failed to resolve path.
91    #[error("Failed to resolve path: {0}")]
92    PathResolution(String),
93
94    /// Failed to encode filename.
95    #[error("Failed to encode filename for ID '{id}': {reason}")]
96    FilenameEncoding {
97        /// The entity ID that failed to encode.
98        id: String,
99        /// The reason for the encoding failure.
100        reason: String,
101    },
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_error_display_deserialization() {
110        let err = MigrationError::DeserializationError("invalid JSON".to_string());
111        let display = format!("{}", err);
112        assert!(display.contains("Failed to deserialize"));
113        assert!(display.contains("invalid JSON"));
114    }
115
116    #[test]
117    fn test_error_display_serialization() {
118        let err = MigrationError::SerializationError("invalid data".to_string());
119        let display = format!("{}", err);
120        assert!(display.contains("Failed to serialize"));
121        assert!(display.contains("invalid data"));
122    }
123
124    #[test]
125    fn test_error_display_entity_not_found() {
126        let err = MigrationError::EntityNotFound("user".to_string());
127        let display = format!("{}", err);
128        assert!(display.contains("Entity 'user' not found"));
129    }
130
131    #[test]
132    fn test_error_display_migration_path_not_defined() {
133        let err = MigrationError::MigrationPathNotDefined {
134            entity: "task".to_string(),
135            version: "2.0.0".to_string(),
136        };
137        let display = format!("{}", err);
138        assert!(display.contains("No migration path defined"));
139        assert!(display.contains("task"));
140        assert!(display.contains("2.0.0"));
141    }
142
143    #[test]
144    fn test_error_display_migration_step_failed() {
145        let err = MigrationError::MigrationStepFailed {
146            from: "1.0.0".to_string(),
147            to: "2.0.0".to_string(),
148            error: "field missing".to_string(),
149        };
150        let display = format!("{}", err);
151        assert!(display.contains("Migration failed"));
152        assert!(display.contains("1.0.0"));
153        assert!(display.contains("2.0.0"));
154        assert!(display.contains("field missing"));
155    }
156
157    #[test]
158    fn test_error_debug() {
159        let err = MigrationError::EntityNotFound("test".to_string());
160        let debug = format!("{:?}", err);
161        assert!(debug.contains("EntityNotFound"));
162    }
163
164    #[test]
165    fn test_error_is_std_error() {
166        let err = MigrationError::DeserializationError("test".to_string());
167        // MigrationError should implement std::error::Error
168        let _: &dyn std::error::Error = &err;
169    }
170
171    #[test]
172    fn test_error_display_circular_migration_path() {
173        let err = MigrationError::CircularMigrationPath {
174            entity: "task".to_string(),
175            path: "1.0.0 -> 2.0.0 -> 1.0.0".to_string(),
176        };
177        let display = format!("{}", err);
178        assert!(display.contains("Circular migration path"));
179        assert!(display.contains("task"));
180        assert!(display.contains("1.0.0 -> 2.0.0 -> 1.0.0"));
181    }
182
183    #[test]
184    fn test_error_display_invalid_version_order() {
185        let err = MigrationError::InvalidVersionOrder {
186            entity: "task".to_string(),
187            from: "2.0.0".to_string(),
188            to: "1.0.0".to_string(),
189        };
190        let display = format!("{}", err);
191        assert!(display.contains("Invalid version order"));
192        assert!(display.contains("task"));
193        assert!(display.contains("2.0.0"));
194        assert!(display.contains("1.0.0"));
195        assert!(display.contains("must increase"));
196    }
197}