use crate::{
aoc::ApiError, config::ConfigError, env::EnvError, process::ProcessError, resolve::ResolveError,
};
use std::{io, path::Path, path::PathBuf};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Env(#[from] EnvError),
#[error(transparent)]
Config(#[from] ConfigError),
#[error(transparent)]
Resolve(#[from] ResolveError),
#[error(transparent)]
Process(#[from] ProcessError),
#[error(transparent)]
Api(#[from] ApiError),
#[error("project does not exist: {path}\n\ncreate it with `aoc init`")]
ProjectMissing {
path: PathBuf,
},
#[error("project already exists: {path}")]
ProjectExists {
path: PathBuf,
},
#[error("failed to {action}: {path}")]
Io {
action: &'static str,
path: PathBuf,
#[source]
source: io::Error,
},
}
pub trait IoResultExt<T> {
fn io_context(self, action: &'static str, path: &Path) -> Result<T, Error>;
}
impl<T> IoResultExt<T> for Result<T, io::Error> {
fn io_context(self, action: &'static str, path: &Path) -> Result<T, Error> {
self.map_err(|source| Error::Io {
action,
path: path.to_path_buf(),
source,
})
}
}
pub fn report(error: &Error) {
use colored::Colorize as _;
eprintln!("{} {error}", "error:".red().bold());
let mut source = std::error::Error::source(error);
while let Some(cause) = source {
eprintln!(" {} {cause}", "caused by:".dimmed());
source = cause.source();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn io_failures_name_the_action_and_path() {
let result: Result<(), io::Error> =
Err(io::Error::new(io::ErrorKind::PermissionDenied, "denied"));
let error = result
.io_context(
"create project directory",
Path::new("/aoc/2024/day07/rust"),
)
.expect_err("the result is an error");
let message = error.to_string();
assert!(message.contains("create project directory"), "{message}");
assert!(message.contains("/aoc/2024/day07/rust"), "{message}");
}
#[test]
fn missing_and_existing_projects_read_clearly() {
let missing = Error::ProjectMissing {
path: PathBuf::from("/aoc/2024/day07/rust"),
};
let existing = Error::ProjectExists {
path: PathBuf::from("/aoc/2024/day07/rust"),
};
assert!(missing.to_string().contains("aoc init"));
assert!(existing.to_string().contains("already exists"));
}
}