cauz 0.0.5

Succinct error handling
Documentation
/*
	Description: Code to simplify provision of error context.
*/

//|
//| External modules
//|
#[allow(unused_imports)]
pub use anyhow::{Context, Error as AnyError, Result as AnyResult, anyhow}; // Re-export to avoid using `anyhow` in other modules.
use std::{
	any::type_name,
	fmt::{Debug, Display, Formatter, Result as FmtResult},
	result::Result as StdResult,
};

//|
//| cauz::Error
//|
#[derive(Debug)]
pub struct Error(pub anyhow::Error);
pub type Result<T, E = Error> = StdResult<T, E>;

//| AnyError -> cauz:Error for operator `?`
impl<E> From<E> for Error
where
	E: Into<anyhow::Error>,
{
	#[track_caller]
	fn from(e: E) -> Self {
		let caller = std::panic::Location::caller();
		Error(anyhow!(e).context(anyhow!("{:?}", (caller.file(), caller.line()))))
	}
}

// | Conflicted
// impl From<Error> for anyhow::Error {
// 	fn from(e: Error) -> Self {
// 		e.0
// 	}
// }

//| Display
impl Display for Error {
	fn fmt(&self, f: &mut Formatter) -> FmtResult {
		Display::fmt(&self.0.chain().map(|e| e.to_string()).collect::<String>(), f)
	}
}

//| is()
impl Error {
	#[inline(always)]
	pub fn is<E>(&self) -> bool
	where
		E: Display + Debug + Send + Sync + 'static,
	{
		self.0.is::<E>()
	}
}

//| Ok()
pub mod cauz2 {
	#[inline(always)]
	#[allow(non_snake_case)]
	pub fn Ok<T>(value: T) -> super::Result<T> {
		super::Result::<T>::Ok(value)
	}
}

// //| bail!: no need. Use `Err(err!())?` instead.
// #[macro_export]
// macro_rules! bail {
// 	($msg:literal $(,)?) => {
// 		return Err(Error(anyhow!($msg)))
// 	};
// 	($err:expr $(,)?) => {
// 		return Err(Error(anyhow!($err)))
// 	};
// 	($fmt:expr, $($arg:tt)*) => {
// 		return Err(Error(anyhow!($fmt)))
// 	};
// }

//|
//| err!
//| Usage: To create an error (chain) with the location.
//| Note: Since the location is include, the result is a `cauz::Error` to avoid triggering `From<E> for Error` which would include the location again.
//|
#[macro_export]
macro_rules! err {
	//|
	//| Usage: standalone only, as cauz() & cauz2() does not need these.
	//|
	() => {
		Error(anyhow!("{:?} ", (file!(), line!())))
	};

	($msg:literal) => {
		Error(anyhow!("{:?} {}", (file!(), line!()), $msg))
	};

	($err:ident) => {
		Error(anyhow!("{:?} {}", (file!(), line!()), $err))
	};

	//|
	//| Usage: inside cauz3() for lazily-evaluated complex context
	//| Note: avoid eager evalution with cauz2().
	//|
	($err:ident, $($args:tt)*) => {
		|| Error(anyhow!("{:?} {} {}", (file!(), line!()), format!($($args)*), $err))
	};

	($fmt:literal, $($args:tt)*) => {
		|| Error(anyhow!("{:?} {}", (file!(), line!()), format!($fmt, $($args)*)))
	};

	//|
	//| Convert anything into an Error
	//# This must be below the pattern of ($err:ident) & ($err:literal, ...) to have a lower matching priority
	//|
	($err:expr) => {
		Error(anyhow!("{:?} {:?}", (file!(), line!()), $err))
	};
}
// pub(crate) use err; // To avoid using #[macro_use] in main.rs. Ref: https://stackoverflow.com/questions/26731243/how-do-i-use-a-macro-across-module-files

//|
//| Cauz
//| Usage: To convert (cauz::Result, Option, and bool) to AnyResult to trigger `impl<E> From<E> for Error` with operator ?.
//|
pub trait Cauz<T> {
	fn cauz(self) -> AnyResult<T>; //| Lazily evaluated `cause`
}

//| cauz::Result
//ToDo: This can be replaced with `impl From<cauz::Error> for cauz::Error` when Rust-Specialization is stabilized
impl<T> Cauz<T> for Result<T> {
	#[inline(always)]
	fn cauz(self) -> AnyResult<T> {
		self.map_err(|e| e.0)
	}
}

//| StdResult
impl<T, E> Cauz<T> for StdResult<T, E>
where
	E: Into<anyhow::Error>,
{
	#[inline(always)]
	fn cauz(self) -> AnyResult<T> {
		self.map_err(|e| anyhow!(e))
	}
}

//| Option: A context function to replace `.cauz(err!(...)` (not lazy) or `.ok_or_else(|| err!(...))` (too long) with `.cauz(err!(...))`.
impl<T> Cauz<T> for Option<T> {
	#[inline(always)]
	fn cauz(self) -> AnyResult<T> {
		self.ok_or(anyhow!(""))
	}
}

//| bool: A context function to replace `ensure!($expr, err!(...))` with `$expr.cauz(err!(...))?`.
impl Cauz<()> for bool {
	#[inline(always)]
	fn cauz(self) -> AnyResult<()> {
		self.then_some(()).ok_or(anyhow!("")) //TODO: remove then_some() and use ok_or() directly when it's stablized.
	}
}

//|
//| Cauz2
//| Usage:
//|		1) To add an eargerly-evaluated simple context to cauz::Error, and
//| 	2) To convert (cauz::Result, Option, and bool) to AnyResult to trigger `impl<E> From<E> for Error` with operator ?.
//|
pub trait Cauz2<C, T> {
	fn cauz2(self, cause: C) -> AnyResult<T>; //| Lazily evaluated `cause`
}

//| cauz::Result
impl<C, T> Cauz2<C, T> for Result<T>
where
	C: Display + Send + Sync + 'static,
{
	#[inline(always)]
	fn cauz2(self, cause: C) -> AnyResult<T> {
		self.map_err(|e| e.0.context(cause))
	}
}

//| StdResult
impl<C, T, E> Cauz2<C, T> for StdResult<T, E>
where
	C: Display + Send + Sync + 'static,
	E: Into<anyhow::Error>,
{
	#[inline(always)]
	fn cauz2(self, cause: C) -> AnyResult<T> {
		self.map_err(|e| anyhow!(e).context(cause))
	}
}

//| Option: A context function to replace `.cauz2(err!(...)` (not lazy) or `.ok_or_else(|| err!(...))` (too long) with `.cauz2(err!(...))`.
impl<C, T> Cauz2<C, T> for Option<T>
where
	C: Display + Send + Sync + 'static,
{
	#[inline(always)]
	fn cauz2(self, cause: C) -> AnyResult<T> {
		self.ok_or(anyhow!("{}", cause))
	}
}

//| bool: A context function to replace `ensure!($expr, err!(...))` with `$expr.cauz2(err!(...))?`.
impl<C> Cauz2<C, ()> for bool
where
	C: Display + Send + Sync + 'static,
{
	#[inline(always)]
	fn cauz2(self, cause: C) -> AnyResult<()> {
		self.then_some(()).ok_or(anyhow!("{}", cause)) //TODO: remove then_some() and use ok_or() directly when it's stablized.
	}
}

//|
//| Cauz3
//| Usage:
//|		1) To add a lazily-evaluated complex context to cauz::Error, and
//|		2) To convert (cauz::Result, Option, and bool) to StdResult with the location.
//|
pub trait Cauz3<C, T, E> {
	fn cauz3(self, cause: impl FnOnce() -> C, op: impl Fn(AnyError) -> E) -> StdResult<T, E>;
}

//| cauz::Result
impl<C, T, E> Cauz3<C, T, E> for Result<T>
where
	C: Display + Send + Sync + 'static,
{
	#[inline(always)]
	#[track_caller]
	fn cauz3(self, cause: impl FnOnce() -> C, op: impl Fn(AnyError) -> E) -> StdResult<T, E> {
		self.map_err(|e| {
			match type_name::<E>() {
				//| Keep the cauz::Error chain. Expect op = `Error` when `cause` already includes the location.
				t if t == type_name::<Error>() => op(e.0.context(cause())),

				//| Keep the cauz::Error chain. Expect op = `|x| x` to trigger `impl<E> From<E> for Error` with operator ?
				t if t == type_name::<AnyError>() => op(e.0.context(cause())),

				//| Break the cauz::Error chain.
				_ => op(anyhow!(Error(e.0.context(cause())).to_string())),
			}
		})
	}
}

//| StdResult
impl<C, T, E, E0> Cauz3<C, T, E> for StdResult<T, E0>
where
	C: Display + Send + Sync + 'static,
	E0: Into<anyhow::Error>,
{
	#[inline(always)]
	#[track_caller]
	fn cauz3(self, cause: impl FnOnce() -> C, op: impl Fn(AnyError) -> E) -> StdResult<T, E> {
		self.map_err(|e| {
			match type_name::<E>() {
				//| Keep the cauz::Error chain. Expect op = `Error` when `cause` already includes the location.
				t if t == type_name::<Error>() => op(anyhow!(e).context(cause())),

				//| Keep the cauz::Error chain. Expect op = `|x| x` to trigger `impl<E> From<E> for Error` with operator ?
				t if t == type_name::<AnyError>() => op(anyhow!(e).context(cause())),

				//| Break the cauz::Error chain.
				_ => op(anyhow!(Error(anyhow!(e).context(cause())).to_string())),
			}
		})
	}
}

//| Option: A context function to replace `.cauz(err!(...)` (not lazy) or `.ok_or_else(|| err!(...))` (too long) with `.cauz(err!(...))`.
impl<C, T, E> Cauz3<C, T, E> for Option<T>
where
	C: Display + Send + Sync + 'static,
{
	#[inline(always)]
	fn cauz3(self, cause: impl FnOnce() -> C, op: impl Fn(AnyError) -> E) -> StdResult<T, E> {
		self.ok_or_else(|| {
			match type_name::<E>() {
				//| Keep the cauz::Error chain. Expect op = `Error` when `cause` already includes the location.
				t if t == type_name::<Error>() => op(anyhow!("{}", cause())),

				//| Keep the cauz::Error chain. Expect op = `|x| x` to trigger `impl<E> From<E> for Error` with operator ?
				t if t == type_name::<AnyError>() => op(anyhow!("{}", cause())),

				//| Break the cauz::Error chain.
				_ => op(anyhow!("{}", cause())),
			}
		})
	}
}

//| bool: A context function to replace `ensure!($expr, err!(...))` with `$expr.cauz(err!(...))?`.
impl<C, E> Cauz3<C, (), E> for bool
where
	C: Display + Send + Sync + 'static,
{
	#[inline(always)]
	fn cauz3(self, cause: impl FnOnce() -> C, op: impl Fn(AnyError) -> E) -> StdResult<(), E> {
		self.then_some(()).ok_or_else(|| {
			//TODO: remove then_some() and use ok_or_else() directly when it's stablized.
			match type_name::<E>() {
				//| Keep the cauz::Error chain. Expect op = `Error` when `cause` already includes the location.
				t if t == type_name::<Error>() => op(anyhow!("{}", cause())),

				//| Keep the cauz::Error chain. Expect op = `|x| x` to trigger `impl<E> From<E> for Error` with operator ?
				t if t == type_name::<AnyError>() => op(anyhow!("{}", cause())),

				//| Break the cauz::Error chain.
				_ => op(anyhow!("{}", cause())),
			}
		})
	}
}