diff options
Diffstat (limited to 'ecs/src/error.rs')
| -rw-r--r-- | ecs/src/error.rs | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/ecs/src/error.rs b/ecs/src/error.rs new file mode 100644 index 0000000..f70524d --- /dev/null +++ b/ecs/src/error.rs @@ -0,0 +1,227 @@ +use std::fmt::{Debug, Display, Write as _}; + +use backtrace::Backtrace; + +#[macro_export] +macro_rules! error { + ($lit: literal) => { + $crate::error::Error::from($lit) + }; + + ($lit: literal, $($tt: tt)*) => { + $crate::error::Error::from(std::format!($lit, $($tt)*)) + }; + + ($err: expr) => { + $crate::error::Error::from($err) + }; +} + +pub struct Error +{ + inner: Box<dyn std::error::Error + Send + Sync>, + backtrace: Backtrace, +} + +impl Debug for Error +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + let error = &*self.inner; + + write!(formatter, "{error}")?; + + if let Some(cause) = error.source() { + write!(formatter, "\n\nCaused by:")?; + let multiple = cause.source().is_some(); + for (n, error) in anyhow::Chain::new(cause).enumerate() { + writeln!(formatter)?; + + let mut indented = Indented { + inner: formatter, + number: if multiple { Some(n) } else { None }, + started: false, + }; + write!(indented, "{error}")?; + } + } + + write!( + formatter, + "\n\nStack backtrace:\n{:?}", + std::fmt::from_fn(|backtrace_formatter| fmt_backtrace( + &self.backtrace, + backtrace_formatter + )) + )?; + + Ok(()) + } +} + +impl Display for Error +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + let error = &*self.inner; + + write!(formatter, "{error}")?; + + if formatter.alternate() { + let chain = anyhow::Chain::new(error); + for cause in chain.skip(1) { + write!(formatter, ": {}", cause)?; + } + } + + Ok(()) + } +} + +impl<Err: Send + Sync + 'static> From<Err> for Error +where + Box<dyn std::error::Error + Send + Sync>: From<Err>, +{ + fn from(err: Err) -> Self + { + Self { + inner: err.into(), + backtrace: Backtrace::new(), + } + } +} + +pub type ErrorHandler = fn(Error, Metadata); + +/// Error metadata. +#[derive(Debug)] +pub struct Metadata +{ + pub source_name: &'static str, + pub source_kind: SourceKind, +} + +/// Error source kind. +#[derive(Debug)] +#[non_exhaustive] +pub enum SourceKind +{ + System, + Observer, +} + +impl Display for SourceKind +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + match self { + SourceKind::System => formatter.write_str("system"), + SourceKind::Observer => formatter.write_str("observer"), + } + } +} + +pub fn err_handler_panic(err: Error, err_metadata: Metadata) +{ + panic!( + "Error occurred in {} '{}': {err:?}", + err_metadata.source_kind, err_metadata.source_name + ); +} + +pub fn err_handler_log_error(err: Error, err_metadata: Metadata) +{ + tracing::error!( + "Error occurred in {} '{}': {err:#}", + err_metadata.source_kind, + err_metadata.source_name + ); +} + +fn fmt_backtrace( + backtrace: &Backtrace, + fmt: &mut std::fmt::Formatter<'_>, +) -> std::fmt::Result +{ + let style = if fmt.alternate() { + backtrace::PrintFmt::Full + } else { + backtrace::PrintFmt::Short + }; + + // When printing paths we try to strip the cwd if it exists, otherwise + // we just print the path as-is. Note that we also only do this for the + // short format, because if it's full we presumably want to print + // everything. + let cwd = std::env::current_dir(); + let mut print_path = + move |fmt: &mut std::fmt::Formatter<'_>, + path: backtrace::BytesOrWideString<'_>| { + let path = path.into_path_buf(); + if style != backtrace::PrintFmt::Full { + if let Ok(cwd) = &cwd { + if let Ok(suffix) = path.strip_prefix(cwd) { + return std::fmt::Display::fmt(&suffix.display(), fmt); + } + } + } + std::fmt::Display::fmt(&path.display(), fmt) + }; + + let mut f = backtrace::BacktraceFmt::new(fmt, style, &mut print_path); + + f.add_context()?; + + for frame in backtrace.frames() { + if frame.symbols().iter().all(|symbol| { + symbol.name().is_some_and(|symbol_name| { + let symbol_name = symbol_name.to_string(); + + symbol_name + .contains("<ecs::error::Error as core::convert::From<Err>>::from") + }) + }) { + continue; + } + + f.frame().backtrace_frame(frame)?; + } + f.finish()?; + Ok(()) +} + +struct Indented<'a, D> +{ + inner: &'a mut D, + number: Option<usize>, + started: bool, +} + +impl<T> std::fmt::Write for Indented<'_, T> +where + T: std::fmt::Write, +{ + fn write_str(&mut self, s: &str) -> std::fmt::Result + { + for (i, line) in s.split('\n').enumerate() { + if !self.started { + self.started = true; + match self.number { + Some(number) => write!(self.inner, "{: >5}: ", number)?, + None => self.inner.write_str(" ")?, + } + } else if i > 0 { + self.inner.write_char('\n')?; + if self.number.is_some() { + self.inner.write_str(" ")?; + } else { + self.inner.write_str(" ")?; + } + } + + self.inner.write_str(line)?; + } + + Ok(()) + } +} |
