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}