summaryrefslogtreecommitdiff
path: root/ecs/src/error.rs
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2026-05-19 00:12:11 +0200
committerHampusM <hampus@hampusmat.com>2026-05-19 00:12:11 +0200
commitd5a744b0909c4b2bec397ae4dcd43b56aba355c6 (patch)
tree1dcf8b769775ab7a0f4ff87e280aa98a0d280d21 /ecs/src/error.rs
parentb13b3f6e13f9ac9fe7fee0b5a81b026f411f0301 (diff)
feat(ecs): add error handlingHEADmaster
Diffstat (limited to 'ecs/src/error.rs')
-rw-r--r--ecs/src/error.rs227
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(())
+ }
+}