Skip to main content

gid_core/storage/
error.rs

1use std::fmt;
2
3/// Categories of storage operations, used for error context.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum StorageOp {
6    Open,
7    Read,
8    Write,
9    Delete,
10    Search,
11    Migrate,
12    Snapshot,
13}
14
15impl fmt::Display for StorageOp {
16    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17        match self {
18            StorageOp::Open => write!(f, "open"),
19            StorageOp::Read => write!(f, "read"),
20            StorageOp::Write => write!(f, "write"),
21            StorageOp::Delete => write!(f, "delete"),
22            StorageOp::Search => write!(f, "search"),
23            StorageOp::Migrate => write!(f, "migrate"),
24            StorageOp::Snapshot => write!(f, "snapshot"),
25        }
26    }
27}
28
29/// Errors originating from the storage layer.
30#[derive(Debug)]
31pub enum StorageError {
32    /// I/O error (file system, path issues).
33    Io {
34        op: StorageOp,
35        detail: String,
36        source: Option<Box<dyn std::error::Error + Send + Sync>>,
37    },
38    /// SQLite-level error (query failure, constraint violation, busy, etc.).
39    Sqlite {
40        op: StorageOp,
41        detail: String,
42        source: Option<Box<dyn std::error::Error + Send + Sync>>,
43    },
44    /// Requested entity (node, edge, metadata key) was not found.
45    NotFound {
46        op: StorageOp,
47        detail: String,
48        source: Option<Box<dyn std::error::Error + Send + Sync>>,
49    },
50    /// Attempted to create an entity that already exists.
51    AlreadyExists {
52        op: StorageOp,
53        detail: String,
54        source: Option<Box<dyn std::error::Error + Send + Sync>>,
55    },
56    /// Schema migration failure.
57    Migration {
58        op: StorageOp,
59        detail: String,
60        source: Option<Box<dyn std::error::Error + Send + Sync>>,
61    },
62    /// Data could not be deserialized or is otherwise malformed.
63    InvalidData {
64        op: StorageOp,
65        detail: String,
66        source: Option<Box<dyn std::error::Error + Send + Sync>>,
67    },
68    /// Detected database corruption (integrity check failure, unexpected state).
69    Corruption {
70        op: StorageOp,
71        detail: String,
72        source: Option<Box<dyn std::error::Error + Send + Sync>>,
73    },
74    /// Database is locked by another writer (SQLITE_BUSY after timeout).
75    /// GOAL-1.17: write contention handling.
76    DatabaseLocked {
77        op: StorageOp,
78        detail: String,
79        source: Option<Box<dyn std::error::Error + Send + Sync>>,
80    },
81    /// Foreign key constraint violation.
82    ForeignKeyViolation {
83        op: StorageOp,
84        detail: String,
85        source: Option<Box<dyn std::error::Error + Send + Sync>>,
86    },
87    /// Schema version mismatch between expected and found versions.
88    SchemaMismatch {
89        expected: String,
90        found: String,
91    },
92}
93
94impl StorageError {
95    /// Helper to extract the operation from any variant.
96    pub fn op(&self) -> StorageOp {
97        match self {
98            StorageError::Io { op, .. }
99            | StorageError::Sqlite { op, .. }
100            | StorageError::NotFound { op, .. }
101            | StorageError::AlreadyExists { op, .. }
102            | StorageError::Migration { op, .. }
103            | StorageError::InvalidData { op, .. }
104            | StorageError::Corruption { op, .. }
105            | StorageError::DatabaseLocked { op, .. }
106            | StorageError::ForeignKeyViolation { op, .. } => *op,
107            StorageError::SchemaMismatch { .. } => StorageOp::Migrate,
108        }
109    }
110
111    /// Helper to extract the detail message from any variant.
112    pub fn detail(&self) -> &str {
113        match self {
114            StorageError::Io { detail, .. }
115            | StorageError::Sqlite { detail, .. }
116            | StorageError::NotFound { detail, .. }
117            | StorageError::AlreadyExists { detail, .. }
118            | StorageError::Migration { detail, .. }
119            | StorageError::InvalidData { detail, .. }
120            | StorageError::Corruption { detail, .. }
121            | StorageError::DatabaseLocked { detail, .. }
122            | StorageError::ForeignKeyViolation { detail, .. } => detail,
123            StorageError::SchemaMismatch { expected, .. } => expected,
124        }
125    }
126}
127
128impl fmt::Display for StorageError {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        match self {
131            StorageError::Io { op, detail, .. } => {
132                write!(f, "storage I/O error during {op}: {detail}")
133            }
134            StorageError::Sqlite { op, detail, .. } => {
135                write!(f, "SQLite error during {op}: {detail}")
136            }
137            StorageError::NotFound { op, detail, .. } => {
138                write!(f, "not found during {op}: {detail}")
139            }
140            StorageError::AlreadyExists { op, detail, .. } => {
141                write!(f, "already exists during {op}: {detail}")
142            }
143            StorageError::Migration { op, detail, .. } => {
144                write!(f, "migration error during {op}: {detail}")
145            }
146            StorageError::InvalidData { op, detail, .. } => {
147                write!(f, "invalid data during {op}: {detail}")
148            }
149            StorageError::Corruption { op, detail, .. } => {
150                write!(f, "corruption detected during {op}: {detail}")
151            }
152            StorageError::DatabaseLocked { op, detail, .. } => {
153                write!(f, "database is locked during {op}: {detail}")
154            }
155            StorageError::ForeignKeyViolation { op, detail, .. } => {
156                write!(f, "foreign key violation during {op}: {detail}")
157            }
158            StorageError::SchemaMismatch { expected, found } => {
159                write!(f, "schema version mismatch: expected {expected}, found {found}")
160            }
161        }
162    }
163}
164
165impl std::error::Error for StorageError {
166    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
167        let src = match self {
168            StorageError::Io { source, .. }
169            | StorageError::Sqlite { source, .. }
170            | StorageError::NotFound { source, .. }
171            | StorageError::AlreadyExists { source, .. }
172            | StorageError::Migration { source, .. }
173            | StorageError::InvalidData { source, .. }
174            | StorageError::Corruption { source, .. }
175            | StorageError::DatabaseLocked { source, .. }
176            | StorageError::ForeignKeyViolation { source, .. } => source,
177            StorageError::SchemaMismatch { .. } => return None,
178        };
179        src.as_ref().map(|e| e.as_ref() as &(dyn std::error::Error + 'static))
180    }
181}
182
183impl From<std::io::Error> for StorageError {
184    fn from(err: std::io::Error) -> Self {
185        StorageError::Io {
186            op: StorageOp::Read,
187            detail: err.to_string(),
188            source: Some(Box::new(err)),
189        }
190    }
191}
192
193impl From<serde_json::Error> for StorageError {
194    fn from(err: serde_json::Error) -> Self {
195        StorageError::InvalidData {
196            op: StorageOp::Read,
197            detail: err.to_string(),
198            source: Some(Box::new(err)),
199        }
200    }
201}
202
203/// Convenience alias for storage results.
204pub type StorageResult<T> = Result<T, StorageError>;