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                            fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
96                                match self {
97                                    @for_variant {
98                                        Self::@variant_name @variant_pattern => {
99                                            write!(f, @format_doc_comment)
100                                        }
101                                    }
102                                }
103                            }
104                        }
105
106                        // Error impl with source() support
107                        impl ::std::error::Error for @Self {
108                            fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
109                                match self {
110                                    @for_variant {
111                                        @if_has_source_field {
112                                            Self::@variant_name @source_pattern => Some(@source_expr),
113                                        }
114                                    }
115                                    _ => None,
116                                }
117                            }
118                        }
119
120                        // From impls for #[facet(error::from)] fields
121                        @for_variant {
122                            @if_has_from_field {
123                                impl ::core::convert::From<@from_field_type> for @Self {
124                                    fn from(source: @from_field_type) -> Self {
125                                        Self::@variant_name(source)
126                                    }
127                                }
128                            }
129                        }
130                    }
131                }
132            }
133            @facet_crate { $($facet_crate)* }
134        }
135    };
136}
137
138/// Internal macro that either chains to next plugin or calls finalize
139#[doc(hidden)]
140#[macro_export]
141macro_rules! __facet_invoke_internal {
142    // No more plugins - call finalize
143    (
144        @tokens { $($tokens:tt)* }
145        @remaining { }
146        @plugins { $($plugins:tt)* }
147        @facet_crate { $($facet_crate:tt)* }
148    ) => {
149        $($facet_crate)*::__facet_finalize! {
150            @tokens { $($tokens)* }
151            @plugins { $($plugins)* }
152            @facet_crate { $($facet_crate)* }
153        }
154    };
155
156    // More plugins - chain to next
157    (
158        @tokens { $($tokens:tt)* }
159        @remaining { $next:path $(, $rest:path)* $(,)? }
160        @plugins { $($plugins:tt)* }
161        @facet_crate { $($facet_crate:tt)* }
162    ) => {
163        $next! {
164            @tokens { $($tokens)* }
165            @remaining { $($rest),* }
166            @plugins { $($plugins)* }
167            @facet_crate { $($facet_crate)* }
168        }
169    };
170}