Skip to main content

kanban_persistence/
error.rs

1use thiserror::Error;
2
3#[derive(Error, Debug)]
4pub enum PersistenceError {
5    #[error("IO error: {0}")]
6    Io(#[from] std::io::Error),
7
8    #[error("serialization error: {0}")]
9    Serialization(String),
10
11    #[error("database error: {0}")]
12    Database(String),
13
14    #[error("unsupported storage locator {locator:?}; supported: {}", supported.join(", "))]
15    UnsupportedLocator {
16        locator: String,
17        supported: Vec<String>,
18    },
19
20    #[error("file conflict: {path} was modified by another instance")]
21    ConflictDetected {
22        path: String,
23        #[source]
24        source: Option<Box<dyn std::error::Error + Send + Sync>>,
25    },
26
27    #[error("unsupported operation: {0}")]
28    Unsupported(String),
29
30    #[error(
31        "file format v{file_version} is newer than this binary's max v{binary_max}; \
32         please upgrade kanban"
33    )]
34    UnsupportedFutureVersion { file_version: u32, binary_max: u32 },
35}
36
37pub type PersistenceResult<T> = Result<T, PersistenceError>;
38
39impl From<PersistenceError> for kanban_domain::KanbanError {
40    fn from(e: PersistenceError) -> Self {
41        match e {
42            PersistenceError::Io(io) => kanban_domain::KanbanError::Io(io),
43            PersistenceError::Serialization(s) => kanban_domain::KanbanError::Serialization(s),
44            PersistenceError::Database(s) => kanban_domain::KanbanError::Database(s),
45            PersistenceError::UnsupportedLocator { locator, supported } => {
46                kanban_domain::KanbanError::Internal(format!(
47                    "No backend registered for {:?}. Available backends: {}",
48                    locator,
49                    if supported.is_empty() {
50                        "none".to_string()
51                    } else {
52                        supported.join(", ")
53                    }
54                ))
55            }
56            PersistenceError::ConflictDetected { path, source } => {
57                kanban_domain::KanbanError::ConflictDetected { path, source }
58            }
59            PersistenceError::Unsupported(s) => kanban_domain::KanbanError::Internal(s),
60            PersistenceError::UnsupportedFutureVersion {
61                file_version,
62                binary_max,
63            } => kanban_domain::KanbanError::UnsupportedFutureVersion {
64                file_version,
65                binary_max,
66            },
67        }
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use kanban_domain::KanbanError;
75
76    #[test]
77    fn test_unsupported_future_version_display_mentions_both_versions() {
78        let err = PersistenceError::UnsupportedFutureVersion {
79            file_version: 99,
80            binary_max: 6,
81        };
82        let msg = err.to_string();
83        assert!(msg.contains("99"), "msg: {msg}");
84        assert!(msg.contains('6'), "msg: {msg}");
85    }
86
87    #[test]
88    fn test_persistence_unsupported_future_version_maps_to_kanban_error() {
89        let pe = PersistenceError::UnsupportedFutureVersion {
90            file_version: 99,
91            binary_max: 6,
92        };
93        let ke: KanbanError = pe.into();
94        assert!(
95            matches!(
96                ke,
97                KanbanError::UnsupportedFutureVersion {
98                    file_version: 99,
99                    binary_max: 6
100                }
101            ),
102            "expected UnsupportedFutureVersion variant"
103        );
104    }
105}