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 as_str_names_each_error_type() {
let test_cases = vec![
(ErrorType::StorageError, "StorageError"),
(ErrorType::ConfigError, "ConfigError"),
(ErrorType::SerializeError, "SerializeError"),
(ErrorType::ValidationError, "ValidationError"),
(ErrorType::ParseError, "ParseError"),
(ErrorType::CertificateError, "CertificateError"),
(ErrorType::TLSConfigError, "TLSConfigError"),
(ErrorType::AsyncRuntimeError, "AsyncRuntimeError"),
(ErrorType::StreamError, "StreamError"),
(ErrorType::ConnectError, "ConnectError"),
(ErrorType::PluginError, "PluginError"),
];
for (etype, expected) in test_cases {
assert_eq!(etype.as_str(), expected);
}
}
#[test]
fn display_chains_type_context_and_cause() {
let test_cases = vec![
(
ExternalError::new(ErrorType::StorageError).with_context("error message"),
"StorageError context: error message",
),
(
ExternalError::new(ErrorType::StorageError)
.with_context("error message with owned string".to_string()),
"StorageError context: error message with owned string",
),
(
ExternalError::new(ErrorType::StorageError)
.with_context("error message with owned string".to_string())
.with_cause(Box::new(std::io::Error::other("inner error"))),
"StorageError context: error message with owned string cause: inner error",
),
(
ExternalError::new(ErrorType::StorageError)
.with_context("outer")
.with_cause(Box::new(Box::new(
ExternalError::new(ErrorType::ConfigError).with_context("inner"),
))),
"StorageError context: outer cause: ConfigError context: inner",
),
(
ExternalError::new(ErrorType::StorageError)
.with_context("outer")
.with_cause(Box::new(Box::new(
ExternalError::new(ErrorType::StorageError).with_context("inner"),
))),
"StorageError context: outer cause: context: inner",
),
];
for (error, expected) in test_cases {
assert_eq!(error.to_string(), expected);
}
}
#[test]
fn or_err_and_or_context_wrap_the_cause() {
let test_cases = vec![
(
Err::<(), _>(std::io::Error::other("inner error")).or_err(ErrorType::StorageError),
"StorageError cause: inner error",
),
(
Err::<(), _>(std::io::Error::other("inner error"))
.or_context(ErrorType::StorageError, "error message"),
"StorageError context: error message cause: inner error",
),
];
for (result, expected) in test_cases {
let error = result.unwrap_err();
assert_eq!(error.to_string(), expected);
}
}
}