Skip to main content

gix_error/exn/
macros.rs

1// Copyright 2025 FastLabs Developers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/// Creates an [`Exn`] and converts it to the function's result error type.
16///
17/// Shorthand for `return Err(Exn::from(err).into())`.
18/// Works with both [`crate::Result`] and typed [`crate::ExnResult`].
19/// Prefer to import the macro with `use gix_error::bail;` (or `use gix::error::bail;` in applications)
20/// and invoke it as `bail!(...)` instead of using a qualified path for readability.
21///
22/// String literals and format arguments implicitly construct a formatted [`Message`](crate::Message),
23/// including captured arguments like `bail!("invalid input: {input}")`.
24/// Like [`message!`](crate::message!), this always formats the message; use [`message()`](crate::message())
25/// inside `bail!` for a static message without formatting.
26/// A string literal can be followed by method calls on the formatted [`Message`](crate::Message),
27/// such as `bail!("invalid record at {offset}".corrupted().with("offset", offset))`.
28/// Explicit format arguments follow the method chain: `bail!("invalid record at {}".corrupted(), offset)`.
29/// These methods apply to the message after formatting, not to the string literal itself.
30/// Parenthesize an ordinary error expression starting with a literal to avoid this shorthand,
31/// for example `bail!(("bad".parse::<u32>().expect_err("invalid number")))`.
32/// Without a classification builder, the shorthand leaves the error unclassified.
33/// Classified constructors also work, such as `bail!(gix_error::validation("invalid input"))`.
34///
35/// # Examples
36///
37/// Create an [`Exn`] from [`Error`]:
38///
39/// [`Exn`]: crate::Exn
40/// [`Error`]: std::error::Error
41///
42/// ```
43/// use std::fs;
44///
45/// use gix_error::{bail, ExnResult};
46/// # fn wrapper() -> ExnResult<(), std::io::Error> {
47/// match fs::read_to_string("/path/to/file") {
48///     Ok(content) => println!("file contents: {content}"),
49///     Err(err) => bail!(err),
50/// }
51/// # Ok(()) }
52/// ```
53///
54/// ```
55/// use gix_error::{bail, Result};
56///
57/// fn public_api() -> Result {
58///     bail!(gix_error::validation("invalid input"));
59/// }
60/// assert!(public_api().expect_err("the input is invalid").is_validation());
61/// ```
62///
63/// Return a formatted message with captured or explicit arguments:
64///
65/// ```
66/// use gix_error::{bail, ExnMessageResult, Result};
67///
68/// fn captured(name: &str) -> Result {
69///     bail!("unknown executable '{name}'");
70/// }
71///
72/// fn explicit(name: &str) -> ExnMessageResult {
73///     bail!("unknown executable '{}'", name);
74/// }
75/// # assert!(captured("other").is_err());
76/// # assert!(explicit("other").is_err());
77/// ```
78///
79/// Chain classification and metadata builders on a formatted message:
80///
81/// ```
82/// use std::path::Path;
83/// use gix_error::{bail, ExnMessageResult, MetadataValue, Result};
84///
85/// fn captured(path: &Path) -> Result {
86///     bail!("Missing reference".not_found().with("path", path));
87/// }
88///
89/// fn explicit(path: &Path) -> ExnMessageResult {
90///     bail!("Missing reference at '{}'".not_found().with("path", path), path.display());
91/// }
92///
93/// let path = Path::new("refs/heads/main");
94/// let error = captured(path).expect_err("the reference is missing");
95/// assert!(error.is_not_found());
96/// assert_eq!(error.metadata().next().expect("reference details")["path"], MetadataValue::Path(path.into()));
97/// let error = explicit(path).expect_err("the reference is missing");
98/// assert_eq!(error.error().message, "Missing reference at 'refs/heads/main'");
99/// assert!(error.is_not_found());
100/// ```
101#[macro_export]
102macro_rules! bail {
103    ($fmt:literal $(.$method:ident($($method_arg:tt)*))+ $(, $($arg:tt)*)?) => {
104        $crate::bail!($crate::message!($fmt $(, $($arg)*)?) $(.$method($($method_arg)*))+)
105    };
106    ($fmt:literal $(,)?) => {
107        $crate::bail!($crate::message!($fmt))
108    };
109    // Strip the grouping used to opt out of builder shorthand before it can trigger `unused_parens`.
110    (($err:expr) $(,)?) => {
111        $crate::bail!($err)
112    };
113    ($err:expr $(,)?) => {{
114        return ::std::result::Result::Err($crate::Exn::from($err).into());
115    }};
116    ($fmt:expr, $($arg:tt)*) => {
117        $crate::bail!($crate::message!($fmt, $($arg)*))
118    };
119}
120
121/// Ensures `$cond` is met; otherwise return an error.
122///
123/// Shorthand for `if !$cond { bail!(...); }`.
124/// Accepts the same error expressions, format arguments, and message builder chains as [`bail!`].
125/// The condition is evaluated once; error, format, and builder arguments are evaluated only on failure.
126///
127/// # Examples
128///
129/// Create an [`Exn`] from an [`Error`]:
130///
131/// [`Exn`]: crate::Exn
132/// [`Error`]: std::error::Error
133///
134/// ```
135/// # fn has_permission(_: &u32, _: &u32) -> bool { true }
136/// # type User = u32;
137/// # let user = 0;
138/// # type Resource = u32;
139/// # let resource = 0;
140/// use std::error::Error;
141/// use std::fmt;
142///
143/// use gix_error::ensure;
144///
145/// #[derive(Debug)]
146/// struct PermissionDenied(User, Resource);
147///
148/// impl fmt::Display for PermissionDenied {
149///     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
150///         write!(fmt, "permission denied")
151///     }
152/// }
153///
154/// impl Error for PermissionDenied {}
155///
156/// ensure!(
157///     has_permission(&user, &resource),
158///     PermissionDenied(user, resource),
159/// );
160/// # Ok::<(), gix_error::Error>(())
161/// ```
162///
163/// Format a classified failure and attach metadata:
164///
165/// ```
166/// use gix_error::{ensure, ExnMessageResult, MetadataValue, Result};
167///
168/// fn captured(count: usize) -> Result {
169///     ensure!(count > 0, "Count must be positive, got {count}".validation().with_input(count));
170///     Ok(())
171/// }
172///
173/// fn explicit(count: usize) -> ExnMessageResult {
174///     ensure!(count > 0, "Count must be positive, got {}".validation().with_input(count), count);
175///     Ok(())
176/// }
177///
178/// captured(1)?;
179/// let error = captured(0).expect_err("zero violates the input constraint");
180/// assert!(error.is_validation());
181/// assert_eq!(error.metadata().next().expect("input details")["input"], MetadataValue::U64(0));
182/// let error = explicit(0).expect_err("zero violates the input constraint");
183/// assert_eq!(error.error().message, "Count must be positive, got 0");
184/// assert!(error.is_validation());
185/// # Ok::<(), gix_error::Error>(())
186/// ```
187#[macro_export]
188macro_rules! ensure {
189    ($cond:expr, $($err:tt)+) => {{
190        if !bool::from($cond) {
191            $crate::bail!($($err)+)
192        }
193    }};
194}
195
196/// Construct a [`Message`](crate::Message) from a string literal or format string.
197/// Note that it always runs `format!()`, use the [`message()`](crate::message()) function for literals instead.
198#[macro_export]
199macro_rules! message {
200    ($message_with_format_args:literal $(,)?) => {
201        $crate::Message::new(format!($message_with_format_args))
202    };
203    ($fmt:expr, $($arg:tt)*) => {
204        $crate::Message::new(format!($fmt, $($arg)*))
205    };
206}