Skip to main content

notify/
error.rs

1//! Error types
2
3use crate::{Config, PathOp};
4use std::error::Error as StdError;
5use std::fmt::Debug;
6use std::path::PathBuf;
7use std::result::Result as StdResult;
8use std::{self, fmt, io};
9
10/// Type alias to use this library's `Error` type in a Result
11pub type Result<T> = StdResult<T, Error>;
12
13/// Error kinds
14#[derive(Debug)]
15pub enum ErrorKind {
16    /// Generic error
17    ///
18    /// May be used in cases where a platform specific error is mapped to this type, or for opaque
19    /// internal errors.
20    Generic(String),
21
22    /// I/O errors.
23    Io(io::Error),
24
25    /// A path does not exist.
26    PathNotFound,
27
28    /// Attempted to remove a watch that does not exist.
29    WatchNotFound,
30
31    /// An invalid value was passed as runtime configuration.
32    InvalidConfig(Config),
33
34    /// Can't watch (more) files, limit on the total number of inotify watches reached
35    MaxFilesWatch,
36}
37
38/// Notify error type.
39///
40/// Errors are emitted either at creation time of a `Watcher`, or during the event stream. They
41/// range from kernel errors to filesystem errors to argument errors.
42///
43/// Errors can be general, or they can be about specific paths or subtrees. In that later case, the
44/// error's `paths` field will be populated.
45#[derive(Debug)]
46pub struct Error {
47    /// Kind of the error.
48    pub kind: ErrorKind,
49
50    /// Relevant paths to the error, if any.
51    pub paths: Vec<PathBuf>,
52}
53
54impl Error {
55    /// Adds a path to the error.
56    #[must_use]
57    pub fn add_path(mut self, path: PathBuf) -> Self {
58        self.paths.push(path);
59        self
60    }
61
62    /// Replaces the paths for the error.
63    #[must_use]
64    pub fn set_paths(mut self, paths: Vec<PathBuf>) -> Self {
65        self.paths = paths;
66        self
67    }
68
69    /// Creates a new Error with empty paths given its kind.
70    #[must_use]
71    pub fn new(kind: ErrorKind) -> Self {
72        Self {
73            kind,
74            paths: Vec::new(),
75        }
76    }
77
78    /// Creates a new generic Error from a message.
79    #[must_use]
80    pub fn generic(msg: &str) -> Self {
81        Self::new(ErrorKind::Generic(msg.into()))
82    }
83
84    /// Creates a new i/o Error from a stdlib `io::Error`.
85    #[must_use]
86    pub fn io(err: io::Error) -> Self {
87        Self::new(ErrorKind::Io(err))
88    }
89
90    /// Similar to [`Error::io`], but specifically handles [`io::ErrorKind::NotFound`].
91    #[must_use]
92    pub fn io_watch(err: io::Error) -> Self {
93        if err.kind() == io::ErrorKind::NotFound {
94            Self::path_not_found()
95        } else {
96            Self::io(err)
97        }
98    }
99
100    /// Creates a new "path not found" error.
101    #[must_use]
102    pub fn path_not_found() -> Self {
103        Self::new(ErrorKind::PathNotFound)
104    }
105
106    /// Creates a new "watch not found" error.
107    #[must_use]
108    pub fn watch_not_found() -> Self {
109        Self::new(ErrorKind::WatchNotFound)
110    }
111
112    /// Creates a new "invalid config" error from the given `Config`.
113    #[must_use]
114    pub fn invalid_config(config: &Config) -> Self {
115        Self::new(ErrorKind::InvalidConfig(*config))
116    }
117}
118
119impl fmt::Display for Error {
120    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
121        let error = match self.kind {
122            ErrorKind::PathNotFound => "No path was found.".into(),
123            ErrorKind::WatchNotFound => "No watch was found.".into(),
124            ErrorKind::InvalidConfig(ref config) => format!("Invalid configuration: {config:?}"),
125            ErrorKind::Generic(ref err) => err.clone(),
126            ErrorKind::Io(ref err) => err.to_string(),
127            ErrorKind::MaxFilesWatch => "OS file watch limit reached.".into(),
128        };
129
130        if self.paths.is_empty() {
131            write!(f, "{error}")
132        } else {
133            write!(f, "{} about {:?}", error, self.paths)
134        }
135    }
136}
137
138impl StdError for Error {
139    fn cause(&self) -> Option<&dyn StdError> {
140        match self.kind {
141            ErrorKind::Io(ref cause) => Some(cause),
142            _ => None,
143        }
144    }
145}
146
147impl From<io::Error> for Error {
148    fn from(err: io::Error) -> Self {
149        Error::io(err)
150    }
151}
152
153impl<T> From<std::sync::mpsc::SendError<T>> for Error {
154    fn from(err: std::sync::mpsc::SendError<T>) -> Self {
155        Error::generic(&format!("internal channel disconnect: {err:?}"))
156    }
157}
158
159impl From<std::sync::mpsc::RecvError> for Error {
160    fn from(err: std::sync::mpsc::RecvError) -> Self {
161        Error::generic(&format!("internal channel disconnect: {err:?}"))
162    }
163}
164
165impl<T> From<std::sync::PoisonError<T>> for Error {
166    fn from(err: std::sync::PoisonError<T>) -> Self {
167        Error::generic(&format!("internal mutex poisoned: {err:?}"))
168    }
169}
170
171/// The error provided by [`crate::Watcher::update_paths`] method.
172///
173/// Operations are applied in order. If an error occurs, processing stops and the
174/// error carries the failed operation (if known) and any remaining operations that
175/// were not attempted.
176#[derive(Debug)]
177pub struct UpdatePathsError {
178    /// The original error
179    pub source: Error,
180
181    /// The operation that caused the error.
182    ///
183    /// If set, all operations before it were applied successfully.
184    /// `None` if the error was not caused by a specific operation
185    /// (e.g. failure to start the watcher after successfully updating paths).
186    pub origin: Option<PathOp>,
187
188    /// The remaining operations that haven't been applied.
189    ///
190    /// This list does not include `origin`. To retry in order, handle `origin`
191    /// first (if present), then `remaining`.
192    pub remaining: Vec<PathOp>,
193}
194
195impl fmt::Display for UpdatePathsError {
196    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197        write!(f, "unable to apply the batch operation: {}", self.source)
198    }
199}
200
201impl StdError for UpdatePathsError {
202    fn source(&self) -> Option<&(dyn StdError + 'static)> {
203        Some(&self.source)
204    }
205}
206
207impl From<UpdatePathsError> for Error {
208    fn from(value: UpdatePathsError) -> Self {
209        value.source
210    }
211}
212
213impl IntoIterator for UpdatePathsError {
214    type Item = PathOp;
215
216    type IntoIter = std::iter::Chain<std::option::IntoIter<PathOp>, std::vec::IntoIter<PathOp>>;
217
218    fn into_iter(self) -> Self::IntoIter {
219        self.origin.into_iter().chain(self.remaining)
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    #[test]
228    fn display_formatted_errors() {
229        let expected = "Some error";
230
231        assert_eq!(expected, format!("{}", Error::generic(expected)));
232
233        assert_eq!(
234            expected,
235            format!("{}", Error::io(io::Error::other(expected)))
236        );
237    }
238
239    #[test]
240    fn display_update_paths() {
241        let actual = UpdatePathsError {
242            source: Error::generic("Some error"),
243            origin: None,
244            remaining: Default::default(),
245        }
246        .to_string();
247
248        assert_eq!(
249            format!("unable to apply the batch operation: Some error"),
250            actual
251        );
252    }
253}