masterror/
macros.rs

1// SPDX-FileCopyrightText: 2025 RAprogramm <andrey.rozanov.vl@gmail.com>
2//
3// SPDX-License-Identifier: MIT
4
5//! Control-flow macros for early returns with typed errors.
6//!
7//! These macros complement the typed [`AppError`](crate::AppError) APIs by
8//! providing a lightweight, allocation-free way to short-circuit functions when
9//! invariants are violated. Unlike the dynamic formatting helpers offered by
10//! `anyhow` or `eyre`, the macros operate on pre-constructed error values so
11//! the compiler keeps strong typing guarantees and no formatting work happens
12//! on the success path.
13//!
14//! ```rust
15//! use masterror::{AppError, AppErrorKind, AppResult};
16//!
17//! fn guard(flag: bool) -> AppResult<()> {
18//!     masterror::ensure!(flag, AppError::bad_request("flag must be true"));
19//!     Ok(())
20//! }
21//!
22//! assert!(guard(true).is_ok());
23//! assert!(matches!(
24//!     guard(false).unwrap_err().kind,
25//!     AppErrorKind::BadRequest
26//! ));
27//! ```
28
29/// Abort the enclosing function with an error when a condition fails.
30///
31/// The macro takes either a bare condition and error expression, or the more
32/// explicit `cond = ..., else = ...` form. The error expression is evaluated
33/// lazily only when the condition is false.
34///
35/// # Examples
36///
37/// Short-circuit a typed error:
38///
39/// ```rust
40/// use masterror::{AppError, AppErrorKind, AppResult};
41///
42/// fn require(flag: bool) -> AppResult<()> {
43///     masterror::ensure!(flag, AppError::bad_request("flag required"));
44///     Ok(())
45/// }
46///
47/// assert!(matches!(
48///     require(false).unwrap_err().kind,
49///     AppErrorKind::BadRequest
50/// ));
51/// ```
52///
53/// Use the verbose syntax for clarity in complex conditions:
54///
55/// ```rust
56/// use masterror::{AppError, AppResult};
57///
58/// fn bounded(value: i32, max: i32) -> AppResult<()> {
59///     masterror::ensure!(
60///         cond = value <= max,
61///         else = AppError::service("value too large")
62///     );
63///     Ok(())
64/// }
65///
66/// assert!(bounded(2, 3).is_ok());
67/// assert!(bounded(5, 3).is_err());
68/// ```
69#[macro_export]
70macro_rules! ensure {
71    (cond = $cond:expr, else = $err:expr $(,)?) => {
72        $crate::ensure!($cond, $err)
73    };
74    ($cond:expr, $err:expr $(,)?) => {
75        if !$cond {
76            return Err($err);
77        }
78    };
79}
80
81/// Abort the enclosing function with the provided error.
82///
83/// This macro is a typed alternative to `anyhow::bail!`, delegating the
84/// decision of how to construct the error to the caller. It never performs
85/// formatting or allocations on the success path.
86///
87/// # Examples
88///
89/// ```rust
90/// use masterror::{AppError, AppErrorKind, AppResult};
91///
92/// fn reject() -> AppResult<()> {
93///     masterror::fail!(AppError::unauthorized("token expired"));
94/// }
95///
96/// let err = reject().unwrap_err();
97/// assert!(matches!(err.kind, AppErrorKind::Unauthorized));
98/// ```
99#[macro_export]
100macro_rules! fail {
101    ($err:expr $(,)?) => {
102        return Err($err);
103    };
104}