error_forge/group_macro.rs
1/// Provides macros for grouping errors and generating automatic
2/// conversions.
3// `StdError` is referenced from generated code in the macro body.
4#[allow(unused_imports)]
5use std::error::Error as StdError;
6
7/// Macro for composing multi-error enums with automatic
8/// `From<OtherError>` conversions and full [`ForgeError`] delegation.
9///
10/// Every variant must wrap exactly one source type that itself
11/// implements [`ForgeError`]. The macro generates:
12///
13/// - the enum declaration,
14/// - `Display` and `Error` implementations that forward to the
15/// wrapped source,
16/// - `From<T>` for each wrapped type,
17/// - a [`ForgeError`] impl whose methods delegate directly to the
18/// wrapped source's `ForgeError` methods (no type-erased
19/// downcast, no fallback values).
20///
21/// # Example
22///
23/// ```
24/// use error_forge::{group, AppError};
25///
26/// // `AppError` already implements `ForgeError`, so it can be
27/// // wrapped directly. Other types you wrap with `group!` must
28/// // also implement `ForgeError` (use `define_errors!` or
29/// // `#[derive(ModError)]` to produce them).
30/// group! {
31/// #[derive(Debug)]
32/// pub enum ServiceError {
33/// App(AppError),
34/// }
35/// }
36///
37/// // `From<AppError>` is generated, so `?` works against any
38/// // function that returns an `AppError`.
39/// let _err: ServiceError = AppError::config("missing").into();
40/// ```
41///
42/// # `ForgeError` requirement
43///
44/// Each wrapped source type must implement [`ForgeError`]. If you
45/// need to compose with a type that does not (e.g. `std::io::Error`),
46/// wrap it once in a `define_errors!` enum variant or
47/// `#[derive(ModError)]` enum and then group the result.
48///
49/// This is a **breaking change from `0.9.x`**, where `group!`
50/// accepted any wrapped type but the resulting `ForgeError` impl
51/// was silently incorrect — every method returned default
52/// fallback values regardless of the wrapped type. The `1.0`
53/// rewrite removes the type-erased downcast and trades it for a
54/// trait bound the compiler can verify.
55///
56/// [`ForgeError`]: crate::error::ForgeError
57#[macro_export]
58macro_rules! group {
59 (
60 $(#[$meta:meta])*
61 $vis:vis enum $name:ident {
62 $(
63 $(#[$vmeta:meta])*
64 $variant:ident($source_type:ty)
65 ),* $(,)?
66 }
67 ) => {
68 $(#[$meta])*
69 $vis enum $name {
70 $(
71 $(#[$vmeta])*
72 $variant($source_type),
73 )*
74 }
75
76 impl ::std::fmt::Display for $name {
77 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
78 match self {
79 $(
80 Self::$variant(source) => ::std::fmt::Display::fmt(source, f),
81 )*
82 }
83 }
84 }
85
86 impl ::std::error::Error for $name {
87 fn source(&self) -> ::std::option::Option<&(dyn ::std::error::Error + 'static)> {
88 match self {
89 $(
90 Self::$variant(source) => {
91 ::std::option::Option::Some(source as &(dyn ::std::error::Error + 'static))
92 }
93 )*
94 }
95 }
96 }
97
98 $(
99 impl ::std::convert::From<$source_type> for $name {
100 fn from(source: $source_type) -> Self {
101 Self::$variant(source)
102 }
103 }
104 )*
105
106 impl $crate::error::ForgeError for $name {
107 fn kind(&self) -> &'static str {
108 match self {
109 $(
110 Self::$variant(source) => $crate::error::ForgeError::kind(source),
111 )*
112 }
113 }
114
115 fn caption(&self) -> &'static str {
116 match self {
117 $(
118 Self::$variant(source) => $crate::error::ForgeError::caption(source),
119 )*
120 }
121 }
122
123 fn is_retryable(&self) -> bool {
124 match self {
125 $(
126 Self::$variant(source) => $crate::error::ForgeError::is_retryable(source),
127 )*
128 }
129 }
130
131 fn is_fatal(&self) -> bool {
132 match self {
133 $(
134 Self::$variant(source) => $crate::error::ForgeError::is_fatal(source),
135 )*
136 }
137 }
138
139 fn status_code(&self) -> u16 {
140 match self {
141 $(
142 Self::$variant(source) => $crate::error::ForgeError::status_code(source),
143 )*
144 }
145 }
146
147 fn exit_code(&self) -> i32 {
148 match self {
149 $(
150 Self::$variant(source) => $crate::error::ForgeError::exit_code(source),
151 )*
152 }
153 }
154
155 fn user_message(&self) -> ::std::string::String {
156 match self {
157 $(
158 Self::$variant(source) => $crate::error::ForgeError::user_message(source),
159 )*
160 }
161 }
162
163 fn dev_message(&self) -> ::std::string::String {
164 match self {
165 $(
166 Self::$variant(source) => $crate::error::ForgeError::dev_message(source),
167 )*
168 }
169 }
170 }
171 };
172}