Skip to main content

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    #[inline]
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        match self {
42            Self::FileNotFound => write!(f, "file not found in database"),
43            Self::IOError(err) => write!(f, "I/O error: {err}"),
44            Self::InvalidGlobSet(err) => write!(f, "failed to build exclusion filter from patterns: {err}"),
45            Self::Glob(err) => write!(f, "glob pattern error: {err}"),
46            Self::FileTooLarge(path, size, max_size) => {
47                write!(
48                    f,
49                    "file at {} is too large to be processed: {} bytes (maximum is {} bytes)",
50                    path.display(),
51                    size,
52                    max_size
53                )
54            }
55            Self::ChangeLogInUse => {
56                write!(f, "cannot commit changelog because it is still in use by another thread")
57            }
58            Self::PoisonedLogMutex => {
59                write!(f, "changelog is in an unrecoverable state because a thread panicked while modifying it")
60            }
61            Self::WatcherInit(err) => write!(f, "failed to initialize file watcher: {err}"),
62            Self::WatcherWatch(err) => write!(f, "failed to watch path: {err}"),
63            Self::WatcherNotActive => write!(f, "watcher is not currently watching - call watch() first"),
64        }
65    }
66}
67
68impl std::error::Error for DatabaseError {
69    #[inline]
70    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
71        match self {
72            Self::IOError(err) => Some(err),
73            Self::InvalidGlobSet(err) => Some(err),
74            Self::WatcherInit(err) | Self::WatcherWatch(err) => Some(err),
75            _ => None,
76        }
77    }
78}
79
80impl From<std::io::Error> for DatabaseError {
81    #[inline]
82    fn from(error: std::io::Error) -> Self {
83        Self::IOError(error)
84    }
85}
86
87impl From<GlobSetError> for DatabaseError {
88    #[inline]
89    fn from(error: GlobSetError) -> Self {
90        Self::InvalidGlobSet(error)
91    }
92}