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}