Development Documentation (main branch) - For stable release docs, see docs.rs/eidetica

eidetica/instance/
errors.rs

1//! Base database error types for the Eidetica library.
2//!
3//! This module defines structured error types for tree operations, entry management,
4//! and database operations, providing better error context and type safety compared to string-based errors.
5
6use thiserror::Error;
7
8use crate::entry::ID;
9
10/// Errors that can occur during base database operations.
11///
12/// # Stability
13///
14/// - New variants may be added in minor versions (enum is `#[non_exhaustive]`)
15/// - Existing variants will not be removed in minor versions
16/// - Field additions/changes require a major version bump
17/// - Helper methods like `is_*()` provide stable APIs
18#[non_exhaustive]
19#[derive(Debug, Error)]
20pub enum InstanceError {
21    /// Database not found by name.
22    #[error("Database not found: {name}")]
23    DatabaseNotFound {
24        /// The name of the database that was not found
25        name: String,
26    },
27
28    /// Database already exists with the given name.
29    #[error("Database already exists: {name}")]
30    DatabaseAlreadyExists {
31        /// The name of the database that already exists
32        name: String,
33    },
34
35    /// Instance already exists on this backend.
36    #[error("Instance already exists on backend (found device key and system databases)")]
37    InstanceAlreadyExists,
38
39    /// Entry does not belong to the specified database.
40    #[error("Entry '{entry_id}' does not belong to database '{database_id}'")]
41    EntryNotInDatabase {
42        /// The ID of the entry
43        entry_id: ID,
44        /// The ID of the database
45        database_id: ID,
46    },
47
48    /// Entry not found by ID.
49    #[error("Entry not found: {entry_id}")]
50    EntryNotFound {
51        /// The ID of the entry that was not found
52        entry_id: ID,
53    },
54
55    /// Transaction has already been committed and cannot be modified.
56    #[error("Transaction has already been committed")]
57    TransactionAlreadyCommitted,
58
59    /// Cannot create transaction with empty tips.
60    #[error("Cannot create transaction with empty tips")]
61    EmptyTipsNotAllowed,
62
63    /// Tip entry does not belong to the specified database.
64    #[error("Tip entry '{tip_id}' does not belong to database '{database_id}'")]
65    InvalidTip {
66        /// The ID of the invalid tip entry
67        tip_id: ID,
68        /// The ID of the database
69        database_id: ID,
70    },
71
72    /// Signing key not found in backend storage.
73    #[error("Signing key '{key_name}' not found in backend")]
74    SigningKeyNotFound {
75        /// The name of the signing key that was not found
76        key_name: String,
77    },
78
79    /// Authentication is required but no key is configured.
80    #[error("Authentication required but no key configured")]
81    AuthenticationRequired,
82
83    /// Device key not found in instance metadata.
84    #[error("Device key not found in instance metadata")]
85    DeviceKeyNotFound,
86
87    /// No authentication configuration found.
88    #[error("No authentication configuration found")]
89    NoAuthConfiguration,
90
91    /// Authentication validation failed.
92    #[error("Authentication validation failed: {reason}")]
93    AuthenticationValidationFailed {
94        /// Description of why authentication validation failed
95        reason: String,
96    },
97
98    /// Insufficient permissions for the requested operation.
99    #[error("Insufficient permissions for operation")]
100    InsufficientPermissions,
101
102    /// Signature verification failed.
103    #[error("Signature verification failed")]
104    SignatureVerificationFailed,
105
106    /// Invalid data type encountered.
107    #[error("Invalid data type: expected {expected}, got {actual}")]
108    InvalidDataType {
109        /// The expected data type
110        expected: String,
111        /// The actual data type found
112        actual: String,
113    },
114
115    /// Serialization failed.
116    #[error("Serialization failed for {context}")]
117    SerializationFailed {
118        /// The context where serialization failed
119        context: String,
120    },
121
122    /// Invalid database configuration.
123    #[error("Invalid database configuration: {reason}")]
124    InvalidDatabaseConfiguration {
125        /// Description of why the database configuration is invalid
126        reason: String,
127    },
128
129    /// Settings validation failed.
130    #[error("Settings validation failed: {reason}")]
131    SettingsValidationFailed {
132        /// Description of why settings validation failed
133        reason: String,
134    },
135
136    /// Invalid operation attempted.
137    #[error("Invalid operation: {reason}")]
138    InvalidOperation {
139        /// Description of why the operation is invalid
140        reason: String,
141    },
142
143    /// Database initialization failed.
144    #[error("Database initialization failed: {reason}")]
145    DatabaseInitializationFailed {
146        /// Description of why database initialization failed
147        reason: String,
148    },
149
150    /// Entry validation failed.
151    #[error("Entry validation failed: {reason}")]
152    EntryValidationFailed {
153        /// Description of why entry validation failed
154        reason: String,
155    },
156
157    /// Database state is corrupted or inconsistent.
158    #[error("Database state corruption detected: {reason}")]
159    DatabaseStateCorruption {
160        /// Description of the corruption detected
161        reason: String,
162    },
163
164    /// Operation is not supported in the current mode or not yet implemented.
165    #[error("Operation not supported: {operation}")]
166    OperationNotSupported {
167        /// Description of the unsupported operation
168        operation: String,
169    },
170
171    /// Instance has been dropped and is no longer available.
172    #[error("Instance has been dropped")]
173    InstanceDropped,
174
175    /// Sync has already been enabled on this Instance.
176    #[error("Sync has already been enabled on this Instance")]
177    SyncAlreadyEnabled,
178
179    /// System database not found during instance initialization.
180    #[error("System database not found: {database_name}")]
181    SystemDatabaseNotFound {
182        /// Name of the system database that was not found
183        database_name: String,
184    },
185}
186
187impl InstanceError {
188    /// Check if this error indicates a resource was not found.
189    pub fn is_not_found(&self) -> bool {
190        matches!(
191            self,
192            InstanceError::DatabaseNotFound { .. }
193                | InstanceError::EntryNotFound { .. }
194                | InstanceError::SigningKeyNotFound { .. }
195                | InstanceError::SystemDatabaseNotFound { .. }
196        )
197    }
198
199    /// Check if this error indicates a resource already exists.
200    pub fn is_already_exists(&self) -> bool {
201        matches!(
202            self,
203            InstanceError::DatabaseAlreadyExists { .. } | InstanceError::InstanceAlreadyExists
204        )
205    }
206
207    /// Check if this error is authentication-related.
208    pub fn is_authentication_error(&self) -> bool {
209        matches!(
210            self,
211            InstanceError::AuthenticationRequired
212                | InstanceError::NoAuthConfiguration
213                | InstanceError::AuthenticationValidationFailed { .. }
214                | InstanceError::InsufficientPermissions
215                | InstanceError::SignatureVerificationFailed
216                | InstanceError::SigningKeyNotFound { .. }
217        )
218    }
219
220    /// Check if this error is operation-related.
221    pub fn is_operation_error(&self) -> bool {
222        matches!(
223            self,
224            InstanceError::TransactionAlreadyCommitted
225                | InstanceError::EmptyTipsNotAllowed
226                | InstanceError::InvalidOperation { .. }
227        )
228    }
229
230    /// Check if this error is validation-related.
231    pub fn is_validation_error(&self) -> bool {
232        matches!(
233            self,
234            InstanceError::EntryNotInDatabase { .. }
235                | InstanceError::InvalidTip { .. }
236                | InstanceError::InvalidDataType { .. }
237                | InstanceError::InvalidDatabaseConfiguration { .. }
238                | InstanceError::SettingsValidationFailed { .. }
239                | InstanceError::EntryValidationFailed { .. }
240        )
241    }
242
243    /// Check if this error indicates corruption or inconsistency.
244    pub fn is_corruption_error(&self) -> bool {
245        matches!(self, InstanceError::DatabaseStateCorruption { .. })
246    }
247
248    /// Get the entry ID if this error is about a specific entry.
249    pub fn entry_id(&self) -> Option<&ID> {
250        match self {
251            InstanceError::EntryNotFound { entry_id }
252            | InstanceError::EntryNotInDatabase { entry_id, .. }
253            | InstanceError::InvalidTip {
254                tip_id: entry_id, ..
255            } => Some(entry_id),
256            _ => None,
257        }
258    }
259
260    /// Get the database ID if this error is about a specific database.
261    pub fn database_id(&self) -> Option<&ID> {
262        match self {
263            InstanceError::EntryNotInDatabase { database_id, .. }
264            | InstanceError::InvalidTip { database_id, .. } => Some(database_id),
265            _ => None,
266        }
267    }
268
269    /// Get the database name if this error is about a named database.
270    pub fn database_name(&self) -> Option<&str> {
271        match self {
272            InstanceError::DatabaseNotFound { name }
273            | InstanceError::DatabaseAlreadyExists { name } => Some(name),
274            _ => None,
275        }
276    }
277}
278
279// Conversion from BaseError to the main Error type
280impl From<InstanceError> for crate::Error {
281    fn from(err: InstanceError) -> Self {
282        // Use the new structured Base variant
283        crate::Error::Instance(err)
284    }
285}
286
287#[cfg(test)]
288mod tests {
289    use super::*;
290
291    #[test]
292    fn test_error_helpers() {
293        let err = InstanceError::DatabaseNotFound {
294            name: "test-database".to_string(),
295        };
296        assert!(err.is_not_found());
297        assert_eq!(err.database_name(), Some("test-database"));
298
299        let err = InstanceError::DatabaseAlreadyExists {
300            name: "existing-database".to_string(),
301        };
302        assert!(err.is_already_exists());
303        assert_eq!(err.database_name(), Some("existing-database"));
304
305        let err = InstanceError::InstanceAlreadyExists;
306        assert!(err.is_already_exists());
307
308        let err = InstanceError::EntryNotFound {
309            entry_id: ID::from("test-entry"),
310        };
311        assert!(err.is_not_found());
312        assert_eq!(err.entry_id(), Some(&ID::from("test-entry")));
313
314        let err = InstanceError::AuthenticationRequired;
315        assert!(err.is_authentication_error());
316
317        let err = InstanceError::TransactionAlreadyCommitted;
318        assert!(err.is_operation_error());
319
320        let err = InstanceError::InvalidDataType {
321            expected: "string".to_string(),
322            actual: "number".to_string(),
323        };
324        assert!(err.is_validation_error());
325
326        let err = InstanceError::DatabaseStateCorruption {
327            reason: "test".to_string(),
328        };
329        assert!(err.is_corruption_error());
330    }
331
332    #[test]
333    fn test_error_conversion() {
334        let base_err = InstanceError::DatabaseNotFound {
335            name: "test".to_string(),
336        };
337        let err: crate::Error = base_err.into();
338        match err {
339            crate::Error::Instance(InstanceError::DatabaseNotFound { name }) => {
340                assert_eq!(name, "test")
341            }
342            _ => panic!("Unexpected error variant"),
343        }
344    }
345}