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}