#![cfg_attr(doc, doc = include_str!("../README.md"))]
#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(all(feature = "alloc", not(feature = "std")))]
use alloc::boxed::Box;
use core::fmt;
pub trait Source: core::error::Error + Send + Sync + 'static {}
impl<T> Source for T where T: core::error::Error + Send + Sync + 'static {}
#[cfg(feature = "alloc")]
struct ErrorMeta {
context: &'static str,
location: &'static core::panic::Location<'static>,
source: Option<Box<dyn Source>>,
}
pub struct Error<Kind> {
kind: Kind,
#[cfg(feature = "alloc")]
meta: Box<ErrorMeta>,
#[cfg(not(feature = "alloc"))]
context: &'static str,
#[cfg(not(feature = "alloc"))]
location: &'static core::panic::Location<'static>,
}
impl<Kind: fmt::Debug> fmt::Debug for Error<Kind> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut dbg = f.debug_struct("Error");
#[cfg(feature = "alloc")]
dbg.field("context", &self.meta.context)
.field("kind", &self.kind)
.field("source", &self.meta.source);
#[cfg(not(feature = "alloc"))]
dbg.field("context", &self.context).field("kind", &self.kind);
dbg.finish()
}
}
impl<Kind> Error<Kind> {
#[cold]
#[must_use]
#[track_caller]
pub fn new(context: &'static str, kind: Kind) -> Self {
Self {
kind,
#[cfg(feature = "alloc")]
meta: Box::new(ErrorMeta {
context,
location: core::panic::Location::caller(),
source: None,
}),
#[cfg(not(feature = "alloc"))]
context,
#[cfg(not(feature = "alloc"))]
location: core::panic::Location::caller(),
}
}
#[cold]
#[must_use]
pub fn with_source<E>(self, source: E) -> Self
where
E: Source,
{
#[cfg(feature = "alloc")]
{
let mut this = self;
this.meta.source = Some(Box::new(source));
this
}
#[cfg(not(feature = "alloc"))]
{
let _ = source;
self
}
}
pub fn kind(&self) -> &Kind {
&self.kind
}
pub fn location(&self) -> &'static core::panic::Location<'static> {
#[cfg(feature = "alloc")]
{
self.meta.location
}
#[cfg(not(feature = "alloc"))]
{
self.location
}
}
pub fn set_context(&mut self, context: &'static str) {
#[cfg(feature = "alloc")]
{
self.meta.context = context;
}
#[cfg(not(feature = "alloc"))]
{
self.context = context;
}
}
pub fn report(&self) -> ErrorReport<'_, Kind> {
ErrorReport {
error: self,
include_locations: false,
}
}
}
impl<Kind> fmt::Display for Error<Kind>
where
Kind: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(feature = "alloc")]
let (context, location) = (self.meta.context, self.meta.location);
#[cfg(not(feature = "alloc"))]
let (context, location) = (self.context, self.location);
if f.alternate() {
write!(f, "[{context} @ {}:{}] {}", location.file(), location.line(), self.kind)
} else {
write!(f, "[{context}] {}", self.kind)
}
}
}
impl<Kind> core::error::Error for Error<Kind>
where
Kind: core::error::Error,
{
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
if let Some(source) = self.kind.source() {
return Some(source);
}
#[cfg(feature = "alloc")]
{
if let Some(e) = &self.meta.source {
return Some(e.as_ref());
}
}
None
}
}
#[cfg(feature = "std")]
impl<Kind> From<Error<Kind>> for std::io::Error
where
Kind: core::error::Error + Send + Sync + 'static,
{
fn from(error: Error<Kind>) -> Self {
Self::other(error)
}
}
pub struct ErrorReport<'a, Kind> {
error: &'a Error<Kind>,
include_locations: bool,
}
impl<Kind> ErrorReport<'_, Kind> {
#[must_use]
pub fn with_locations(mut self) -> Self {
self.include_locations = true;
self
}
}
#[cfg(feature = "std")]
impl<Kind> fmt::Display for ErrorReport<'_, Kind>
where
Kind: core::error::Error,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use core::error::Error as _;
let include_locations = self.include_locations || f.alternate();
if include_locations {
write!(f, "{:#}", self.error)?;
} else {
write!(f, "{}", self.error)?;
}
let mut next_source = self.error.source();
while let Some(e) = next_source {
if include_locations {
write!(f, ", caused by: {e:#}")?;
} else {
write!(f, ", caused by: {e}")?;
}
next_source = e.source();
}
Ok(())
}
}
#[cfg(not(feature = "std"))]
impl<E> fmt::Display for ErrorReport<'_, E>
where
E: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let include_locations = self.include_locations || f.alternate();
if include_locations {
write!(f, "{:#}", self.error)?;
} else {
write!(f, "{}", self.error)?;
}
#[cfg(feature = "alloc")]
if let Some(source) = &self.error.meta.source {
if include_locations {
write!(f, ", caused by: {source:#}")?;
} else {
write!(f, ", caused by: {source}")?;
}
}
Ok(())
}
}
pub trait ErrorMapping<InnerKind>: Sized {
#[track_caller]
fn map_error(error: Error<InnerKind>) -> Error<Self>;
}
pub trait ResultExt<T, InnerKind> {
#[track_caller]
fn map_err_as<OuterKind>(self) -> Result<T, Error<OuterKind>>
where
OuterKind: ErrorMapping<InnerKind>;
#[track_caller]
fn map_err_kind<OuterKind>(
self,
context: &'static str,
wrap: impl FnOnce(Error<InnerKind>) -> OuterKind,
) -> Result<T, Error<OuterKind>>;
#[track_caller]
fn map_err_source<OuterKind>(self, context: &'static str, kind: OuterKind) -> Result<T, Error<OuterKind>>
where
Error<InnerKind>: Source;
}
impl<T, InnerKind> ResultExt<T, InnerKind> for Result<T, Error<InnerKind>> {
#[track_caller]
fn map_err_as<OuterKind>(self) -> Result<T, Error<OuterKind>>
where
OuterKind: ErrorMapping<InnerKind>,
{
match self {
Ok(value) => Ok(value),
Err(error) => Err(OuterKind::map_error(error)),
}
}
#[track_caller]
fn map_err_kind<OuterKind>(
self,
context: &'static str,
wrap: impl FnOnce(Error<InnerKind>) -> OuterKind,
) -> Result<T, Error<OuterKind>> {
match self {
Ok(value) => Ok(value),
Err(error) => Err(Error::new(context, wrap(error))),
}
}
#[track_caller]
fn map_err_source<OuterKind>(self, context: &'static str, kind: OuterKind) -> Result<T, Error<OuterKind>>
where
Error<InnerKind>: Source,
{
match self {
Ok(value) => Ok(value),
Err(error) => Err(Error::new(context, kind).with_source(error)),
}
}
}
#[macro_export]
macro_rules! bail {
($kind:expr $(,)?) => {
return ::core::result::Result::Err($crate::Error::new("", $kind))
};
($context:expr, $kind:expr $(,)?) => {
return ::core::result::Result::Err($crate::Error::new($context, $kind))
};
($context:expr, $kind:expr, source: $source:expr $(,)?) => {
return ::core::result::Result::Err($crate::Error::new($context, $kind).with_source($source))
};
}
#[macro_export]
macro_rules! ensure {
($condition:expr, $kind:expr $(,)?) => {
if !($condition) {
return ::core::result::Result::Err($crate::Error::new("", $kind));
}
};
($condition:expr, $context:expr, $kind:expr $(,)?) => {
if !($condition) {
return ::core::result::Result::Err($crate::Error::new($context, $kind));
}
};
}