error_stack_macros2/
lib.rs

1//! [![crates.io latest version](https://img.shields.io/crates/v/error-stack-macros2?label=version&logo=rust)](https://crates.io/crates/error-stack-macros2)
2//! [![crates.io downloads](https://img.shields.io/crates/d/error-stack-macros2)](https://crates.io/crates/error-stack-macros2)
3//! [![Tests status](https://img.shields.io/github/actions/workflow/status/LuisFerLCC/error-stack-macros2/test.yml?branch=master&label=tests)](https://github.com/LuisFerLCC/error-stack-macros2/actions/workflows/test.yml)
4//! [![Contributor Covenant Code of Conduct](https://img.shields.io/badge/Contributor%20Covenant-3.0-5e0d73?logo=contributorcovenant)](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}