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}