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
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}