use std::{error, fmt};
#[derive(Debug, PartialEq, Eq)]
pub enum Kind {
User,
System,
}
impl Kind {
fn format_description(&self, description: &str) -> String {
match self {
Kind::User => format!("Oh no! {description}"),
Kind::System => format!("Whoops! {description} (This isn't your fault)"),
}
}
}
#[derive(Debug)]
pub struct Error {
kind: Kind,
error: Box<dyn error::Error + Send + Sync>,
advice: &'static [&'static str],
}
impl Error {
pub fn new<E: Into<Box<dyn error::Error + Send + Sync>>>(
error: E,
kind: Kind,
advice: &'static [&'static str],
) -> Self {
Self {
error: error.into(),
kind,
advice,
}
}
pub fn description(&self) -> String {
match self.error.downcast_ref::<Error>() {
Some(err) => err.description(),
None => format!("{}", self.error),
}
}
pub fn message(&self) -> String {
let description = self.description();
let hero_message = self.kind.format_description(&description);
match (self.caused_by(), self.advice()) {
(cause, advice) if !cause.is_empty() && !advice.is_empty() => {
format!(
"{}\n\nThis was caused by:\n - {}\n\nTo try and fix this, you can:\n - {}",
hero_message,
cause.join("\n - "),
advice.join("\n - ")
)
}
(cause, _) if !cause.is_empty() => {
format!(
"{}\n\nThis was caused by:\n - {}",
hero_message,
cause.join("\n - ")
)
}
(_, advice) if !advice.is_empty() => {
format!(
"{}\n\nTo try and fix this, you can:\n - {}",
hero_message,
advice.join("\n - ")
)
}
_ => hero_message,
}
}
fn caused_by(&self) -> Vec<String> {
let mut causes = Vec::new();
let mut current_error: &dyn error::Error = self.error.as_ref();
while let Some(err) = current_error.source() {
if let Some(err) = err.downcast_ref::<Error>() {
causes.push(err.description());
} else {
causes.push(format!("{}", err));
}
current_error = err;
}
causes
}
fn advice(&self) -> Vec<&'static str> {
let mut advice = self.advice.to_vec();
let mut cause = self.error.as_ref();
while let Some(err) = cause.downcast_ref::<Error>() {
advice.extend_from_slice(err.advice);
cause = err.error.as_ref();
}
advice.reverse();
let mut seen = std::collections::HashSet::new();
advice.retain(|item| seen.insert(*item));
advice
}
pub fn is(&self, kind: Kind) -> bool {
self.kind == kind
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
self.error.source()
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message())
}
}