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