use crate::{Config, PathOp};
use std::error::Error as StdError;
use std::fmt::Debug;
use std::path::PathBuf;
use std::result::Result as StdResult;
use std::{self, fmt, io};
pub type Result<T> = StdResult<T, Error>;
#[derive(Debug)]
pub enum ErrorKind {
Generic(String),
Io(io::Error),
PathNotFound,
WatchNotFound,
InvalidConfig(Config),
MaxFilesWatch,
}
#[derive(Debug)]
pub struct Error {
pub kind: ErrorKind,
pub paths: Vec<PathBuf>,
}
impl Error {
#[must_use]
pub fn add_path(mut self, path: PathBuf) -> Self {
self.paths.push(path);
self
}
#[must_use]
pub fn set_paths(mut self, paths: Vec<PathBuf>) -> Self {
self.paths = paths;
self
}
#[must_use]
pub fn new(kind: ErrorKind) -> Self {
Self {
kind,
paths: Vec::new(),
}
}
#[must_use]
pub fn generic(msg: &str) -> Self {
Self::new(ErrorKind::Generic(msg.into()))
}
#[must_use]
pub fn io(err: io::Error) -> Self {
Self::new(ErrorKind::Io(err))
}
#[must_use]
pub fn io_watch(err: io::Error) -> Self {
if err.kind() == io::ErrorKind::NotFound {
Self::path_not_found()
} else {
Self::io(err)
}
}
#[must_use]
pub fn path_not_found() -> Self {
Self::new(ErrorKind::PathNotFound)
}
#[must_use]
pub fn watch_not_found() -> Self {
Self::new(ErrorKind::WatchNotFound)
}
#[must_use]
pub fn invalid_config(config: &Config) -> Self {
Self::new(ErrorKind::InvalidConfig(*config))
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let error = match self.kind {
ErrorKind::PathNotFound => "No path was found.".into(),
ErrorKind::WatchNotFound => "No watch was found.".into(),
ErrorKind::InvalidConfig(ref config) => format!("Invalid configuration: {config:?}"),
ErrorKind::Generic(ref err) => err.clone(),
ErrorKind::Io(ref err) => err.to_string(),
ErrorKind::MaxFilesWatch => "OS file watch limit reached.".into(),
};
if self.paths.is_empty() {
write!(f, "{error}")
} else {
write!(f, "{} about {:?}", error, self.paths)
}
}
}
impl StdError for Error {
fn cause(&self) -> Option<&dyn StdError> {
match self.kind {
ErrorKind::Io(ref cause) => Some(cause),
_ => None,
}
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
Error::io(err)
}
}
impl<T> From<std::sync::mpsc::SendError<T>> for Error {
fn from(err: std::sync::mpsc::SendError<T>) -> Self {
Error::generic(&format!("internal channel disconnect: {err:?}"))
}
}
impl From<std::sync::mpsc::RecvError> for Error {
fn from(err: std::sync::mpsc::RecvError) -> Self {
Error::generic(&format!("internal channel disconnect: {err:?}"))
}
}
impl<T> From<std::sync::PoisonError<T>> for Error {
fn from(err: std::sync::PoisonError<T>) -> Self {
Error::generic(&format!("internal mutex poisoned: {err:?}"))
}
}
#[derive(Debug)]
pub struct UpdatePathsError {
pub source: Error,
pub origin: Option<PathOp>,
pub remaining: Vec<PathOp>,
}
impl fmt::Display for UpdatePathsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "unable to apply the batch operation: {}", self.source)
}
}
impl StdError for UpdatePathsError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(&self.source)
}
}
impl From<UpdatePathsError> for Error {
fn from(value: UpdatePathsError) -> Self {
value.source
}
}
impl IntoIterator for UpdatePathsError {
type Item = PathOp;
type IntoIter = std::iter::Chain<std::option::IntoIter<PathOp>, std::vec::IntoIter<PathOp>>;
fn into_iter(self) -> Self::IntoIter {
self.origin.into_iter().chain(self.remaining)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_formatted_errors() {
let expected = "Some error";
assert_eq!(expected, format!("{}", Error::generic(expected)));
assert_eq!(
expected,
format!("{}", Error::io(io::Error::other(expected)))
);
}
#[test]
fn display_update_paths() {
let actual = UpdatePathsError {
source: Error::generic("Some error"),
origin: None,
remaining: Default::default(),
}
.to_string();
assert_eq!(
format!("unable to apply the batch operation: Some error"),
actual
);
}
}