use core::fmt;
#[cfg(feature = "std")]
mod backtrace;
mod context;
pub(super) mod opaque;
use crate::{
BoxError,
extra::OpaqueError,
std::{Box, String},
};
pub trait ErrorContext: private::SealedErrorContext {
type Context;
type OpaqueContext;
fn into_box_error(self) -> Self::Context;
fn into_opaque_error(self) -> Self::OpaqueContext;
fn context<M>(self, value: M) -> Self::Context
where
M: fmt::Debug + fmt::Display + Send + Sync + 'static;
fn context_hex<M>(self, value: M) -> Self::Context
where
M: fmt::Debug + Send + Sync + 'static;
fn context_debug<M>(self, value: M) -> Self::Context
where
M: fmt::Debug + Send + Sync + 'static;
fn context_field<M>(self, key: &'static str, value: M) -> Self::Context
where
M: fmt::Debug + fmt::Display + Send + Sync + 'static;
fn context_str_field<M>(self, key: &'static str, value: M) -> Self::Context
where
M: Into<String>;
fn context_hex_field<M>(self, key: &'static str, value: M) -> Self::Context
where
M: fmt::Debug + Send + Sync + 'static;
fn context_debug_field<M>(self, key: &'static str, value: M) -> Self::Context
where
M: fmt::Debug + Send + Sync + 'static;
fn with_context<C, F>(self, cb: F) -> Self::Context
where
C: fmt::Debug + fmt::Display + Send + Sync + 'static,
F: FnOnce() -> C;
fn with_context_hex<C, F>(self, cb: F) -> Self::Context
where
C: fmt::Debug + Send + Sync + 'static,
F: FnOnce() -> C;
fn with_context_debug<C, F>(self, cb: F) -> Self::Context
where
C: fmt::Debug + Send + Sync + 'static,
F: FnOnce() -> C;
fn with_context_field<C, F>(self, key: &'static str, cb: F) -> Self::Context
where
C: fmt::Debug + fmt::Display + Send + Sync + 'static,
F: FnOnce() -> C;
fn with_context_str_field<C, F>(self, key: &'static str, cb: F) -> Self::Context
where
C: Into<String>,
F: FnOnce() -> C;
fn with_context_hex_field<C, F>(self, key: &'static str, cb: F) -> Self::Context
where
C: fmt::Debug + Send + Sync + 'static,
F: FnOnce() -> C;
fn with_context_debug_field<C, F>(self, key: &'static str, cb: F) -> Self::Context
where
C: fmt::Debug + Send + Sync + 'static,
F: FnOnce() -> C;
}
impl<T, E: Into<BoxError>> ErrorContext for Result<T, E> {
type Context = Result<T, BoxError>;
type OpaqueContext = Result<T, OpaqueError>;
#[inline(always)]
fn into_box_error(self) -> Self::Context {
self.map_err(Into::into)
}
#[inline(always)]
fn into_opaque_error(self) -> Self::OpaqueContext {
self.map_err(OpaqueError::from_box_error)
}
#[inline(always)]
fn context<M>(self, value: M) -> Self::Context
where
M: fmt::Debug + fmt::Display + Send + Sync + 'static,
{
self.map_err(|error| error.context(value))
}
#[inline(always)]
fn context_hex<M>(self, value: M) -> Self::Context
where
M: fmt::Debug + Send + Sync + 'static,
{
self.map_err(|error| error.context_hex(value))
}
#[inline(always)]
fn context_debug<M>(self, value: M) -> Self::Context
where
M: fmt::Debug + Send + Sync + 'static,
{
self.map_err(|error| error.context_debug(value))
}
#[inline(always)]
fn context_field<M>(self, key: &'static str, value: M) -> Self::Context
where
M: fmt::Debug + fmt::Display + Send + Sync + 'static,
{
self.map_err(|error| error.context_field(key, value))
}
#[inline(always)]
fn context_str_field<M>(self, key: &'static str, value: M) -> Self::Context
where
M: Into<String>,
{
self.map_err(|error| error.context_str_field(key, value))
}
#[inline(always)]
fn context_hex_field<M>(self, key: &'static str, value: M) -> Self::Context
where
M: fmt::Debug + Send + Sync + 'static,
{
self.map_err(|error| error.context_hex_field(key, value))
}
#[inline(always)]
fn context_debug_field<M>(self, key: &'static str, value: M) -> Self::Context
where
M: fmt::Debug + Send + Sync + 'static,
{
self.map_err(|error| error.context_debug_field(key, value))
}
#[inline(always)]
fn with_context<C, F>(self, cb: F) -> Self::Context
where
C: fmt::Debug + fmt::Display + Send + Sync + 'static,
F: FnOnce() -> C,
{
self.map_err(|error| error.with_context(cb))
}
#[inline(always)]
fn with_context_hex<C, F>(self, cb: F) -> Self::Context
where
C: fmt::Debug + Send + Sync + 'static,
F: FnOnce() -> C,
{
self.map_err(|error| error.with_context_hex(cb))
}
#[inline(always)]
fn with_context_debug<C, F>(self, cb: F) -> Self::Context
where
C: fmt::Debug + Send + Sync + 'static,
F: FnOnce() -> C,
{
self.map_err(|error| error.with_context_debug(cb))
}
#[inline(always)]
fn with_context_field<C, F>(self, key: &'static str, cb: F) -> Self::Context
where
C: fmt::Debug + fmt::Display + Send + Sync + 'static,
F: FnOnce() -> C,
{
self.map_err(|error| error.with_context_field(key, cb))
}
#[inline(always)]
fn with_context_str_field<C, F>(self, key: &'static str, cb: F) -> Self::Context
where
C: Into<String>,
F: FnOnce() -> C,
{
self.map_err(|error| error.with_context_str_field(key, cb))
}
#[inline(always)]
fn with_context_hex_field<C, F>(self, key: &'static str, cb: F) -> Self::Context
where
C: fmt::Debug + Send + Sync + 'static,
F: FnOnce() -> C,
{
self.map_err(|error| error.with_context_hex_field(key, cb))
}
#[inline(always)]
fn with_context_debug_field<C, F>(self, key: &'static str, cb: F) -> Self::Context
where
C: fmt::Debug + Send + Sync + 'static,
F: FnOnce() -> C,
{
self.map_err(|error| error.with_context_debug_field(key, cb))
}
}
macro_rules! forward_none_context {
($(
fn $name:ident < $($gen:ident : [$($bound:tt)*]),+ > ( $($arg:ident : $argty:ty),* ) => ( $($carg:expr),* );
)+) => {
$(
fn $name < $($gen),+ > (self, $($arg : $argty),*) -> Self::Context
where $($gen : $($bound)*),+
{
match self {
Some(value) => Ok(value),
None => Err(BoxError::from_static_str("Option is None")
.context_debug_field("type", core::any::type_name::<Self>())
.$name($($carg),*)),
}
}
)+
};
}
impl<T> ErrorContext for Option<T> {
type Context = Result<T, BoxError>;
type OpaqueContext = Result<T, OpaqueError>;
fn into_box_error(self) -> Self::Context {
match self {
Some(value) => Ok(value),
None => Err(BoxError::from_static_str("Option is None")
.context_debug_field("type", core::any::type_name::<Self>())),
}
}
#[inline(always)]
fn into_opaque_error(self) -> Self::OpaqueContext {
self.into_box_error().into_opaque_error()
}
forward_none_context! {
fn context<M: [fmt::Debug + fmt::Display + Send + Sync + 'static]>(value: M) => (value);
fn context_hex<M: [fmt::Debug + Send + Sync + 'static]>(value: M) => (value);
fn context_debug<M: [fmt::Debug + Send + Sync + 'static]>(value: M) => (value);
fn context_field<M: [fmt::Debug + fmt::Display + Send + Sync + 'static]>(key: &'static str, value: M) => (key, value);
fn context_str_field<M: [Into<String>]>(key: &'static str, value: M) => (key, value);
fn context_hex_field<M: [fmt::Debug + Send + Sync + 'static]>(key: &'static str, value: M) => (key, value);
fn context_debug_field<M: [fmt::Debug + Send + Sync + 'static]>(key: &'static str, value: M) => (key, value);
fn with_context<C: [fmt::Debug + fmt::Display + Send + Sync + 'static], F: [FnOnce() -> C]>(cb: F) => (cb);
fn with_context_hex<C: [fmt::Debug + Send + Sync + 'static], F: [FnOnce() -> C]>(cb: F) => (cb);
fn with_context_debug<C: [fmt::Debug + Send + Sync + 'static], F: [FnOnce() -> C]>(cb: F) => (cb);
fn with_context_field<C: [fmt::Debug + fmt::Display + Send + Sync + 'static], F: [FnOnce() -> C]>(key: &'static str, cb: F) => (key, cb);
fn with_context_str_field<C: [Into<String>], F: [FnOnce() -> C]>(key: &'static str, cb: F) => (key, cb);
fn with_context_hex_field<C: [fmt::Debug + Send + Sync + 'static], F: [FnOnce() -> C]>(key: &'static str, cb: F) => (key, cb);
fn with_context_debug_field<C: [fmt::Debug + Send + Sync + 'static], F: [FnOnce() -> C]>(key: &'static str, cb: F) => (key, cb);
}
}
pub trait ErrorExt: private::SealedErrorExt {
fn into_box_error(self) -> BoxError;
fn into_opaque_error(self) -> OpaqueError;
fn context<M>(self, value: M) -> BoxError
where
M: fmt::Debug + fmt::Display + Send + Sync + 'static;
fn context_hex<M>(self, value: M) -> BoxError
where
M: fmt::Debug + Send + Sync + 'static;
fn context_debug<M>(self, value: M) -> BoxError
where
M: fmt::Debug + Send + Sync + 'static;
fn context_field<M>(self, key: &'static str, value: M) -> BoxError
where
M: fmt::Debug + fmt::Display + Send + Sync + 'static;
fn context_str_field<M>(self, key: &'static str, value: M) -> BoxError
where
M: Into<String>;
fn context_hex_field<M>(self, key: &'static str, value: M) -> BoxError
where
M: fmt::Debug + Send + Sync + 'static;
fn context_debug_field<M>(self, key: &'static str, value: M) -> BoxError
where
M: fmt::Debug + Send + Sync + 'static;
fn with_context<C, F>(self, cb: F) -> BoxError
where
C: fmt::Debug + fmt::Display + Send + Sync + 'static,
F: FnOnce() -> C;
fn with_context_hex<C, F>(self, cb: F) -> BoxError
where
C: fmt::Debug + Send + Sync + 'static,
F: FnOnce() -> C;
fn with_context_debug<C, F>(self, cb: F) -> BoxError
where
C: fmt::Debug + Send + Sync + 'static,
F: FnOnce() -> C;
fn with_context_field<C, F>(self, key: &'static str, cb: F) -> BoxError
where
C: fmt::Debug + fmt::Display + Send + Sync + 'static,
F: FnOnce() -> C;
fn with_context_str_field<C, F>(self, key: &'static str, cb: F) -> BoxError
where
C: Into<String>,
F: FnOnce() -> C;
fn with_context_hex_field<C, F>(self, key: &'static str, cb: F) -> BoxError
where
C: fmt::Debug + Send + Sync + 'static,
F: FnOnce() -> C;
fn with_context_debug_field<C, F>(self, key: &'static str, cb: F) -> BoxError
where
C: fmt::Debug + Send + Sync + 'static,
F: FnOnce() -> C;
#[cfg(feature = "std")]
fn backtrace(self) -> BoxError;
}
impl<Error: Into<BoxError>> ErrorExt for Error {
#[inline(always)]
fn into_box_error(self) -> BoxError {
self.into()
}
#[inline(always)]
fn into_opaque_error(self) -> OpaqueError {
OpaqueError::from_box_error(self)
}
fn context<M>(self, value: M) -> BoxError
where
M: fmt::Debug + fmt::Display + Send + Sync + 'static,
{
let mut err = self.into();
if let Some(existing) = err.downcast_mut::<self::context::ErrorWithContext>() {
existing.insert_value(value);
return err;
}
let mut wrapped = self::context::ErrorWithContext::new(err);
wrapped.insert_value(value);
Box::new(wrapped)
}
#[inline(always)]
fn context_hex<M>(self, value: M) -> BoxError
where
M: fmt::Debug + Send + Sync + 'static,
{
self.context(self::context::HexContextValue(value))
}
#[inline(always)]
fn context_debug<M>(self, value: M) -> BoxError
where
M: fmt::Debug + Send + Sync + 'static,
{
self.context(self::context::DebugContextValue(value))
}
fn context_field<M>(self, key: &'static str, value: M) -> BoxError
where
M: fmt::Debug + fmt::Display + Send + Sync + 'static,
{
let mut err = self.into();
if let Some(existing) = err.downcast_mut::<self::context::ErrorWithContext>() {
existing.insert_key_value(key, value);
return err;
}
let mut wrapped = self::context::ErrorWithContext::new(err);
wrapped.insert_key_value(key, value);
Box::new(wrapped)
}
fn context_str_field<M>(self, key: &'static str, value: M) -> BoxError
where
M: Into<String>,
{
let mut err = self.into();
if let Some(existing) = err.downcast_mut::<self::context::ErrorWithContext>() {
existing.insert_key_value_str(key, value);
return err;
}
let mut wrapped = self::context::ErrorWithContext::new(err);
wrapped.insert_key_value_str(key, value);
Box::new(wrapped)
}
#[inline(always)]
fn context_hex_field<M>(self, key: &'static str, value: M) -> BoxError
where
M: fmt::Debug + Send + Sync + 'static,
{
self.context_field(key, self::context::HexContextValue(value))
}
#[inline(always)]
fn context_debug_field<M>(self, key: &'static str, value: M) -> BoxError
where
M: fmt::Debug + Send + Sync + 'static,
{
self.context_field(key, self::context::DebugContextValue(value))
}
#[inline(always)]
fn with_context<C, F>(self, cb: F) -> BoxError
where
C: fmt::Debug + fmt::Display + Send + Sync + 'static,
F: FnOnce() -> C,
{
self.context(cb())
}
#[inline(always)]
fn with_context_hex<C, F>(self, cb: F) -> BoxError
where
C: fmt::Debug + Send + Sync + 'static,
F: FnOnce() -> C,
{
self.context(self::context::HexContextValue(cb()))
}
#[inline(always)]
fn with_context_debug<C, F>(self, cb: F) -> BoxError
where
C: fmt::Debug + Send + Sync + 'static,
F: FnOnce() -> C,
{
self.context(self::context::DebugContextValue(cb()))
}
#[inline(always)]
fn with_context_field<C, F>(self, key: &'static str, cb: F) -> BoxError
where
C: fmt::Debug + fmt::Display + Send + Sync + 'static,
F: FnOnce() -> C,
{
self.context_field(key, cb())
}
#[inline(always)]
fn with_context_str_field<C, F>(self, key: &'static str, cb: F) -> BoxError
where
C: Into<String>,
F: FnOnce() -> C,
{
self.context_str_field(key, cb())
}
#[inline(always)]
fn with_context_hex_field<C, F>(self, key: &'static str, cb: F) -> BoxError
where
C: fmt::Debug + Send + Sync + 'static,
F: FnOnce() -> C,
{
self.context_field(key, self::context::HexContextValue(cb()))
}
#[inline(always)]
fn with_context_debug_field<C, F>(self, key: &'static str, cb: F) -> BoxError
where
C: fmt::Debug + Send + Sync + 'static,
F: FnOnce() -> C,
{
self.context_field(key, self::context::DebugContextValue(cb()))
}
#[cfg(feature = "std")]
fn backtrace(self) -> BoxError {
let source = self.into();
Box::new(self::backtrace::ErrorWithBacktrace::new(source))
}
}
pub trait BoxErrorExt: private::SealedBoxErrorExt {
fn from_static_str(e: &'static str) -> Self;
}
impl BoxErrorExt for BoxError {
fn from_static_str(e: &'static str) -> Self {
OpaqueError::from_static_str(e).into_box_error()
}
}
mod private {
use crate::BoxError;
pub trait SealedErrorContext {}
impl<T, E> SealedErrorContext for Result<T, E> where E: Into<crate::BoxError> {}
impl<T> SealedErrorContext for Option<T> {}
pub trait SealedErrorExt {}
impl<Error: Into<crate::BoxError>> SealedErrorExt for Error {}
pub trait SealedBoxErrorExt {}
impl SealedBoxErrorExt for BoxError {}
}
#[cfg(test)]
mod tests {
use core::cell::Cell;
use super::*;
use crate::StdError;
#[derive(Debug, Clone)]
struct BoomError;
impl fmt::Display for BoomError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "boom")
}
}
impl core::error::Error for BoomError {}
#[test]
fn result_context_adds_context_to_error() {
let res: Result<(), BoomError> = Err(BoomError);
let err = res.context("ctx").unwrap_err();
let s = format!("{err}");
assert!(s.starts_with("boom"), "got: {s:?}");
assert!(s.contains(" | "), "got: {s:?}");
assert!(s.contains(r#""ctx""#), "got: {s:?}");
}
#[test]
fn result_context_field_adds_keyed_context_to_error() {
let res: Result<(), BoomError> = Err(BoomError);
let err = res.context_field("path", "/a,b/c").unwrap_err();
let s = format!("{err}");
assert!(s.starts_with("boom"), "got: {s:?}");
assert!(s.contains(r#"path="/a,b/c""#), "got: {s:?}");
}
#[test]
fn result_with_context_is_lazy_and_called_once() {
let res: Result<(), BoomError> = Err(BoomError);
let calls = Cell::new(0);
let err = res
.with_context(|| {
calls.set(calls.get() + 1);
"lazy"
})
.unwrap_err();
assert_eq!(calls.get(), 1);
let s = format!("{err}");
assert!(s.contains(r#""lazy""#), "got: {s:?}");
}
#[test]
fn result_with_context_field_is_lazy_and_called_once() {
let res: Result<(), BoomError> = Err(BoomError);
let calls = Cell::new(0);
let err = res
.with_context_field("k", || {
calls.set(calls.get() + 1);
"v"
})
.unwrap_err();
assert_eq!(calls.get(), 1);
let s = format!("{err}");
assert!(s.contains(r#"k="v""#), "got: {s:?}");
}
#[test]
fn option_context_none_returns_error_with_context() {
let opt: Option<i32> = None;
let err = opt.context("missing").unwrap_err();
let s = format!("{err}");
assert!(s.starts_with("Option is None"), "got: {s:?}");
assert!(s.contains(r#""missing""#), "got: {s:?}");
}
#[test]
fn option_context_field_none_returns_error_with_keyed_context() {
let opt: Option<i32> = None;
let err = opt.context_field("user_id", 42).unwrap_err();
let s = format!("{err}");
assert!(s.starts_with("Option is None"), "got: {s:?}");
assert!(s.contains(r#"user_id="42""#), "got: {s:?}");
}
#[test]
fn option_with_context_none_is_lazy_and_called_once() {
let opt: Option<i32> = None;
let calls = Cell::new(0);
let err = opt
.with_context(|| {
calls.set(calls.get() + 1);
"lazy"
})
.unwrap_err();
assert_eq!(calls.get(), 1);
let s = format!("{err}");
assert!(s.contains(r#""lazy""#), "got: {s:?}");
}
#[test]
fn option_with_context_field_none_is_lazy_and_called_once() {
let opt: Option<i32> = None;
let calls = Cell::new(0);
let err = opt
.with_context_field("k", || {
calls.set(calls.get() + 1);
"v"
})
.unwrap_err();
assert_eq!(calls.get(), 1);
let s = format!("{err}");
assert!(s.contains(r#"k="v""#), "got: {s:?}");
}
#[test]
fn errorext_context_reuses_existing_context_wrapper() {
let err1: BoxError = BoomError.context("a");
let err2: BoxError = err1.context("b");
let s = format!("{err2}");
assert!(s.contains(r#""a""#), "got: {s:?}");
assert!(s.contains(r#""b""#), "got: {s:?}");
assert_eq!(s.matches(" | ").count(), 1, "got: {s:?}");
}
#[test]
fn errorext_context_field_reuses_existing_context_wrapper() {
let err1: BoxError = BoomError.context_field("k1", "v1");
let err2: BoxError = err1.context_field("k2", "v2");
let s = format!("{err2}");
assert!(s.contains(r#"k1="v1""#), "got: {s:?}");
assert!(s.contains(r#"k2="v2""#), "got: {s:?}");
assert_eq!(s.matches(" | ").count(), 1, "got: {s:?}");
}
#[test]
#[cfg(feature = "std")]
fn errorext_backtrace_wraps_error_and_preserves_source() {
let err: BoxError = BoomError.backtrace();
assert_eq!(format!("{err}"), "boom");
let pretty = format!("{err:#}");
assert!(pretty.starts_with("boom\n"), "got: {pretty:?}");
assert!(
pretty.contains("\nBacktrace:\n") || pretty.contains("\nBacktrace:\r\n"),
"got: {pretty:?}"
);
let src = err.source().expect("source exists");
assert_eq!(src.to_string(), "boom");
}
#[test]
fn option_with_context_str_field_none_includes_type_debug_field() {
let opt: Option<i32> = None;
let err = opt
.with_context_str_field("k", || "v".to_owned())
.unwrap_err();
let s = format!("{err}");
assert!(s.starts_with("Option is None"), "got: {s:?}");
assert!(s.contains("type="), "got: {s:?}");
assert!(s.contains("Option<i32>"), "got: {s:?}");
assert!(s.contains(r#"k="v""#), "got: {s:?}");
}
#[test]
fn option_with_context_hex_field_none_includes_type_debug_field() {
let opt: Option<i32> = None;
let err = opt.with_context_hex_field("addr", || 0xfeu8).unwrap_err();
let s = format!("{err}");
assert!(s.starts_with("Option is None"), "got: {s:?}");
assert!(s.contains("type="), "got: {s:?}");
assert!(s.contains("Option<i32>"), "got: {s:?}");
assert!(s.contains("addr="), "got: {s:?}");
}
#[test]
fn option_with_context_debug_field_none_includes_type_debug_field() {
let opt: Option<i32> = None;
let err = opt
.with_context_debug_field("payload", || ("a", 1u32))
.unwrap_err();
let s = format!("{err}");
assert!(s.starts_with("Option is None"), "got: {s:?}");
assert!(s.contains("type="), "got: {s:?}");
assert!(s.contains("Option<i32>"), "got: {s:?}");
assert!(s.contains("payload="), "got: {s:?}");
}
#[test]
fn errorcontext_for_result_converts_error_into_boxerror() {
#[derive(Debug)]
struct MyErr;
impl core::fmt::Display for MyErr {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "myerr")
}
}
impl StdError for MyErr {}
let res: Result<(), MyErr> = Err(MyErr);
let err = res.context("ctx").unwrap_err();
let s = format!("{err}");
assert!(s.starts_with("myerr"), "got: {s:?}");
assert!(s.contains(r#""ctx""#), "got: {s:?}");
}
}