use std::{
borrow::Cow,
fmt::{self, Display},
io,
};
use crate::{
accessible::is_running_in_accessible_mode,
extension::{PRETTY_SUPPORTED_ALIASES, PRETTY_SUPPORTED_EXTENSIONS},
};
#[derive(Debug, Clone)]
pub enum Error {
IoError { reason: String },
Lz4Error { reason: String },
NotFound { error_title: String },
AlreadyExists { error_title: String },
InvalidZipArchive(Cow<'static, str>),
PermissionDenied { error_title: String },
UnsupportedZipArchive(&'static str),
CompressingRootFolder,
WalkdirError { reason: String },
Custom { reason: FinalError },
InvalidFormatFlag { text: String, reason: String },
SevenzipError { reason: String },
UnsupportedFormat { reason: String },
InvalidPassword { reason: String },
}
impl Error {
pub fn rar_no_support() -> Self {
Self::UnsupportedFormat {
reason: "RAR support is disabled for this build, possibly due to licensing restrictions.".into(),
}
}
pub fn bzip3_no_support() -> Self {
Self::UnsupportedFormat {
reason: "BZip3 support is disabled for this build, possibly due to missing bindgen-cli dependency.".into(),
}
}
}
pub type Result<T, E = self::Error> = std::result::Result<T, E>;
pub type CowStr = Cow<'static, str>;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct FinalError {
title: CowStr,
details: Vec<CowStr>,
hints: Vec<CowStr>,
}
impl Display for FinalError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use crate::utils::colors::*;
if is_running_in_accessible_mode() {
write!(f, "{}ERROR{}: {}", *RED, *RESET, self.title)?;
} else {
write!(f, "{}[ERROR]{} {}", *RED, *RESET, self.title)?;
}
for detail in &self.details {
write!(f, "\n - {}{}{}", *YELLOW, detail, *RESET)?;
}
if !self.hints.is_empty() {
writeln!(f)?;
if is_running_in_accessible_mode() {
write!(f, "\n{}hints:{}", *GREEN, *RESET)?;
for hint in &self.hints {
write!(f, "\n{hint}")?;
}
} else {
for hint in &self.hints {
write!(f, "\n{}hint:{} {}", *GREEN, *RESET, hint)?;
}
}
}
Ok(())
}
}
impl FinalError {
#[must_use]
pub fn with_title(title: impl Into<CowStr>) -> Self {
Self {
title: title.into(),
details: vec![],
hints: vec![],
}
}
#[must_use]
pub fn detail(mut self, detail: impl Into<CowStr>) -> Self {
self.details.push(detail.into());
self
}
#[must_use]
pub fn hint(mut self, hint: impl Into<CowStr>) -> Self {
self.hints.push(hint.into());
self
}
pub fn hint_all_supported_formats(self) -> Self {
self.hint(format!("Supported extensions are: {PRETTY_SUPPORTED_EXTENSIONS}"))
.hint(format!("Supported aliases are: {PRETTY_SUPPORTED_ALIASES}"))
}
}
impl From<Error> for FinalError {
fn from(err: Error) -> Self {
match err {
Error::WalkdirError { reason } => Self::with_title(reason),
Error::NotFound { error_title } => Self::with_title(error_title).detail("File not found"),
Error::CompressingRootFolder => Self::with_title("It seems you're trying to compress the root folder.")
.detail("This is unadvisable since ouch does compressions in-memory.")
.hint("Use a more appropriate tool for this, such as rsync."),
Error::IoError { reason } => Self::with_title(reason),
Error::Lz4Error { reason } => Self::with_title(reason),
Error::AlreadyExists { error_title } => Self::with_title(error_title).detail("File already exists"),
Error::InvalidZipArchive(reason) => Self::with_title("Invalid zip archive").detail(reason),
Error::PermissionDenied { error_title } => Self::with_title(error_title).detail("Permission denied"),
Error::UnsupportedZipArchive(reason) => Self::with_title("Unsupported zip archive").detail(reason),
Error::InvalidFormatFlag { reason, text } => {
Self::with_title(format!("Failed to parse `--format {}`", &text))
.detail(reason)
.hint_all_supported_formats()
.hint("")
.hint("Examples:")
.hint(" --format tar")
.hint(" --format gz")
.hint(" --format tar.gz")
}
Error::Custom { reason } => reason.clone(),
Error::SevenzipError { reason } => Self::with_title("7z error").detail(reason),
Error::UnsupportedFormat { reason } => {
Self::with_title("Recognised but unsupported format").detail(reason.clone())
}
Error::InvalidPassword { reason } => Self::with_title("Invalid password").detail(reason.clone()),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let err = FinalError::from(self.clone());
write!(f, "{err}")
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
let error_title = err.to_string();
match err.kind() {
io::ErrorKind::NotFound => Self::NotFound { error_title },
io::ErrorKind::PermissionDenied => Self::PermissionDenied { error_title },
io::ErrorKind::AlreadyExists => Self::AlreadyExists { error_title },
_other => Self::IoError { reason: error_title },
}
}
}
#[cfg(feature = "bzip3")]
impl From<bzip3::Error> for Error {
fn from(err: bzip3::Error) -> Self {
use bzip3::Error as Bz3Error;
match err {
Bz3Error::Io(inner) => inner.into(),
Bz3Error::BlockSize | Bz3Error::ProcessBlock(_) | Bz3Error::InvalidSignature => {
FinalError::with_title("bzip3 error").detail(err.to_string()).into()
}
}
}
}
impl From<zip::result::ZipError> for Error {
fn from(err: zip::result::ZipError) -> Self {
use zip::result::ZipError;
match err {
ZipError::Io(io_err) => Self::from(io_err),
ZipError::InvalidArchive(filename) => Self::InvalidZipArchive(filename),
ZipError::FileNotFound => Self::Custom {
reason: FinalError::with_title("Unexpected error in zip archive").detail("File not found"),
},
ZipError::UnsupportedArchive(filename) => Self::UnsupportedZipArchive(filename),
ZipError::InvalidPassword => Self::InvalidPassword {
reason: "The provided password is incorrect".to_string(),
},
_ => Self::Custom {
reason: FinalError::with_title("Unexpected error in zip archive").detail(err.to_string()),
},
}
}
}
#[cfg(feature = "unrar")]
impl From<unrar::error::UnrarError> for Error {
fn from(err: unrar::error::UnrarError) -> Self {
Self::Custom {
reason: FinalError::with_title("Unexpected error in rar archive").detail(format!("{:?}", err.code)),
}
}
}
impl From<sevenz_rust2::Error> for Error {
fn from(err: sevenz_rust2::Error) -> Self {
Self::SevenzipError {
reason: err.to_string(),
}
}
}
impl From<ignore::Error> for Error {
fn from(err: ignore::Error) -> Self {
Self::WalkdirError {
reason: err.to_string(),
}
}
}
impl From<FinalError> for Error {
fn from(err: FinalError) -> Self {
Self::Custom { reason: err }
}
}