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::metadata`] - Adds arbitrary key-value pairs for structured logging.
13//!
14//! # Examples
15//!
16//! ```
17//! use error_rail::{context, location, rail, tag, metadata, ErrorPipeline};
18//!
19//! let result: Result<(), &str> = Err("failed");
20//! let pipeline = ErrorPipeline::new(result)
21//!     .with_context(context!("user_id: {}", 123))
22//!     .with_context(location!())
23//!     .with_context(tag!("auth"))
24//!     .with_context(metadata!("retry_count", "3"))
25//!     .finish();
26//!
27//! // Equivalent rail! shorthand that also returns a boxed composable result
28//! let _ = rail!({
29//!     Err::<(), &str>("failed")
30//!         .map_err(|err| err)
31//! });
32//! ```
33
34/// Wraps a `Result`-producing expression or block and converts it into a
35/// [`BoxedComposableResult`](crate::types::BoxedComposableResult).
36///
37/// This macro provides a convenient shorthand for creating an [`ErrorPipeline`](crate::ErrorPipeline)
38/// and immediately calling `finish()` to box the result. It accepts either a single expression
39/// or a block of code that produces a `Result`.
40///
41/// # Syntax
42///
43/// - `rail!(expr)` - Wraps a single `Result`-producing expression
44/// - `rail!({ ... })` - Wraps a block that produces a `Result`
45///
46/// # Returns
47///
48/// A [`BoxedComposableResult<T, E>`](crate::types::BoxedComposableResult) where the error type
49/// is wrapped in a [`ComposableError`](crate::types::ComposableError).
50///
51/// # Examples
52///
53/// ```
54/// use error_rail::{rail, ErrorContext};
55///
56/// // Simple expression
57/// let result = rail!(Err::<(), &str>("failed"));
58/// assert!(result.is_err());
59///
60/// // Block syntax with multiple statements
61/// let result = rail!({
62///     let value = std::fs::read_to_string("config.txt");
63///     value
64/// });
65///
66/// // Chaining with context after rail!
67/// let result = rail!(Err::<(), &str>("io error"))
68///     .map_err(|e| e.with_context(ErrorContext::tag("disk")));
69/// ```
70#[macro_export]
71macro_rules! rail {
72    ($expr:expr $(,)?) => {
73        $crate::ErrorPipeline::new($expr).finish()
74    };
75}
76
77/// Creates a lazily-evaluated error context that defers string formatting.
78///
79/// This macro wraps the provided format string and arguments in a [`LazyContext`](crate::types::LazyContext),
80/// which only evaluates the closure when the error actually occurs. This avoids the performance
81/// overhead of string formatting on the success path.
82///
83/// # Arguments
84///
85/// Accepts the same arguments as the standard `format!` macro.
86///
87/// # Examples
88///
89/// ```
90/// use error_rail::{context, ComposableError};
91///
92/// let user_id = 42;
93/// let err = ComposableError::<&str, u32>::new("auth failed")
94///     .with_context(context!("user_id: {}", user_id));
95/// ```
96#[macro_export]
97macro_rules! context {
98    ($($arg:tt)*) => {
99        $crate::types::LazyContext::new(move || format!($($arg)*))
100    };
101}
102
103/// Captures the current source file and line number as error context.
104///
105/// This macro creates an [`ErrorContext::location`](crate::types::ErrorContext::location)
106/// using the `file!()` and `line!()` built-in macros, providing precise source location
107/// information for debugging.
108///
109/// # Examples
110///
111/// ```
112/// use error_rail::{location, ComposableError};
113///
114/// let err = ComposableError::<&str, u32>::new("io error")
115///     .with_context(location!());
116/// ```
117#[macro_export]
118macro_rules! location {
119    () => {
120        $crate::types::ErrorContext::location(file!(), line!())
121    };
122}
123
124/// Creates a categorical tag for error classification.
125///
126/// This macro creates an [`ErrorContext::tag`](crate::types::ErrorContext::tag) that can be
127/// used to categorize and filter errors by domain (e.g., "db", "auth", "network").
128///
129/// # Arguments
130///
131/// * `$tag` - A string or expression that can be converted into a tag
132///
133/// # Examples
134///
135/// ```
136/// use error_rail::{tag, ComposableError};
137///
138/// let err = ComposableError::<&str, u32>::new("connection failed")
139///     .with_context(tag!("network"));
140/// ```
141#[macro_export]
142macro_rules! tag {
143    ($tag:expr) => {
144        $crate::types::ErrorContext::tag($tag)
145    };
146}
147
148/// Creates a key-value metadata pair for structured error context.
149///
150/// This macro creates an [`ErrorContext::metadata`](crate::types::ErrorContext::metadata)
151/// entry that can be used for structured logging, filtering, or monitoring.
152///
153/// # Arguments
154///
155/// * `$key` - The metadata key
156/// * `$value` - The metadata value
157///
158/// # Examples
159///
160/// ```
161/// use error_rail::{metadata, ComposableError};
162///
163/// let err = ComposableError::<&str, u32>::new("rate limit exceeded")
164///     .with_context(metadata!("retry_after", "60"));
165/// ```
166#[macro_export]
167macro_rules! metadata {
168    ($key:expr, $value:expr) => {
169        $crate::types::ErrorContext::metadata($key, $value)
170    };
171}