use std::{fmt, io};
use thiserror::Error;
#[derive(Error, Debug)]
pub struct CondowError {
msg: String,
#[source]
source: Option<anyhow::Error>,
kind: CondowErrorKind,
}
impl CondowError {
pub fn new<T: Into<String>>(msg: T, kind: CondowErrorKind) -> Self {
Self {
msg: msg.into(),
source: None,
kind,
}
}
pub fn new_invalid_range<T: Into<String>>(msg: T) -> Self {
Self::new(msg, CondowErrorKind::InvalidRange)
}
pub fn new_not_found<T: Into<String>>(msg: T) -> Self {
Self::new(msg, CondowErrorKind::NotFound)
}
pub fn new_access_denied<T: Into<String>>(msg: T) -> Self {
Self::new(msg, CondowErrorKind::AccessDenied)
}
pub fn new_remote<T: Into<String>>(msg: T) -> Self {
Self::new(msg, CondowErrorKind::Remote)
}
pub fn new_io<T: Into<String>>(msg: T) -> Self {
Self::new(msg, CondowErrorKind::Io)
}
pub fn new_other<T: Into<String>>(msg: T) -> Self {
Self::new(msg, CondowErrorKind::Other)
}
pub fn with_source<E: Into<anyhow::Error>>(mut self, err: E) -> Self {
self.source = Some(err.into());
self
}
pub fn msg(&self) -> &str {
&self.msg
}
pub fn kind(&self) -> CondowErrorKind {
self.kind
}
pub fn is_retryable(&self) -> bool {
self.kind.is_retryable()
}
}
impl fmt::Display for CondowError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.msg)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum CondowErrorKind {
InvalidRange,
NotFound,
AccessDenied,
Remote,
Io,
Other,
}
impl CondowErrorKind {
pub fn is_retryable(self) -> bool {
use CondowErrorKind::*;
match self {
InvalidRange => false,
NotFound => false,
AccessDenied => false,
Remote => true,
Io => true,
Other => false,
}
}
}
impl From<CondowErrorKind> for CondowError {
fn from(error_kind: CondowErrorKind) -> Self {
CondowError::new(format!("An error occurred: {:?}", error_kind), error_kind)
}
}
impl From<io::Error> for CondowError {
fn from(io_err: io::Error) -> Self {
use io::ErrorKind;
match io_err.kind() {
ErrorKind::NotFound => CondowError::new_not_found(format!("io error: {io_err}")),
ErrorKind::PermissionDenied => {
CondowError::new_access_denied(format!("permission denied: {io_err}"))
}
_ => CondowError::new_io(format!("io error: {io_err}")),
}
.with_source(io_err)
}
}
impl From<CondowError> for io::Error {
fn from(err: CondowError) -> Self {
match err.kind() {
CondowErrorKind::NotFound => io::Error::new(io::ErrorKind::NotFound, err),
CondowErrorKind::AccessDenied => io::Error::new(io::ErrorKind::PermissionDenied, err),
_ => io::Error::new(io::ErrorKind::Other, err),
}
}
}
pub fn http_status_to_error(
status_code: u16,
status_str: &str,
is_server_error: bool,
body: &[u8],
) -> CondowError {
let message = if let Ok(body_str) = std::str::from_utf8(body) {
body_str
} else {
"<<< response body is not valid UTF-8 >>>"
};
let message = format!("{} - {}", status_str, message);
match status_code {
404 => CondowError::new_not_found(message),
401 | 403 => CondowError::new_access_denied(message),
_ => {
if is_server_error {
CondowError::new_remote(message)
} else {
CondowError::new_other(message)
}
}
}
}