eidetica/instance/
errors.rs1use thiserror::Error;
7
8use crate::entry::ID;
9
10#[non_exhaustive]
19#[derive(Debug, Error)]
20pub enum InstanceError {
21 #[error("Database not found: {name}")]
23 DatabaseNotFound {
24 name: String,
26 },
27
28 #[error("Database already exists: {name}")]
30 DatabaseAlreadyExists {
31 name: String,
33 },
34
35 #[error("Instance already exists on backend (found device key and system databases)")]
37 InstanceAlreadyExists,
38
39 #[error("Entry '{entry_id}' does not belong to database '{database_id}'")]
41 EntryNotInDatabase {
42 entry_id: ID,
44 database_id: ID,
46 },
47
48 #[error("Entry not found: {entry_id}")]
50 EntryNotFound {
51 entry_id: ID,
53 },
54
55 #[error("Transaction has already been committed")]
57 TransactionAlreadyCommitted,
58
59 #[error("Cannot create transaction with empty tips")]
61 EmptyTipsNotAllowed,
62
63 #[error("Tip entry '{tip_id}' does not belong to database '{database_id}'")]
65 InvalidTip {
66 tip_id: ID,
68 database_id: ID,
70 },
71
72 #[error("Signing key '{key_name}' not found in backend")]
74 SigningKeyNotFound {
75 key_name: String,
77 },
78
79 #[error("Authentication required but no key configured")]
81 AuthenticationRequired,
82
83 #[error("Device key not found in instance metadata")]
85 DeviceKeyNotFound,
86
87 #[error("No authentication configuration found")]
89 NoAuthConfiguration,
90
91 #[error("Authentication validation failed: {reason}")]
93 AuthenticationValidationFailed {
94 reason: String,
96 },
97
98 #[error("Insufficient permissions for operation")]
100 InsufficientPermissions,
101
102 #[error("Signature verification failed")]
104 SignatureVerificationFailed,
105
106 #[error("Invalid data type: expected {expected}, got {actual}")]
108 InvalidDataType {
109 expected: String,
111 actual: String,
113 },
114
115 #[error("Serialization failed for {context}")]
117 SerializationFailed {
118 context: String,
120 },
121
122 #[error("Invalid database configuration: {reason}")]
124 InvalidDatabaseConfiguration {
125 reason: String,
127 },
128
129 #[error("Settings validation failed: {reason}")]
131 SettingsValidationFailed {
132 reason: String,
134 },
135
136 #[error("Invalid operation: {reason}")]
138 InvalidOperation {
139 reason: String,
141 },
142
143 #[error("Database initialization failed: {reason}")]
145 DatabaseInitializationFailed {
146 reason: String,
148 },
149
150 #[error("Entry validation failed: {reason}")]
152 EntryValidationFailed {
153 reason: String,
155 },
156
157 #[error("Database state corruption detected: {reason}")]
159 DatabaseStateCorruption {
160 reason: String,
162 },
163
164 #[error("Operation not supported: {operation}")]
166 OperationNotSupported {
167 operation: String,
169 },
170
171 #[error("Instance has been dropped")]
173 InstanceDropped,
174
175 #[error("Sync has already been enabled on this Instance")]
177 SyncAlreadyEnabled,
178
179 #[error("System database not found: {database_name}")]
181 SystemDatabaseNotFound {
182 database_name: String,
184 },
185}
186
187impl InstanceError {
188 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 pub fn is_already_exists(&self) -> bool {
201 matches!(
202 self,
203 InstanceError::DatabaseAlreadyExists { .. } | InstanceError::InstanceAlreadyExists
204 )
205 }
206
207 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 pub fn is_operation_error(&self) -> bool {
222 matches!(
223 self,
224 InstanceError::TransactionAlreadyCommitted
225 | InstanceError::EmptyTipsNotAllowed
226 | InstanceError::InvalidOperation { .. }
227 )
228 }
229
230 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 pub fn is_corruption_error(&self) -> bool {
245 matches!(self, InstanceError::DatabaseStateCorruption { .. })
246 }
247
248 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 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 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
279impl From<InstanceError> for crate::Error {
281 fn from(err: InstanceError) -> Self {
282 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}