diff --git a/Cargo.lock b/Cargo.lock index 44856eb..4f12e8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,6 +366,7 @@ dependencies = [ "serde", "serde_derive", "tera", + "thiserror", "toml", "tree_magic", "ttl_cache", @@ -1142,22 +1143,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.39" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.39" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 1.0.104", + "syn 2.0.46", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 266377c..aae96b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,3 +31,4 @@ clap = { version = "4.5", features = ["derive"] } exitcode = "1.1.2" notify = "4.0.16" ttl_cache = "0.5.1" +thiserror = "1.0.63" \ No newline at end of file diff --git a/src/config/mod.rs b/src/config/mod.rs index 6ad51ec..c655a3f 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,11 +1,19 @@ -use crate::error; use serde_derive::Deserialize; use std::collections::HashMap; use std::fs; +use std::io; #[cfg(test)] mod tests; +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("An io Error was thrown while reading the config")] + Io(#[from] io::Error), + #[error("An Error was thrown while trying to parse the config as TOML")] + Toml(#[from] toml::de::Error), +} + #[derive(Debug, Deserialize)] #[allow(dead_code)] pub struct Config { @@ -35,24 +43,9 @@ pub struct Filter { impl Config { #[allow(dead_code)] - pub fn new(config_path: &String) -> std::result::Result { - let contents = match fs::read_to_string(config_path) { - Ok(c) => c, - Err(e) => { - return Err(error::Error::new(format!( - "Unable to read config '{}' contents: {}", - config_path, e - ))) - } - }; - - let result = toml::from_str(&contents); - match result { - Ok(c) => Ok(c), - Err(e) => Err(error::Error::new(format!( - "Couldn't parse config '{}' as TOML: {}", - config_path, e - ))), - } + pub fn new(config_path: &String) -> std::result::Result { + let contents = fs::read_to_string(config_path)?; + + Ok(toml::from_str(&contents)?) } } diff --git a/src/config/tests.rs b/src/config/tests.rs index 342a645..f992a95 100644 --- a/src/config/tests.rs +++ b/src/config/tests.rs @@ -12,13 +12,14 @@ fn test_configs() { Config::new(cur_case).unwrap(); // should panic if error is returned } - let err_test_cases = [ - ("tests/configs/bad-missing-directories.toml".to_string(),"Couldn't parse config 'tests/configs/bad-missing-directories.toml' as TOML: missing field `directories` for key `libraries.shows.filter`") - ]; + let err_test_cases = [( + "tests/configs/bad-missing-directories.toml".to_string(), + "An Error was thrown while trying to parse the config as TOML", + )]; for cur_case in err_test_cases.iter() { assert!(Config::new(&cur_case.0) .unwrap_err() - .get_message() + .to_string() .contains(cur_case.1)); } } diff --git a/src/error/mod.rs b/src/error/mod.rs deleted file mode 100644 index e99cb46..0000000 --- a/src/error/mod.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::error; -use std::fmt; - -#[cfg(test)] -mod tests; - -#[derive(Debug)] -pub struct Error { - message: String, -} - -impl Error { - pub fn new(msg: String) -> Error { - Error { message: msg } - } - - #[allow(dead_code)] - pub fn get_message(&self) -> &String { - &self.message - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Error: {}", &self.message) - } -} - -impl error::Error for Error {} diff --git a/src/error/tests.rs b/src/error/tests.rs deleted file mode 100644 index de0bc9d..0000000 --- a/src/error/tests.rs +++ /dev/null @@ -1,7 +0,0 @@ -use super::*; - -#[test] -fn test_error() { - let err = Error::new("blah".to_string()); - assert!(format!("{}", err).contains("Error: blah")); -} diff --git a/src/fs_notify/mod.rs b/src/fs_notify/mod.rs index f952656..d60efdb 100644 --- a/src/fs_notify/mod.rs +++ b/src/fs_notify/mod.rs @@ -1,5 +1,4 @@ use crate::config::FsWatch; -use crate::error::Error; use notify::{Op, RawEvent, RecommendedWatcher, RecursiveMode, Watcher}; use std::collections::HashSet; use std::env::consts::OS; @@ -14,6 +13,16 @@ mod tests_supported_os; #[cfg(target_family = "windows")] mod tests_unsupported_os; +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("The feature '{0}' is unsupported")] + UnsupportedFeature(String), + #[error("An error was thrown by the filesystem notification system")] + Notify(#[from] notify::Error), + #[error("An error was thrown while trying to interract with the notification system")] + Send(#[from] std::sync::mpsc::SendError), +} + pub struct Notify<'a> { _watcher: RecommendedWatcher, on_event_sender: Sender, @@ -31,29 +40,14 @@ impl<'a> Notify<'a> { on_event_sender: Sender, ) -> Result<(Notify<'a>, Sender), Error> { if OS == "windows" { - return Err(Error::new( - "Directory watching is currently not supported in this OS".to_string(), - )); + return Err(Error::UnsupportedFeature("directory watching".to_string())); } let (watcher_sender, watcher_receiver) = channel(); - let mut watcher: RecommendedWatcher = match Watcher::new_raw(watcher_sender) { - Ok(w) => w, - Err(e) => { - return Err(Error::new(format!( - "Unable to initialize code for notifying on filesystem changes: {:?}", - e - ))) - } - }; + let mut watcher: RecommendedWatcher = Watcher::new_raw(watcher_sender)?; for cur_path in paths { - if let Err(e) = watcher.watch(cur_path, RecursiveMode::Recursive) { - return Err(Error::new(format!( - "Could not watch '{}' for changes: {}", - cur_path, e - ))); - } + watcher.watch(cur_path, RecursiveMode::Recursive)?; } let (unwatch_sender, unwatch_receiver) = channel(); @@ -143,13 +137,8 @@ impl<'a> Notify<'a> { } #[allow(dead_code)] - pub fn unwatch(unwatch_sender: &Sender) -> Option { - if let Err(e) = unwatch_sender.send(true) { - return Some(Error::new(format!( - "Could not notify FS watcher to stop: {}", - e - ))); - } - None + pub fn unwatch(unwatch_sender: &Sender) -> Result<(), Error> { + unwatch_sender.send(true)?; + Ok(()) } } diff --git a/src/fs_notify/tests_supported_os.rs b/src/fs_notify/tests_supported_os.rs index f23b4e8..2e97194 100644 --- a/src/fs_notify/tests_supported_os.rs +++ b/src/fs_notify/tests_supported_os.rs @@ -40,7 +40,7 @@ fn test_watch() { fs::File::create(generate_test_output_filename("watch", p)).unwrap(); write!(woot_file, "{}", test_str_clone).unwrap(); } - Notify::unwatch(&unwatch_sender); + let _ = Notify::unwatch(&unwatch_sender); run_tests_sender.send(true).unwrap(); }); @@ -104,7 +104,7 @@ fn test_notify_ttl() { write!(woot_file, "nope").unwrap(); } } - Notify::unwatch(&unwatch_sender); + let _ = Notify::unwatch(&unwatch_sender); run_tests_sender.send(true).unwrap(); }); diff --git a/src/library/mod.rs b/src/library/mod.rs index 56e5b79..266f465 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -1,5 +1,4 @@ use crate::config; -use crate::error; use crate::mime_type; use crate::template; use std::collections; @@ -14,6 +13,22 @@ mod tests; const TEMPLATE_VAR_FILE_PATH: &str = "file_path"; const TEMPLATE_VAR_MIME_TYPE: &str = "mime_type"; +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("A config error was thrown")] + Config(#[from] config::Error), + #[error("A MIME type error was thrown")] + Mime(#[from] mime_type::Error), + #[error("A templating error was thrown")] + Template(#[from] template::Error), + #[error("An IO error was thrown")] + Io(#[from] std::io::Error), + #[error("A regex error was thrown")] + Regex(#[from] regex::Error), + #[error("Could not read path {0}")] + ReadPath(Box), +} + #[derive(Debug)] pub struct Library<'a> { config: &'a config::Libraries, @@ -30,7 +45,7 @@ impl<'a> Library<'a> { } #[allow(dead_code)] - pub fn process(&self, path: Option<&Path>) -> Result { + pub fn process(&self, path: Option<&Path>) -> Result { let mut num_processed_files: u64 = 0; if let Some(p) = path { @@ -52,41 +67,15 @@ impl<'a> Library<'a> { Ok(num_processed_files) } - fn process_dir(&self, dir: &Path) -> Result { + fn process_dir(&self, dir: &Path) -> Result { let mut num_processed_files: u64 = 0; // iteratively go through all files in directory - let paths = match fs::read_dir(dir) { - Err(e) => { - return Err(error::Error::new(format!( - "Unable to read directory contents for '{:?}': {}", - dir, e - ))) - } - Ok(p) => p, - }; + let paths = fs::read_dir(dir)?; for cur_entry_res in paths { - let cur_entry = match cur_entry_res { - Err(e) => { - return Err(error::Error::new(format!( - "An error occurred reading a directory entry for '{:?}': {}", - dir, e - ))) - } - Ok(de) => de, - }; - - let file_type = match cur_entry.file_type() { - Err(e) => { - return Err(error::Error::new(format!( - "Could not determine the file_type of {:?}: {}", - cur_entry.path(), - e - ))) - } - Ok(f) => f, - }; + let cur_entry = cur_entry_res?; + let file_type = cur_entry.file_type()?; if file_type.is_dir() { num_processed_files += self.process_dir(&cur_entry.path())?; } else if self.process_file(&cur_entry.path())? { @@ -96,7 +85,7 @@ impl<'a> Library<'a> { Ok(num_processed_files) } - fn process_file(&self, path: &Path) -> Result { + fn process_file(&self, path: &Path) -> Result { let mime_type = match mime_type::File::new(path).get_mime_type() { Err(e) => { eprintln!("{}", e); @@ -109,15 +98,7 @@ impl<'a> Library<'a> { if let Some(regexes) = &self.config.filter.mime_type_regexes { let mut is_matched = false; for cur_regex in regexes.iter() { - let re = match regex::Regex::new(cur_regex.as_str()) { - Err(e) => { - return Err(error::Error::new(format!( - "Couldn't parse regex '{}' to use to test the MIME type for '{:?}': {}", - cur_regex, path, e - ))) - } - Ok(r) => r, - }; + let re = regex::Regex::new(cur_regex.as_str())?; if re.is_match(mime_type.as_str()) { is_matched = true; @@ -134,14 +115,11 @@ impl<'a> Library<'a> { self.run_command(path, mime_type.as_str()) } - fn run_command(&self, path: &Path, mime_type: &str) -> Result { + fn run_command(&self, path: &Path, mime_type: &str) -> Result { if *self.skip_running_commands { match path.as_os_str().to_str() { None => { - return Err(error::Error::new(format!( - "Could not extract string from path {:?}", - path - ))); + return Err(Error::ReadPath(path.into())); } Some(s) => { println!("{}", s); @@ -152,10 +130,7 @@ impl<'a> Library<'a> { let path_str = match path.as_os_str().to_str() { None => { - return Err(error::Error::new(format!( - "Could not extract string from path {:?}", - path - ))) + return Err(Error::ReadPath(path.into())); } Some(s) => s, }; @@ -171,13 +146,9 @@ impl<'a> Library<'a> { } else { Command::new("sh").arg("-c").arg(cmd_str).output() }; - match output { - Err(e) => Err(error::Error::new(format!( - "Got an error while running command against file '{:?}': {}", - path, e - ))), - Ok(_) => Ok(true), - } + output?; + + Ok(true) } pub fn contains_path(&self, path: &Path) -> bool { diff --git a/src/main.rs b/src/main.rs index 9d90fd1..90feb24 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,4 @@ mod config; -mod error; mod fs_notify; mod library; mod mime_type; diff --git a/src/mime_type/mod.rs b/src/mime_type/mod.rs index 56140e0..daa6dbf 100644 --- a/src/mime_type/mod.rs +++ b/src/mime_type/mod.rs @@ -1,4 +1,3 @@ -use crate::error; use std::io::Read; use std::path::Path; @@ -9,6 +8,12 @@ mod tests; #[allow(dead_code)] const MAX_FILE_READ_BYTES: u64 = 10240; +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("An IO error was thrown while trying to determine the MIME type of a file")] + Io(#[from] std::io::Error), +} + #[derive(Debug)] pub struct File<'a> { path: &'a Path, @@ -21,28 +26,14 @@ impl<'a> File<'a> { } #[allow(dead_code)] - pub fn get_mime_type(&'a self) -> Result { - let mut file_obj = match std::fs::File::open(self.path) { - Ok(f) => f, - Err(e) => { - return Err(error::Error::new(format!( - "Unable to open file '{:?}' to test its type: {}", - self.path, e - ))) - } - }; + pub fn get_mime_type(&'a self) -> Result { + let mut file_obj = std::fs::File::open(self.path)?; let mut buf = Vec::with_capacity(MAX_FILE_READ_BYTES as usize); - if let Err(e) = file_obj + file_obj .by_ref() .take(MAX_FILE_READ_BYTES) - .read_to_end(&mut buf) - { - return Err(error::Error::new(format!( - "Unable to read file '{:?}' to test its type: {}", - self.path, e - ))); - } + .read_to_end(&mut buf)?; let mime_type = tree_magic::from_u8(buf.as_slice()); diff --git a/src/mime_type/tests.rs b/src/mime_type/tests.rs index 11781fc..5337f3c 100644 --- a/src/mime_type/tests.rs +++ b/src/mime_type/tests.rs @@ -58,14 +58,14 @@ fn test_is_of_type() { let err_test_cases = [( File::new(Path::new("tests/files/unavialabile_file")), - "Unable to open file", + "An IO error was thrown while trying to determine the MIME type of a file", )]; for cur_case in err_test_cases.iter() { assert!(cur_case .0 .get_mime_type() .unwrap_err() - .get_message() + .to_string() .contains(cur_case.1)); } } diff --git a/src/template/mod.rs b/src/template/mod.rs index 35436cf..05ffcc1 100644 --- a/src/template/mod.rs +++ b/src/template/mod.rs @@ -1,30 +1,29 @@ -use crate::error; use std::collections; #[cfg(test)] mod tests; +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("A Tera templating error occurred")] + Io(#[from] tera::Error), +} + pub struct Template { tmpl: String, } impl Template { - pub fn new(tmpl: String) -> Result { + pub fn new(tmpl: String) -> Result { Ok(Template { tmpl }) } - pub fn render(&self, data: &collections::HashMap<&str, &str>) -> Result { + pub fn render(&self, data: &collections::HashMap<&str, &str>) -> Result { let mut context = tera::Context::new(); for (cur_key, cur_val) in data { context.insert(*cur_key, *cur_val); } - match tera::Tera::one_off(self.tmpl.as_str(), &context, false) { - Err(e) => Err(error::Error::new(format!( - "Could not render template: {:?}", - e - ))), - Ok(s) => Ok(s), - } + Ok(tera::Tera::one_off(self.tmpl.as_str(), &context, false)?) } } diff --git a/src/template/tests.rs b/src/template/tests.rs index 112c37b..db9f802 100644 --- a/src/template/tests.rs +++ b/src/template/tests.rs @@ -32,7 +32,7 @@ fn test_render() { let err_test_cases: Vec<(&str, collections::HashMap<&str, &str>, &str)> = vec![( "Test {{ var_1 }} before {{ var_2 }}.", [("var_1", "hello")].iter().cloned().collect(), - "Variable `var_2` not found in context while rendering", + "A Tera templating error occurred", )]; for cur_test_case in err_test_cases.iter() { @@ -40,7 +40,7 @@ fn test_render() { assert!(cur_template .render(&cur_test_case.1) .unwrap_err() - .get_message() + .to_string() .contains(cur_test_case.2)); } }