use std::{
borrow::Cow,
fmt::{self, Display},
io,
sync::Arc,
};
use crate::format::ArchiveFormat;
pub type Result<T> = std::result::Result<T, ArchiveError>;
pub(crate) type ErrorStr = Cow<'static, str>;
#[derive(Debug, Clone)]
pub enum ArchiveError {
Io {
context: ErrorStr,
kind: io::ErrorKind,
message: ErrorStr,
},
Format {
format: ArchiveFormat,
message: ErrorStr,
},
Compression {
algorithm: ErrorStr,
message: ErrorStr,
},
NotFound { path: ErrorStr },
PermissionDenied { path: ErrorStr },
AlreadyExists { path: ErrorStr },
InvalidArchive {
format: ArchiveFormat,
reason: ErrorStr,
},
Unsupported { feature: ErrorStr },
InvalidPassword,
Custom { message: ErrorStr },
Nested {
context: ErrorStr,
source: Arc<dyn std::error::Error + Send + Sync>,
},
}
impl Display for ArchiveError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ArchiveError::Io {
context,
message,
..
} => {
write!(f, "I/O error during {}: {}", context, message)
}
ArchiveError::Format {
format,
message,
} => {
write!(f, "{} format error: {}", format, message)
}
ArchiveError::Compression {
algorithm,
message,
} => {
write!(f, "{} compression error: {}", algorithm, message)
}
ArchiveError::NotFound {
path,
} => {
write!(f, "File or archive not found: {}", path)
}
ArchiveError::PermissionDenied {
path,
} => {
write!(f, "Permission denied accessing: {}", path)
}
ArchiveError::AlreadyExists {
path,
} => {
write!(f, "File or directory already exists: {}", path)
}
ArchiveError::InvalidArchive {
format,
reason,
} => {
write!(f, "Invalid {} archive: {}", format, reason)
}
ArchiveError::Unsupported {
feature,
} => {
write!(f, "Unsupported feature: {}", feature)
}
ArchiveError::InvalidPassword => {
write!(f, "Invalid password provided for encrypted archive")
}
ArchiveError::Custom {
message,
} => {
write!(f, "{}", message)
}
ArchiveError::Nested {
context,
source,
} => {
write!(f, "{}: {}", context, source)
}
}
}
}
impl std::error::Error for ArchiveError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ArchiveError::Nested {
source, ..
} => Some(source.as_ref()),
_ => None,
}
}
}
impl ArchiveError {
pub const fn io_static(
context: &'static str,
kind: io::ErrorKind,
message: &'static str,
) -> Self {
Self::Io {
context: Cow::Borrowed(context),
kind,
message: Cow::Borrowed(message),
}
}
pub fn io_dynamic(
context: impl Into<String>,
kind: io::ErrorKind,
message: impl Into<String>,
) -> Self {
Self::Io {
context: Cow::Owned(context.into()),
kind,
message: Cow::Owned(message.into()),
}
}
pub fn io_from_error(context: impl Into<ErrorStr>, source: io::Error) -> Self {
let kind = source.kind();
let message = source.to_string();
let msg_cow = match kind {
io::ErrorKind::NotFound => Cow::Borrowed("file not found"),
io::ErrorKind::PermissionDenied => Cow::Borrowed("permission denied"),
io::ErrorKind::AlreadyExists => Cow::Borrowed("file already exists"),
_ => Cow::Owned(message),
};
Self::Io {
context: context.into(),
kind,
message: msg_cow,
}
}
pub const fn format_static(format: ArchiveFormat, message: &'static str) -> Self {
Self::Format {
format,
message: Cow::Borrowed(message),
}
}
pub fn format_dynamic(format: ArchiveFormat, message: impl Into<String>) -> Self {
Self::Format {
format,
message: Cow::Owned(message.into()),
}
}
pub const fn not_found_static(path: &'static str) -> Self {
Self::NotFound {
path: Cow::Borrowed(path),
}
}
pub fn not_found_dynamic(path: impl Into<String>) -> Self {
Self::NotFound {
path: Cow::Owned(path.into()),
}
}
pub fn nested(
context: impl Into<ErrorStr>,
source: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::Nested {
context: context.into(),
source: Arc::new(source),
}
}
pub fn zip_invalid(reason: &'static str) -> Self {
Self::format_static(ArchiveFormat::Zip, reason)
}
pub fn tar_invalid(reason: &'static str) -> Self {
Self::format_static(ArchiveFormat::Tar, reason)
}
pub fn unsupported_static(feature: &'static str) -> Self {
Self::Unsupported {
feature: Cow::Borrowed(feature),
}
}
pub fn custom_static(message: &'static str) -> Self {
Self::Custom {
message: Cow::Borrowed(message),
}
}
}
impl From<zip::result::ZipError> for ArchiveError {
fn from(err: zip::result::ZipError) -> Self {
use zip::result::ZipError;
match err {
ZipError::Io(io_err) => Self::io_from_error("ZIP I/O operation", io_err),
ZipError::InvalidArchive(msg) => {
Self::InvalidArchive {
format: ArchiveFormat::Zip,
reason: msg,
}
}
ZipError::UnsupportedArchive(msg) => {
Self::Unsupported {
feature: Cow::Owned(format!("ZIP feature: {}", msg)),
}
}
ZipError::FileNotFound => {
Self::NotFound {
path: Cow::Borrowed("file in ZIP archive"),
}
}
ZipError::InvalidPassword => Self::InvalidPassword,
_ => {
Self::Custom {
message: Cow::Owned(format!("ZIP error: {}", err)),
}
}
}
}
}
impl From<sevenz_rust2::Error> for ArchiveError {
fn from(err: sevenz_rust2::Error) -> Self {
Self::Custom {
message: Cow::Owned(format!("7-Zip error: {}", err)),
}
}
}
impl From<io::Error> for ArchiveError {
fn from(err: io::Error) -> Self {
match err.kind() {
io::ErrorKind::NotFound => {
Self::NotFound {
path: Cow::Borrowed("unknown"),
}
}
io::ErrorKind::PermissionDenied => {
Self::PermissionDenied {
path: Cow::Borrowed("unknown"),
}
}
io::ErrorKind::AlreadyExists => {
Self::AlreadyExists {
path: Cow::Borrowed("unknown"),
}
}
_ => Self::io_from_error("I/O operation", err),
}
}
}
pub(crate) trait ErrorStrExt {
fn from_static(s: &'static str) -> Self;
fn from_string(s: String) -> Self;
}
impl ErrorStrExt for ErrorStr {
fn from_static(s: &'static str) -> Self {
Cow::Borrowed(s)
}
fn from_string(s: String) -> Self {
Cow::Owned(s)
}
}
pub trait ErrorContext<T> {
fn with_context(self, context: String) -> Result<T>;
fn with_static_context(self, context: &'static str) -> Result<T>;
}
impl<T> ErrorContext<T> for std::result::Result<T, io::Error> {
fn with_context(self, context: String) -> Result<T> {
self.map_err(|err| ArchiveError::io_from_error(ErrorStr::from_string(context), err))
}
fn with_static_context(self, context: &'static str) -> Result<T> {
self.map_err(|err| ArchiveError::io_from_error(ErrorStr::from_static(context), err))
}
}
impl<T> ErrorContext<T> for Result<T> {
fn with_context(self, context: String) -> Result<T> {
self.map_err(|err| ArchiveError::nested(ErrorStr::from_string(context), err))
}
fn with_static_context(self, context: &'static str) -> Result<T> {
self.map_err(|err| ArchiveError::nested(ErrorStr::from_static(context), err))
}
}