Skip to main content

facet_error/
lib.rs

1//! # facet-error
2//!
3//! A `thiserror` replacement powered by facet reflection.
4//!
5//! ## Usage
6//!
7//! ```ignore
8//! use facet::Facet;
9//! use facet_error as error;
10//!
11//! #[derive(Facet, Debug)]
12//! #[facet(derive(Error))]
13//! pub enum MyError {
14//!     /// data store disconnected
15//!     #[facet(error::from)]
16//!     Disconnect(std::io::Error),
17//!
18//!     /// invalid header (expected {expected}, found {found})
19//!     InvalidHeader { expected: String, found: String },
20//!
21//!     /// unknown error
22//!     Unknown,
23//! }
24//! ```
25//!
26//! This generates:
27//! - `impl Display for MyError` using doc comments as format strings
28//! - `impl Error for MyError` with proper `source()` implementation
29//! - `impl From<std::io::Error> for MyError` for variants with `#[facet(error::from)]`
30
31// ============================================================================
32// ATTRIBUTE GRAMMAR
33// ============================================================================
34
35// Error extension attributes for use with #[facet(error::attr)] syntax.
36//
37// After importing `use facet_error as error;`, users can write:
38//   #[facet(error::from)]
39//   #[facet(error::source)]
40
41facet::define_attr_grammar! {
42    ns "error";
43    crate_path ::facet_error;
44
45    /// Error attribute types for field configuration.
46    #[non_exhaustive]
47    pub enum Attr {
48        /// Marks a field as the error source and generates a `From` impl.
49        ///
50        /// Usage: `#[facet(error::from)]`
51        ///
52        /// This attribute:
53        /// - Marks the field as the source in `Error::source()`
54        /// - Generates a `From<FieldType> for ErrorType` implementation
55        From,
56
57        /// Marks a field as the error source without generating a `From` impl.
58        ///
59        /// Usage: `#[facet(error::source)]`
60        ///
61        /// This attribute only marks the field as the source in `Error::source()`.
62        Source,
63    }
64}
65
66// ============================================================================
67// PLUGIN TEMPLATE
68// ============================================================================
69
70/// Plugin chain entry point.
71///
72/// Called by `#[derive(Facet)]` when `#[facet(derive(Error))]` is present.
73/// Adds the Error plugin template to the chain and forwards to the next plugin or finalize.
74#[macro_export]
75macro_rules! __facet_invoke {
76    (
77        @tokens { $($tokens:tt)* }
78        @remaining { $($remaining:tt)* }
79        @plugins { $($plugins:tt)* }
80        @facet_crate { $($facet_crate:tt)* }
81    ) => {
82        // Forward with our template added to plugins
83        $crate::__facet_invoke_internal! {
84            @tokens { $($tokens)* }
85            @remaining { $($remaining)* }
86            @plugins {
87                $($plugins)*
88                @plugin {
89                    @name { "Error" }
90                    @template {
91                        // Template using @ directives for code generation
92                        // This will be evaluated by __facet_finalize!
93
94                        // Display impl - use doc comments as format strings
95                        impl ::core::fmt::Display for @Self {
96                            #[allow(unused_assignments, unused_variables)] // not all fields used in format strings
97                            fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
98                                match self {
99                                    @for_variant {
100                                        Self::@variant_name @variant_pattern => {
101                                            write!(f, @format_doc_comment)
102                                        }
103                                    }
104                                }
105                            }
106                        }
107
108                        // Error impl with source() support
109                        impl ::std::error::Error for @Self {
110                            fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
111                                match self {
112                                    @for_variant {
113                                        @if_field_attr(error::source) {
114                                            Self::@variant_name @variant_pattern_only(error::source) => {
115                                                Some(@field_expr)
116                                            }
117                                        }
118                                        @if_field_attr(error::from) {
119                                            Self::@variant_name @variant_pattern_only(error::from) => {
120                                                Some(@field_expr)
121                                            }
122                                        }
123                                    }
124                                    _ => None,
125                                }
126                            }
127                        }
128
129                        // From impls for #[facet(error::from)] fields
130                        @for_variant {
131                            @if_any_field_attr(error::from) {
132                                @for_field {
133                                    @if_attr(error::from) {
134                                        impl ::core::convert::From<@field_type> for @Self {
135                                            fn from(source: @field_type) -> Self {
136                                                @if_struct_variant {
137                                                    Self::@variant_name {
138                                                        @field_name: source,
139                                                        @for_field {
140                                                            @if_attr(default) {
141                                                                @field_name: @attr_args,
142                                                            }
143                                                        }
144                                                    }
145                                                }
146                                                @if_tuple_variant {
147                                                    Self::@variant_name(source)
148                                                }
149                                            }
150                                        }
151                                    }
152                                }
153                            }
154                        }
155                    }
156                }
157            }
158            @facet_crate { $($facet_crate)* }
159        }
160    };
161}
162
163/// Internal macro that either chains to next plugin or calls finalize
164#[doc(hidden)]
165#[macro_export]
166macro_rules! __facet_invoke_internal {
167    // No more plugins - call finalize
168    (
169        @tokens { $($tokens:tt)* }
170        @remaining { }
171        @plugins { $($plugins:tt)* }
172        @facet_crate { $($facet_crate:tt)* }
173    ) => {
174        $($facet_crate)*::__facet_finalize! {
175            @tokens { $($tokens)* }
176            @plugins { $($plugins)* }
177            @facet_crate { $($facet_crate)* }
178        }
179    };
180
181    // More plugins - chain to next
182    (
183        @tokens { $($tokens:tt)* }
184        @remaining { $next:path $(, $rest:path)* $(,)? }
185        @plugins { $($plugins:tt)* }
186        @facet_crate { $($facet_crate:tt)* }
187    ) => {
188        $next! {
189            @tokens { $($tokens)* }
190            @remaining { $($rest),* }
191            @plugins { $($plugins)* }
192            @facet_crate { $($facet_crate)* }
193        }
194    };
195}