error2_derive/
lib.rs

1//! Derive macro for the `error2` crate.
2//!
3//! This crate provides the `#[derive(Error2)]` procedural macro for automatically
4//! implementing error types with backtrace support.
5//!
6//! # Usage
7//!
8//! ```
9//! use error2::prelude::*;
10//!
11//! #[derive(Debug, Error2)]
12//! pub enum MyError {
13//!     #[error2(display("IO error"))]
14//!     Io {
15//!         source: std::io::Error,
16//!         backtrace: Backtrace,
17//!     },
18//! }
19//! ```
20//!
21//! See the main `error2` crate documentation for complete usage information.
22
23mod error2;
24mod generics;
25mod messages;
26mod parser;
27mod types;
28
29use proc_macro::TokenStream;
30use syn::{DeriveInput, parse_macro_input};
31
32/// Derives the `Error2` trait for an error type.
33///
34/// This macro automatically implements:
35/// - `std::error::Error`
36/// - `error2::Error2`
37/// - `Display` (only if `#[error2(display(...))]` is specified)
38///
39/// It also generates helper structs (named `{Type}2` or `{Variant}2`) for type conversion.
40///
41/// # Attributes
42///
43/// ## Type-Level Attributes
44///
45/// Applied to the struct or enum definition:
46///
47/// ### `display`
48///
49/// Specifies the display format for the error message. Only applicable to structs.
50/// If omitted, no `Display` implementation is generated, allowing custom implementation.
51///
52/// ```
53/// # use error2::prelude::*;
54/// #[derive(Debug, Error2)]
55/// #[error2(display("Failed to process: {item}"))]
56/// struct ProcessError {
57///     item: String,
58///     backtrace: Backtrace,
59/// }
60/// ```
61///
62/// ### `vis`
63///
64/// Controls the visibility of generated helper structs and their fields.
65/// Default is inherited from the error type.
66///
67/// ```
68/// # use error2::prelude::*;
69/// #[derive(Debug, Error2)]
70/// #[error2(vis(pub(crate)), display("error: {code}"))]
71/// pub struct MyError {
72///     code: i32,
73///     backtrace: Backtrace,
74/// }
75/// // Generates:
76/// // pub(crate) struct MyError2<T: Into<i32>> {
77/// //     pub(crate) code: T
78/// // }
79/// ```
80///
81/// ### `module`
82///
83/// Puts generated code into a submodule with the same name as the type (in snake_case).
84///
85/// ```
86/// # use error2::prelude::*;
87/// # mod test {
88/// # use error2::prelude::*;
89/// #[derive(Debug, Error2)]
90/// #[error2(module, display("my error"))]
91/// pub struct MyError {
92///     backtrace: Backtrace,
93/// }
94///
95/// #[derive(Debug, Error2)]
96/// #[error2(module, display("read config error"))]
97/// pub struct ReadConfigError {
98///     backtrace: Backtrace,
99/// }
100///
101/// // Generates:
102/// // mod my_error { pub(super) struct MyError2; }
103/// // mod read_config_error { pub(super) struct ReadConfigError2; }
104///
105/// // Usage - helper structs are in their respective modules:
106/// # fn test() -> Result<(), MyError> {
107/// my_error::MyError2.fail()?;
108/// # Ok(())
109/// # }
110/// #
111/// # fn test2() -> Result<(), ReadConfigError> {
112/// read_config_error::ReadConfigError2.fail()?;
113/// # Ok(())
114/// # }
115/// # }
116/// ```
117///
118/// ## Variant-Level Attributes
119///
120/// Applied to enum variants:
121///
122/// ### `display`
123///
124/// Specifies the display format for this variant. If omitted, no `Display`
125/// implementation is generated for this variant.
126///
127/// ```
128/// # use error2::prelude::*;
129/// #[derive(Debug, Error2)]
130/// pub enum AppError {
131///     #[error2(display("IO error at {path}"))]
132///     Io {
133///         path: String,
134///         source: std::io::Error,
135///         backtrace: Backtrace,
136///     },
137///
138///     #[error2(display("Not found: {item}"))]
139///     NotFound { item: String, backtrace: Backtrace },
140/// }
141/// ```
142///
143/// # Generated Helper Structs
144///
145/// The macro generates helper structs for type conversion, named by appending `2`:
146///
147/// **For structs:**
148/// ```
149/// # use error2::prelude::*;
150/// # use std::fmt;
151/// #[derive(Debug, Error2)]
152/// #[error2(display("my error"))]
153/// struct MyError {
154///     backtrace: Backtrace,
155/// }
156///
157/// // Generates: struct MyError2;
158/// ```
159///
160/// **For enum variants:**
161/// ```
162/// # use error2::prelude::*;
163/// #[derive(Debug, Error2)]
164/// enum AppError {
165///     #[error2(display("file error"))]
166///     FileError {
167///         source: std::io::Error,
168///         backtrace: Backtrace,
169///     },
170/// }
171/// // Generates: struct FileError2;
172/// ```
173///
174/// These helper structs contain only the non-`source` and non-`backtrace` fields.
175/// **All fields are generic with `Into` trait bounds**, allowing automatic type conversion:
176///
177/// ```
178/// # use error2::prelude::*;
179/// #[derive(Debug, Error2)]
180/// #[error2(display("read error: {path}"))]
181/// struct ReadError {
182///     path: String,
183///     source: std::io::Error,
184///     backtrace: Backtrace,
185/// }
186/// // Generates (assuming ReadError has inherited visibility):
187/// // struct ReadError2<T: Into<String>> { path: T }
188///
189/// # fn example() -> Result<(), ReadError> {
190/// // No need to call .into() - automatic conversion:
191/// std::fs::read_to_string("file.txt").context(ReadError2 { path: "file.txt" })?; // &str -> String
192/// //
193/// # Ok(())
194/// # }
195/// ```
196///
197/// For expensive conversions, use `.with_context()` for lazy evaluation:
198///
199/// ```
200/// # use error2::prelude::*;
201/// # use std::path::Path;
202/// # #[derive(Debug, Error2)]
203/// # #[error2(display("read error: {path}"))]
204/// # struct ReadError {
205/// #     path: String,
206/// #     source: std::io::Error,
207/// #     backtrace: Backtrace,
208/// # }
209/// # fn example(path: &Path) -> Result<(), ReadError> {
210/// // Only converts on error:
211/// std::fs::read(path).with_context(|| ReadError2 {
212///     path: path.display().to_string(),
213/// })?;
214/// # Ok(())
215/// # }
216/// ```
217///
218/// # Boxing Large Source Errors
219///
220/// To avoid large `Result<T, E>` types, you can wrap the source error in a `Box` or other wrapper:
221///
222/// ```
223/// # use error2::prelude::*;
224/// use std::io;
225///
226/// #[derive(Debug, Error2)]
227/// #[error2(display("IO error: {path}"))]
228/// struct IoError {
229///     path: String,
230///     // Box the source to keep Result size small
231///     source: Box<io::Error>,
232///     backtrace: Backtrace,
233/// }
234///
235/// # fn example(path: &str) -> Result<(), IoError> {
236/// // io::Error is automatically boxed via Into trait
237/// std::fs::read_to_string(path).context(IoError2 { path })?;
238/// # Ok(())
239/// # }
240/// ```
241///
242/// This works because the helper struct implements `Into<Box<E>>` for `E`.
243/// Any wrapper type that implements `E: Into<Wrapper<E>>` can be used.
244///
245/// # Display Implementation
246///
247/// **Important:** The `Display` trait is only implemented when `display` attribute is present.
248/// This allows you to provide custom `Display` implementations:
249///
250/// ```
251/// # use error2::prelude::*;
252/// use std::fmt;
253///
254/// #[derive(Debug, Error2)]
255/// struct MyError {
256///     code: i32,
257///     backtrace: Backtrace,
258/// }
259///
260/// // Custom Display implementation (no display attribute needed)
261/// impl fmt::Display for MyError {
262///     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
263///         write!(f, "Error code: {:#x}", self.code)
264///     }
265/// }
266/// ```
267#[proc_macro_derive(Error2, attributes(error2))]
268pub fn error2(input: TokenStream) -> TokenStream {
269    let input = parse_macro_input!(input as DeriveInput);
270
271    error2::generate(input)
272        .unwrap_or_else(|e| e.to_compile_error())
273        .into()
274}