use std::{error::Error as ErrorTrait, fmt};
use super::message::Message;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ErrorType {
StorageError,
ConfigError,
SerializeError,
ValidationError,
ParseError,
CertificateError,
TLSConfigError,
AsyncRuntimeError,
StreamError,
ConnectError,
PluginError,
}
impl ErrorType {
pub fn as_str(&self) -> &'static str {
match self {
ErrorType::StorageError => "StorageError",
ErrorType::ConfigError => "ConfigError",
ErrorType::ValidationError => "ValidationError",
ErrorType::ParseError => "ParseError",
ErrorType::CertificateError => "CertificateError",
ErrorType::SerializeError => "SerializeError",
ErrorType::TLSConfigError => "TLSConfigError",
ErrorType::AsyncRuntimeError => "AsyncRuntimeError",
ErrorType::StreamError => "StreamError",
ErrorType::ConnectError => "ConnectError",
ErrorType::PluginError => "PluginError",
}
}
}
#[derive(Debug)]
pub struct ExternalError {
pub etype: ErrorType,
pub cause: Option<Box<dyn ErrorTrait + Send + Sync>>,
pub context: Option<Message>,
}
impl ExternalError {
pub fn new(etype: ErrorType) -> Self {
ExternalError {
etype,
cause: None,
context: None,
}
}
pub fn with_context(mut self, message: impl Into<Message>) -> Self {
self.context = Some(message.into());
self
}
pub fn with_cause(mut self, cause: Box<dyn ErrorTrait + Send + Sync>) -> Self {
self.cause = Some(cause);
self
}
fn chain_display(
&self,
previous: Option<&ExternalError>,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
if previous.map(|p| p.etype != self.etype).unwrap_or(true) {
write!(f, "{}", self.etype.as_str())?
}
if let Some(c) = self.context.as_ref() {
write!(f, " context: {}", c.as_str())?;
}
if let Some(c) = self.cause.as_ref() {
if let Some(e) = c.downcast_ref::<Box<ExternalError>>() {
write!(f, " cause: ")?;
e.chain_display(Some(self), f)
} else {
write!(f, " cause: {}", c)
}
} else {
Ok(())
}
}
}
impl fmt::Display for ExternalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.chain_display(None, f)
}
}
impl ErrorTrait for ExternalError {}
pub trait OrErr<T, E> {
fn or_err(self, et: ErrorType) -> Result<T, ExternalError>
where
E: Into<Box<dyn ErrorTrait + Send + Sync>>;
fn or_context(self, et: ErrorType, context: &'static str) -> Result<T, ExternalError>
where
E: Into<Box<dyn ErrorTrait + Send + Sync>>;
}
impl<T, E> OrErr<T, E> for Result<T, E> {
fn or_err(self, et: ErrorType) -> Result<T, ExternalError>
where
E: Into<Box<dyn ErrorTrait + Send + Sync>>,
{
self.map_err(|err| ExternalError::new(et).with_cause(err.into()))
}
fn or_context(self, et: ErrorType, context: &'static str) -> Result<T, ExternalError>
where
E: Into<Box<dyn ErrorTrait + Send + Sync>>,
{
self.map_err(|err| {
ExternalError::new(et)
.with_cause(err.into())
.with_context(context)
})
}
}
#[derive(Debug, thiserror::Error)]
#[error("backend error: {message}")]
pub struct BackendError {
pub message: String,
pub status_code: Option<reqwest::StatusCode>,
pub header: Option<reqwest::header::HeaderMap>,
}
#[derive(Debug, thiserror::Error)]
#[error("download piece {piece_number} from parent {parent_id} failed")]
pub struct DownloadFromParentFailed {
pub piece_number: u32,
pub parent_id: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_create_error() {
let error = ExternalError::new(ErrorType::StorageError).with_context("error message");
assert_eq!(format!("{}", error), "StorageError context: error message");
let error = ExternalError::new(ErrorType::StorageError)
.with_context(format!("error message {}", "with owned string"));
assert_eq!(
format!("{}", error),
"StorageError context: error message with owned string"
);
let error = ExternalError::new(ErrorType::StorageError)
.with_context(format!("error message {}", "with owned string"))
.with_cause(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
"inner error",
)));
assert_eq!(
format!("{}", error),
"StorageError context: error message with owned string cause: inner error"
);
}
#[test]
fn should_extend_result_with_error() {
let result: Result<(), std::io::Error> = Err(std::io::Error::new(
std::io::ErrorKind::Other,
"inner error",
));
let error = result.or_err(ErrorType::StorageError).unwrap_err();
assert_eq!(format!("{}", error), "StorageError cause: inner error");
let result: Result<(), std::io::Error> = Err(std::io::Error::new(
std::io::ErrorKind::Other,
"inner error",
));
let error = result
.or_context(ErrorType::StorageError, "error message")
.unwrap_err();
assert_eq!(
format!("{}", error),
"StorageError context: error message cause: inner error"
);
}
}