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    pub enum Attr {
47        /// Marks a field as the error source and generates a `From` impl.
48        ///
49        /// Usage: `#[facet(error::from)]`
50        ///
51        /// This attribute:
52        /// - Marks the field as the source in `Error::source()`
53        /// - Generates a `From<FieldType> for ErrorType` implementation
54        From,
55
56        /// Marks a field as the error source without generating a `From` impl.
57        ///
58        /// Usage: `#[facet(error::source)]`
59        ///
60        /// This attribute only marks the field as the source in `Error::source()`.
61        Source,
62    }
63}
64
65// ============================================================================
66// PLUGIN TEMPLATE
67// ============================================================================
68
69/// Plugin chain entry point.
70///
71/// Called by `#[derive(Facet)]` when `#[facet(derive(Error))]` is present.
72/// Adds the Error plugin template to the chain and forwards to the next plugin or finalize.
73#[macro_export]
74macro_rules! __facet_invoke {
75    (
76        @tokens { $($tokens:tt)* }
77        @remaining { $($remaining:tt)* }
78        @plugins { $($plugins:tt)* }
79        @facet_crate { $($facet_crate:tt)* }
80    ) => {
81        // Forward with our template added to plugins
82        $crate::__facet_invoke_internal! {
83            @tokens { $($tokens)* }
84            @remaining { $($remaining)* }
85            @plugins {
86                $($plugins)*
87                @plugin {
88                    @name { "Error" }
89                    @template {
90                        // Template using @ directives for code generation
91                        // This will be evaluated by __facet_finalize!
92
93                        // Display impl - use doc comments as format strings
94                        impl ::core::fmt::Display for @Self {
95                            #[allow(unused_variables)] // not all fields used in format strings
96                            fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
97                                match self {
98                                    @for_variant {
99                                        Self::@variant_name @variant_pattern => {
100                                            write!(f, @format_doc_comment)
101                                        }
102                                    }
103                                }
104                            }
105                        }
106
107                        // Error impl with source() support
108                        impl ::std::error::Error for @Self {
109                            fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
110                                match self {
111                                    @for_variant {
112                                        @if_field_attr(error::source) {
113                                            Self::@variant_name @variant_pattern_only(error::source) => {
114                                                Some(@field_expr)
115                                            }
116                                        }
117                                        @if_field_attr(error::from) {
118                                            Self::@variant_name @variant_pattern_only(error::from) => {
119                                                Some(@field_expr)
120                                            }
121                                        }
122                                    }
123                                    _ => None,
124                                }
125                            }
126                        }
127
128                        // From impls for #[facet(error::from)] fields
129                        @for_variant {
130                            @if_any_field_attr(error::from) {
131                                @for_field {
132                                    @if_attr(error::from) {
133                                        impl ::core::convert::From<@field_type> for @Self {
134                                            fn from(source: @field_type) -> Self {
135                                                @if_struct_variant {
136                                                    Self::@variant_name {
137                                                        @field_name: source,
138                                                        @for_field {
139                                                            @if_attr(default) {
140                                                                @field_name: @attr_args,
141                                                            }
142                                                        }
143                                                    }
144                                                }
145                                                @if_tuple_variant {
146                                                    Self::@variant_name(source)
147                                                }
148                                            }
149                                        }
150                                    }
151                                }
152                            }
153                        }
154                    }
155                }
156            }
157            @facet_crate { $($facet_crate)* }
158        }
159    };
160}
161
162/// Internal macro that either chains to next plugin or calls finalize
163#[doc(hidden)]
164#[macro_export]
165macro_rules! __facet_invoke_internal {
166    // No more plugins - call finalize
167    (
168        @tokens { $($tokens:tt)* }
169        @remaining { }
170        @plugins { $($plugins:tt)* }
171        @facet_crate { $($facet_crate:tt)* }
172    ) => {
173        $($facet_crate)*::__facet_finalize! {
174            @tokens { $($tokens)* }
175            @plugins { $($plugins)* }
176            @facet_crate { $($facet_crate)* }
177        }
178    };
179
180    // More plugins - chain to next
181    (
182        @tokens { $($tokens:tt)* }
183        @remaining { $next:path $(, $rest:path)* $(,)? }
184        @plugins { $($plugins:tt)* }
185        @facet_crate { $($facet_crate:tt)* }
186    ) => {
187        $next! {
188            @tokens { $($tokens)* }
189            @remaining { $($rest),* }
190            @plugins { $($plugins)* }
191            @facet_crate { $($facet_crate)* }
192        }
193    };
194}