cauz 0.1.0

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

//|
//| External modules
//|
pub use anyhow::{Context, Error as AnyError, Result as AnyResult, anyhow}; // Re-export to avoid using `anyhow` in other modules.
use std::{
	error::Error as StdError,
	fmt::{Debug, Display, Formatter, Result as FmtResult},
	result::Result as StdResult,
};
pub use std::{ffi::OsStr, path::Path};

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

//| ?-conversion for AnyError
impl<E> From<E> for Error
where
	E: Into<AnyError>,
{
	#[track_caller]
	fn from(e: E) -> Self {
		let caller = std::panic::Location::caller();
		let fullname = OsStr::new(caller.file());
		let name = Path::new(fullname).file_name().unwrap_or(fullname);
		Error(e.into().context(anyhow!("({}:{})", name.display(), caller.line())))
	}
}

// //| Conflicted. Use Cauz instead for Box<dyn ...>
// impl<E> From<E> for Error
// where
// 	E: std::error::Error + Send + Sync + 'static,
// {
// 	#[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<Box<dyn std::error::Error + Send + Sync>> for Error {
// 	#[track_caller]
// 	fn from(e: Box<dyn std::error::Error + Send + Sync>) -> 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:
//| 	- Standalone only, as cauz() & cauz2() do not need these.
//| Note: Since the location is included, the result is a `cauz::Error` to avoid triggering `From<E> for Error` which would include the location again.
//|
#[macro_export]
macro_rules! err {
	() => {{
		let fullname = OsStr::new(file!());
		let name = Path::new(fullname).file_name().unwrap_or(fullname);
		Error(anyhow!("({}:{})", name.display(), line!()))

	}};

	($msg:literal) => {{
		let fullname = OsStr::new(file!());
		let name = Path::new(fullname).file_name().unwrap_or(fullname);
		Error(anyhow!("({}:{}) {}", name.display(), line!(), $msg))
	}};

	($err:ident) => {{
		let fullname = OsStr::new(file!());
		let name = Path::new(fullname).file_name().unwrap_or(fullname);
		Error(anyhow!($err).context(anyhow!("({}:{}) ", name.display(), line!())))
	}};

	($fmt:literal, $($args:tt)*) => {{
		let fullname = OsStr::new(file!());
		let name = Path::new(fullname).file_name().unwrap_or(fullname);
		Error(anyhow!("({}:{}) {}", name.display(), line!(), format!($fmt, $($args)*)))
	}};

	($err:ident, $($args:tt)*) => {{
		let fullname = OsStr::new(file!());
		let name = Path::new(fullname).file_name().unwrap_or(fullname);
		Error(anyhow!($err).context(anyhow!("({}:{}) {}", name.display(), line!(), format!($($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) => {{
		let fullname = OsStr::new(file!());
		let name = Path::new(fullname).file_name().unwrap_or(fullname);
		Error(anyhow!("({}:{}) {:?}", name.display(), 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

//|
//| err2!
//|
//| Usage:
//|		- To create an error (chain) with the location.
//|		- Used inside cauz3() for lazily-evaluated complex context
//| 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.
//|		- Avoid eager evalution with cauz2().
//|
#[macro_export]
macro_rules! err2 {
	($fmt:literal, $($args:tt)*) => {
		|x: AnyError| {
			let fullname = OsStr::new(file!());
			let name = Path::new(fullname).file_name().unwrap_or(fullname);
			Error(x.context(anyhow!("({}:{}) {}", name.display(), line!(), format!($fmt, $($args)*))))
		}
	};

	($err:ident, $($args:tt)*) => {
		|x: AnyError| {
			let fullname = OsStr::new(file!());
			let name = Path::new(fullname).file_name().unwrap_or(fullname);
			Error(x.context($err).context(anyhow!("({}:{}) {}", name.display(), line!(), format!($($args)*))))
		}
	};
}

//|
//| 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: Disable this to avoid redundancy
// impl<T, E> Cauz<T> for StdResult<T, E>
// where
// 	E: Into<AnyError>,
// {
// 	#[inline(always)]
// 	fn cauz(self) -> AnyResult<T> {
// 		self.map_err(|e| anyhow!(e))
// 	}
// }

//| Box<dyn std::Error>
impl<T> Cauz<T> for StdResult<T, Box<dyn StdError + Send + Sync>> {
	#[inline(always)]
	fn cauz(self) -> AnyResult<T> {
		self.map_err(|e| anyhow!(e))
	}
}

//| Option: prepares for operator-?
impl<T> Cauz<T> for Option<T> {
	#[inline(always)]
	fn cauz(self) -> AnyResult<T> {
		self.ok_or(anyhow!(""))
	}
}

//| bool: prepares for operator-?
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<AnyError>,
{
	#[inline(always)]
	fn cauz2(self, cause: C) -> AnyResult<T> {
		self.map_err(|e| anyhow!(e).context(cause))
	}
}

// //| Conflicted
// //| Box<dyn std::Error>
// impl<C, T> Cauz2<C, T> for StdResult<T, Box<dyn StdError + Send + Sync>>
// where
// 	C: Display + Send + Sync + 'static,
// {
// 	#[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(..)`.
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(..)?`.
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<T, E> {
	fn cauz3(self, cause: impl FnOnce(AnyError) -> E) -> StdResult<T, E>;
}

//| cauz::Result
impl<T, E> Cauz3<T, E> for Result<T> {
	#[inline(always)]
	fn cauz3(self, cause: impl FnOnce(AnyError) -> E) -> StdResult<T, E> {
		self.map_err(|e| cause(e.0))
	}
}

//| StdResult
//todo: Combine this and the above.
impl<T, E0, E> Cauz3<T, E> for StdResult<T, E0>
where
	E0: Into<AnyError>,
{
	#[inline(always)]
	fn cauz3(self, cause: impl FnOnce(AnyError) -> E) -> StdResult<T, E> {
		self.map_err(|e| cause(e.into()))
	}
}

//| Option: A context function to replace `.cauz(err!(...)` (not lazy) or `.ok_or_else(|| err2!(..))` (too long) with `.cauz3(err2!(..))`.
impl<T, E> Cauz3<T, E> for Option<T> {
	#[inline(always)]
	fn cauz3(self, cause: impl FnOnce(AnyError) -> E) -> StdResult<T, E> {
		self.ok_or_else(|| cause(anyhow!("")))
	}
}

//| bool: A context function to replace `ensure!($expr, err2!(..))` with `$expr.cauz(err2!(..))?`.
impl<E> Cauz3<(), E> for bool {
	#[inline(always)]
	fn cauz3(self, cause: impl FnOnce(AnyError) -> E) -> StdResult<(), E> {
		self.then_some(()).ok_or_else(|| cause(anyhow!(""))) //TODO: remove then_some() and use ok_or_else() directly when it's stablized.
	}
}