thiserror_ext_derive/
lib.rs

1#![allow(rustdoc::broken_intra_doc_links)]
2
3//! Procedural macros for `thiserror_ext`.
4
5use expand::{DeriveCtorType, DeriveNewType};
6use proc_macro::TokenStream;
7use syn::{parse_macro_input, DeriveInput};
8
9mod expand;
10mod thiserror;
11
12/// Generates constructor functions for different variants of the error type.
13///
14/// The arguments of the constructor functions can be any types that implement
15/// [`Into`] for the corresponding fields of the variant, enabling convenient
16/// construction of the error type.
17///
18/// # Example
19///
20/// ```ignore
21/// #[derive(Debug, thiserror::Error, thiserror_ext::Construct)]
22/// enum Error {
23///     #[error("unsupported feature: {0}")]
24///     UnsupportedFeature { name: String },
25///
26///     #[error("internal error: {0}")]
27///     #[construct(skip)] // to skip generating the constructor
28///     InternalError(String),
29/// }
30///
31/// // Any type that implements `Into<String>` is accepted as the argument.
32/// let _: Error = Error::unsupported_feature("foo");
33/// ```
34///
35/// # New type
36///
37/// If a new type is specified with `#[thiserror_ext(newtype(..))]`, the
38/// constructors will be implemented on the new type instead.
39///
40/// See the documentation of [`thiserror_ext::Box`] or [`thiserror_ext::Arc`]
41/// for more details.
42///
43/// [`thiserror_ext::Box`]: derive@Box
44/// [`thiserror_ext::Arc`]: derive@Arc
45#[proc_macro_derive(Construct, attributes(thiserror_ext, construct))]
46pub fn derive_construct(input: TokenStream) -> TokenStream {
47    let input = parse_macro_input!(input as DeriveInput);
48
49    expand::derive_ctor(&input, DeriveCtorType::Construct)
50        .unwrap_or_else(|err| err.to_compile_error())
51        .into()
52}
53
54/// Generates extension traits for converting the external error type into the
55/// the provided one, with extra context.
56///
57/// This can be helpful when the external error type does not provide enough
58/// context for the application. `thiserror` does not allow specifying `#[from]`
59/// on the error field if there're extra fields in the variant.
60///
61/// The extension trait is only generated when there's a field named `source`
62/// or marked with `#[source]` but not `#[from]`. The rest of the fields (except
63/// the backtrace) are treated as the context. Both single and multiple context
64/// fields are supported.
65///
66/// # Example
67///
68/// ```ignore
69/// #[derive(Debug, thiserror::Error, thiserror_ext::ContextInto)]
70/// enum Error {
71///     #[error("cannot parse int from `{from}`")]
72///     ParseInt {
73///         source: std::num::ParseIntError,
74///         from: String,
75///     },
76///
77///     #[error("cannot parse float from `{from}`")]
78///     #[context_into(skip)] // to skip generating the extension
79///     ParseFloat {
80///         source: std::num::ParseIntError,
81///         from: String,
82///     },
83/// }
84///
85/// // Specify the `from` as "foo" and convert it into `Error::ParseInt`.
86/// let _: Error = "foo".parse::<i32>().unwrap_err().into_parse_int("foo");
87///
88/// // Can also be called on `Result<T, ExternalError>`
89/// let _: Result<i32, Error> = "foo".parse().into_parse_int("foo");
90///
91/// // Call `into_*_with` with a closure to lazily evaluate the context.
92/// let _: Result<i32, Error> = "foo".parse().into_parse_int_with(|| format!("{}", 1 + 1));
93/// ```
94///
95/// # New type
96///
97/// If a new type is specified with `#[thiserror_ext(newtype(..))]`, the
98/// extensions will convert the errors into the new type instead.
99///
100/// See the documentation of [`thiserror_ext::Box`] or [`thiserror_ext::Arc`]
101/// for more details.
102///
103/// [`thiserror_ext::Box`]: derive@Box
104/// [`thiserror_ext::Arc`]: derive@Arc
105#[proc_macro_derive(ContextInto, attributes(thiserror_ext, context_into))]
106pub fn derive_context_into(input: TokenStream) -> TokenStream {
107    let input = parse_macro_input!(input as DeriveInput);
108
109    expand::derive_ctor(&input, DeriveCtorType::ContextInto)
110        .unwrap_or_else(|err| err.to_compile_error())
111        .into()
112}
113
114/// Generates macros for different variants of the error type to construct
115/// it or directly bail out.
116///
117/// # Inline formatting
118///
119/// It's common to put a message string in the error variant. With this macro,
120/// one can directly format the message in the macro call, instead of calling
121/// [`format!`].
122///
123/// To mark a field as the message to be formatted, name it `message` or mark
124/// it with `#[message]`. The message field can be any type that implements
125/// `From<String>`.
126///
127/// ## Example
128///
129/// ```ignore
130/// #[derive(Debug, thiserror::Error, thiserror_ext::Macro)]
131/// enum Error {
132///     #[error("internal error: {msg}")]
133///     Internal { #[message] msg: Box<str> },
134/// }
135///
136/// // Equivalent to `Error::Internal { msg: format!(..).into() }`.
137/// let _: Error = internal!("{} is a bad number", 42);
138///
139/// // Equivalent to `return Err(Error::Internal { msg: format!(..).into() }.into())`.
140/// bail_internal!("{} is a bad number", 42);
141/// ```
142///
143/// # Extra fields
144///
145/// If there're extra fields along with the message field, one can specify
146/// the values of them with `field = value` syntax before the message. The
147/// values can be any types that implement [`Into`] for the corresponding
148/// fields.
149///
150/// Fields can be omitted, in which case [`Default::default()`] will be used.
151///
152/// ## Example
153///
154/// ```ignore
155/// #[derive(Debug, thiserror::Error, thiserror_ext::Macro)]
156/// #[error("not yet implemented: {message}")]
157/// struct NotYetImplemented {
158///     issue: Option<i32>,
159///     pr: Option<i32>,
160///     message: String,
161/// }
162///
163/// let _: Error = not_yet_implemented!(issue = 42, pr = 88, "foo");
164/// let _: Error = not_yet_implemented!(issue = 42, "foo"); // pr = None
165/// let _: Error = not_yet_implemented!(pr = 88, "foo");    // issue = None
166/// let _: Error = not_yet_implemented!("foo");             // issue = None, pr = None
167/// ```
168///
169/// # Visibility
170///
171/// There's a different rule set for the visibility of the macros. The macros
172/// generated by this proc-macro are marked with `#[macro_export]` only if the
173/// visibility of the error type is `pub`, otherwise they're just re-exported
174/// with the same visibility as the error type and only work in the same crate.
175///
176/// There're some extra configurations to help to better handle the visibility,
177/// specified in `#[thiserror_ext(macro(..))]`:
178///
179/// - `vis = ..`: use a different visibility for the macro re-export.
180/// - `mangle`: mangle the macro names so that they don't conflict with other
181///   macros with the same name in the crate root.
182/// - `path = "crate::.."`: the path to the current module. When specified,
183///   types in the generated macros will use the qualified path like
184///   `$crate::foo::bar::Error`, enabling the callers to use the macros without
185///   importing the error type.
186///
187/// # New type
188///
189/// If a new type is specified with `#[thiserror_ext(newtype(..))]`, the macros
190/// will generate the new type instead.
191///
192/// See the documentation of [`thiserror_ext::Box`] or [`thiserror_ext::Arc`]
193/// for more details.
194///
195/// [`thiserror_ext::Box`]: derive@Box
196/// [`thiserror_ext::Arc`]: derive@Arc
197#[proc_macro_derive(Macro, attributes(thiserror_ext, message))]
198pub fn derive_macro(input: TokenStream) -> TokenStream {
199    let input = parse_macro_input!(input as DeriveInput);
200
201    expand::derive_macro(&input)
202        .unwrap_or_else(|err| err.to_compile_error())
203        .into()
204}
205
206/// Generates a new type that wraps the original error type in a [`struct@Box`].
207///
208/// Specify the name of the new type with `#[thiserror_ext(newtype(name = ..))]`.
209///
210/// # Reduce size
211///
212/// The most common motivation for using this macro is to reduce the size of
213/// the original error type. As a sum-type, a [`Result`] is at least as large
214/// as its largest variant. Large error type may hurt the performance of a
215/// function call returning a [`Result`]. With this macro, the new type always
216/// has the same size as a [`struct@Box`].
217///
218/// On the other hand, returning an error should be an exceptional case in most
219/// cases. Therefore, even though boxing the error type may lead to extra
220/// allocation, it's usually acceptable.
221///
222/// ## Example
223///
224/// ```ignore
225/// #[derive(Debug, thiserror::Error, thiserror_ext::Box)]
226/// #[thiserror_ext(newtype(name = Error))]
227/// enum ErrorKind {
228///     #[error("foo")]
229///     Foo,
230///     #[error("io")]
231///     Io(#[from] std::io::Error),
232/// }
233///
234/// // The size of `Error` is one pointer.
235/// assert_eq!(std::mem::size_of::<Error>(), std::mem::size_of::<usize>());
236///
237/// // Convert to `Error`, from `ErrorKind` or other types that can be converted
238/// // to `ErrorKind`.
239/// let error: Error = ErrorKind::Foo.into();
240/// let error: Error = io_error().into();
241///
242/// // Get the reference or the value of the inner error.
243/// let _: &ErrorKind = error.inner();
244/// let _: ErrorKind = error.into_inner();
245/// ```
246///
247/// # Backtrace
248///
249/// Another use case is to capture backtrace when the error is created. Without
250/// a new type, one has to manually add a [`Backtrace`] field to each variant
251/// of the error type. The new type allows one to capture backtrace in a single
252/// place.
253///
254/// Specify `#[thiserror_ext(newtype(.., backtrace))]` to enable capturing
255/// backtrace. The extra backtrace is captured **only if** the original error
256/// type does not [`provide`] one. Typically, this should be maintained by the
257/// `#[backtrace]` attribute from `thiserror`.
258///
259/// ## Example
260///
261/// ```ignore
262/// # use std::backtrace::Backtrace;
263/// #[derive(Debug, thiserror::Error, thiserror_ext::Box)]
264/// #[thiserror_ext(newtype(name = Error, backtrace))]
265/// enum ErrorKind {
266///     #[error("foo")]
267///     Foo,
268/// }
269///
270/// let error: Error = ErrorKind::Foo.into();
271/// let backtrace: &Backtrace = std::error::request_ref(&error).unwrap();
272/// ```
273///
274/// [`Backtrace`]: std::backtrace::Backtrace
275/// [`provide`]: std::error::Error::provide
276#[proc_macro_derive(Box, attributes(thiserror_ext))]
277pub fn derive_box(input: TokenStream) -> TokenStream {
278    let input = parse_macro_input!(input as DeriveInput);
279
280    expand::derive_new_type(&input, DeriveNewType::Box)
281        .unwrap_or_else(|err| err.to_compile_error())
282        .into()
283}
284
285/// Generates a new type that wraps the original error type in an [`Arc`].
286///
287/// Specify the name of the new type with `#[thiserror_ext(newtype(name = ..))]`.
288///
289/// This is similar to [`thiserror_ext::Box`] but wraps the original error type
290/// in an [`Arc`], so that it can always be cloned and shared across threads.
291/// See [`thiserror_ext::Box`] for the explanation and examples.
292///
293/// [`Arc`]: std::sync::Arc
294/// [`thiserror_ext::Box`]: derive@Box
295#[proc_macro_derive(Arc, attributes(thiserror_ext))]
296pub fn derive_arc(input: TokenStream) -> TokenStream {
297    let input = parse_macro_input!(input as DeriveInput);
298
299    expand::derive_new_type(&input, DeriveNewType::Arc)
300        .unwrap_or_else(|err| err.to_compile_error())
301        .into()
302}
303
304/// Generates the [`Debug`] implementation that delegates to the [`Report`] of
305/// an error.
306///
307/// Generally, the [`Debug`] representation of an error should not be used in
308/// user-facing scenarios. However, if [`Result::unwrap`] or [`Result::expect`]
309/// is called, or an error is used as [`Termination`], the standard library
310/// will format the error with [`Debug`]. By delegating to [`Report`], we ensure
311/// that the error is still formatted in a user-friendly way and the source
312/// chain can be kept in these cases.
313///
314/// # Example
315/// ```ignore
316/// #[derive(thiserror::Error, thiserror_ext::ReportDebug)]
317/// #[error("inner")]
318/// struct Inner;
319///
320/// #[derive(thiserror::Error, thiserror_ext::ReportDebug)]
321/// #[error("outer")]
322/// struct Outer {
323///     #[source]
324///     inner: Inner,
325/// }
326///
327/// let error = Outer { inner: Inner };
328/// println!("{:?}", error);
329/// ```
330///
331/// [`Report`]: thiserror_ext::Report
332/// [`Termination`]: std::process::Termination
333///
334/// # New type
335///
336/// Since the new type delegates its [`Debug`] implementation to the original
337/// error type, if the original error type derives [`ReportDebug`], the new type
338/// will also behave the same.
339#[proc_macro_derive(ReportDebug)]
340pub fn derive_report_debug(input: TokenStream) -> TokenStream {
341    let input = parse_macro_input!(input as DeriveInput);
342
343    expand::derive_report_debug(&input)
344        .unwrap_or_else(|err| err.to_compile_error())
345        .into()
346}