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}