#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::{format, vec};
use error_stack::fmt::ColorMode;
#[cfg(all(not(feature = "std"), feature = "tracing"))]
use core::fmt;
#[cfg(not(feature = "std"))]
use core::{
any::{self, TypeId},
mem,
panic::Location,
};
#[cfg(all(feature = "std", feature = "tracing"))]
use std::fmt;
#[cfg(feature = "std")]
use std::{
any::{self, TypeId},
mem,
panic::Location,
path::Path,
};
#[cfg(feature = "tracing")]
use tracing::{Level, debug, error, info, trace, warn};
pub use bigerror_derive::ThinContext;
pub use error_stack::{self, IntoReport, Report, ResultExt, bail, ensure};
pub mod attachment;
pub mod context;
pub use attachment::{Expectation, Field, Index, KeyValue, Type};
use attachment::{Dbg, Debug, Display};
pub use context::*;
pub fn init_colour() {
Report::set_color_mode(ColorMode::Color);
}
pub fn init_emphasis() {
Report::set_color_mode(ColorMode::Emphasis);
}
pub fn init_no_ansi() {
Report::set_color_mode(ColorMode::None);
}
pub trait ThinContext
where
Self: Sized + core::error::Error + Send + Sync + 'static,
{
const VALUE: Self;
fn report<C: core::error::Error + Send + Sync + 'static>(ctx: C) -> Report<Self> {
Report::new(ctx).change_context(Self::VALUE)
}
#[track_caller]
fn attach_with<A>(attachment: impl FnOnce() -> A) -> Report<Self>
where
A: Display,
{
Report::new(Self::VALUE).attach(attachment())
}
#[track_caller]
fn attach<A>(value: A) -> Report<Self>
where
A: Display,
{
Report::new(Self::VALUE).attach(value)
}
#[track_caller]
fn attach_dbg<A>(value: A) -> Report<Self>
where
A: Debug,
{
Self::attach(Dbg(value))
}
#[track_caller]
fn attach_kv<K, V>(key: K, value: V) -> Report<Self>
where
K: Display,
V: Display,
{
Self::attach(KeyValue(key, value))
}
#[track_caller]
fn attach_kv_dbg<K, V>(key: K, value: V) -> Report<Self>
where
K: Display,
V: Debug,
{
Self::attach(KeyValue::dbg(key, value))
}
#[track_caller]
fn attach_field<S: Display>(key: &'static str, status: S) -> Report<Self> {
Self::attach(Field::new(key, status))
}
#[track_caller]
fn expected_actual<A: attachment::Display>(expected: A, actual: A) -> Report<Self> {
Self::attach(Expectation { expected, actual })
}
#[track_caller]
fn attach_ty_val<A: Display>(value: A) -> Report<Self> {
Self::attach_kv(ty!(A), value)
}
#[track_caller]
fn attach_ty_dbg<A: Debug>(value: A) -> Report<Self> {
Self::attach_kv_dbg(ty!(A), value)
}
#[track_caller]
fn attach_ty<A>() -> Report<Self> {
Self::attach(ty!(A))
}
#[track_caller]
fn attach_ty_status<A: Send + Sync + 'static>(status: impl Display) -> Report<Self> {
Self::attach(Field::new(ty!(A), status))
}
}
pub trait ReportAs<T> {
fn report_as<C: ThinContext>(self) -> Result<T, Report<C>>;
}
impl<T, E: core::error::Error + Send + Sync + 'static> ReportAs<T> for Result<T, E> {
#[inline]
#[track_caller]
fn report_as<C: ThinContext>(self) -> Result<T, Report<C>> {
match self {
Ok(v) => Ok(v),
Err(e) => {
let ty = any::type_name_of_val(&e);
let mut external_report = Report::new(e).attach(ty);
let mut curr_source = external_report.current_context().source();
let mut child_errs = vec![];
while let Some(child_err) = curr_source {
let new_err = format!("{child_err}");
if Some(&new_err) != child_errs.last() {
child_errs.push(new_err);
}
curr_source = child_err.source();
}
while let Some(child_err) = child_errs.pop() {
external_report = external_report.attach(child_err);
}
Err(external_report.into_ctx())
}
}
}
}
pub trait IntoContext {
fn into_ctx<C2: ThinContext>(self) -> Report<C2>;
}
impl<C: 'static> IntoContext for Report<C> {
#[inline]
#[track_caller]
fn into_ctx<C2: ThinContext>(self) -> Report<C2> {
if TypeId::of::<C>() == TypeId::of::<C2>() {
unsafe {
return mem::transmute::<Self, Report<C2>>(self.attach_opaque(*Location::caller()));
}
}
self.change_context(C2::VALUE)
}
}
pub trait ResultIntoContext: ResultExt {
fn into_ctx<C2: ThinContext>(self) -> Result<Self::Ok, Report<C2>>;
fn and_then_ctx<U, F, C2>(self, op: F) -> Result<U, Report<C2>>
where
C2: ThinContext,
F: FnOnce(Self::Ok) -> Result<U, Report<C2>>;
fn map_ctx<U, F, C2>(self, op: F) -> Result<U, Report<C2>>
where
C2: ThinContext,
F: FnOnce(Self::Ok) -> U;
}
impl<T, E: IntoReport> ResultIntoContext for Result<T, E>
where
<E as IntoReport>::Context: Sized + 'static,
{
#[inline]
#[track_caller]
fn into_ctx<C2: ThinContext>(self) -> Result<T, Report<C2>> {
match self {
Ok(ok) => Ok(ok),
Err(e) => Err(IntoContext::into_ctx(e.into_report())),
}
}
#[inline]
#[track_caller]
fn and_then_ctx<U, F, C2>(self, op: F) -> Result<U, Report<C2>>
where
C2: ThinContext,
F: FnOnce(T) -> Result<U, Report<C2>>,
{
match self {
Ok(t) => op(t),
Err(e) => Err(IntoContext::into_ctx(e.into_report())),
}
}
#[inline]
#[track_caller]
fn map_ctx<U, F, C2>(self, op: F) -> Result<U, Report<C2>>
where
C2: ThinContext,
F: FnOnce(T) -> U,
{
match self {
Ok(t) => Ok(op(t)),
Err(e) => Err(IntoContext::into_ctx(e.into_report())),
}
}
}
pub trait AttachExt {
#[must_use]
fn attach_kv<K, V>(self, key: K, value: V) -> Self
where
K: Display,
V: Display;
#[must_use]
fn attach_kv_dbg<K, V>(self, key: K, value: V) -> Self
where
K: Display,
V: Debug;
#[must_use]
fn attach_field_status<S>(self, name: &'static str, status: S) -> Self
where
S: Display;
#[must_use]
fn attach_dbg<A>(self, value: A) -> Self
where
A: Debug;
#[must_use]
fn attach_ty_val<A>(self, value: A) -> Self
where
Self: Sized,
A: Display,
{
self.attach_kv(ty!(A), value)
}
#[must_use]
#[cfg(feature = "std")]
fn attach_path<P: AsRef<Path>>(self, path: P) -> Self
where
Self: Sized,
{
let path = path.as_ref().display().to_string();
self.attach_kv("path", path)
}
}
impl<C> AttachExt for Report<C> {
#[inline]
#[track_caller]
fn attach_kv<K, V>(self, key: K, value: V) -> Self
where
K: Display,
V: Display,
{
self.attach(KeyValue(key, value))
}
#[inline]
#[track_caller]
fn attach_kv_dbg<K, V>(self, key: K, value: V) -> Self
where
K: Display,
V: Debug,
{
self.attach(KeyValue::dbg(key, value))
}
#[inline]
#[track_caller]
fn attach_field_status<S>(self, name: &'static str, status: S) -> Self
where
S: Display,
{
self.attach(Field::new(name, status))
}
#[inline]
#[track_caller]
fn attach_dbg<A>(self, value: A) -> Self
where
A: Debug,
{
self.attach(Dbg(value))
}
}
impl<T, C> AttachExt for Result<T, Report<C>> {
#[inline]
#[track_caller]
fn attach_kv<K, V>(self, key: K, value: V) -> Self
where
K: Display,
V: Display,
{
match self {
Ok(ok) => Ok(ok),
Err(report) => Err(report.attach(KeyValue(key, value))),
}
}
#[inline]
#[track_caller]
fn attach_kv_dbg<K, V>(self, key: K, value: V) -> Self
where
K: Display,
V: Debug,
{
match self {
Ok(ok) => Ok(ok),
Err(report) => Err(report.attach(KeyValue::dbg(key, value))),
}
}
#[inline]
#[track_caller]
fn attach_field_status<S>(self, name: &'static str, status: S) -> Self
where
S: Display,
{
match self {
Ok(ok) => Ok(ok),
Err(report) => Err(report.attach(Field::new(name, status))),
}
}
#[inline]
#[track_caller]
fn attach_dbg<A>(self, value: A) -> Self
where
A: Debug,
{
match self {
Ok(ok) => Ok(ok),
Err(report) => Err(report.attach(Dbg(value))),
}
}
}
#[cfg(feature = "tracing")]
pub trait LogError<T, E>
where
E: fmt::Debug,
{
fn log_err(self);
fn log_attached_err<A>(self, attachment: A)
where
A: fmt::Debug + Send + Sync + 'static;
fn and_log_err(self) -> Result<T, E>;
fn and_log(self, level: Level) -> Result<T, E>;
fn and_attached_err<A>(self, attachment: A) -> Result<T, E>
where
A: fmt::Debug + Send + Sync + 'static;
}
#[cfg(feature = "tracing")]
impl<T, E> LogError<T, E> for Result<T, E>
where
E: fmt::Debug,
{
#[inline]
#[track_caller]
fn log_err(self) {
if let Err(e) = self {
error!(message = ?e);
}
}
#[inline]
#[track_caller]
fn log_attached_err<A>(self, attachment: A)
where
A: fmt::Debug + Send + Sync + 'static,
{
if let Err(e) = self {
error!(err = ?e, "{attachment:?}");
}
}
#[inline]
#[track_caller]
fn and_log(self, level: Level) -> Self {
if let Err(err) = &self {
match level {
Level::TRACE => trace!(?err),
Level::DEBUG => debug!(?err),
Level::INFO => info!(?err),
Level::WARN => warn!(?err),
Level::ERROR => error!(?err),
}
}
self
}
#[inline]
#[track_caller]
fn and_log_err(self) -> Self {
if let Err(e) = &self {
error!(message = ?e);
}
self
}
#[inline]
#[track_caller]
fn and_attached_err<A>(self, attachment: A) -> Self
where
A: fmt::Debug + Send + Sync + 'static,
{
if let Err(e) = &self {
error!(err = ?e, "{attachment:?}");
}
self
}
}
pub trait ClearResult<T, E> {
#[allow(clippy::result_unit_err)]
fn clear_err(self) -> Result<T, ()>;
fn clear_ok(self) -> Result<(), E>;
}
impl<T, E> ClearResult<T, E> for Result<T, E> {
fn clear_err(self) -> Result<T, ()> {
self.map_err(|_| ())
}
fn clear_ok(self) -> Result<(), E> {
self.map(|_| ())
}
}
pub trait OptionReport<T>
where
Self: Sized,
{
fn expect_or(self) -> Result<T, Report<NotFound>>;
fn expect_kv<K, V>(self, key: K, value: V) -> Result<T, Report<NotFound>>
where
K: Display,
V: Display;
fn expect_field(self, field: &'static str) -> Result<T, Report<NotFound>>;
#[inline]
#[track_caller]
fn expect_kv_dbg<K, V>(self, key: K, value: V) -> Result<T, Report<NotFound>>
where
K: Display,
V: Debug,
{
self.expect_kv(key, Dbg(value))
}
#[inline]
#[track_caller]
fn expect_by<K: Display>(self, key: K) -> Result<T, Report<NotFound>> {
self.expect_kv(Index(key), ty!(T))
}
#[deprecated(
note = "Use `bigerror::OptionReport::expect_with` instead",
since = "0.12.0"
)]
#[inline]
#[track_caller]
fn expect_by_fn<F, K>(self, key_fn: F) -> Result<T, Report<NotFound>>
where
K: Display,
F: FnOnce() -> K,
{
self.expect_kv(Index(key_fn()), ty!(T))
}
#[inline]
#[track_caller]
fn expect_with<F, K>(self, key_fn: F) -> Result<T, Report<NotFound>>
where
K: Display,
F: FnOnce() -> K,
{
self.expect_kv(Index(key_fn()), ty!(T))
}
#[inline]
#[track_caller]
fn expect_or_ctx<C>(self) -> Result<T, Report<C>>
where
C: ThinContext,
{
self.expect_or().into_ctx()
}
}
impl<T> OptionReport<T> for Option<T> {
#[inline]
#[track_caller]
fn expect_or(self) -> Result<T, Report<NotFound>> {
match self {
Some(v) => Ok(v),
None => Err(NotFound::attach_ty::<T>()),
}
}
#[inline]
#[track_caller]
fn expect_kv<K, V>(self, key: K, value: V) -> Result<T, Report<NotFound>>
where
K: Display,
V: Display,
{
match self {
Some(v) => Ok(v),
None => Err(NotFound::attach_kv(key, value)),
}
}
#[inline]
#[track_caller]
fn expect_field(self, field: &'static str) -> Result<T, Report<NotFound>> {
match self {
Some(v) => Ok(v),
None => Err(NotFound::with_field(field)),
}
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! __field {
({ } @[$field:ident] ref[$($ref:tt)*] var[$($var:tt)+] fns[$($fn:tt)*]) => {
(stringify!($field), $($ref)*$($var)*$($fn)*)
};
({ } @[] ref[$($ref:tt)*] var[$($var:tt)+] fns[$($fn:tt)*]) => {
(concat!($(stringify!($var),)+), $($ref)*$($var)*$($fn)*)
};
({ & $($lifetime:lifetime)? $($rest:tt)+ }
@[] ref[$($ref:tt)*] var[] fns[]) => {
$crate::__field!(
{ $($rest)+ }
@[]
ref[$($ref)* &$($lifetime)?]
var[]
fns[]
)
};
({ * $($rest:tt)+ }
@[] ref[$($ref:tt)*] var[] fns[]) => {
$crate::__field!(
{ $($rest)+ }
@[]
ref[$($ref)* *]
var[]
fns[]
)
};
({ .%$field:ident $($rest:tt)* }
@[] ref[$($ref:tt)*] var[$($var:tt)+] fns[$($fn:tt)*]) => {
$crate::__field!(
{ .$field $($rest)* }
@[ $field]
ref[$($ref)*]
var[$($var)+]
fns[$($fn)*]
)
};
({ .$method:ident($($arg:expr)? $(, $tail_args:expr)*) $($rest:tt)* }
@[$($field:ident)?] ref[$($ref:tt)*] var[$($var:tt)*] fns[$($fn:tt)*]) => {
$crate::__field!(
{ $($rest)* }
@[$($field)?]
ref[$($ref)*]
var[$($var)*]
fns[$($fn)* .$method($($arg)? $(, $tail_args)*)]
)
};
({ .$property:ident $($rest:tt)* }
@[$($field:ident)?] ref[$($ref:tt)*] var[$($var:tt)*] fns[$($fn:tt)*]) => {
$crate::__field!(
{ $($rest)* }
@[$($field)?]
ref[$($ref)*]
var[$($var)* .$property]
fns[$($fn)*]
)
};
({ $var:ident $($rest:tt)* }
@[] ref[$($ref:tt)*] var[] fns[]) => {
$crate::__field!(
{ $($rest)* }
@[]
ref[$($ref)*]
var[$var]
fns[]
)
};
($($body:tt)+) => {
$crate::__field!( { $($body)+ } @[] ref[] var[] fns[])
};
}
#[macro_export]
macro_rules! expect_field {
($($body:tt)+) => {
{
let (__field, __expr)= $crate::__field!($($body)+);
$crate::OptionReport::expect_field(__expr, __field)
}
};
}
#[cfg(test)]
#[derive(Default)]
struct MyStruct {
my_field: Option<()>,
_string: String,
}
#[cfg(test)]
impl MyStruct {
fn __field<T>(_property: &'static str, _t: T) {}
const fn my_field(&self) -> Option<()> {
self.my_field
}
}
#[cfg(test)]
mod test {
#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
#[cfg(not(feature = "std"))]
use alloc::string::String;
use crate::attachment::Invalid;
use super::*;
#[derive(ThinContext)]
#[bigerror(crate)]
pub struct MyError;
macro_rules! assert_err {
($result:expr $(,)?) => {
let result = $result;
assert!(result.is_err(), "{:?}", result.unwrap());
if option_env!("PRINTERR").is_some() {
crate::init_colour();
#[cfg(feature = "std")]
println!("\n{:?}", result.unwrap_err());
}
};
($result:expr, $($arg:tt)+) => {
let result = $result;
assert!(result.is_err(), $($arg)+);
if option_env!("PRINTERR").is_some() {
crate::init_colour();
#[cfg(feature = "std")]
println!("\n{:?}", result.unwrap_err());
}
};
}
#[test]
fn report_as() {
fn output() -> Result<usize, Report<MyError>> {
"NaN".parse::<usize>().report_as()
}
assert_err!(output());
}
#[test]
fn reportable() {
fn output() -> Result<usize, Report<MyError>> {
"NaN".parse::<usize>().map_err(MyError::report)
}
assert_err!(output());
}
#[test]
fn box_reportable() {
fn output() -> Result<usize, Box<dyn core::error::Error + Sync + Send>> {
Ok("NaN".parse::<usize>().map_err(Box::new)?)
}
assert_err!(output().map_err(BoxError::from).change_context(MyError));
}
#[test]
fn convresion_error() {
fn output() -> Result<usize, Report<ConversionError>> {
"NaN"
.parse::<usize>()
.map_err(ConversionError::from::<&str, usize>)
.attach(ParseError)
}
assert_err!(output().change_context(MyError));
}
#[test]
fn error_in_error_handling() {
fn output() -> Result<usize, Report<ConversionError>> {
"NaN"
.parse::<usize>()
.map_err(ConversionError::from::<&str, usize>)
.map_err(|e| match "More NaN".parse::<u32>() {
Ok(attachment) => e.attach(attachment),
Err(attachment_err) => e
.attach(ParseError)
.attach_kv("\"More Nan\"", attachment_err),
})
}
assert_err!(output().change_context(MyError));
}
#[test]
fn option_report() {
assert_err!(None::<()>.expect_or());
let id: u32 = 0xdead_beef;
assert_err!(None::<bool>.expect_kv("id", id));
assert!(Some(true).expect_kv("id", id).unwrap());
struct OptionField<'a> {
name: Option<&'a str>,
}
let field_none = OptionField { name: None };
assert_err!(field_none.name.expect_field("name"));
let field_some = OptionField {
name: Some("biggy"),
};
assert_eq!("biggy", field_some.name.expect_field("name").unwrap());
}
#[test]
fn into_ctx() {
fn output() -> Result<usize, Report<MyError>> {
"NaN"
.parse::<usize>()
.map_err(|e| ConversionError::from::<&str, usize>(e).into_ctx())
}
assert_err!(output());
}
#[test]
fn result_into_ctx() {
fn output() -> Result<usize, Report<MyError>> {
"NaN"
.parse::<usize>()
.map_err(ConversionError::from::<&str, usize>)
.change_context(MyError)
.into_ctx()
}
assert_err!(output());
}
#[test]
fn attach_ty_status() {
fn try_even(num: usize) -> Result<(), Report<MyError>> {
if num % 2 != 0 {
return Err(InvalidInput::attach_ty_status::<usize>(Invalid).into_ctx());
}
Ok(())
}
let my_input = try_even(3);
assert_err!(my_input);
}
#[test]
fn expect_field() {
let my_struct = MyStruct::default();
let my_field = expect_field!(my_struct.my_field.as_ref());
assert_err!(my_field);
let my_field = expect_field!(my_struct.my_field);
assert_err!(my_field);
let my_field = expect_field!(*&my_struct.%my_field());
assert_err!(my_field);
let my_field = my_struct.my_field;
let my_field = expect_field!(my_field.to_owned().to_owned());
assert_err!(my_field);
}
fn __field() {
let my_struct = MyStruct::default();
let (field, val) = __field!(my_struct._string.as_ref());
MyStruct::__field::<&str>(field, val);
}
#[test]
fn expectation() {
let my_struct = MyStruct::default();
let my_field = my_struct
.my_field
.ok_or_else(|| InvalidInput::expected_actual("Some", "None"));
assert_err!(my_field);
}
#[test]
fn attach_ty_val() {
fn compare(mine: usize, other: usize) -> Result<(), Report<MyError>> {
if other != mine {
bail!(
InvalidInput::attach("expected my number!")
.attach_ty_val(other)
.into_ctx()
);
}
Ok(())
}
let my_number = 2;
let other_number = 3;
assert_err!(compare(my_number, other_number));
}
#[test]
fn attach_kv_macro() {
let my_number = 2;
let other_number = 3;
fn compare(mine: usize, other: usize) -> Result<(), Report<MyError>> {
if other != mine {
return Err(InvalidInput::attach("expected my number!"))
.attach_with(|| kv!(ty: other)) .into_ctx();
}
Ok(())
}
assert_err!(compare(my_number, other_number));
}
#[test]
fn expect_by() {
let arr = ["a", "b"];
let get_oob = arr.get(2).expect_by(2);
assert_err!(get_oob);
}
}