use std::borrow::Cow;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Write;
#[derive(Debug)]
#[non_exhaustive]
pub struct Error {
message: String,
context: Vec<String>,
}
impl Error {
#[must_use]
pub const fn new(message: String) -> Self {
Self { message, context: Vec::new() }
}
#[cold]
#[must_use]
pub const fn new_cold(message: String) -> Self {
Self::new(message)
}
pub fn push_context(&mut self, context: impl StringLike) {
self.context.push(context.into_string());
}
#[must_use = "returns a new error with additional context"]
pub fn with_context(mut self, context: impl StringLike) -> 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 {}
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 trait StringLike {
fn into_string(self) -> String;
}
impl StringLike for &str {
fn into_string(self) -> String {
self.to_owned()
}
}
impl StringLike for String {
fn into_string(self) -> String {
self
}
}
impl StringLike for Cow<'_, str> {
fn into_string(self) -> String {
self.into_owned()
}
}
impl<S: StringLike, F: FnOnce() -> S> StringLike for F {
fn into_string(self) -> String {
self().into_string()
}
}
pub type Result<T> = std::result::Result<T, Error>;
pub trait Context<T> {
fn ctx(self, context: impl StringLike) -> Result<T>;
}
impl<T> Context<T> for Result<T> {
fn ctx(self, context: impl StringLike) -> Self {
self.map_err(|err| err.with_context(context.into_string()))
}
}
impl<T> Context<T> for std::result::Result<T, &str> {
fn ctx(self, context: impl StringLike) -> Result<T> {
self.map_err(|e| Error::new(e.to_owned()).with_context(context))
}
}
impl<T> Context<T> for std::result::Result<T, String> {
fn ctx(self, context: impl StringLike) -> Result<T> {
self.map_err(|e| Error::new(e).with_context(context))
}
}
pub trait ContextAny<T> {
fn ctx_any(self, context: impl StringLike) -> Result<T>;
}
impl<T, E: std::error::Error> ContextAny<T> for std::result::Result<T, E> {
fn ctx_any(self, context: impl StringLike) -> Result<T> {
self.map_err(|e| Error::new(ascii_capitalize(e.to_string())).with_context(context))
}
}
#[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_cold(format!($($arg)*)))
};
}
pub use bail;
pub use err;