Skip to content

Commit

Permalink
Switch to thiserror for error handling
Browse files Browse the repository at this point in the history
To remove alot of the boilerplate code linked to error handling, switch to thiserror.

Signed-off-by: Jason Rogena <[email protected]>
  • Loading branch information
Jason Rogena committed Jul 27, 2024
1 parent c645192 commit 878ba0b
Show file tree
Hide file tree
Showing 14 changed files with 95 additions and 186 deletions.
11 changes: 6 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
33 changes: 13 additions & 20 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -35,24 +43,9 @@ pub struct Filter {

impl Config {
#[allow(dead_code)]
pub fn new(config_path: &String) -> std::result::Result<Config, error::Error> {
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<Config, Error> {
let contents = fs::read_to_string(config_path)?;

Ok(toml::from_str(&contents)?)
}
}
9 changes: 5 additions & 4 deletions src/config/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
29 changes: 0 additions & 29 deletions src/error/mod.rs

This file was deleted.

7 changes: 0 additions & 7 deletions src/error/tests.rs

This file was deleted.

43 changes: 16 additions & 27 deletions src/fs_notify/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<bool>),
}

pub struct Notify<'a> {
_watcher: RecommendedWatcher,
on_event_sender: Sender<String>,
Expand All @@ -31,29 +40,14 @@ impl<'a> Notify<'a> {
on_event_sender: Sender<String>,
) -> Result<(Notify<'a>, Sender<bool>), 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();
Expand Down Expand Up @@ -143,13 +137,8 @@ impl<'a> Notify<'a> {
}

#[allow(dead_code)]
pub fn unwatch(unwatch_sender: &Sender<bool>) -> Option<Error> {
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<bool>) -> Result<(), Error> {
unwatch_sender.send(true)?;
Ok(())
}
}
4 changes: 2 additions & 2 deletions src/fs_notify/tests_supported_os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});

Expand Down Expand Up @@ -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();
});

Expand Down
87 changes: 29 additions & 58 deletions src/library/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::config;
use crate::error;
use crate::mime_type;
use crate::template;
use std::collections;
Expand All @@ -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<Path>),
}

#[derive(Debug)]
pub struct Library<'a> {
config: &'a config::Libraries,
Expand All @@ -30,7 +45,7 @@ impl<'a> Library<'a> {
}

#[allow(dead_code)]
pub fn process(&self, path: Option<&Path>) -> Result<u64, error::Error> {
pub fn process(&self, path: Option<&Path>) -> Result<u64, Error> {
let mut num_processed_files: u64 = 0;

if let Some(p) = path {
Expand All @@ -52,41 +67,15 @@ impl<'a> Library<'a> {
Ok(num_processed_files)
}

fn process_dir(&self, dir: &Path) -> Result<u64, error::Error> {
fn process_dir(&self, dir: &Path) -> Result<u64, Error> {
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())? {
Expand All @@ -96,7 +85,7 @@ impl<'a> Library<'a> {
Ok(num_processed_files)
}

fn process_file(&self, path: &Path) -> Result<bool, error::Error> {
fn process_file(&self, path: &Path) -> Result<bool, Error> {
let mime_type = match mime_type::File::new(path).get_mime_type() {
Err(e) => {
eprintln!("{}", e);
Expand All @@ -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;
Expand All @@ -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<bool, error::Error> {
fn run_command(&self, path: &Path, mime_type: &str) -> Result<bool, Error> {
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);
Expand All @@ -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,
};
Expand All @@ -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 {
Expand Down
Loading

0 comments on commit 878ba0b

Please sign in to comment.