mago_database/
error.rs

1use std::path::PathBuf;
2
3use globset::Error as GlobSetError;
4
5/// The primary error type for all database loading and mutation operations.
6///
7/// This enum consolidates errors from various sources, including file I/O,
8/// pattern matching, and concurrent access issues, into a single, unified type.
9#[derive(Debug)]
10pub enum DatabaseError {
11    /// An attempt was made to access a file that does not exist in the database.
12    FileNotFound,
13    /// An error occurred during a filesystem read or write operation.
14    IOError(std::io::Error),
15    /// The set of user-provided glob patterns could not be compiled into a `GlobSet`.
16    InvalidGlobSet(GlobSetError),
17    /// The file being loaded into the database is too large to be processed.
18    FileTooLarge(PathBuf, usize, usize),
19    /// An attempt was made to commit or consume a `ChangeLog` while other
20    /// references to it still exist, indicating that other threads may not have
21    /// finished their work.
22    ChangeLogInUse,
23    /// The lock on a `ChangeLog` was "poisoned."
24    ///
25    /// This happens when a thread panics while holding the lock, leaving the
26    /// data in an unrecoverable and potentially inconsistent state.
27    PoisonedLogMutex,
28    /// Failed to initialize the file system watcher.
29    WatcherInit(notify::Error),
30    /// Failed to add a path to the file system watcher.
31    WatcherWatch(notify::Error),
32    /// Attempted to wait on a watcher that is not currently watching.
33    WatcherNotActive,
34}
35
36impl std::fmt::Display for DatabaseError {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        match self {
39            Self::FileNotFound => write!(f, "file not found in database"),
40            Self::IOError(err) => write!(f, "I/O error: {err}"),
41            Self::InvalidGlobSet(err) => write!(f, "failed to build exclusion filter from patterns: {err}"),
42            Self::FileTooLarge(path, size, max_size) => {
43                write!(
44                    f,
45                    "file at {} is too large to be processed: {} bytes (maximum is {} bytes)",
46                    path.display(),
47                    size,
48                    max_size
49                )
50            }
51            Self::ChangeLogInUse => {
52                write!(f, "cannot commit changelog because it is still in use by another thread")
53            }
54            Self::PoisonedLogMutex => {
55                write!(f, "changelog is in an unrecoverable state because a thread panicked while modifying it")
56            }
57            Self::WatcherInit(err) => write!(f, "failed to initialize file watcher: {err}"),
58            Self::WatcherWatch(err) => write!(f, "failed to watch path: {err}"),
59            Self::WatcherNotActive => write!(f, "watcher is not currently watching - call watch() first"),
60        }
61    }
62}
63
64impl std::error::Error for DatabaseError {
65    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
66        match self {
67            Self::IOError(err) => Some(err),
68            Self::InvalidGlobSet(err) => Some(err),
69            Self::WatcherInit(err) | Self::WatcherWatch(err) => Some(err),
70            _ => None,
71        }
72    }
73}
74
75impl From<std::io::Error> for DatabaseError {
76    fn from(error: std::io::Error) -> Self {
77        Self::IOError(error)
78    }
79}
80
81impl From<GlobSetError> for DatabaseError {
82    fn from(error: GlobSetError) -> Self {
83        Self::InvalidGlobSet(error)
84    }
85}