error_stack_macros2/lib.rs
1//! [](https://crates.io/crates/error-stack-macros2)
2//! [](https://crates.io/crates/error-stack-macros2)
3//! [](https://github.com/LuisFerLCC/error-stack-macros2/actions/workflows/test.yml)
4//! [](https://github.com/LuisFerLCC/error-stack-macros2/blob/master/.github/CODE_OF_CONDUCT.md)
5//!
6//! Community-made procedural macros for [`error-stack`].
7//!
8//! # Example
9//!
10//! Here is the same example shown in the [`error-stack`] `README`, modified to
11//! use the macros provided by this crate:
12//!
13//! ```rust
14//! use error_stack::{Report, ResultExt};
15//! use error_stack_macros2::Error;
16//!
17//! #[derive(Debug, Error)]
18//! #[display("invalid experiment description")]
19//! struct ParseExperimentError;
20//!
21//! fn parse_experiment(
22//! description: &str
23//! ) -> Result<(u64, u64), Report<ParseExperimentError>> {
24//! let value = description
25//! .parse::<u64>()
26//! .attach_with(|| {
27//! format!("{description:?} could not be parsed as experiment")
28//! })
29//! .change_context(ParseExperimentError)?;
30//!
31//! Ok((value, 2 * value))
32//! }
33//!
34//! #[derive(Debug, Error)]
35//! #[display("experiment error: could not run experiment")]
36//! struct ExperimentError;
37//!
38//! fn start_experiments(
39//! experiment_ids: &[usize],
40//! experiment_descriptions: &[&str],
41//! ) -> Result<Vec<u64>, Report<ExperimentError>> {
42//! let experiments = experiment_ids
43//! .iter()
44//! .map(|exp_id| {
45//! let description = experiment_descriptions
46//! .get(*exp_id)
47//! .ok_or_else(|| {
48//! Report::new(ExperimentError)
49//! .attach(format!(
50//! "experiment {exp_id} has no valid description")
51//! )
52//! })?;
53//!
54//! let experiment = parse_experiment(description)
55//! .attach(format!("experiment {exp_id} could not be parsed"))
56//! .change_context(ExperimentError)?;
57//!
58//! Ok(move || experiment.0 * experiment.1)
59//! })
60//! .collect::<Result<Vec<_>, Report<ExperimentError>>>()
61//! .attach("unable to set up experiments")?;
62//!
63//! Ok(experiments.iter().map(|experiment| experiment()).collect())
64//! }
65//!
66//! let experiment_ids = &[0, 2];
67//! let experiment_descriptions = &["10", "20", "3o"];
68//! let err = start_experiments(experiment_ids, experiment_descriptions)
69//! .unwrap_err();
70//!
71//! assert_eq!(err.to_string(), "experiment error: could not run experiment");
72//! ```
73//!
74//! # Support
75//!
76//! Need help using `error-stack-macros2`? Don't hesitate to reach out on
77//! [GitHub Discussions](https://github.com/LuisFerLCC/error-stack-macros2/discussions/categories/q-a)!
78//!
79//! # Links
80//!
81//! - [Documentation]
82//! - [GitHub](https://github.com/LuisFerLCC/error-stack-macros2)
83//! - [crates.io](https://crates.io/crates/error-stack-macros2)
84//!
85//! # Contributing
86//!
87//! Before creating an issue, please consider the following:
88//!
89//! - Refer to the [documentation] to
90//! make sure the error is actually a bug and not a mistake of your own.
91//! - Make sure the issue hasn't already been reported or suggested.
92//! - Please report any security vulnerabilities privately through
93//! [Security Advisories](https://github.com/LuisFerLCC/error-stack-macros2/security/advisories/new).
94//! - After following these steps, you can file an issue using one of our
95//! [templates](https://github.com/LuisFerLCC/error-stack-macros2/issues/new/choose).
96//! Please make sure to follow our
97//! [Code of Conduct](https://github.com/LuisFerLCC/error-stack-macros2/blob/master/.github/CODE_OF_CONDUCT.md).
98//! - If you wish to [submit a pull request](https://github.com/LuisFerLCC/error-stack-macros2/compare)
99//! alongside your issue, please follow our
100//! [contribution guidelines](https://github.com/LuisFerLCC/error-stack-macros2/blob/master/.github/CONTRIBUTING.md).
101//!
102//! # Disclaimer
103//!
104//! This crate is not affiliated with the official [`error-stack`] crate or its
105//! maintainers.
106//!
107//! [`error-stack`]: https://crates.io/crates/error-stack
108//! [documentation]: https://docs.rs/error-stack-macros2
109
110use proc_macro::TokenStream;
111use quote::quote;
112use syn::parse_macro_input;
113
114mod types;
115use types::ErrorStackDeriveInput;
116
117mod util;
118
119/// Derive macro for the [`Error`] trait that implements the best practices for
120/// [`error-stack`].
121///
122/// # Overview
123/// This derive macro allows you to automatically implement the required
124/// [`Display`] and [`Error`] traits for custom types that you want to use as
125/// context types in [`error-stack`] [`Report`]s without all the boilerplate.
126///
127/// The macro has a `display` attribute, which specifies a formatting string to
128/// print a value of the given type or enum variant.
129///
130/// # Examples
131///
132/// ## Unit struct (recommended)
133///
134/// ```
135/// use error_stack_macros2::Error;
136///
137/// #[derive(Debug, Error)]
138/// #[display("invalid card string")]
139/// struct ParseCardError;
140/// ```
141///
142/// ## Enum
143///
144/// ```
145/// use error_stack_macros2::Error;
146///
147/// #[derive(Debug, Error)]
148/// #[display("credit card error")] // optional default
149/// enum CreditCardError {
150/// #[display("credit card not found")]
151/// InvalidInput(String),
152///
153/// #[display("failed to retrieve credit card")]
154/// Other,
155/// }
156/// ```
157///
158/// ## Field interpolation (discouraged)
159///
160/// ```
161/// use error_stack_macros2::Error;
162///
163/// #[derive(Debug, Error)]
164/// #[display("invalid card string: {0:?}")]
165/// struct ParseCardError(String);
166///
167/// let err = ParseCardError("1234567".to_string());
168/// assert_eq!(err.to_string(), "invalid card string: \"1234567\"");
169/// ```
170///
171/// # This may look familiar...
172///
173/// This derive macro is heavily inspired by the popular [`thiserror`] crate. In
174/// fact, you **can** use the [`thiserror`] crate to derive the same traits for
175/// your types. However, [`error-stack`] is very opinionated about how context
176/// types should be designed and used, and this derive macro enforces those
177/// best practices, whereas [`thiserror`] is more flexible and designed for
178/// general use cases.
179///
180/// Also, due to this macro's more simple and restricted design, it can
181/// potentially be more efficient than [`thiserror`] in terms of compile time
182/// and generated code size.
183///
184/// [`Error`]: core::error::Error
185/// [`error-stack`]: https://crates.io/crates/error-stack
186/// [`Report`]: https://docs.rs/error-stack/latest/error_stack/struct.Report.html
187/// [`Display`]: core::fmt::Display
188/// [`thiserror`]: https://crates.io/crates/thiserror
189#[proc_macro_derive(Error, attributes(display))]
190pub fn impl_error_stack(input: TokenStream) -> TokenStream {
191 let derive_input = parse_macro_input!(input as ErrorStackDeriveInput);
192 quote! { #derive_input }.into()
193}