oofs/lib.rs
1use builder::*;
2use context::*;
3use core::fmt::{self, Debug, Display, Write};
4use std::error::{self, Error};
5use tags::Tags;
6
7#[cfg(all(
8 feature = "debug_non_copyable_disabled",
9 feature = "debug_non_copyable_full"
10))]
11compile_error!(
12 "features `debug_non_copyable_disabled` and `debug_non_copyable_full` are mutually exclusive"
13);
14
15pub type Result<T, E = Oof> = std::result::Result<T, E>;
16
17pub use ext::OofExt;
18pub use oofs_derive::oofs;
19
20/// Create a custom error `Oof` similar to `anyhow!`
21///
22/// You can format the error just like you do for `println!` and `anyhow!`.
23///
24/// Ex)
25/// ```rust
26/// # use oofs::{Oof, oofs, oof};
27/// # #[oofs]
28/// # fn _ex() -> Result<(), Oof> {
29/// return oof!("custom error {}", "failure").into_res();
30/// # }
31/// ```
32///
33/// [Oof::into_res()](struct.Oof.html#method.into_res) wraps `Oof` in `Result::Err(_)`, so you can return it directly.
34///
35/// Since the macro returns `Oof`, you can chain methods like `tag` and `attach`.
36///
37/// Ex)
38/// ```rust
39/// # use oofs::{Oof, oofs, oof};
40/// # #[oofs]
41/// # fn _ex() -> Result<(), Oof> {
42/// struct MyTag;
43///
44/// let x = 123usize;
45///
46/// return oof!("custom error {}", "failure").tag::<MyTag>().attach(x).into_res();
47/// # }
48/// ```
49#[macro_export]
50macro_rules! oof {
51 ($($arg:tt)*) => {
52 $crate::Oof::custom(format!($($arg)*))
53 };
54}
55
56/// Check that a given expression evaluates to `true`, else return an error.
57///
58/// First parameter is an expression that evaluates to `bool`.
59/// If the expression evaluates to `false`, the macro will return `Err(Oof)`.
60///
61/// Ex)
62/// ```rust
63/// # use oofs::*;
64/// # use std::time::Instant;
65/// # #[oofs]
66/// # fn _ex() -> Result<(), Oof> {
67/// ensure!(false);
68/// # Ok(())
69/// # }
70/// ```
71///
72/// Optionally, you can input custom context message like for `format!(...)`.
73///
74/// Ex)
75/// ```rust
76/// # use oofs::*;
77/// # use std::time::Instant;
78/// # #[oofs]
79/// # fn _ex() -> Result<(), Oof> {
80/// let x = 123usize;
81/// let y = "some value";
82///
83/// ensure!(false, "custom context with value {:?} and {}", x, y);
84/// # Ok(())
85/// # }
86/// ```
87///
88/// Also, you can provide tags and attachments in braces.
89///
90/// Ex)
91/// ```rust
92/// # use oofs::*;
93/// # use std::time::Instant;
94/// # #[oofs]
95/// # fn _ex() -> Result<(), Oof> {
96/// struct MyTag;
97/// struct OtherTag;
98///
99/// let x = 123usize;
100/// let y = "some value";
101/// let z = "lazy attachment";
102///
103/// ensure!(false, {
104/// tag: [MyTag, OtherTag],
105/// attach: [&y, "attachment", Instant::now()],
106/// attach_lazy: [|| format!("context {}", &z)]
107/// });
108///
109/// ensure!(false, "custom context with value {:?}", x, {
110/// tag: [MyTag, OtherTag],
111/// attach: [&y, "attachment", Instant::now()],
112/// attach_lazy: [|| format!("context {}", &z)]
113/// });
114/// # Ok(())
115/// # }
116/// ```
117#[macro_export]
118macro_rules! ensure {
119 ($cond:expr $(, $($rest:tt)*)?) => {
120 $crate::ensure!(@fmt $cond, (), $($($rest)*)?);
121 };
122 (@fmt $cond:expr, (), $({ $($rest:tt)* })?) => {
123 $crate::ensure!(@meta $cond, $crate::oof!("assertion failed: `{}`", stringify!($cond)), $($($rest)*)?);
124 };
125 (@fmt $cond:expr, ($($fmt:expr,)*), $({ $($rest:tt)* })?) => {
126 $crate::ensure!(@meta $cond, $crate::oof!($($fmt),*), $($($rest)*)?);
127 };
128 (@fmt $cond:expr, ($($fmt:expr,)*), $arg:expr $(, $($rest:tt)*)?) => {
129 $crate::ensure!(@fmt $cond, ($($fmt,)* $arg,), $($($rest)*)?);
130 };
131 (@meta $cond:expr, $ret:expr, tag: [$($tag:ty),* $(,)?] $(, $($rest:tt)*)?) => {
132 $crate::ensure!(@meta $cond, $ret $(.tag::<$tag>())*, $($($rest)*)?);
133 };
134 (@meta $cond:expr, $ret:expr, attach: [$($a:expr),* $(,)?] $(, $($rest:tt)*)?) => {
135 $crate::ensure!(@meta $cond, $ret $(.attach($a))*, $($($rest)*)?);
136 };
137 (@meta $cond:expr, $ret:expr, attach_lazy: [$($l:expr),* $(,)?] $(, $($rest:tt)*)?) => {
138 $crate::ensure!(@meta $cond, $ret $(.attach_lazy($l))*, $($($rest)*)?);
139 };
140 (@meta $cond:expr, $ret:expr, ) => {
141 if !$cond {
142 return $ret.into_res();
143 }
144 };
145}
146
147/// Check that two given expressions are same, else return an error.
148///
149/// First two parameters are parameters to be compared.
150/// If the parameters are not same, the macro will return `Err(Oof)`.
151///
152/// Ex)
153/// ```rust
154/// # use oofs::*;
155/// # use std::time::Instant;
156/// # #[oofs]
157/// # fn _ex() -> Result<(), Oof> {
158/// ensure_eq!(1u8, 2u8);
159/// # Ok(())
160/// # }
161/// ```
162///
163/// Optionally, you can input custom context message like for `format!(...)`.
164///
165/// Ex)
166/// ```rust
167/// # use oofs::*;
168/// # use std::time::Instant;
169/// # #[oofs]
170/// # fn _ex() -> Result<(), Oof> {
171/// struct MyTag;
172/// struct OtherTag;
173///
174/// let x = 123usize;
175/// let y = "some value";
176/// let z = "lazy attachment";
177///
178/// ensure_eq!(1u8, 2u8, "custom context with value {:?}", x);
179/// # Ok(())
180/// # }
181/// ```
182///
183/// Also, you can provide tags and attachments in braces.
184///
185/// Ex)
186/// ```rust
187/// # use oofs::*;
188/// # use std::time::Instant;
189/// # #[oofs]
190/// # fn _ex() -> Result<(), Oof> {
191/// struct MyTag;
192/// struct OtherTag;
193///
194/// let x = 123usize;
195/// let y = "some value";
196/// let z = "lazy attachment";
197///
198/// ensure_eq!(1u8, 2u8, {
199/// tag: [MyTag, OtherTag],
200/// attach: [&y, "attachment", Instant::now()],
201/// attach_lazy: [|| format!("context {}", &z)]
202/// });
203///
204/// ensure_eq!(1u8, 2u8, "custom context with value {:?}", x, {
205/// tag: [MyTag, OtherTag],
206/// attach: [&y, "attachment", Instant::now()],
207/// attach_lazy: [|| format!("context {}", &z)]
208/// });
209/// # Ok(())
210/// # }
211/// ```
212#[macro_export]
213macro_rules! ensure_eq {
214 ($l:expr, $r:expr $(, { $($rest:tt)* })?) => {
215 match (&$l, &$r) {
216 (left, right) => {
217 $crate::ensure!(*left == *right, "assertion failed: `(left == right)`", {
218 attach_lazy: [
219 || format!(" left: {:?}", &*left),
220 || format!("right: {:?}", &*right)
221 ],
222 $($($rest)*)?
223 });
224 }
225 }
226 };
227 ($l:expr, $r:expr $(, $($rest:tt)*)?) => {
228 match (&$l, &$r) {
229 (left, right) => {
230 $crate::ensure!(*left == *right $(, $($rest)*)?);
231 }
232 }
233 };
234}
235
236/// Wraps a custom error with `Oof`
237///
238/// Ex)
239/// ```rust
240/// # use oofs::*;
241/// # use std::time::Instant;
242/// # #[oofs]
243/// # fn _ex() -> Result<(), Oof> {
244/// return wrap_err(std::io::Error::new(std::io::ErrorKind::Other, "Some Error")).into_res();
245/// # Ok(())
246/// # }
247/// ```
248///
249/// Since `wrap_err(_)` returns `Oof`, you can chain methods like `tag` and `attach`.
250///
251/// Ex)
252/// ```rust
253/// # use oofs::*;
254/// # use std::time::Instant;
255/// # #[oofs]
256/// # fn _ex() -> Result<(), Oof> {
257/// struct MyTag;
258/// let x = 123u8;
259///
260/// return wrap_err(std::io::Error::new(std::io::ErrorKind::Other, "Some Error"))
261/// .tag::<MyTag>()
262/// .attach(x)
263/// .into_res();
264/// # Ok(())
265/// # }
266/// ```
267#[cfg_attr(feature = "location", track_caller)]
268pub fn wrap_err(e: impl 'static + Send + Sync + Error) -> Oof {
269 Oof::builder().with_source(e).build()
270}
271
272/// Error type for oofs.
273///
274/// `Oof` implements `std::error::Error`.
275pub struct Oof {
276 source: Option<Box<dyn 'static + Send + Sync + Error>>,
277 context: Box<Context>,
278 tags: Tags,
279 attachments: Vec<String>,
280 #[cfg(feature = "location")]
281 location: Location,
282}
283
284impl Display for Oof {
285 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286 let context = self.context.as_ref();
287
288 write!(f, "{context}")?;
289
290 #[cfg(feature = "location")]
291 write!(f, " at `{}`", self.location)?;
292
293 if matches!(context, Context::Generated(_)) || !self.attachments.is_empty() {
294 writeln!(f)?;
295 }
296
297 if let Context::Generated(c) = context {
298 c.fmt_args(f)?;
299 }
300
301 if !self.attachments.is_empty() {
302 writeln!(f, "\nAttachments:")?;
303 for (i, a) in self.attachments.iter().enumerate() {
304 let mut indented = Indented {
305 inner: f,
306 number: Some(i),
307 started: false,
308 };
309
310 write!(indented, "{}", a)?;
311 writeln!(f)?;
312 }
313 }
314
315 Ok(())
316 }
317}
318
319impl Debug for Oof {
320 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321 if f.alternate() {
322 #[cfg(not(feature = "location"))]
323 let debug = f
324 .debug_struct("Oof")
325 .field("context", &self.context)
326 .field("source", &self.source)
327 .field("tags", &self.tags)
328 .field("attachments", &self.attachments)
329 .finish();
330
331 #[cfg(feature = "location")]
332 let debug = f
333 .debug_struct("Oof")
334 .field("context", &self.context)
335 .field("source", &self.source)
336 .field("location", &self.location)
337 .field("tags", &self.tags)
338 .field("attachments", &self.attachments)
339 .finish();
340
341 return debug;
342 }
343
344 write!(f, "{self}")?;
345
346 if let Some(cause) = self.source() {
347 write!(f, "\nCaused by:")?;
348
349 let multiple = cause.source().is_some();
350 for (n, error) in chain::Chain::new(cause).enumerate() {
351 writeln!(f)?;
352
353 let mut indented = Indented {
354 inner: f,
355 number: if multiple { Some(n) } else { None },
356 started: false,
357 };
358
359 write!(indented, "{error}")?;
360 }
361 }
362
363 Ok(())
364 }
365}
366
367impl error::Error for Oof {
368 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
369 if let Some(e) = &self.source {
370 Some(e.as_ref())
371 } else {
372 None
373 }
374 }
375}
376
377impl Oof {
378 /// Create a new `Oof` with custom context message.
379 ///
380 /// You can also use [oof!(...)](oof).
381 #[cfg_attr(feature = "location", track_caller)]
382 pub fn custom(message: String) -> Oof {
383 Self::builder().with_custom(message).build()
384 }
385
386 /// Wraps `Oof` in `Result::Err(_)`.
387 ///
388 /// Use it to easily return an `Err(Oof)` instead of manually wrapping it in `Err(_)`.
389 ///
390 /// Ex)
391 /// ```rust
392 /// use oofs::oof;
393 /// # use oofs::{Oof, oofs};
394 /// # #[oofs]
395 /// # fn _ex() -> Result<(), Oof> {
396 ///
397 /// return oof!("custom error").into_res();
398 /// # Ok(())
399 /// # }
400 /// ```
401 pub fn into_res<T, E>(self) -> Result<T, E>
402 where
403 E: From<Self>,
404 {
405 Err(self.into())
406 }
407
408 #[cfg_attr(feature = "location", track_caller)]
409 fn builder() -> OofBuilder {
410 OofBuilder::new()
411 }
412
413 /// Check if this `Oof` is tagged as given type.
414 ///
415 /// This method only checks one level deep.
416 /// To check all nested errors, use [Oof::tagged_nested](struct.Oof.html#method.tagged_nested).
417 pub fn tagged<T: 'static>(&self) -> bool {
418 self.tags.tagged::<T>()
419 }
420
421 /// Check if this `Oof` is tagged in all nested errors.
422 ///
423 /// This method checks all levels.
424 pub fn tagged_nested<T: 'static>(&self) -> bool {
425 if self.tagged::<T>() {
426 return true;
427 }
428
429 for cause in chain::Chain::new(self).skip(1) {
430 if let Some(e) = cause.downcast_ref::<Oof>() {
431 if e.tagged::<T>() {
432 return true;
433 }
434 }
435 }
436
437 false
438 }
439
440 /// Check if this `Oof` is tagged in all nested errors in reverse order.
441 ///
442 /// This method checks all levels.
443 pub fn tagged_nested_rev<T: 'static>(&self) -> bool {
444 for cause in chain::Chain::new(self).skip(1).rev() {
445 if let Some(e) = cause.downcast_ref::<Oof>() {
446 if e.tagged::<T>() {
447 return true;
448 }
449 }
450 }
451
452 if self.tagged::<T>() {
453 return true;
454 }
455
456 false
457 }
458
459 /// Tag `Oof` with type and return Self.
460 pub fn tag<T: 'static>(mut self) -> Self {
461 self.tags.tag::<T>();
462 self
463 }
464
465 /// Attach any value that implements `std::fmt::Debug`.
466 ///
467 /// This attached value will be listed as attachments in the displayed error.
468 ///
469 /// Ex)
470 /// ```rust
471 /// use oofs::{oof, oofs};
472 /// # use oofs::Oof;
473 ///
474 /// # #[oofs]
475 /// # fn _ex() -> Result<(), Oof> {
476 /// let x = 123u8;
477 ///
478 /// return oof!("custom error")
479 /// .attach(x)
480 /// .attach("some attachment")
481 /// .into_res();
482 /// # Ok(())
483 /// # }
484 /// ```
485 ///
486 /// Above example will output:
487 ///
488 /// ```text
489 /// custom error at `oofs/tests/basic.rs:9:13`
490 ///
491 /// Attachments:
492 /// 0: 123
493 /// 1: "some attachment"
494 /// ```
495 pub fn attach<D: fmt::Debug>(mut self, debuggable: D) -> Self {
496 self.attachments.push(format!("{debuggable:?}"));
497 self
498 }
499
500 /// Lazily load and attach any value that implements `ToString`.
501 ///
502 /// This attached value will be listed as attachments in the displayed error.
503 ///
504 /// Ex)
505 /// ```rust
506 /// use oofs::{oof, oofs};
507 /// # use oofs::Oof;
508 ///
509 /// # #[oofs]
510 /// # fn _ex() -> Result<(), Oof> {
511 ///
512 /// return oof!("custom error")
513 /// .attach_lazy(|| "some attachment")
514 /// .into_res();
515 /// # Ok(())
516 /// # }
517 /// ```
518 ///
519 /// Above example will output:
520 ///
521 /// ```text
522 /// custom error at `oofs/tests/basic.rs:9:13`
523 ///
524 /// Attachments:
525 /// 0: "some attachment"
526 /// ```
527 pub fn attach_lazy<D: ToString, F: FnOnce() -> D>(mut self, f: F) -> Self {
528 self.attachments.push(f().to_string());
529 self
530 }
531}
532
533mod builder;
534mod chain;
535mod context;
536mod ext;
537mod tags;
538mod var_check;
539
540/// Module used by attribute `#[oofs]`
541pub mod __used_by_attribute {
542 pub use crate::{builder::*, context::*, tags::*, var_check::*};
543
544 pub const DEBUG_NON_COPYABLE: bool = cfg!(all(
545 not(feature = "debug_non_copyable_disabled"),
546 any(debug_assertions, feature = "debug_non_copyable_full")
547 ));
548}