use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Write;
#[derive(Debug)]
#[non_exhaustive]
pub struct Error {
message: String,
context: Vec<String>,
source: Option<Box<dyn std::error::Error + 'static>>,
}
impl Error {
#[must_use]
pub const fn new(message: String) -> Self {
Self {
message,
context: Vec::new(),
source: None,
}
}
#[must_use = "returns a new error with the specified source"]
pub fn with_source(self, source: Box<dyn std::error::Error>) -> Self {
Self { source: Some(source), ..self }
}
pub fn push_context(&mut self, context: impl Into<String>) {
self.context.push(context.into());
}
#[must_use = "returns a new error with additional context"]
pub fn with_context(mut self, context: impl Into<String>) -> Self {
self.push_context(context);
self
}
#[must_use]
pub fn chain_with(&self, arrow: &str) -> String {
let mut output = self.message.clone();
for context in &self.context {
let _ = write!(output, "\n{arrow} while {context}");
}
output
}
#[must_use]
pub fn chain(&self) -> String {
self.chain_with(">")
}
#[must_use]
pub fn chain_pretty(&self) -> String {
self.chain_with("↳")
}
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.as_deref()
}
}
impl From<String> for Error {
fn from(message: String) -> Self {
Self::new(message)
}
}
impl From<&str> for Error {
fn from(message: &str) -> Self {
Self::new(message.to_string())
}
}
pub type Result<T> = std::result::Result<T, Error>;
pub trait Context<T> {
fn context(self, context: &str) -> Result<T>;
fn with_context(self, f: impl FnOnce() -> String) -> Result<T>;
}
impl<T> Context<T> for Result<T> {
fn context(self, context: &str) -> Self {
self.map_err(|err| err.with_context(context))
}
fn with_context(self, f: impl FnOnce() -> String) -> Self {
self.map_err(|err| err.with_context(f()))
}
}
impl<T> Context<T> for std::result::Result<T, &str> {
fn context(self, context: &str) -> Result<T> {
self.map_err(|error| Error::new(error.to_owned()).with_context(context))
}
fn with_context(self, f: impl FnOnce() -> String) -> Result<T> {
self.map_err(|error| Error::new(error.to_owned()).with_context(f()))
}
}
impl<T> Context<T> for std::result::Result<T, String> {
fn context(self, context: &str) -> Result<T> {
self.map_err(|error| Error::new(error).with_context(context))
}
fn with_context(self, f: impl FnOnce() -> String) -> Result<T> {
self.map_err(|error| Error::new(error).with_context(f()))
}
}
pub trait ContextSrc<T> {
fn context_src(self, context: &str) -> Result<T>;
fn with_context_src(self, f: impl FnOnce() -> String) -> Result<T>;
}
impl<T, E: std::error::Error + 'static> ContextSrc<T> for std::result::Result<T, E> {
fn context_src(self, context: &str) -> Result<T> {
self.map_err(|error| {
Error::new(ascii_capitalize(error.to_string()))
.with_context(context)
.with_source(Box::new(error))
})
}
fn with_context_src(self, f: impl FnOnce() -> String) -> Result<T> {
self.map_err(|error| {
Error::new(ascii_capitalize(error.to_string()))
.with_context(f())
.with_source(Box::new(error))
})
}
}
#[must_use]
fn ascii_capitalize(mut string: String) -> String {
if let Some(first) = string.get_mut(0..1) {
first.make_ascii_uppercase();
}
string
}
#[macro_export]
macro_rules! err {
($($arg:tt)*) => {
$crate::error::Error::new(format!($($arg)*))
};
}
#[macro_export]
macro_rules! bail {
($($arg:tt)*) => {
return Err($crate::error::Error::new(format!($($arg)*)))
};
}
pub use bail;
pub use err;