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
117/// Derive macro for the [`Error`] trait that implements the best practices for
118/// [`error-stack`].
119///
120/// # Overview
121/// This derive macro allows you to automatically implement the required
122/// [`Display`] and [`Error`] traits for custom types that you want to use as
123/// context types in [`error-stack`] [`Report`]s without all the boilerplate.
124///
125/// The macro has a `display` attribute, which specifies a formatting string to
126/// print a value of the given type or enum variant.
127///
128/// # Examples
129///
130/// ## Unit struct (recommended)
131///
132/// ```
133/// use error_stack_macros2::Error;
134///
135/// #[derive(Debug, Error)]
136/// #[display("invalid card string")]
137/// struct ParseCardError;
138/// ```
139///
140/// ## Enum
141///
142/// ```
143/// use error_stack_macros2::Error;
144///
145/// #[derive(Debug, Error)]
146/// #[display("credit card error")] // optional default
147/// enum CreditCardError {
148/// #[display("credit card not found")]
149/// InvalidInput(String),
150///
151/// #[display("failed to retrieve credit card")]
152/// Other,
153/// }
154/// ```
155///
156/// ## Field interpolation (discouraged)
157///
158/// ```
159/// use error_stack_macros2::Error;
160///
161/// #[derive(Debug, Error)]
162/// #[display("invalid card string: {0:?}")]
163/// struct ParseCardError(String);
164///
165/// let err = ParseCardError("1234567".to_string());
166/// assert_eq!(err.to_string(), "invalid card string: \"1234567\"");
167/// ```
168///
169/// # This may look familiar...
170///
171/// This derive macro is heavily inspired by the popular [`thiserror`] crate. In
172/// fact, you **can** use the [`thiserror`] crate to derive the same traits for
173/// your types. However, [`error-stack`] is very opinionated about how context
174/// types should be designed and used, and this derive macro enforces those
175/// best practices, whereas [`thiserror`] is more flexible and designed for
176/// general use cases.
177///
178/// Also, due to this macro's more simple and restricted design, it can
179/// potentially be more efficient than [`thiserror`] in terms of compile time
180/// and generated code size.
181///
182/// [`Error`]: core::error::Error
183/// [`error-stack`]: https://crates.io/crates/error-stack
184/// [`Report`]: https://docs.rs/error-stack/latest/error_stack/struct.Report.html
185/// [`Display`]: core::fmt::Display
186/// [`thiserror`]: https://crates.io/crates/thiserror
187#[proc_macro_derive(Error, attributes(display))]
188pub fn impl_error_stack(input: TokenStream) -> TokenStream {
189 let derive_input = parse_macro_input!(input as ErrorStackDeriveInput);
190 quote! { #derive_input }.into()
191}