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    /// An error occurred while processing a glob pattern.
18    Glob(String),
19    /// The file being loaded into the database is too large to be processed.
20    FileTooLarge(PathBuf, usize, usize),
21    /// An attempt was made to commit or consume a `ChangeLog` while other
22    /// references to it still exist, indicating that other threads may not have
23    /// finished their work.
24    ChangeLogInUse,
25    /// The lock on a `ChangeLog` was "poisoned."
26    ///
27    /// This happens when a thread panics while holding the lock, leaving the
28    /// data in an unrecoverable and potentially inconsistent state.
29    PoisonedLogMutex,
30    /// Failed to initialize the file system watcher.
31    WatcherInit(notify::Error),
32    /// Failed to add a path to the file system watcher.
33    WatcherWatch(notify::Error),
34    /// Attempted to wait on a watcher that is not currently watching.
35    WatcherNotActive,
36}
37
38impl std::fmt::Display for DatabaseError {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        match self {
41            Self::FileNotFound => write!(f, "file not found in database"),
42            Self::IOError(err) => write!(f, "I/O error: {err}"),
43            Self::InvalidGlobSet(err) => write!(f, "failed to build exclusion filter from patterns: {err}"),
44            Self::Glob(err) => write!(f, "glob pattern error: {err}"),
45            Self::FileTooLarge(path, size, max_size) => {
46                write!(
47                    f,
48                    "file at {} is too large to be processed: {} bytes (maximum is {} bytes)",
49                    path.display(),
50                    size,
51                    max_size
52                )
53            }
54            Self::ChangeLogInUse => {
55                write!(f, "cannot commit changelog because it is still in use by another thread")
56            }
57            Self::PoisonedLogMutex => {
58                write!(f, "changelog is in an unrecoverable state because a thread panicked while modifying it")
59            }
60            Self::WatcherInit(err) => write!(f, "failed to initialize file watcher: {err}"),
61            Self::WatcherWatch(err) => write!(f, "failed to watch path: {err}"),
62            Self::WatcherNotActive => write!(f, "watcher is not currently watching - call watch() first"),
63        }
64    }
65}
66
67impl std::error::Error for DatabaseError {
68    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
69        match self {
70            Self::IOError(err) => Some(err),
71            Self::InvalidGlobSet(err) => Some(err),
72            Self::WatcherInit(err) | Self::WatcherWatch(err) => Some(err),
73            _ => None,
74        }
75    }
76}
77
78impl From<std::io::Error> for DatabaseError {
79    fn from(error: std::io::Error) -> Self {
80        Self::IOError(error)
81    }
82}
83
84impl From<GlobSetError> for DatabaseError {
85    fn from(error: GlobSetError) -> Self {
86        Self::InvalidGlobSet(error)
87    }
88}