#![doc = include_str!("../README.md")]
mod context;
mod trace;
pub use context::*;
pub use trace::*;
mod imp {
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
pub enum Never {}
}
use imp::Never;
use miette::Diagnostic;
use std::fmt::{self, Debug};
use std::sync::Arc;
pub type Result<Ok = (), Kind = Never> = std::result::Result<Ok, Error<Kind>>;
pub(crate) const MARKER_ERROR_TRACE: &str = "MARKER_ERROR_TRACE";
pub struct Error<Kind = Never> {
imp: Arc<ErrorImpl<Kind>>,
}
struct ErrorImpl<Kind> {
category: ErrorCategory<Kind>,
trace: Option<ErrorTrace>,
}
#[derive(Debug, thiserror::Error)]
enum ErrorCategory<Kind> {
#[error(transparent)]
Categorized(Kind),
#[error("{message}")]
Uncategorized {
message: String,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error(transparent)]
TransparentUncategorized(#[from] Box<dyn std::error::Error + Send + Sync>),
#[error("{message}")]
ManyUncategorized { message: String, errors: Vec<Error<Kind>> },
}
impl<Kind> Error<Kind> {
pub fn print(&self)
where
Kind: miette::Diagnostic,
{
eprint!("{}", ErrorRender(self));
}
pub fn kind(&self) -> Option<&Kind> {
match &self.imp.category {
ErrorCategory::Categorized(kind) => Some(kind),
_ => None,
}
}
pub fn from_kind(kind: Kind) -> Self {
Self::from_category(ErrorCategory::Categorized(kind))
}
fn from_category(category: ErrorCategory<Kind>) -> Self {
let trace = if std::env::var(MARKER_ERROR_TRACE).is_ok() {
Some(ErrorTrace::capture())
} else {
None
};
let imp = ErrorImpl { category, trace };
Self { imp: Arc::new(imp) }
}
pub fn wrap(source: impl Into<Box<dyn std::error::Error + Send + Sync>>, message: impl Into<String>) -> Self {
Self::from_category(ErrorCategory::Uncategorized {
message: message.into(),
source: Some(source.into()),
})
}
pub fn root(message: impl Into<String>) -> Self {
Self::from_category(ErrorCategory::Uncategorized {
message: message.into(),
source: None,
})
}
pub fn try_many(errors: impl IntoIterator<Item = Self>, message: impl Into<String>) -> Result<(), Kind> {
let mut errors = errors.into_iter();
let Some(first) = errors.next() else {
return Ok(());
};
let Some(second) = errors.next() else {
return Err(first);
};
Err(Self::many([first, second].into_iter().chain(errors), message))
}
pub fn many(errors: impl IntoIterator<Item = Error<Kind>>, message: impl Into<String>) -> Self {
Self::from_category(ErrorCategory::ManyUncategorized {
message: message.into(),
errors: Vec::from_iter(errors),
})
}
pub fn transparent(error: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
Self::from_category(ErrorCategory::TransparentUncategorized(error.into()))
}
}
impl<Kind> fmt::Debug for Error<Kind>
where
Kind: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.imp.category, f)
}
}
impl<Kind> fmt::Display for Error<Kind>
where
Kind: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.imp.category, f)?;
if let Some(trace) = &self.imp.trace {
write!(f, "\n{trace}")?;
}
Ok(())
}
}
impl<Kind> std::error::Error for Error<Kind>
where
Kind: std::error::Error,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.imp.category.source()
}
}
impl<Kind> Diagnostic for Error<Kind>
where
Kind: Diagnostic,
{
fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
self.kind().and_then(Diagnostic::code)
}
fn severity(&self) -> Option<miette::Severity> {
self.kind().and_then(Diagnostic::severity)
}
fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
self.kind().and_then(Diagnostic::help)
}
fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
self.kind().and_then(Diagnostic::url)
}
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
self.kind().and_then(Diagnostic::source_code)
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
self.kind().and_then(Diagnostic::labels)
}
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
match &self.imp.category {
ErrorCategory::Categorized(kind) => kind.related(),
ErrorCategory::ManyUncategorized { errors, .. } => Some(Box::new(errors.iter().map(|err| err as _))),
ErrorCategory::Uncategorized { .. } | ErrorCategory::TransparentUncategorized(_) => None,
}
}
fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
self.kind().and_then(Diagnostic::diagnostic_source)
}
}
struct ErrorRender<'a, Kind>(&'a Error<Kind>);
impl<Kind> std::fmt::Display for ErrorRender<'_, Kind>
where
Kind: miette::Diagnostic,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let handler = miette::MietteHandlerOpts::new().width(140).build();
miette::ReportHandler::debug(&handler, self.0, f)
}
}
impl<Kind> From<Kind> for Error<Kind> {
fn from(value: Kind) -> Self {
Self::from_kind(value)
}
}