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}