use std::{
error::Error,
fmt::{Debug, Display, Write},
hint::cold_path,
path::Path,
};
#[macro_export]
macro_rules! unwrap_or_return_some_err {
($expression:expr) => {
match $expression {
Ok(v) => v,
Err(e) => return Some(Err(e)),
}
};
}
pub trait GetCode {
#[inline]
#[must_use]
fn get_code(&self) -> i32 {
1
}
}
impl GetCode for std::io::Error {
#[inline]
fn get_code(&self) -> i32 {
if let Some(code) = self.raw_os_error() {
return code;
}
let mut source = self.source();
while let Some(err) = source {
if let Some(e) = err.downcast_ref::<std::io::Error>()
&& let Some(code) = e.raw_os_error()
{
return code;
}
source = err.source();
}
1
}
}
pub trait OrFail<T> {
fn unwrap_or_fail(self) -> T;
fn unwrap_or_die(self, msg: &str) -> T;
}
impl<T, E> OrFail<T> for Result<T, E>
where
E: GetCode + Display + Error + 'static,
{
fn unwrap_or_fail(self) -> T {
match self {
Ok(result) => result,
Err(e) => {
cold_path();
e.fail()
}
}
}
fn unwrap_or_die(self, msg: &str) -> T {
match self {
Ok(result) => result,
Err(e) => {
cold_path();
e.die(msg)
}
}
}
}
pub trait Fail {
fn fail(self) -> !;
fn die(self, msg: &str) -> !;
}
impl<E> Fail for E
where
E: GetCode + Display + Error + 'static,
{
fn fail(self) -> ! {
if let Ok(bin) = std::env::current_exe() {
eprintln!("Error in {b}", b = bin.display());
} else {
eprintln!("Error in program");
}
print_stack(&self);
std::process::exit(self.get_code());
}
fn die(self, msg: &str) -> ! {
if let Ok(bin) = std::env::current_exe() {
eprintln!("Error in {b}: {msg}", b = bin.display());
} else {
eprintln!("Error: {msg}");
}
print_stack(&self);
std::process::exit(self.get_code());
}
}
#[must_use]
#[derive(Debug)]
pub struct ErrorWithContext {
repr: Box<ErrorWithContextRepr>,
}
impl ErrorWithContext {
pub fn new(description: impl Into<String>) -> Self {
ErrorWithContext {
repr: Box::new(ErrorWithContextRepr {
description: description.into(),
subitem: None,
source: None,
}),
}
}
}
#[derive(Debug)]
struct ErrorWithContextRepr {
description: String,
subitem: Option<String>,
source: Option<Box<dyn Error + Send + Sync>>,
}
impl Display for ErrorWithContextRepr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.description)?;
if let Some(subitem) = &self.subitem {
write!(
f,
"\n| {}",
IndentWrapper {
val: subitem,
indent: "| ",
}
)?;
}
Ok(())
}
}
impl std::fmt::Display for ErrorWithContext {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.repr)
}
}
impl Error for ErrorWithContext {
#[inline]
fn source(&self) -> Option<&(dyn Error + 'static)> {
match &self.repr.source {
Some(source) => Some(source.as_ref()),
None => None,
}
}
}
impl From<ErrorWithContext> for std::io::Error {
#[inline]
fn from(e: ErrorWithContext) -> Self {
std::io::Error::other(e)
}
}
pub trait WithErrorContext {
fn with_context(self, description: impl Into<String>) -> ErrorWithContext;
fn with_type_context<T>(self) -> ErrorWithContext;
fn with_path_context(self, msg: impl Display, file: impl AsRef<Path>) -> ErrorWithContext;
}
impl<E: Error + Send + Sync + 'static> WithErrorContext for E {
fn with_context(self, description: impl Into<String>) -> ErrorWithContext {
ErrorWithContext {
repr: Box::new(ErrorWithContextRepr {
description: description.into(),
subitem: None,
source: Some(Box::new(self)),
}),
}
}
fn with_type_context<T>(self) -> ErrorWithContext {
let name = std::any::type_name::<T>();
let description = format!(
"Failure in {}",
name.split('<').next().unwrap_or(name).rsplit("::").next().unwrap_or(name)
);
ErrorWithContext {
repr: Box::new(ErrorWithContextRepr {
description,
subitem: None,
source: Some(Box::new(self)),
}),
}
}
fn with_path_context(self, msg: impl Display, file: impl AsRef<Path>) -> ErrorWithContext {
Self::with_context(self, format!("{msg}: '{path}'", path = file.as_ref().display()))
}
}
pub trait WithSubitem {
fn with_subitem(self, message: impl Into<String>) -> ErrorWithContext;
}
impl WithSubitem for ErrorWithContext {
fn with_subitem(mut self, message: impl Into<String>) -> ErrorWithContext {
let subitem = &mut self.repr.subitem;
let message = message.into();
if let Some(subitem) = subitem {
subitem.push('\n');
subitem.push_str(&message);
} else {
*subitem = Some(message);
}
self
}
}
pub trait ResultWithErrorContext {
type Ok;
fn with_context(self, description: impl Into<String>) -> Result<Self::Ok, ErrorWithContext>;
fn with_type_context<T>(self) -> Result<Self::Ok, ErrorWithContext>;
fn with_path_context(self, msg: impl Display, file: impl AsRef<Path>) -> Result<Self::Ok, ErrorWithContext>;
}
impl<Ok, E: WithErrorContext> ResultWithErrorContext for Result<Ok, E> {
type Ok = Ok;
#[inline]
fn with_context(self, description: impl Into<String>) -> Result<Ok, ErrorWithContext> {
self.map_err(|e| {
cold_path();
e.with_context(description)
})
}
#[inline]
fn with_type_context<T>(self) -> Result<Ok, ErrorWithContext> {
self.map_err(|e| {
cold_path();
e.with_type_context::<T>()
})
}
#[inline]
fn with_path_context(self, msg: impl Display, file: impl AsRef<Path>) -> Result<Ok, ErrorWithContext> {
self.map_err(|e| {
cold_path();
e.with_path_context(msg, file)
})
}
}
pub trait ResultWithSubitem {
#[must_use]
fn with_subitem(self, message: impl Into<String>) -> Self;
}
impl<T> ResultWithSubitem for Result<T, ErrorWithContext> {
#[inline]
fn with_subitem(self, message: impl Into<String>) -> Self {
self.map_err(|e| {
cold_path();
e.with_subitem(message)
})
}
}
struct IndentFormatter<'a, 'b> {
formatter: &'a mut std::fmt::Formatter<'b>,
indent: &'static str,
}
impl Write for IndentFormatter<'_, '_> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
let mut parts = s.split('\n');
let Some(first_part) = parts.next() else { return Ok(()) };
self.formatter.write_str(first_part)?;
for part in parts {
self.formatter.write_char('\n')?;
self.formatter.write_str(self.indent)?;
self.formatter.write_str(part)?;
}
Ok(())
}
fn write_char(&mut self, c: char) -> std::fmt::Result {
if c == '\n' {
self.formatter.write_char('\n')?;
self.formatter.write_str(self.indent)
} else {
self.formatter.write_char(c)
}
}
}
struct IndentWrapper<T> {
val: T,
indent: &'static str,
}
impl<T: Display> Display for IndentWrapper<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
IndentFormatter {
formatter: f,
indent: self.indent,
},
"{}",
self.val
)
}
}
#[cold]
fn print_stack(err: &(dyn Error + 'static)) {
let mut maybe_err = Some(err);
while let Some(err) = maybe_err {
eprintln!(
" → {err}",
err = IndentWrapper {
val: err,
indent: " ",
}
);
maybe_err = err.source();
}
}