Skip to main content

mdk_sqlite_storage/
error.rs

1//! Error types for the SQLite storage implementation.
2
3/// Error type for SQLite storage operations.
4#[derive(Debug, thiserror::Error)]
5pub enum Error {
6    /// SQLite database error
7    #[error("Database error: {0}")]
8    Database(String),
9    /// Error from rusqlite
10    #[error("SQLite error: {0}")]
11    Rusqlite(#[from] rusqlite::Error),
12    /// Error during database migration
13    #[error("Migration error: {0}")]
14    Refinery(#[from] refinery::Error),
15    /// Error from OpenMLS
16    #[error("OpenMLS error: {0}")]
17    OpenMls(String),
18    /// Input validation error
19    #[error("{field_name} exceeds maximum length of {max_size} bytes (got {actual_size} bytes)")]
20    Validation {
21        /// Name of the field that failed validation
22        field_name: String,
23        /// Maximum allowed size/length in bytes
24        max_size: usize,
25        /// Actual size/length in bytes
26        actual_size: usize,
27    },
28
29    // Encryption-related errors
30    /// Database encryption key has invalid length (expected 32 bytes)
31    #[error("Invalid encryption key length: expected 32 bytes, got {0} bytes")]
32    InvalidKeyLength(usize),
33
34    /// Wrong encryption key provided for existing database
35    #[error("Wrong encryption key: database cannot be decrypted with the provided key")]
36    WrongEncryptionKey,
37
38    /// Attempted to open an unencrypted database with encryption enabled
39    #[error(
40        "Cannot open unencrypted database with encryption: database was created without encryption"
41    )]
42    UnencryptedDatabaseWithEncryption,
43
44    /// Failed to generate random key
45    #[error("Failed to generate encryption key: {0}")]
46    KeyGeneration(String),
47
48    /// File permission error
49    #[error("File permission error: {0}")]
50    FilePermission(String),
51
52    // Keyring-related errors
53    /// Keyring operation failed
54    #[error("Keyring error: {0}")]
55    Keyring(String),
56
57    /// Keyring store not initialized
58    ///
59    /// The host application must initialize a platform-specific keyring store
60    /// before using encrypted storage. See the MDK documentation for platform-specific
61    /// setup instructions.
62    #[error(
63        "Keyring store not initialized. The host application must call keyring_core::set_default_store() with a platform-specific store before using encrypted storage. Details: {0}"
64    )]
65    KeyringNotInitialized(String),
66
67    /// Keyring entry missing for existing database
68    ///
69    /// The database file exists but the encryption key is not in the keyring.
70    /// This can happen if the keyring was cleared, the key was deleted, or the
71    /// database was copied from another machine. The database cannot be opened
72    /// without the original encryption key.
73    #[error(
74        "Database exists at '{db_path}' but no encryption key found in keyring (service='{service_id}', key='{db_key_id}'). The database cannot be opened without the original encryption key. If the key was lost, the database data is unrecoverable."
75    )]
76    KeyringEntryMissingForExistingDatabase {
77        /// Path to the database file
78        db_path: String,
79        /// Service identifier used for keyring lookup
80        service_id: String,
81        /// Key identifier used for keyring lookup
82        db_key_id: String,
83    },
84}
85
86impl From<std::io::Error> for Error {
87    fn from(e: std::io::Error) -> Self {
88        Self::Database(format!("IO error: {}", e))
89    }
90}
91
92impl From<Error> for rusqlite::Error {
93    fn from(err: Error) -> Self {
94        rusqlite::Error::FromSqlConversionFailure(
95            0,
96            rusqlite::types::Type::Text,
97            Box::new(std::io::Error::new(
98                std::io::ErrorKind::InvalidData,
99                err.to_string(),
100            )),
101        )
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_error_display_database() {
111        let err = Error::Database("connection failed".to_string());
112        assert!(err.to_string().contains("connection failed"));
113        assert!(err.to_string().contains("Database error"));
114    }
115
116    #[test]
117    fn test_error_display_openmls() {
118        let err = Error::OpenMls("key generation failed".to_string());
119        assert_eq!(err.to_string(), "OpenMLS error: key generation failed");
120    }
121
122    #[test]
123    fn test_error_display_invalid_key_length() {
124        let err = Error::InvalidKeyLength(16);
125        let msg = err.to_string();
126        assert!(msg.contains("16"));
127        assert!(msg.contains("32"));
128    }
129
130    #[test]
131    fn test_error_display_wrong_encryption_key() {
132        let err = Error::WrongEncryptionKey;
133        let msg = err.to_string();
134        assert!(msg.contains("Wrong encryption key"));
135    }
136
137    #[test]
138    fn test_error_display_unencrypted_database_with_encryption() {
139        let err = Error::UnencryptedDatabaseWithEncryption;
140        let msg = err.to_string();
141        assert!(msg.contains("unencrypted database"));
142    }
143
144    #[test]
145    fn test_error_display_key_generation() {
146        let err = Error::KeyGeneration("entropy failure".to_string());
147        let msg = err.to_string();
148        assert!(msg.contains("entropy failure"));
149        assert!(msg.contains("generate encryption key"));
150    }
151
152    #[test]
153    fn test_error_display_file_permission() {
154        let err = Error::FilePermission("access denied".to_string());
155        let msg = err.to_string();
156        assert!(msg.contains("access denied"));
157        assert!(msg.contains("permission"));
158    }
159
160    #[test]
161    fn test_error_display_keyring() {
162        let err = Error::Keyring("keychain locked".to_string());
163        let msg = err.to_string();
164        assert!(msg.contains("keychain locked"));
165    }
166
167    #[test]
168    fn test_error_display_keyring_not_initialized() {
169        let err = Error::KeyringNotInitialized("no store configured".to_string());
170        let msg = err.to_string();
171        assert!(msg.contains("not initialized"));
172        assert!(msg.contains("set_default_store"));
173    }
174
175    #[test]
176    fn test_error_display_validation() {
177        let err = Error::Validation {
178            field_name: "name".to_string(),
179            max_size: 100,
180            actual_size: 150,
181        };
182        let msg = err.to_string();
183        assert!(msg.contains("name"));
184        assert!(msg.contains("100"));
185        assert!(msg.contains("150"));
186    }
187
188    #[test]
189    fn test_error_debug() {
190        let err = Error::Database("test".to_string());
191        let debug_str = format!("{:?}", err);
192        assert!(debug_str.contains("Database"));
193        assert!(debug_str.contains("test"));
194    }
195
196    #[test]
197    fn test_error_from_io_error() {
198        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
199        let err: Error = io_err.into();
200        match err {
201            Error::Database(msg) => assert!(msg.contains("file not found")),
202            _ => panic!("Expected Database error variant"),
203        }
204    }
205
206    #[test]
207    fn test_error_from_rusqlite_error() {
208        let rusqlite_err = rusqlite::Error::InvalidQuery;
209        let err: Error = rusqlite_err.into();
210
211        match err {
212            Error::Rusqlite(_) => {}
213            _ => panic!("Expected Rusqlite variant"),
214        }
215    }
216
217    #[test]
218    fn test_error_into_rusqlite_error() {
219        let err = Error::WrongEncryptionKey;
220        let rusqlite_err: rusqlite::Error = err.into();
221        // Verify it converts to a rusqlite error (the specific type is less important)
222        let msg = rusqlite_err.to_string();
223        assert!(!msg.is_empty());
224    }
225
226    #[test]
227    fn test_error_to_rusqlite_error() {
228        let err = Error::Database("test error".to_string());
229        let rusqlite_err: rusqlite::Error = err.into();
230
231        match rusqlite_err {
232            rusqlite::Error::FromSqlConversionFailure(_, _, _) => {}
233            _ => panic!("Expected FromSqlConversionFailure variant"),
234        }
235    }
236
237    #[test]
238    fn test_error_debug_format() {
239        let err = Error::InvalidKeyLength(24);
240        let debug_str = format!("{:?}", err);
241        assert!(debug_str.contains("InvalidKeyLength"));
242        assert!(debug_str.contains("24"));
243    }
244
245    #[test]
246    fn test_error_display_keyring_entry_missing_for_existing_database() {
247        let err = Error::KeyringEntryMissingForExistingDatabase {
248            db_path: "/path/to/db.sqlite".to_string(),
249            service_id: "com.example.app".to_string(),
250            db_key_id: "mdk.db.key".to_string(),
251        };
252        let msg = err.to_string();
253        assert!(msg.contains("/path/to/db.sqlite"));
254        assert!(msg.contains("com.example.app"));
255        assert!(msg.contains("mdk.db.key"));
256        assert!(msg.contains("no encryption key found"));
257        assert!(msg.contains("unrecoverable"));
258    }
259
260    #[test]
261    fn test_validation_error_fields() {
262        let err = Error::Validation {
263            field_name: "description".to_string(),
264            max_size: 1024,
265            actual_size: 2048,
266        };
267
268        if let Error::Validation {
269            field_name,
270            max_size,
271            actual_size,
272        } = err
273        {
274            assert_eq!(field_name, "description");
275            assert_eq!(max_size, 1024);
276            assert_eq!(actual_size, 2048);
277        } else {
278            panic!("Expected Validation variant");
279        }
280    }
281}