use core::{
error::Error,
fmt::{self, Write},
panic::Location,
};
use std::{env::current_exe, ffi::OsStr, path::PathBuf};
use crate::style::{BOLD, RED, RESET};
pub trait ErrorLogger {
#[track_caller]
fn log_error(self) -> Self;
}
impl<T, E: fmt::Display> ErrorLogger for Result<T, E> {
#[track_caller]
fn log_error(self) -> Self {
if let Err(error) = self.as_ref() {
let location = Location::caller();
#[cfg(feature = "log")]
log::error!("[{location}] {error}");
#[cfg(not(feature = "log"))]
eprintln!("{BOLD}{RED}error{RESET}{BOLD}:{RESET} [{location}] {error}");
}
self
}
}
impl<T> ErrorLogger for Option<T> {
#[track_caller]
fn log_error(self) -> Self {
if self.is_none() {
let location = Location::caller();
#[cfg(feature = "log")]
log::error!("[{location}] value was None");
#[cfg(not(feature = "log"))]
eprintln!("{BOLD}{RED}error{RESET}{BOLD}:{RESET} [{location}] value was None");
}
self
}
}
pub type ReportProgramExit = Result<(), ProgramReport>;
pub struct ProgramReport(Box<dyn Error + 'static>);
impl<E: Error + 'static> From<E> for ProgramReport {
fn from(value: E) -> Self {
Self(Box::new(value))
}
}
impl fmt::Debug for ProgramReport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self}")
}
}
impl fmt::Display for ProgramReport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let report = Report::new(self.0.as_ref(), ErrorStackStyle::Stacked { indent: 2 });
let exe_path = current_exe().unwrap_or_else(|_| PathBuf::from("program"));
let exe = exe_path
.file_name()
.unwrap_or_else(|| OsStr::new("program"))
.to_string_lossy();
writeln!(f, "`{exe}` exited unsuccessfully")?;
write!(f, "{report}")
}
}
pub trait IntoErrorReport<'a, T>: Sized {
fn into_report(self) -> Result<T, Report<'a>>;
}
impl<'a, T, E: Error + 'a> IntoErrorReport<'a, T> for Result<T, E> {
fn into_report(self) -> Result<T, Report<'a>> {
self.map_err(|source| Report::new(source, ErrorStackStyle::default()))
}
}
impl<'a, T> IntoErrorReport<'a, T> for Option<T> {
fn into_report(self) -> Result<T, Report<'a>> {
#[derive(Debug)]
struct NoneError;
impl fmt::Display for NoneError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "value was none")
}
}
impl Error for NoneError {}
self.ok_or_else(|| Report::new(NoneError, ErrorStackStyle::default()))
}
}
pub struct Report<'a> {
pub source: Box<dyn Error + 'a>,
pub style: ErrorStackStyle<'a>,
}
impl<'a> Report<'a> {
pub fn new<E: Error + 'a>(source: E, style: ErrorStackStyle<'a>) -> Self {
Self {
source: Box::new(source),
style,
}
}
#[track_caller]
pub fn log_error(&self) {
let location = Location::caller();
#[cfg(feature = "log")]
log::error!("[{location}] {self}");
#[cfg(not(feature = "log"))]
eprintln!("{BOLD}{RED}error{RESET}{BOLD}:{RESET} [{location}] {self}");
}
}
impl Error for Report<'static> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(self.source.as_ref())
}
}
impl fmt::Debug for Report<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self}")
}
}
impl fmt::Display for Report<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let output = self.style.display(self.source.as_ref())?;
writeln!(f, "{output}")
}
}
pub type FmtErrorClosure<'a> = Box<dyn Fn(&mut String, usize, &dyn Error) -> fmt::Result + 'a>;
pub enum ErrorStackStyle<'a> {
Inline,
Stacked {
indent: usize,
},
Custom(FmtErrorClosure<'a>),
}
impl Default for ErrorStackStyle<'_> {
fn default() -> Self {
Self::Stacked { indent: 2 }
}
}
impl ErrorStackStyle<'_> {
pub fn display(&self, source: &dyn Error) -> Result<String, fmt::Error> {
let mut output = String::new();
let fmt_fn = self.fmt_fn();
let mut current_error = Some(source);
let mut index = 1;
while let Some(error) = current_error {
fmt_fn(&mut output, index, error)?;
current_error = error.source();
index += 1;
}
Ok(output)
}
fn fmt_fn(&self) -> FmtErrorClosure<'_> {
match &self {
Self::Inline => Box::new(|f, i, e| write!(f, " ----- {i}. {e}")),
Self::Stacked { indent } => Box::new(|f, i, e| {
writeln!(
f,
"{}{BOLD}{RED}{i}{RESET}{BOLD}.{RESET} {e}",
" ".repeat(*indent)
)
}),
Self::Custom(f) => Box::new(f),
}
}
}