Skip to main content

cauz/
lib.rs

1/*
2	Description: Code to simplify provision of error context.
3*/
4
5//|
6//| External modules
7//|
8pub use anyhow::{Context, Error as AnyError, Result as AnyResult, anyhow}; // Re-export to avoid using `anyhow` in other modules.
9use std::{
10	error::Error as StdError,
11	fmt::{Debug, Display, Formatter, Result as FmtResult},
12	result::Result as StdResult,
13};
14pub use std::{ffi::OsStr, path::Path};
15
16//|
17//| cauz::Error
18//|
19#[derive(Debug)]
20pub struct Error(pub anyhow::Error);
21pub type Result<T, E = Error> = StdResult<T, E>;
22
23//| ?-conversion for AnyError
24impl<E> From<E> for Error
25where
26	E: Into<AnyError>,
27{
28	#[track_caller]
29	fn from(e: E) -> Self {
30		let caller = std::panic::Location::caller();
31		let fullname = OsStr::new(caller.file());
32		let name = Path::new(fullname).file_name().unwrap_or(fullname);
33		Error(e.into().context(anyhow!("({}:{})", name.display(), caller.line())))
34	}
35}
36
37// //| Conflicted. Use Cauz instead for Box<dyn ...>
38// impl<E> From<E> for Error
39// where
40// 	E: std::error::Error + Send + Sync + 'static,
41// {
42// 	#[track_caller]
43// 	fn from(e: E) -> Self {
44// 		let caller = std::panic::Location::caller();
45// 		Error(anyhow!(e).context(anyhow!("{:?}", (caller.file(), caller.line()))))
46// 	}
47// }
48
49// //| Conflicted
50// impl From<Box<dyn std::error::Error + Send + Sync>> for Error {
51// 	#[track_caller]
52// 	fn from(e: Box<dyn std::error::Error + Send + Sync>) -> Self {
53// 		let caller = std::panic::Location::caller();
54// 		Error(anyhow!(e).context(anyhow!("{:?}", (caller.file(), caller.line()))))
55// 	}
56// }
57
58// //| Conflicted
59// impl From<Error> for anyhow::Error {
60// 	fn from(e: Error) -> Self {
61// 		e.0
62// 	}
63// }
64
65//| Display
66impl Display for Error {
67	fn fmt(&self, f: &mut Formatter) -> FmtResult {
68		Display::fmt(&self.0.chain().map(|e| e.to_string() + " ").collect::<String>(), f)
69	}
70}
71
72//| is()
73impl Error {
74	#[inline(always)]
75	pub fn is<E>(&self) -> bool
76	where
77		E: Display + Debug + Send + Sync + 'static,
78	{
79		self.0.is::<E>()
80	}
81}
82
83//| Ok()
84pub mod cauz2 {
85	#[inline(always)]
86	#[allow(non_snake_case)]
87	pub fn Ok<T>(value: T) -> super::Result<T> {
88		super::Result::<T>::Ok(value)
89	}
90}
91
92// //| bail!: no need. Use `Err(err!())?` instead.
93// #[macro_export]
94// macro_rules! bail {
95// 	($msg:literal $(,)?) => {
96// 		return Err(Error(anyhow!($msg)))
97// 	};
98// 	($err:expr $(,)?) => {
99// 		return Err(Error(anyhow!($err)))
100// 	};
101// 	($fmt:expr, $($arg:tt)*) => {
102// 		return Err(Error(anyhow!($fmt)))
103// 	};
104// }
105
106//|
107//| err!
108//|
109//| Usage:
110//| 	- Standalone only, as cauz() & cauz2() do not need these.
111//| 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.
112//|
113#[macro_export]
114macro_rules! err {
115	() => {{
116		let fullname = OsStr::new(file!());
117		let name = Path::new(fullname).file_name().unwrap_or(fullname);
118		Error(anyhow!("({}:{})", name.display(), line!()))
119
120	}};
121
122	($msg:literal) => {{
123		let fullname = OsStr::new(file!());
124		let name = Path::new(fullname).file_name().unwrap_or(fullname);
125		Error(anyhow!("({}:{}) {}", name.display(), line!(), $msg))
126	}};
127
128	($err:ident) => {{
129		let fullname = OsStr::new(file!());
130		let name = Path::new(fullname).file_name().unwrap_or(fullname);
131		Error(anyhow!($err).context(anyhow!("({}:{}) ", name.display(), line!())))
132	}};
133
134	($fmt:literal, $($args:tt)*) => {{
135		let fullname = OsStr::new(file!());
136		let name = Path::new(fullname).file_name().unwrap_or(fullname);
137		Error(anyhow!("({}:{}) {}", name.display(), line!(), format!($fmt, $($args)*)))
138	}};
139
140	($err:ident, $($args:tt)*) => {{
141		let fullname = OsStr::new(file!());
142		let name = Path::new(fullname).file_name().unwrap_or(fullname);
143		Error(anyhow!($err).context(anyhow!("({}:{}) {}", name.display(), line!(), format!($($args)*))))
144	}};
145
146	//|
147	//| Convert anything into an Error
148	//# This must be below the pattern of ($err:ident) & ($err:literal, ...) to have a lower matching priority
149	//|
150	($err:expr) => {{
151		let fullname = OsStr::new(file!());
152		let name = Path::new(fullname).file_name().unwrap_or(fullname);
153		Error(anyhow!("({}:{}) {:?}", name.display(), line!(), $err))
154	}};
155}
156// 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
157
158//|
159//| err2!
160//|
161//| Usage:
162//|		- To create an error (chain) with the location.
163//|		- Used inside cauz3() for lazily-evaluated complex context
164//| Note:
165//|		- Since the location is include, the result is a `cauz::Error` to avoid triggering `From<E> for Error` which would include the location again.
166//|		- Avoid eager evalution with cauz2().
167//|
168#[macro_export]
169macro_rules! err2 {
170	($fmt:literal, $($args:tt)*) => {
171		|x: AnyError| {
172			let fullname = OsStr::new(file!());
173			let name = Path::new(fullname).file_name().unwrap_or(fullname);
174			Error(x.context(anyhow!("({}:{}) {}", name.display(), line!(), format!($fmt, $($args)*))))
175		}
176	};
177
178	($err:ident, $($args:tt)*) => {
179		|x: AnyError| {
180			let fullname = OsStr::new(file!());
181			let name = Path::new(fullname).file_name().unwrap_or(fullname);
182			Error(x.context($err).context(anyhow!("({}:{}) {}", name.display(), line!(), format!($($args)*))))
183		}
184	};
185}
186
187//|
188//| Cauz
189//| Usage: To convert (cauz::Result, Option, and bool) to AnyResult to trigger `impl<E> From<E> for Error` with operator ?.
190//|
191pub trait Cauz<T> {
192	fn cauz(self) -> AnyResult<T>; //| Lazily evaluated `cause`
193}
194
195//| cauz::Result
196//ToDo: This can be replaced with `impl From<cauz::Error> for cauz::Error` when Rust-Specialization is stabilized
197impl<T> Cauz<T> for Result<T> {
198	#[inline(always)]
199	fn cauz(self) -> AnyResult<T> {
200		self.map_err(|e| e.0)
201	}
202}
203
204//| StdResult: Disable this to avoid redundancy
205// impl<T, E> Cauz<T> for StdResult<T, E>
206// where
207// 	E: Into<AnyError>,
208// {
209// 	#[inline(always)]
210// 	fn cauz(self) -> AnyResult<T> {
211// 		self.map_err(|e| anyhow!(e))
212// 	}
213// }
214
215//| Box<dyn std::Error>
216impl<T> Cauz<T> for StdResult<T, Box<dyn StdError + Send + Sync>> {
217	#[inline(always)]
218	fn cauz(self) -> AnyResult<T> {
219		self.map_err(|e| anyhow!(e))
220	}
221}
222
223//| Option: prepares for operator-?
224impl<T> Cauz<T> for Option<T> {
225	#[inline(always)]
226	fn cauz(self) -> AnyResult<T> {
227		self.ok_or(anyhow!(""))
228	}
229}
230
231//| bool: prepares for operator-?
232impl Cauz<()> for bool {
233	#[inline(always)]
234	fn cauz(self) -> AnyResult<()> {
235		self.then_some(()).ok_or(anyhow!("")) //TODO: remove then_some() and use ok_or() directly when it's stablized.
236	}
237}
238
239//|
240//| Cauz2
241//| Usage:
242//|		1) To add an eargerly-evaluated simple context to cauz::Error, and
243//| 	2) To convert (cauz::Result, Option, and bool) to AnyResult to trigger `impl<E> From<E> for Error` with operator ?.
244//|
245pub trait Cauz2<C, T> {
246	fn cauz2(self, cause: C) -> AnyResult<T>; //| Lazily evaluated `cause`
247}
248
249//| cauz::Result
250impl<C, T> Cauz2<C, T> for Result<T>
251where
252	C: Display + Send + Sync + 'static,
253{
254	#[inline(always)]
255	fn cauz2(self, cause: C) -> AnyResult<T> {
256		self.map_err(|e| e.0.context(cause))
257	}
258}
259
260//| StdResult
261impl<C, T, E> Cauz2<C, T> for StdResult<T, E>
262where
263	C: Display + Send + Sync + 'static,
264	E: Into<AnyError>,
265{
266	#[inline(always)]
267	fn cauz2(self, cause: C) -> AnyResult<T> {
268		self.map_err(|e| anyhow!(e).context(cause))
269	}
270}
271
272// //| Conflicted
273// //| Box<dyn std::Error>
274// impl<C, T> Cauz2<C, T> for StdResult<T, Box<dyn StdError + Send + Sync>>
275// where
276// 	C: Display + Send + Sync + 'static,
277// {
278// 	#[inline(always)]
279// 	fn cauz2(self, cause: C) -> AnyResult<T> {
280// 		self.map_err(|e| anyhow!(e).context(cause))
281// 	}
282// }
283
284//| Option: A context function to replace `.cauz2(err!(..)` (not lazy) or `.ok_or_else(|| err!(..))` (too long) with `.cauz2(..)`.
285impl<C, T> Cauz2<C, T> for Option<T>
286where
287	C: Display + Send + Sync + 'static,
288{
289	#[inline(always)]
290	fn cauz2(self, cause: C) -> AnyResult<T> {
291		self.ok_or(anyhow!("{}", cause))
292	}
293}
294
295//| bool: A context function to replace `ensure!($expr, err!(..))` with `$expr.cauz2(..)?`.
296impl<C> Cauz2<C, ()> for bool
297where
298	C: Display + Send + Sync + 'static,
299{
300	#[inline(always)]
301	fn cauz2(self, cause: C) -> AnyResult<()> {
302		self.then_some(()).ok_or(anyhow!("{}", cause)) //TODO: remove then_some() and use ok_or() directly when it's stablized.
303	}
304}
305
306//|
307//| Cauz3
308//| Usage:
309//|		1) To add a lazily-evaluated complex context to cauz::Error, and
310//|		2) To convert (cauz::Result, Option, and bool) to StdResult with the location.
311//|
312pub trait Cauz3<T, E> {
313	fn cauz3(self, cause: impl FnOnce(AnyError) -> E) -> StdResult<T, E>;
314}
315
316//| cauz::Result
317impl<T, E> Cauz3<T, E> for Result<T> {
318	#[inline(always)]
319	fn cauz3(self, cause: impl FnOnce(AnyError) -> E) -> StdResult<T, E> {
320		self.map_err(|e| cause(e.0))
321	}
322}
323
324//| StdResult
325//todo: Combine this and the above.
326impl<T, E0, E> Cauz3<T, E> for StdResult<T, E0>
327where
328	E0: Into<AnyError>,
329{
330	#[inline(always)]
331	fn cauz3(self, cause: impl FnOnce(AnyError) -> E) -> StdResult<T, E> {
332		self.map_err(|e| cause(e.into()))
333	}
334}
335
336//| Option: A context function to replace `.cauz(err!(...)` (not lazy) or `.ok_or_else(|| err2!(..))` (too long) with `.cauz3(err2!(..))`.
337impl<T, E> Cauz3<T, E> for Option<T> {
338	#[inline(always)]
339	fn cauz3(self, cause: impl FnOnce(AnyError) -> E) -> StdResult<T, E> {
340		self.ok_or_else(|| cause(anyhow!("")))
341	}
342}
343
344//| bool: A context function to replace `ensure!($expr, err2!(..))` with `$expr.cauz(err2!(..))?`.
345impl<E> Cauz3<(), E> for bool {
346	#[inline(always)]
347	fn cauz3(self, cause: impl FnOnce(AnyError) -> E) -> StdResult<(), E> {
348		self.then_some(()).ok_or_else(|| cause(anyhow!(""))) //TODO: remove then_some() and use ok_or_else() directly when it's stablized.
349	}
350}