use std::fmt::{Debug, Display}; use crate::World; #[macro_export] macro_rules! error { ($lit: literal) => { $crate::error::Error::message($lit) }; ($lit: literal, $($tt: tt)*) => { $crate::error::Error::message(std::format!($lit, $($tt)*)) }; ($err: expr) => { $crate::error::Error::from($err) }; } pub struct Error { inner: anyhow::Error, } impl Error { pub fn new(err: Err) -> Self where Err: std::error::Error + Send + Sync + 'static, { Self::from(err) } pub fn message(message: Message) -> Self where Message: Display + Debug + Send + Sync + 'static, { Self { inner: anyhow::Error::msg(message) } } pub fn context(self, context: Ctx) -> Error where Ctx: Display + Send + Sync + 'static, { Self { inner: self.inner.context(context) } } /// Retrieves the message text of a system-defined Windows Win32 error code and /// returns it as a `Error`. If this fails (for example if the error code is invalid), /// the returned `Error` contains the error code number. #[cfg(windows)] pub fn from_system_defined_win32_error(error: u32) -> Self { use std::ffi::CStr; use std::ptr::null; use windows_sys::Win32::System::Diagnostics::Debug::{ FormatMessageA, FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_IGNORE_INSERTS, }; let mut buffer = vec![0u8; 256]; let len = unsafe { FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, null(), error, 0, buffer.as_mut_ptr(), (buffer.len() - 1) as u32, null(), ) } as usize; if len == 0 { return Self::message(format!("Win32 error {error}")); } let buf = unsafe { CStr::from_bytes_with_nul_unchecked(&buffer[..len + 1]) }; Self::message(buf.to_string_lossy().trim().to_owned()) } } impl Debug for Error { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ::fmt(&self.inner, formatter) } } impl Display for Error { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ::fmt(&self.inner, formatter) } } impl From for Error where Err: std::error::Error + Send + Sync + 'static, { fn from(err: Err) -> Self { Self { inner: err.into() } } } pub trait Context { fn context(self, context: Ctx) -> Result where Ctx: Display + Send + Sync + 'static; fn with_context(self, context_func: impl FnOnce() -> Ctx) -> Result where Ctx: Display + Send + Sync + 'static; } impl Context for Result where Err: std::error::Error + Send + Sync + 'static, { fn context(self, context: Ctx) -> Result where Ctx: Display + Send + Sync + 'static, { self.map_err(|err| Error::from(err).context(context)) } fn with_context(self, context_func: impl FnOnce() -> Ctx) -> Result where Ctx: Display + Send + Sync + 'static, { self.map_err(|err| Error::from(err).context(context_func())) } } impl Context for Result { fn context(self, context: Ctx) -> Result where Ctx: Display + Send + Sync + 'static, { self.map_err(|err| err.context(context)) } fn with_context(self, context_func: impl FnOnce() -> Ctx) -> Result where Ctx: Display + Send + Sync + 'static, { self.map_err(|err| err.context(context_func())) } } pub type HandlerFn = fn(&World, 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(_world: &World, err: Error, err_metadata: Metadata) { std::panic::panic_any(err.context(format!( "Error occurred in {} '{}'", err_metadata.source_kind, err_metadata.source_name ))); } pub fn err_handler_log_error(_world: &World, err: Error, err_metadata: Metadata) { tracing::error!( "Error occurred in {} '{}': {err:#}", err_metadata.source_kind, err_metadata.source_name ); } pub(crate) fn set_panic_hook() { let previous_panic_hook = std::panic::take_hook(); std::panic::set_hook(Box::new(move |info| { let Some(err) = info.payload().downcast_ref::() else { return previous_panic_hook(info); }; print!( "Thread '{}' ({:?}) panicked", std::thread::current().name().unwrap_or(""), get_current_os_thread_id() ); if let Some(location) = info.location() { print!(" at {location}"); } print!("\n{err:?}"); if err.inner.backtrace().status() == std::backtrace::BacktraceStatus::Disabled { print!(concat!( "\nnote: run with `RUST_BACKTRACE=1` environment variable ", "to display a backtrace" )); } println!(""); })); } fn get_current_os_thread_id() -> u64 { cfg_select! { target_os = "linux" => { (unsafe { libc::gettid() }) as u64 } windows => { (unsafe { windows_sys::Win32::System::Threading::GetCurrentThreadId() }) as u64 } _ => { compile_error!("Unsupported target OS"); } } }