devela/code/util/
error.rs

1// devela::code::util::error
2//
3//! Defines [`impl_error!`]
4//
5// MAYBE
6// - add handy constructors (tuples, fields...)
7// - add better docs for arguments.
8
9/// Helper to define individual and composite error types.
10///
11/// It also helps implementing `From` and `TryFrom` between them.
12//
13// NOTES:
14// - alternative sections for tuple-struct and field-struct variants are indicated in the margin.
15// - we are employing the trick `$(;$($_a:lifetime)?` for the optional semicolon terminator,
16//   where the never expected lifetime allows to refer to the non-identifier `;` later on.
17macro_rules! impl_error {
18    (
19    // Defines a standalone error tuple-struct with elements.
20    //
21    // # Args
22    // $attributes:   attributes for the const & struct declarations, and its implementations.
23    // $struct_name:  the name of the individual error struct.
24    // $struct_vis:   its visibility.
25    //
26    //  $e_vis
27    //  $e_ty:
28    //
29    //  $f_attr
30    //  $f_vis
31    //  $f_name
32    //  $f_ty
33    //
34    // $DOC_NAME
35    // $doc_str
36    //
37    individual:
38        $(#[$attributes:meta])*
39        $struct_vis:vis struct $struct_name:ident
40        $(( $($e_vis:vis $e_ty:ty),+ $(,)? ))? $(;$($_a:lifetime)?)?              // tuple-struct↓
41        $({ $($(#[$f_attr:meta])* $f_vis:vis $f_name:ident: $f_ty:ty),+ $(,)? })? // field-struct↑
42        $DOC_NAME:ident = $doc_str:literal,
43        $self:ident + $fmt:ident => $display_expr:expr
44        $(,)?
45    ) => {
46        $(#[$attributes])*
47        $crate::CONST! { pub(crate) $DOC_NAME = $doc_str; }
48
49        $(#[$attributes])*
50        #[doc = crate::TAG_ERROR!()]
51        #[doc = $DOC_NAME!()]
52        #[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
53        $struct_vis struct $struct_name
54            $(( $($e_vis $e_ty),+ ) )? $(; $($_a)?)?                              // tuple-struct↓
55        $({ $( $(#[$f_attr])* $f_vis $f_name: $f_ty),+ })?                        // field-struct↑
56
57        $(#[$attributes])*
58        impl $crate::Display for $struct_name {
59            fn fmt(&$self, $fmt: &mut $crate::Formatter<'_>) -> $crate::FmtResult<()> {
60                $display_expr
61            }
62        }
63        $(#[$attributes])*
64        impl $crate::Error for $struct_name {}
65        $(#[$attributes])*
66        impl $crate::ExtError for $struct_name {
67            type Kind = ();
68            fn error_eq(&self, other: &Self) -> bool { self == other }
69            fn error_kind(&self) -> Self::Kind {}
70        }
71    };
72    (
73    // Defines a composite Error enum, as well as:
74    // - implements Error, ExtError and Display.
75    // - implements From and TryFrom in reverse.
76    composite: fmt($fmt:ident)
77        $(#[$enum_attr:meta])*
78        $vis:vis enum $composite_error_name:ident { $(
79            $(#[$variant_attr:meta])*
80            $DOC_VARIANT:ident:
81            $variant_name:ident
82                $(( $($e_name:ident| $e_numb:literal: $e_ty:ty),+ ))?             // tuple-struct↓
83                $({ $($(#[$f_attr:meta])* $f_name:ident: $f_ty:ty),+ })?          // field-struct↑
84                => $individual_error_name:ident
85                    $(( $($e_display_expr:expr),+ ))?                             // tuple-struct↓
86                    $({ $($f_display_name:ident: $f_display_exp:expr),+ })?       // field-struct↑
87        ),+ $(,)? }
88    ) => {
89        #[doc = crate::TAG_ERROR_COMPOSITE!()]
90        $(#[$enum_attr])*
91        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
92        $vis enum $composite_error_name { $(
93            $(#[$variant_attr])*
94            #[doc = $DOC_VARIANT!()]
95            $variant_name
96                $(( $($e_ty),+ ))?                                                // tuple-struct↓
97                $({ $($(#[$f_attr])* $f_name: $f_ty),+ })?                        // field-struct↑
98        ),+ }
99
100        // implements Error, ExtError & Display:
101        impl $crate::Error for $composite_error_name {}
102        impl $crate::ExtError for $composite_error_name {
103            type Kind = ();
104            fn error_eq(&self, other: &Self) -> bool { self == other }
105            fn error_kind(&self) -> Self::Kind {}
106        }
107        impl $crate::Display for $composite_error_name  {
108            fn fmt(&self, $fmt: &mut $crate::Formatter<'_>) -> $crate::FmtResult<()> {
109                match self { $(
110                    $(#[$variant_attr])*
111                    $composite_error_name::$variant_name
112                    $(( $($e_name),+ ))?                                          // tuple-struct↓
113                    $({ $($f_name),+ })?                                          // field-struct↑
114                    =>
115                        $crate::Display::fmt(&$individual_error_name
116                            $(( $($e_display_expr),+ ))?                          // tuple-struct↓
117                            $({ $($f_display_name: $f_display_exp),+})?           // field-struct↑
118                        , $fmt),
119                )+ }
120            }
121        }
122        // implements From multiple individual errors for a composite error,
123        // and implements TryFrom in reverse:
124        $(
125            $(#[$variant_attr])*
126            $crate::impl_error! { from(_f): $individual_error_name, for: $composite_error_name
127                => $variant_name
128                $(( $($e_name, $crate::field_of![_f, $e_numb] ),+ ))?             // tuple-struct↓
129                $({ $($f_name, $crate::field_of![_f, $f_name] ),+ })?             // field-struct↑
130            }
131        )+
132    };
133    (
134    // Implements `From` an individual error type to a composite error containing it,
135    // as well as implementing `TryFrom` in reverse.
136    from($fn_arg:ident): $from_individual:ident, for: $for_composite:ident
137    => $variant_name:ident
138        $(( $($e_name:ident, $e_expr:expr),+ ))?                                  // tuple-struct↓
139        $({ $($f_name:ident,  $f_expr:expr),+ })?                                 // field-struct↑
140    $(,)? ) => {
141        $crate::paste! {
142            impl $for_composite {
143                #[doc = "*const* version of `From<" $from_individual "> for " $for_composite "`."]
144                #[allow(dead_code, reason = "seldomly used")]
145                // EXPERIMENTAL:IMPROVE make sure to only make it const if the type allows it
146                pub const fn [<from_ $from_individual:snake:lower>]($fn_arg: $from_individual)
147                -> $for_composite {
148                    $for_composite::$variant_name
149                        $(( $($e_expr),+ ))?                                      // tuple-struct↓
150                        $({ $($f_name: $f_expr),+ })?                             // field-struct↑
151                }
152            }
153        }
154
155        /* (example)
156        impl From<NotEnoughElements> for DataNotEnough {
157            fn from (_f: NotEnoughElements) -> DataNotEnough {
158                DataNotEnough::$Elements(_f.0)
159            }
160        } */
161        impl From<$from_individual> for $for_composite {
162            fn from($fn_arg: $from_individual) -> $for_composite {
163                $for_composite::$variant_name
164                    $(( $($e_expr),+ ))?                                          // tuple-struct↓
165                    $({ $($f_name: $f_expr),+ })?                                 // field-struct↑
166            }
167        }
168        /* (example)
169        impl TryFrom<DataNotEnough> for NotEnoughElements {
170            type Error = FailedErrorConversion;
171            fn from (_f: DataNotEnough) -> Result<NotEnoughElements, FailedErrorConversion> {
172                match _f {
173                    DataNotEnough::Elements(i) => NotEnoughElements(i),
174                    _ => Err(FailedErrorConversion)
175                }
176            }
177        } */
178        impl TryFrom<$for_composite> for $from_individual {
179            type Error = crate::FailedErrorConversion;
180            fn try_from($fn_arg: $for_composite) -> Result<$from_individual, Self::Error> {
181                match $fn_arg {
182                    $for_composite::$variant_name
183                        $(( $($e_name),+ ))?                                      // tuple-struct↓
184                        $({ $($f_name),+ })?                                      // field-struct↑
185                        => Ok($from_individual
186                            $(( $($e_name),+ ))?                                  // tuple-struct↓
187                            $({ $($f_name),+ })?                                  // field-struct↑
188                        ),
189                        #[allow(unreachable_patterns)]
190                        _ => Err(crate::FailedErrorConversion)
191                }
192            }
193        }
194    };
195    (
196    // Implements `From` a composite error type and a composite error superset of it,
197    // as well as implementing `TryFrom` in the inverse direction.
198    // E.g. from(f): DataNotEnough for: DataError
199    composite: from($fn_arg:ident): $from_subset:ident, for: $for_superset:ident { $(
200        $from_variant:ident
201            $(( $($from_elem:ident),+ ))?                                         // tuple-struct↓
202            $({ $($from_field:ident),+ })?                                        // field-struct↑
203                => $for_variant:ident
204                    $(( $($for_elem:ident),+ ))?                                  // tuple-struct↓
205                    $({ $($for_field:ident),+ })?                                 // field-struct↑
206    ),+ $(,)? }
207    ) => {
208        impl From<$from_subset> for $for_superset {
209            fn from($fn_arg: $from_subset) -> $for_superset { match $fn_arg { $(
210                $from_subset::$from_variant
211                    $(( $($from_elem),+ ))?                                       // tuple-struct↓
212                    $({ $($from_field),+ })?                                      // field-struct↑
213                    => $for_superset::$for_variant
214                        $(( $($for_elem),+ ))?                                    // tuple-struct↓
215                        $({ $($for_field),+ })?                                   // field-struct↑
216            ),+ } }
217        }
218        impl TryFrom<$for_superset> for $from_subset {
219            type Error = crate::FailedErrorConversion;
220            fn try_from($fn_arg: $for_superset)
221                -> Result<$from_subset, crate::FailedErrorConversion> { match $fn_arg { $(
222                    $for_superset::$for_variant
223                        $(( $($for_elem),+ ))?                                    // tuple-struct↓
224                        $({ $($for_field),+ })?                                   // field-struct↑
225                            => Ok($from_subset::$from_variant
226                                $(( $($from_elem),+ ))?                           // tuple-struct↓
227                                $({ $($from_field),+ })?                          // field-struct↑
228                    ),)+
229                    _ => Err(crate::FailedErrorConversion)
230                }
231            }
232        }
233    };
234}
235pub(crate) use impl_error;
236
237#[cfg(test)]
238mod tests {
239    use super::impl_error;
240
241    #[test]
242    fn impl_error() {
243        /* define individual errors */
244
245        impl_error! { individual: pub struct UnitStruct;
246            DOC_UNIT_STRUCT = "docstring", self+f => write!(f, "display"),
247        }
248        impl_error! { individual: pub struct SingleElement(pub(crate) Option<u8>);
249            DOC_SINGLE_ELEMENT = "docstring", self+f => write!(f, "display"),
250        }
251        impl_error! { individual: pub struct MultipleElements(pub i32, u32,);
252            DOC_MULTI_ELEMENT = "docstring", self+f => write!(f, "display")
253        }
254        impl_error! { individual: pub struct StructFields {
255                #[doc = "field1"] pub f1: bool,
256                #[doc = "field2"] f2: Option<char>
257            }
258            DOC_STRUCT_FIELDS = "docstring", self+f => write!(f, "display")
259        }
260
261        /* define composite errors
262         * (includes conversions between variants and indidual errors) */
263
264        impl_error! { composite: fmt(f)
265            /// A composite error superset.
266            pub enum CompositeSuperset {
267                DOC_UNIT_STRUCT: SuperUnit => UnitStruct,
268                DOC_SINGLE_ELEMENT: SuperSingle(i|0: Option<u8>) => SingleElement(*i),
269                DOC_MULTI_ELEMENT: SuperMultiple(i|0: i32, j|1: u32) => MultipleElements(*i, *j),
270                DOC_STRUCT_FIELDS: SuperStruct { f1: bool, f2: Option<char> }
271                    => StructFields { f1: *f1, f2: *f2 },
272            }
273        }
274        impl_error! { composite: fmt(f)
275            /// A composite error subset.
276            // removed the unit struct variant (the most trivial case)
277            pub enum CompositeSubset {
278                DOC_SINGLE_ELEMENT: SubSingle(i|0: Option<u8>) => SingleElement(*i),
279                DOC_MULTI_ELEMENT: SubMultiple(i|0: i32, j|1: u32) => MultipleElements(*i, *j),
280                DOC_STRUCT_FIELDS: SubStruct { f1: bool, f2: Option<char> }
281                    => StructFields { f1: *f1, f2: *f2 },
282            }
283        }
284
285        /* implement conversions between composite errors */
286
287        impl_error! { composite: from(f): CompositeSubset, for: CompositeSuperset {
288            SubSingle(i) => SuperSingle(i),
289            SubMultiple(i, j) => SuperMultiple(i, j),
290            SubStruct { f1, f2 } => SuperStruct { f1, f2 },
291        }}
292
293        /* check the conversions between individual and composite errors */
294
295        assert_eq![CompositeSuperset::SuperUnit, UnitStruct.into()];
296        assert![UnitStruct::try_from(CompositeSuperset::SuperUnit).is_ok()];
297        assert![UnitStruct::try_from(CompositeSuperset::SuperSingle(None)).is_err()];
298
299        assert_eq![CompositeSuperset::SuperSingle(Some(1)), SingleElement(Some(1)).into()];
300        assert![SingleElement::try_from(CompositeSuperset::SuperSingle(Some(2))).is_ok()];
301
302        assert_eq![CompositeSuperset::SuperMultiple(3, 5), MultipleElements(3, 5).into()];
303        assert![MultipleElements::try_from(CompositeSuperset::SuperMultiple(7, 13)).is_ok()];
304
305        assert_eq![
306            CompositeSuperset::SuperStruct { f1: true, f2: Some('a') },
307            StructFields { f1: true, f2: Some('a') }.into()
308        ];
309        assert![
310            StructFields::try_from(CompositeSuperset::SuperStruct { f1: false, f2: None }).is_ok()
311        ];
312
313        /* check the conversions between composite errors */
314
315        assert_eq![
316            CompositeSuperset::SuperSingle(Some(1)),
317            CompositeSubset::SubSingle(Some(1)).into()
318        ];
319        assert_eq![
320            CompositeSubset::try_from(CompositeSuperset::SuperSingle(Some(2))),
321            Ok(CompositeSubset::SubSingle(Some(2)))
322        ];
323
324        assert_eq![
325            CompositeSuperset::SuperMultiple(4, 6),
326            CompositeSubset::SubMultiple(4, 6).into()
327        ];
328        assert_eq![
329            CompositeSubset::try_from(CompositeSuperset::SuperMultiple(5, 7)),
330            Ok(CompositeSubset::SubMultiple(5, 7))
331        ];
332
333        assert_eq![
334            CompositeSuperset::SuperStruct { f1: true, f2: Some('z') },
335            CompositeSubset::SubStruct { f1: true, f2: Some('z') }.into()
336        ];
337        assert_eq![
338            CompositeSubset::try_from(CompositeSuperset::SuperStruct { f1: true, f2: None }),
339            Ok(CompositeSubset::SubStruct { f1: true, f2: None })
340        ];
341    }
342}