rootcause/
macros.rs

1/// Creates a new error report.
2///
3/// This macro provides a convenient way to create [`Report`](crate::Report)
4/// instances with automatic type inference for thread-safety markers and error
5/// handlers.
6///
7/// # Two Usage Modes
8///
9/// ## Format String Mode
10///
11/// When the first argument is a string literal, the macro works like
12/// [`format!()`], creating a report with a formatted string as context:
13///
14/// ```
15/// use rootcause::prelude::*;
16///
17/// let report: Report = report!("File not found");
18/// let report: Report = report!("Failed to open {}", "config.toml");
19/// ```
20///
21/// The resulting report has type `Report<Dynamic, Mutable, SendSync>`. The
22/// context is typically a `String`, but when there are no format arguments, it
23/// may be optimized to a `&'static str`.
24///
25/// ## Context Object Mode
26///
27/// When given any other expression, the macro creates a report from that value:
28///
29/// ```
30/// use rootcause::prelude::*;
31/// # use std::io;
32///
33/// # fn get_io_error() -> io::Error {
34/// #     io::Error::new(io::ErrorKind::NotFound, "file not found")
35/// # }
36/// let error: io::Error = get_io_error();
37/// let report: Report<io::Error> = report!(error);
38/// ```
39///
40/// This mode automatically:
41/// - Infers the thread-safety marker based on whether the context type is `Send
42///   + Sync`
43/// - Selects the appropriate handler based on the context type
44///
45/// This is similar to calling [`Report::new`], but with better type inference.
46///
47/// # Examples
48///
49/// ## Basic String Reports
50///
51/// ```
52/// use std::{any::TypeId, rc::Rc};
53///
54/// use rootcause::prelude::*;
55///
56/// // Static string (no formatting)
57/// let report: Report<markers::Dynamic, markers::Mutable, markers::SendSync> =
58///     report!("Something broke");
59/// assert_eq!(
60///     report.current_context_type_id(),
61///     TypeId::of::<&'static str>()
62/// );
63///
64/// // Formatted string
65/// let report: Report<markers::Dynamic, markers::Mutable, markers::SendSync> =
66///     report!("Something broke hard: {}", "it was bad");
67/// assert_eq!(report.current_context_type_id(), TypeId::of::<String>());
68/// assert_eq!(
69///     report.current_context_handler_type_id(),
70///     TypeId::of::<handlers::Display>()
71/// );
72/// ```
73///
74/// ## Error Type Reports
75///
76/// ```
77/// use std::{any::TypeId, io};
78///
79/// use rootcause::prelude::*;
80///
81/// # fn something_that_fails() -> Result<(), std::io::Error> {
82/// #    std::fs::read("/nonexistant")?; Ok(())
83/// # }
84/// let io_error: std::io::Error = something_that_fails().unwrap_err();
85/// let report: Report<std::io::Error, markers::Mutable, markers::SendSync> = report!(io_error);
86/// assert_eq!(
87///     report.current_context_handler_type_id(),
88///     TypeId::of::<handlers::Error>()
89/// );
90/// ```
91///
92/// ## Debug-Only Types
93///
94/// When using a type that implements [`Debug`](core::fmt::Debug) but not
95/// [`Display`](core::fmt::Display), the report uses [`crate::handlers::Debug`]
96/// which shows "Context of type `TypeName`" when displayed:
97///
98/// ```
99/// use std::any::TypeId;
100///
101/// use rootcause::prelude::*;
102///
103/// #[derive(Debug)]
104/// struct InternalState {
105///     value: usize,
106/// }
107///
108/// let state = InternalState { value: 42 };
109/// let report: Report<InternalState> = report!(state);
110///
111/// // Display shows a generic message with the type name
112/// let output = format!("{}", report);
113/// assert!(output.contains("InternalState"));
114/// assert!(!output.contains("value")); // Debug details not shown in Display
115///
116/// assert_eq!(
117///     report.current_context_handler_type_id(),
118///     TypeId::of::<handlers::Debug>()
119/// );
120/// ```
121///
122/// ## Local (Non-Send) Reports
123///
124/// When using non-thread-safe types like [`Rc`](std::rc::Rc), the macro
125/// automatically infers the [`Local`](crate::markers::Local) marker:
126///
127/// ```
128/// use std::{any::TypeId, rc::Rc};
129///
130/// use rootcause::prelude::*;
131///
132/// # fn something_else_that_fails() -> Result<(), Rc<std::io::Error>> {
133/// #    std::fs::read("/nonexistant")?; Ok(())
134/// # }
135/// let local_io_error: Rc<std::io::Error> = something_else_that_fails().unwrap_err();
136/// let report: Report<Rc<std::io::Error>, markers::Mutable, markers::Local> =
137///     report!(local_io_error);
138/// assert_eq!(
139///     report.current_context_handler_type_id(),
140///     TypeId::of::<handlers::Display>()
141/// );
142/// ```
143///
144/// [`format!()`]: std::format
145/// [`Report::new`]: crate::Report::new
146#[macro_export]
147macro_rules! report {
148    ($msg:literal $(,)?) => {
149        $crate::__private::format_report($crate::__private::format_args!($msg))
150    };
151    ($context:expr $(,)?) => {
152        {
153            use $crate::__private::kind::*;
154            let context = $context;
155            let handler = (&&&&Wrap(&context)).handler();
156            let thread_safety = (&context).thread_safety();
157            macro_helper_new_report(handler, thread_safety, context)
158        }
159    };
160    ($fmt:expr, $($arg:tt)*) => {
161        $crate::Report::<
162            _,
163            $crate::markers::Mutable,
164            $crate::markers::SendSync
165        >::new_custom::<$crate::handlers::Display>(
166            $crate::__private::format!($fmt, $($arg)*)
167        ).into_dynamic()
168    };
169}
170
171/// Creates a report attachment with contextual data.
172///
173/// This macro creates a [`ReportAttachment`] that can be added to error reports
174/// to provide additional context. It accepts the same arguments as the
175/// [`report!`] macro but produces an attachment instead of a full report.
176///
177/// Attachments are useful for adding supplementary information to errors
178/// without changing the main error context. For example, you might attach
179/// configuration values, request parameters, or debugging information.
180///
181/// # Usage Modes
182///
183/// Like [`report!`], this macro supports both format string mode and context
184/// object mode. See the [`report!`] documentation for details on each mode.
185///
186/// # Examples
187///
188/// ## String Attachments
189///
190/// ```
191/// use std::any::TypeId;
192///
193/// use rootcause::{prelude::*, report_attachment, report_attachment::ReportAttachment};
194///
195/// // Static string
196/// let attachment: ReportAttachment<markers::Dynamic, markers::SendSync> =
197///     report_attachment!("Additional context");
198/// assert_eq!(attachment.inner_type_id(), TypeId::of::<&'static str>());
199/// assert_eq!(
200///     attachment.inner_handler_type_id(),
201///     TypeId::of::<handlers::Display>()
202/// );
203///
204/// // Formatted string
205/// let attachment: ReportAttachment<markers::Dynamic, markers::SendSync> =
206///     report_attachment!("Error occurred at line: {}", 42);
207/// assert_eq!(attachment.inner_type_id(), TypeId::of::<String>());
208/// assert_eq!(
209///     attachment.inner_handler_type_id(),
210///     TypeId::of::<handlers::Display>()
211/// );
212/// ```
213///
214/// ## Structured Data Attachments
215///
216/// ```
217/// use std::any::TypeId;
218///
219/// use rootcause::{prelude::*, report_attachment, report_attachment::ReportAttachment};
220///
221/// #[derive(Debug)]
222/// struct ErrorData {
223///     code: i32,
224///     message: String,
225/// }
226///
227/// impl std::fmt::Display for ErrorData {
228///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229///         write!(f, "Error {}: {}", self.code, self.message)
230///     }
231/// }
232///
233/// impl std::error::Error for ErrorData {}
234///
235/// let error_data = ErrorData {
236///     code: 404,
237///     message: "Not found".to_string(),
238/// };
239/// let attachment: ReportAttachment<ErrorData, markers::SendSync> = report_attachment!(error_data);
240/// assert_eq!(
241///     attachment.inner_handler_type_id(),
242///     TypeId::of::<handlers::Display>()
243/// );
244/// ```
245///
246/// ## Local (Non-Send) Attachments
247///
248/// ```
249/// use std::rc::Rc;
250///
251/// use rootcause::{prelude::*, report_attachment, report_attachment::ReportAttachment};
252///
253/// let local_data: Rc<String> = Rc::new("Local context".to_string());
254/// let attachment: ReportAttachment<Rc<String>, markers::Local> = report_attachment!(local_data);
255/// ```
256///
257/// [`ReportAttachment`]: crate::report_attachment::ReportAttachment
258/// [`Report`]: crate::Report
259#[macro_export]
260macro_rules! report_attachment {
261    ($msg:literal $(,)?) => {
262        $crate::__private::format_report_attachment($crate::__private::format_args!($msg))
263    };
264    ($context:expr $(,)?) => {
265        {
266            use $crate::__private::kind::*;
267            let context = $context;
268            let handler = (&&&Wrap(&context)).handler();
269            let thread_safety = (&context).thread_safety();
270            macro_helper_new_report_attachment(handler, thread_safety, context)
271        }
272    };
273    ($fmt:expr, $($arg:tt)*) => {
274        $crate::report_attachment::ReportAttachment::<
275            _,
276            $crate::markers::SendSync
277        >::new_custom::<$crate::handlers::Display>(
278            $crate::__private::format!($fmt, $($arg)*)
279        ).into_dynamic()
280    };
281}
282
283/// Returns early from a function with an error report.
284///
285/// This macro creates a new [`Report`] and immediately returns it wrapped in an
286/// `Err`. It's a convenience shorthand for `return Err(report!(...).into())`.
287///
288/// The macro is similar to the [`bail!`] macro from the [`anyhow`] crate and
289/// accepts the same arguments as the [`report!`] macro.
290///
291/// # When to Use
292///
293/// Use `bail!` when you want to:
294/// - Return an error immediately without additional processing
295/// - Keep error-handling code concise and readable
296/// - Avoid writing explicit `return Err(...)` statements
297///
298/// # Examples
299///
300/// ## Basic Validation
301///
302/// ```
303/// use rootcause::prelude::*;
304///
305/// fn validate_positive(value: i32) -> Result<(), Report> {
306///     if value < 0 {
307///         bail!("Value must be non-negative, got {}", value);
308///     }
309///     Ok(())
310/// }
311///
312/// assert!(validate_positive(-5).is_err());
313/// assert!(validate_positive(10).is_ok());
314/// ```
315///
316/// ## Multiple Validation Checks
317///
318/// ```
319/// use rootcause::prelude::*;
320///
321/// fn validate_age(age: i32) -> Result<(), Report> {
322///     if age < 0 {
323///         bail!("Age cannot be negative: {}", age);
324///     }
325///     if age > 150 {
326///         bail!("Age seems unrealistic: {}", age);
327///     }
328///     Ok(())
329/// }
330/// ```
331///
332/// [`bail!`]: https://docs.rs/anyhow/latest/anyhow/macro.bail.html
333/// [`anyhow`]: https://docs.rs/anyhow/latest/anyhow/
334/// [`Report`]: crate::Report
335#[macro_export]
336macro_rules! bail {
337    ($($args:tt)*) => {
338        return $crate::__private::Err($crate::report!($($args)*).into())
339    };
340}