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}