error_rail/macros/
mod.rs

1//! Ergonomic macros for creating lazy or structured [`ErrorContext`](crate::types::ErrorContext).
2//!
3//! These macros provide convenient shortcuts for attaching rich metadata to errors:
4//!
5//! - [`macro@crate::rail`] - Wraps a `Result`-producing block and converts it into a
6//!   [`BoxedComposableResult`](crate::types::BoxedComposableResult) via `ErrorPipeline::finish`.
7//! - [`macro@crate::context`] - Defers formatting until the context is consumed, avoiding
8//!   unnecessary allocations on the success path.
9//! - [`macro@crate::location`] - Automatically captures the current file path and line number
10//!   using `file!()` and `line!()`.
11//! - [`macro@crate::tag`] - Attaches a short categorical label for filtering and searching.
12//! - [`macro@crate::group`] - Creates a lazily-evaluated grouped context that combines
13//!   multiple fields (message, tags, location, metadata) into one cohesive unit while deferring
14//!   all formatting until the error occurs.
15//!
16//! # Examples
17//!
18//! ```
19//! use error_rail::{context, rail, group, ErrorPipeline};
20//!
21//! let result: Result<(), &str> = Err("failed");
22//! let pipeline = ErrorPipeline::new(result)
23//!     .with_context(context!("user_id: {}", 123))
24//!     .with_context(group!(
25//!         tag("auth"),
26//!         location(file!(), line!()),
27//!         metadata("retry_count", "3")
28//!     ))
29//!     .finish_boxed();
30//!
31//! // Equivalent rail! shorthand that also returns a boxed composable result
32//! let _ = rail!({
33//!     Err::<(), &str>("failed")
34//!         .map_err(|err| err)
35//! });
36//! ```
37
38/// Wraps a `Result`-producing expression or block and converts it into a
39/// [`BoxedComposableResult`](crate::types::BoxedComposableResult).
40///
41/// This macro provides a convenient shorthand for creating an [`ErrorPipeline`](crate::ErrorPipeline)
42/// and immediately calling `finish()` to box the result. It accepts either a single expression
43/// or a block of code that produces a `Result`.
44///
45/// # Syntax
46///
47/// - `rail!(expr)` - Wraps a single `Result`-producing expression
48/// - `rail!({ ... })` - Wraps a block that produces a `Result`
49///
50/// # Returns
51///
52/// A [`BoxedComposableResult<T, E>`](crate::types::BoxedComposableResult) where the error type
53/// is wrapped in a [`ComposableError`](crate::types::ComposableError).
54///
55/// # Examples
56///
57/// ```rust
58/// use error_rail::{rail, group};
59///
60/// // Simple expression
61/// let result = rail!(Err::<(), &str>("failed"));
62/// assert!(result.is_err());
63///
64/// // Block syntax with multiple statements
65/// let result = rail!({
66///     let value = std::fs::read_to_string("config.txt");
67///     value
68/// });
69///
70/// // Using with group! macro for structured context
71/// let result = rail!({
72///     std::fs::read_to_string("config.txt")
73/// })
74/// .map_err(|e| e.with_context(group!(
75///     tag("config"),
76///     location(file!(), line!()),
77///     metadata("file", "config.txt")
78/// )));
79/// ```
80#[macro_export]
81macro_rules! rail {
82    ($expr:expr $(,)?) => {
83        $crate::ErrorPipeline::new($expr).finish_boxed()
84    };
85}
86
87/// Creates a lazily-evaluated error context that defers string formatting.
88///
89/// This macro wraps the provided format string and arguments in a [`LazyContext`](crate::types::LazyContext),
90/// which only evaluates the closure when the error actually occurs. This avoids the performance
91/// overhead of string formatting on the success path.
92///
93/// # Arguments
94///
95/// Accepts the same arguments as the standard `format!` macro.
96///
97/// # Examples
98///
99/// ```
100/// use error_rail::{context, ComposableError};
101///
102/// let user_id = 42;
103/// let err = ComposableError::<&str>::new("auth failed")
104///     .with_context(context!("user_id: {}", user_id));
105/// ```
106#[macro_export]
107macro_rules! context {
108    ($($arg:tt)*) => {
109        $crate::types::LazyContext::new(move || format!($($arg)*))
110    };
111}
112
113/// Captures the current source file and line number as error context.
114///
115/// This macro creates an [`ErrorContext::location`](crate::types::ErrorContext::location)
116/// using the `file!()` and `line!()` built-in macros, providing precise source location
117/// information for debugging.
118///
119/// # Deprecated
120///
121/// Use [`group!`](crate::group) instead since version 0.5.0.
122///
123/// # Examples
124///
125/// ```
126/// use error_rail::{location, ComposableError};
127///
128/// let err = ComposableError::<&str>::new("io error")
129///     .with_context(location!());
130/// ```
131#[macro_export]
132#[deprecated(since = "0.5.0", note = "Use `group!` instead")]
133macro_rules! location {
134    () => {
135        $crate::types::ErrorContext::location(file!(), line!())
136    };
137}
138
139/// Creates a categorical tag for error classification.
140///
141/// This macro creates an [`ErrorContext::tag`](crate::types::ErrorContext::tag) that can be
142/// used to categorize and filter errors by domain (e.g., "db", "auth", "network").
143///
144/// # Deprecated
145///
146/// Use [`group!`](crate::group) instead since version 0.5.0.
147///
148/// # Arguments
149///
150/// * `$tag` - A string or expression that can be converted into a tag
151///
152/// # Examples
153///
154/// ```
155/// use error_rail::{tag, ComposableError};
156///
157/// let err = ComposableError::<&str>::new("connection failed")
158///     .with_context(tag!("network"));
159/// ```
160#[macro_export]
161#[deprecated(since = "0.5.0", note = "Use `group!` instead")]
162macro_rules! tag {
163    ($tag:expr) => {
164        $crate::types::ErrorContext::tag($tag)
165    };
166}
167
168/// Creates a key-value metadata pair for structured error context.
169///
170/// This macro creates an [`ErrorContext::metadata`](crate::types::ErrorContext::metadata)
171/// entry that can be used for structured logging, filtering, or monitoring.
172///
173/// # Deprecated
174///
175/// Use [`group!`](crate::group) instead since version 0.5.0.
176///
177/// # Arguments
178///
179/// * `$key` - The metadata key
180/// * `$value` - The metadata value
181///
182/// # Examples
183///
184/// ```
185/// use error_rail::{metadata, ComposableError};
186///
187/// let err = ComposableError::<&str>::new("rate limit exceeded")
188///     .with_context(metadata!("retry_after", "60"));
189/// ```
190#[macro_export]
191#[deprecated(since = "0.5.0", note = "Use `group!` instead")]
192macro_rules! metadata {
193    ($key:expr, $value:expr) => {
194        $crate::types::ErrorContext::metadata($key, $value)
195    };
196}
197
198/// Implements `IntoErrorContext` for a custom type.
199///
200/// This macro simplifies the implementation of the [`IntoErrorContext`](crate::traits::IntoErrorContext)
201/// trait for user-defined types. It converts the type into an [`ErrorContext`](crate::types::ErrorContext)
202/// using its `Display` implementation.
203///
204/// # Arguments
205///
206/// * `$type` - The type to implement `IntoErrorContext` for.
207///
208/// # Examples
209///
210/// ```
211/// use error_rail::{impl_error_context, ErrorContext, traits::IntoErrorContext};
212/// use std::fmt;
213///
214/// struct MyError {
215///     code: u32,
216/// }
217///
218/// impl fmt::Display for MyError {
219///     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220///         write!(f, "Error code: {}", self.code)
221///     }
222/// }
223///
224/// impl_error_context!(MyError);
225///
226/// let err = MyError { code: 404 };
227/// let ctx = err.into_error_context();
228/// assert_eq!(ctx.to_string(), "Error code: 404");
229/// ```
230#[macro_export]
231macro_rules! impl_error_context {
232    ($type:ty) => {
233        impl $crate::traits::IntoErrorContext for $type {
234            fn into_error_context(self) -> $crate::types::ErrorContext {
235                $crate::types::ErrorContext::new(self.to_string())
236            }
237        }
238    };
239}
240
241/// Creates a grouped error context that combines multiple context types.
242///
243/// This macro creates a lazily-evaluated grouped context that combines message,
244/// tags, location, and metadata into a single cohesive unit. All formatting is
245/// deferred until the error actually occurs, avoiding unnecessary allocations
246/// on the success path.
247///
248/// # Arguments
249///
250/// The macro accepts function-call style arguments:
251/// * `message("format string", args...)` - Optional formatted message
252/// * `tag("label")` - Categorical tags (can be repeated)
253/// * `location(file, line)` - Source file and line number
254/// * `metadata("key", "value")` - Key-value pairs (can be repeated)
255///
256/// # Examples
257///
258/// ```
259/// use error_rail::{group, ComposableError};
260///
261/// let attempts = 3;
262/// let err = ComposableError::<&str>::new("auth failed")
263///     .with_context(group!(
264///         message("user_id: {}", attempts),
265///         tag("auth"),
266///         location(file!(), line!()),
267///         metadata("retry_count", "3"),
268///         metadata("timeout", "30s")
269///     ));
270/// ```
271#[macro_export]
272macro_rules! group {
273    // Empty group
274    () => {
275        $crate::types::LazyGroupContext::new(move || {
276            $crate::types::ErrorContext::Group($crate::types::GroupContext::default())
277        })
278    };
279
280    // With fields - use function-call style
281    (
282        $($field:ident($($arg:tt)*)),* $(,)?
283    ) => {
284        $crate::types::LazyGroupContext::new(move || {
285            let mut builder = $crate::types::ErrorContext::builder();
286            $(
287                $crate::__group_field!(builder, $field, $($arg)*);
288            )*
289            builder.build()
290        })
291    };
292}
293
294/// Internal macro for processing individual group fields
295#[macro_export]
296#[doc(hidden)]
297macro_rules! __group_field {
298    // Message field
299    ($builder:expr, message, $($arg:tt)*) => {
300        $builder = $builder.message(format!($($arg)*));
301    };
302
303    // Tag field
304    ($builder:expr, tag, $tag:expr) => {
305        $builder = $builder.tag($tag);
306    };
307
308    // Location field
309    ($builder:expr, location, $file:expr, $line:expr) => {
310        $builder = $builder.location($file, $line);
311    };
312
313    // Metadata field
314    ($builder:expr, metadata, $key:expr, $value:expr) => {
315        $builder = $builder.metadata($key, $value);
316    };
317}
318
319/// Captures the current backtrace as lazy error context.
320///
321/// This macro creates a [`LazyContext`](crate::types::LazyContext) that captures the stack
322/// backtrace only when the error actually occurs, avoiding the performance overhead of
323/// backtrace generation on the success path.
324///
325/// The backtrace is captured using [`std::backtrace::Backtrace::capture()`] and converted
326/// to a string representation when the context is evaluated.
327///
328/// # Examples
329///
330/// ```
331/// use error_rail::{ComposableError, backtrace};
332///
333/// let err = ComposableError::<&str>::new("panic occurred")
334///     .with_context(backtrace!());
335/// ```
336#[macro_export]
337#[cfg(feature = "std")]
338macro_rules! backtrace {
339    () => {{
340        $crate::types::LazyContext::new(|| std::backtrace::Backtrace::capture().to_string())
341    }};
342}
343
344/// Creates a backtrace context that always captures regardless of environment.
345///
346/// This macro uses `force_capture()` to always generate a backtrace, ignoring
347/// `RUST_BACKTRACE`/`RUST_LIB_BACKTRACE` settings. Use this for debugging
348/// scenarios where you need guaranteed backtrace information.
349///
350/// # Performance Note
351///
352/// This has higher overhead than `backtrace!()` since it always captures
353/// stack frames, regardless of environment settings.
354///
355/// # Examples
356///
357/// ```
358/// use error_rail::{ComposableError, backtrace_force};
359///
360/// let err = ComposableError::<&str>::new("panic occurred")
361///     .with_context(backtrace_force!());
362/// ```
363#[macro_export]
364#[cfg(feature = "std")]
365macro_rules! backtrace_force {
366    () => {{
367        $crate::types::LazyContext::new(|| std::backtrace::Backtrace::force_capture().to_string())
368    }};
369}