smarterr_macro/
lib.rs

1#![recursion_limit = "512"]
2
3//! # SmartErr
4//!
5//! SmartErr, an error handling library, introduces several convenient aproaches
6//!  to raise, gather and distibute domain-specific errors in libraries and/or
7//!  applications.
8//!
9//! With **_SmartErr_** you'll be able to
10//!
11//! * raise errors with `raise` and `throw` methods on regular types (numbers,
12//!  strings, boolean, _Option_, _Result_, etc) as an error source.
13//!  Look at [Raising errors](#raising-errors) section to find out more details.
14//! * define the exact set of errors emitted by the function or introduce
15//!  global set for the public API.
16//! * passthrough unhandled errors of the called functions or handle them
17//!  completelly or partially with special `handle` method generated
18//!  for every specific situation.
19//! * attach _context_ to errors and define specific messages both for new and
20//!  non-handled errors. [Defining errors](#defining-errors) section describes
21//!  this approache.
22//!
23//! ## Quick overview
24//!
25//! See [this](#fbs-example) example below.
26//!
27//! ## Raising errors
28//!
29//! Some functions may return simple types instead of _Result_. This part of
30//!  the library is devoted to the processing of this kind of results. Simple
31//!  values are converted with `raise` (or `raise_with`) and `throw` (or
32//!  `throw_with`) methods from _Throwable_ trait.
33//! `raise` emits an error if source is NOT treated as failure and `throw` emits
34//!  an error if it's already in a failure state. Here is a reference table for
35//!  types that have an implementation of _Throwable_ trait:
36//!
37//! | Source type                | `throw` condition for the error | `raise` condition |
38//! | -------------------------- | ------------------------------- | ----------------- |
39//! | numbers (i32, usize, etc)  | != 0                            | == 0              |
40//! | bool                       | false                           | true              |
41//! | strings (&str, String etc) | is_empty()                      | !is_empty()       |
42//! | Option                     | Some                            | None              |
43//! | Result                     | Ok                              | Err               |
44//!
45//! If the condition is not met, the original value will be returned.
46//!
47//! Assume there is some numeric input.
48//! To convert it into _Result_ using _Throwable_:
49//! ```rust
50//! fn raw_throwable(val: i32) -> Result<i32, RawError<i32>> {
51//!     val.throw()
52//!     //val.throw_with("raw error")
53//! }
54//!
55//! #[test]
56//! pub fn test_throwable()  {
57//!     assert_eq!(raw_throwable(0).unwrap(), 0);
58//!     assert_eq!(raw_throwable(10).is_err(), true);
59//!     assert_eq!(format!("{}", raw_throwable(10).unwrap_err()),
60//!         "raw error { value: 10 }"
61//!     );
62//! }
63//! ```
64//! To convert with _Erroneous_:
65//!
66//! ```rust
67//! smarterr_fledged!(DomainErrors{
68//!     DomainError<<i32>> -> "Domain error"
69//! });
70//!
71//! fn raw_erroneous(val: i32) -> Result<i32, RawError<i32>> {
72//!     val.throw_err(RawError::new_with(val, "raw error"))
73//! }
74//!
75//! fn raw_erroneous_then(val: i32) -> Result<i32, RawError<i32>> {
76//!     val.throw_then(|v| RawError::new_with(v, "raw error"))
77//! }
78//!
79//! fn raw_erroneous_ctx(val: i32) -> Result<i32, DomainErrors> {
80//!     val.throw_ctx(DomainErrorCtx{})
81//! }
82//!
83//! #[test]
84//! pub fn test_erroneous()  {
85//!     assert_eq!(raw_erroneous(0).unwrap(), 0);
86//!     assert_eq!(raw_erroneous_then(10).is_err(), true);
87//!     assert_eq!(format!("{}", raw_erroneous_then(10).unwrap_err()),
88//!         "raw error { value: 10 }"
89//!     );
90//!     assert_eq!(format!("{}", raw_erroneous_ctx(10).unwrap_err()),
91//!         "Domain error, caused by: raw error { value: 10 }"
92//!     );
93//! }
94//! ```
95//! Domain error processing is described in
96//!  [Defining errors](#definig_errors) section.
97//!
98//! `raise` alternative could be used instead of `throw` as well. The only
99//!  difference is that the `raise` condition is the opposite of `throw`.
100//!
101//! ## Defining errors
102//!
103//! There are 2 approaches to define errors:
104//! * "_fledged_": domain errors are defined globally (within the selected
105//!  visibility)
106//! * _function-based_: error set is specific for the each function
107//!   
108//! Both shares the same sintax, with limited inheritance for the fledged style.
109//!
110//! ### Fledged style
111//!
112//! Fledged style is mostly convenient for standalone doman-specific errors.
113//! The following example demonstrates the usage of _smarterr_fledged_ macros
114//!  which is designed to support fledged approach.
115//! ```rust
116//! smarterr_fledged!(pub PlanetsError {
117//!     MercuryError{} -> "Mercury error",
118//!     pub MarsError{ind: usize} -> "Mars Error",
119//!     SaturnError<<i32>> -> "Saturn error",
120//!     EarthError<ParseIntError> -> "EarthError",
121//! });
122//! ```
123//! First it should be defined the name of the error set and (optionally) it's
124//!  visibility. Then goes certain errors definition inside curly braces.
125//!  It follows simple pattern:
126//! ```
127//!     [visibility] name[<[< source error type >]>] [{ context struct }] -> "error message",
128//! ```
129//! The following code will be generated under the hood (shown without minor
130//!  details and cutted to _MarsError_ only):
131//!
132//! ```rust
133//! #[derive(Debug)]
134//! pub enum PlanetsError {
135//!     MercuryError(MercuryError),
136//!     MarsError(MarsError),
137//!     SaturnError(SaturnError),
138//!     EarthError(EarthError),
139//! }
140//!
141//! /* cutted: Error and Display implementations for PlanetsError */
142//!
143//! #[derive(Debug)]
144//! pub struct MarsError {
145//!     ctx: MarsErrorCtx,
146//! }
147//!
148//! impl MarsError {
149//!     pub fn new<ES>(_src: ES, ctx: MarsErrorCtx) -> Self {
150//!         MarsError { ctx }
151//!     }
152//!     pub fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
153//!         None
154//!     }
155//!     pub fn default_message(&self) -> &'static str {
156//!         "Mars Error"
157//!     }
158//! }
159//!
160//! /* cutted: Display implementation for MarsError */
161//!
162//! #[derive(Debug)]
163//! #[allow(dead_code)]
164//! pub struct MarsErrorCtx {
165//!     ind: usize,
166//! }
167//!
168//! impl<ES: std::fmt::Debug + 'static> smarterr::IntoError<PlanetsError, ES> for MercuryErrorCtx {
169//!     fn into_error(self, source: ES) -> PlanetsError {
170//!         PlanetsError::MercuryError(MercuryError::new(source, self))
171//!     }
172//! }
173//! impl<ES: std::fmt::Debug + 'static> smarterr::IntoError<PlanetsError, ES> for MarsErrorCtx {
174//!     fn into_error(self, source: ES) -> PlanetsError {
175//!         PlanetsError::MarsError(MarsError::new(source, self))
176//!     }
177//! }
178//! impl smarterr::IntoError<PlanetsError, i32> for SaturnErrorCtx {
179//!     fn into_error(self, source: i32) -> PlanetsError {
180//!         PlanetsError::SaturnError(SaturnError::new(source, self))
181//!     }
182//! }
183//! impl smarterr::IntoError<PlanetsError, ParseIntError> for EarthErrorCtx {
184//!     fn into_error(self, source: ParseIntError) -> PlanetsError {
185//!         PlanetsError::EarthError(EarthError::new(source, self))
186//!     }
187//! }
188//! ```
189//!
190//! Several key details for the generated code:
191//!
192//! 1. Domain error set is the enum.
193//! 2. For each error (enum value) additional structure is created, its name is
194//!  the same as the name of the error.
195//! 3. If context has been defined, the corresponding structure will be created.
196//!  Its name is the error name followed with the `Ctx` suffix.
197//!
198//! The example above it pretty simple and does not demonstate source error
199//!  definition. Usually you'd like to set up source error. There are several
200//!  posibilites:
201//!
202//! | source        | definition example                             |
203//! | ------------- | ---------------------------------------------- |
204//! | no source     | `MercuryError -> "Mercury error"`              |
205//! | dyn Error     | `MercuryError<> -> "Mercury error"`            |
206//! | certain error | `MercuryError<SourceError> -> "Mercury error"` |
207//! | dyn Debug     | `MercuryError<<>> -> "Mercury error"`          |
208//! | certain Debug | `MercuryError<<i32>> -> "Mercury error"`       |
209//!
210//! Raising errors is pretty simple:
211//! ```rust
212//! "z12".parse::<i32>().throw_ctx(EarthErrorCtx{})
213//! ```
214//! Note that it's done with _*Ctx_ structure (EarthErrorCtx in this example)
215//!  which has an implementation of _smarterr::IntoError_ trait.
216//!
217//! ## Function-based style
218//!
219//! This is a common situation when there are several functions calling from each
220//!  other. Usually each function returns its own error set and some unhandled
221//!  errors from the called one. Generally it is possible to use one error set
222//!  (enum) for all functions but that's not quite right. The functions' contracts
223//!  are inaccurate since they return subset of the common enum and some errors
224//!  will never happen. If some functions are public it might be a problem to hide
225//!  unused errors from the internals.
226//!
227//! The more precise solution is to define its own error set for each function.
228//!  But besides being quite difficult, it creates another problem. Some errors
229//!  may be defined several times for each error set and require mapping between
230//!  them even that they are the same. _SmartErr_ solves this problem providing
231//!  all necessary and optimized stuff behind the scenes.
232//!
233//! For this, 2 additional keywords were introduced:
234//!
235//! * _from_ keyword. It should be used if some errors from the called function
236//!  need to be rethrown.
237//! * _handle_ keyword. It is intended to mark errors from the called function
238//!  which will be handled.
239//!
240//! Here's how it works:
241//!
242//! #### FBS example
243//! ```rust
244//! #[smarterr(
245//!     AlfaError{ind: i32, ext: String} -> "Alfa error",
246//!     BetaError<>{ind: i32} -> "Beta error",
247//!     BetaWrappedError<ParseIntError> -> "Beta Wrapped Error",
248//!     GammaError<<>>{ext: String} -> "Gamma error",
249//!     GammaWrappedError<<i32>>{ext: String} -> "Gamma Wrapped error",
250//! )]
251//! pub fn greek_func(err_ind: usize) -> String {
252//!     let ok_str = "All is ok".to_string();
253//!     let err_str = "Error raised".to_string();
254//!     let ext = "ext".to_string();
255//!     match err_ind {
256//!         0 => Ok(ok_str),
257//!         1 => err_str.raise_ctx(AlfaErrorCtx { ind: -1, ext }),
258//!         2 => "z12".parse::<i32>().throw_ctx(BetaErrorCtx { ind: -2 }).map(|_| ok_str),
259//!         3 => "z12".parse::<i32>().throw_ctx(BetaWrappedErrorCtx {}).map(|_| ok_str),
260//!         4 => err_str.raise_ctx(GammaErrorCtx { ext }),
261//!         5 => 5000000.throw_ctx(GammaWrappedErrorCtx { ext }).map(|_| ok_str),
262//!         _ => Ok(ok_str),
263//!     }
264//! }
265//!
266//! #[smarterr(
267//!     from GreekFuncError {
268//!         AlfaError, BetaError<>, BetaWrappedError<ParseIntError>, GammaError<<>>,
269//!         handled GammaWrappedError
270//!     },
271//!     XError{ind: i32, ext: String} -> "X error",
272//!     YError{ind: i32} -> "Y error",
273//!     pub ZError<<String>>{ind: usize} -> "Z Error",
274//! )]
275//! fn latin_func(err_ind: usize) {
276//!     greek_func(err_ind).handle(|h| match h {
277//!         GreekFuncErrorHandled::GammaWrappedError(data) =>
278//!             data.ctx.ext.throw_ctx(ZErrorCtx { ind: err_ind }),
279//!     })?;
280//!     Ok(())
281//! }
282//!
283//! #[smarterr(
284//!     from GreekFuncError {
285//!         AlfaError -> "Imported Alfa error",
286//!         BetaError<> -> "Imported Beta error",
287//!         BetaWrappedError<std::num::ParseIntError> -> "Imported Beta Wrapped Error",
288//!         handled GammaError,
289//!         handled GammaWrappedError,
290//!     },
291//!     from LatinFuncError {
292//!         AlfaError, BetaError<>, BetaWrappedError<ParseIntError>, ZError<<String>>,
293//!         handled { GammaError, XError, YError }
294//!     },
295//!     FirstError{ind: i32, ext: String} -> "First error",
296//!     SecondError{ind: i32} -> "Second error",
297//!     ThirdError{} -> "Third Error",
298//! )]
299//! pub fn numeric_func(err_ind: usize) -> String {
300//!     let g = greek_func(err_ind).handle(|h| match h {
301//!         GreekFuncErrorHandled::GammaWrappedError(e) =>
302//!             e.ctx.ext.clone().raise_ctx(FirstErrorCtx{ind: err_ind as i32, ext: e.ctx.ext}),
303//!         GreekFuncErrorHandled::GammaError(e) =>
304//!             e.ctx.ext.raise_ctx(SecondErrorCtx{ ind: err_ind as i32 }),
305//!     })?;
306//!
307//!     latin_func(err_ind).handle(|h| match h {
308//!         LatinFuncErrorHandled::XError(e)=>
309//!             ().raise_ctx(FirstErrorCtx{ ind: err_ind as i32, ext: e.ctx.ext }),
310//!         LatinFuncErrorHandled::YError(e)=>
311//!             ().raise_ctx(SecondErrorCtx{ ind: e.ctx.ind }),
312//!         LatinFuncErrorHandled::GammaError(_) => Ok(())
313//!     })?;
314//!
315//!     let t = ().raise_ctx(MarsErrorCtx{ind: err_ind});
316//!     t.throw_ctx(BetaErrorCtx{ ind: err_ind as i32 })?;
317//!
318//!     Ok(g)
319//! }
320//! ```
321//! It is also possible to define errors for methods. The only difference is that
322//! theses errors must be defined outside the implementation block. `smarterr_mod`
323//! macro is intended to do this. It should be used as an attribute for the
324//! implementation block. The name of the module should be passed as an argument.
325//! Here's an example:
326//! ```rust
327//! #[smarterr_mod(test_err)]
328//! impl Test {
329//!     #[smarterr(InitFailed{pub a: String, pub b: String} -> "Init error")]
330//!     pub fn new(a: &str, b: &str) -> Self {
331//!         Ok(Self {
332//!             a: a.parse()
333//!                 .throw_ctx(test_err::InitFailedCtx { a: a.to_string(), b: b.to_string() })?,
334//!             b: b.parse()
335//!                 .throw_ctx(test_err::InitFailedCtx { a: a.to_string(), b: b.to_string() })?,
336//!         })
337//!     }
338//! }
339//! ```
340
341use std::collections::BTreeSet;
342
343use convert_case::{Case, Casing};
344use proc_macro::TokenStream;
345use proc_macro2::Ident;
346use quote::{quote, TokenStreamExt};
347use syn::{
348    parse::Parse,
349    parse_macro_input,
350    punctuated::Punctuated,
351    token::{Brace, Comma, Gt, Lt, Pub, RArrow, Shl, Shr},
352    FieldsNamed, ItemFn, ItemImpl, LitStr, ReturnType, Token, Type, Visibility,
353};
354
355mod keywords {
356    syn::custom_keyword![from];
357    syn::custom_keyword![handled];
358}
359
360struct ErrorNs {
361    visibility: Visibility,
362    name: Ident,
363}
364
365struct FledgedError {
366    visibility: Visibility,
367    name: Ident,
368    definition: Punctuated<OwnError, Comma>,
369}
370
371struct SmartErrors {
372    errors: Option<Punctuated<ErrorDef, Comma>>,
373}
374
375enum ErrorDef {
376    Own(OwnError),
377    Inherited(InheritedErrors),
378}
379
380struct OwnError {
381    visibility: Option<Visibility>,
382    name: Ident,
383    definition: Option<FieldsNamed>,
384    source: Option<Type>,
385    is_typed_source: bool,
386    is_boxed_source: bool,
387    msg: Option<LitStr>,
388}
389
390struct InheritedErrors {
391    source: Ident,
392    errors: Option<Punctuated<InheritedErrorDef, Comma>>,
393}
394
395enum InheritedErrorDef {
396    Unhandled(UnhandledError),
397    Handled(HandledError),
398}
399
400type UnhandledError = OwnError;
401
402struct HandledError {
403    names: Vec<Ident>,
404}
405
406impl Parse for ErrorNs {
407    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
408        Ok(ErrorNs {
409            visibility: input.parse::<Visibility>().unwrap_or(Visibility::Inherited {}),
410            name: input.parse()?,
411        })
412    }
413}
414
415impl Parse for FledgedError {
416    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
417        let content;
418        Ok(FledgedError {
419            visibility: input.parse::<Visibility>().unwrap_or(Visibility::Inherited {}),
420            name: input.parse()?,
421            definition: {
422                _ = syn::braced!(content in input);
423                content.parse_terminated(OwnError::parse, Token![,])?
424            },
425        })
426    }
427}
428
429impl Parse for SmartErrors {
430    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
431        let t: Option<Punctuated<ErrorDef, Comma>> = input.parse_terminated(ErrorDef::parse, Token![,]).ok();
432        Ok(SmartErrors { errors: t })
433    }
434}
435
436impl Parse for ErrorDef {
437    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
438        if input.peek(keywords::from) {
439            Ok(ErrorDef::Inherited(InheritedErrors::parse(input)?))
440        } else {
441            Ok(ErrorDef::Own(OwnError::parse(input)?))
442        }
443    }
444}
445
446impl Parse for OwnError {
447    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
448        Ok(OwnError {
449            visibility: input.parse::<Visibility>().ok().and_then(|v| match v {
450                Visibility::Inherited => None,
451                _ => Some(v),
452            }),
453            name: input.parse()?,
454            is_typed_source: input.peek(Lt),
455            is_boxed_source: input.peek(Shl),
456            source: {
457                if input.peek(Shl) {
458                    _ = input.parse::<Shl>()?;
459                    let e = input.parse::<Type>().ok();
460                    _ = input.parse::<Shr>()?;
461                    e
462                } else if input.peek(Lt) {
463                    _ = input.parse::<Lt>()?;
464                    let e = input.parse::<Type>().ok();
465                    _ = input.parse::<Gt>()?;
466                    e
467                } else {
468                    None
469                }
470            },
471            definition: input.parse().ok(),
472            msg: {
473                if input.parse::<RArrow>().is_ok() {
474                    Some(input.parse()?)
475                } else {
476                    None
477                }
478            },
479        })
480    }
481}
482
483impl Parse for InheritedErrors {
484    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
485        input.parse::<keywords::from>()?;
486        Ok(InheritedErrors {
487            source: input.parse()?,
488            errors: {
489                let content;
490                let _ = syn::braced!(content in input);
491                content.parse_terminated(InheritedErrorDef::parse, Token![,]).ok()
492            },
493        })
494    }
495}
496
497impl Parse for InheritedErrorDef {
498    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
499        if input.peek(keywords::handled) {
500            Ok(InheritedErrorDef::Handled(HandledError::parse(input)?))
501        } else {
502            let own = OwnError::parse(input)?;
503            if own.definition.is_some() {
504                Err(syn::Error::new(
505                    input.span(),
506                    "Inherited error cannot contain its own definition",
507                ))
508            } else {
509                Ok(InheritedErrorDef::Unhandled(own))
510            }
511        }
512    }
513}
514
515impl Parse for HandledError {
516    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
517        input.parse::<keywords::handled>()?;
518
519        let mut names = vec![];
520        if input.peek(Brace) {
521            let content;
522            let _ = syn::braced!(content in input);
523            for ident in content.parse_terminated(Ident::parse, Token![,])? {
524                names.push(ident);
525            }
526        } else {
527            names.push(input.parse()?);
528        }
529        Ok(HandledError { names })
530    }
531}
532
533impl OwnError {
534    fn to_enum_error_item(&self, tokens: &mut proc_macro2::TokenStream) {
535        let name = &self.name;
536        tokens.append_all(quote!(#name (#name),));
537    }
538
539    fn to_ctx(&self, visibility: &Visibility, tokens: &mut proc_macro2::TokenStream) {
540        let name = &self.name;
541        let visibility = self.visibility.as_ref().unwrap_or(visibility);
542        let msg = self.msg.as_ref().map(|l| l.value()).unwrap_or("".to_string());
543        let ctx_name_str = format!("{}Ctx", self.name);
544        let ctx_name: Ident = Ident::new(&ctx_name_str, self.name.span());
545        let definition = self.definition.clone().unwrap_or(FieldsNamed {
546            brace_token: Brace::default().into(),
547            named: Punctuated::new(),
548        });
549
550        let mut is_default = false;
551        let ts = if let Some(st) = &self.source {
552            let (struct_st, new_src) = if !self.is_boxed_source {
553                (quote! { #st }, quote! { src })
554            } else {
555                (
556                    quote! { smarterr::RawError<#st> },
557                    quote! { smarterr::RawError::new(src) },
558                )
559            };
560            quote! {
561                #[derive(std::fmt::Debug)]
562                #visibility struct #name {
563                    pub src: #struct_st,
564                    pub ctx: #ctx_name,
565                }
566                impl #name {
567                    pub fn new(src: #st, ctx: #ctx_name) -> Self {
568                        #name { src: #new_src, ctx }
569                    }
570                    pub fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
571                        Some(&self.src as _)
572                    }
573                    pub fn default_message(&self) -> &'static str {
574                        #msg
575                    }
576                }
577            }
578        } else if self.is_typed_source {
579            let (st, struct_st, new_src, src) = if !self.is_boxed_source {
580                (
581                    quote! { std::error::Error + 'static },
582                    quote! { Box<dyn std::error::Error + 'static> },
583                    quote! { Box::new(src) },
584                    quote! { Some(&*self.src) },
585                )
586            } else {
587                (
588                    quote! { std::fmt::Debug + 'static },
589                    quote! { smarterr::RawError<Box<dyn std::fmt::Debug + 'static>> },
590                    quote! { smarterr::RawError::new (Box::new(src)) },
591                    quote! { Some(&self.src as _) },
592                )
593            };
594            quote! {
595                #[derive(std::fmt::Debug)]
596                #visibility struct #name {
597                    pub src: #struct_st,
598                    pub ctx: #ctx_name,
599                }
600
601                impl #name {
602                    pub fn new<ES: #st>(src: ES, ctx: #ctx_name) -> Self {
603                        #name { src: #new_src, ctx }
604                    }
605
606                    pub fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
607                        #src
608                    }
609
610                    pub fn default_message(&self) -> &'static str {
611                        #msg
612                    }
613                }
614            }
615        } else {
616            is_default = true;
617            quote! {
618                #[derive(std::fmt::Debug)]
619                #visibility struct #name {
620                    pub ctx: #ctx_name,
621                }
622
623                impl #name {
624                    pub fn new<ES>(_src: ES, ctx: #ctx_name) -> Self {
625                        #name { ctx }
626                    }
627
628                    pub fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
629                        None
630                    }
631
632                    pub fn default_message(&self) -> &'static str {
633                        #msg
634                    }
635                }
636            }
637        };
638        tokens.append_all(ts);
639
640        let write = if is_default {
641            quote!(write!(f, "{}", x)?;)
642        } else {
643            quote!(write!(f, "{}, caused by: {}", x, self.src)?;)
644        };
645        tokens.append_all(quote! {
646            impl std::fmt::Display for #name {
647                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
648                    let x = format!("{:?}", self.ctx).replace("\"", "\'");
649                    let x = x.strip_prefix(#ctx_name_str).unwrap_or("");
650                    #write
651                    Ok(())
652                }
653            }
654
655            #[allow(dead_code)]
656            #[derive(std::fmt::Debug)]
657            #visibility struct #ctx_name #definition
658        });
659    }
660
661    fn to_into_error_impl(&self, others: &BTreeSet<String>, err_enum: &Ident, tokens: &mut proc_macro2::TokenStream) {
662        let name = &self.name;
663        if others.contains(&name.to_string()) {
664            return;
665        }
666        let ctx_name: Ident = Ident::new(&format!("{}Ctx", self.name), self.name.span());
667
668        let ts = if let Some(st) = &self.source {
669            quote! {
670                impl smarterr::IntoError<#err_enum, #st> for #ctx_name {
671                    fn into_error(self, source: #st) -> #err_enum {
672                        #err_enum::#name(#name::new(source, self))
673                    }
674                }
675            }
676        } else if self.is_boxed_source {
677            quote! {
678                impl<ES: std::fmt::Debug + 'static> smarterr::IntoError<#err_enum, ES> for #ctx_name {
679                    fn into_error(self, source: ES) -> #err_enum {
680                        #err_enum::#name(#name::new(source, self))
681                    }
682                }
683            }
684        } else if self.is_typed_source {
685            quote! {
686                impl<ES: std::error::Error + 'static> smarterr::IntoError<#err_enum, ES> for #ctx_name {
687                    fn into_error(self, source: ES) -> #err_enum {
688                        #err_enum::#name(#name::new(source, self))
689                    }
690                }
691            }
692        } else {
693            quote! {
694                impl<ES: std::fmt::Debug + 'static> smarterr::IntoError<#err_enum, ES> for #ctx_name {
695                    fn into_error(self, source: ES) -> #err_enum {
696                        #err_enum::#name(#name::new(source, self))
697                    }
698                }
699            }
700        };
701        tokens.append_all(ts);
702    }
703
704    fn to_errors_sources(&self, others: &BTreeSet<String>, err_enum: &Ident, tokens: &mut proc_macro2::TokenStream) {
705        let name = &self.name;
706        if others.contains(&name.to_string()) || self.source.is_none() && !self.is_typed_source {
707            return;
708        }
709        tokens.append_all(quote! {
710            #err_enum::#name(err) => err.source(),
711        });
712    }
713
714    fn to_display(&self, others: &BTreeSet<String>, err_enum: &Ident, tokens: &mut proc_macro2::TokenStream) {
715        let name = &self.name;
716        if others.contains(&name.to_string()) {
717            return;
718        }
719
720        tokens.append_all(quote! {
721            #err_enum::#name(err) => {
722                write!(f, "{}{}", err.default_message(), err)?;
723            }
724        });
725    }
726}
727
728impl UnhandledError {
729    fn to_unhandled_enum_error_item(&self, others: &BTreeSet<String>, tokens: &mut proc_macro2::TokenStream) {
730        let name = &self.name;
731        if !others.contains(&name.to_string()) {
732            tokens.append_all(quote!(#name (#name),));
733        }
734    }
735
736    fn to_handler_action(
737        &self,
738        err_enum: &Ident,
739        from_err_enum: &Ident,
740        module: &Option<Ident>,
741        from: &mut proc_macro2::TokenStream,
742        handles: &mut proc_macro2::TokenStream,
743    ) {
744        let name = &self.name;
745        if let Some(mod_name) = module {
746            handles.append_all(quote!(#mod_name::#from_err_enum::#name(e) => Err(#mod_name::#err_enum::#name(e)),));
747            from.append_all(quote!(#mod_name::#from_err_enum::#name(ctx) => #mod_name::#err_enum::#name(ctx),));
748        } else {
749            handles.append_all(quote!(#from_err_enum::#name(e) => Err(#err_enum::#name(e)),));
750            from.append_all(quote!(#from_err_enum::#name(ctx) => #err_enum::#name(ctx),));
751        }
752    }
753}
754
755impl HandledError {
756    fn to_handled_enum_error_item(&self, handled_enum_errors: &mut proc_macro2::TokenStream) {
757        for name in &self.names {
758            handled_enum_errors.append_all(quote!(#name(#name),));
759        }
760    }
761
762    fn to_handler_action(
763        &self,
764        from_err_enum: &Ident,
765        handled_err_enum: &Ident,
766        handles: &mut proc_macro2::TokenStream,
767    ) {
768        for name in &self.names {
769            handles.append_all(quote!(
770                #from_err_enum::#name(e) => handler(#handled_err_enum::#name(e)),
771            ));
772        }
773    }
774}
775
776#[proc_macro_attribute]
777pub fn smarterr_mod(metadata: TokenStream, input: TokenStream) -> TokenStream {
778    let mut input = parse_macro_input!(input as ItemImpl);
779    let meta = parse_macro_input!(metadata as ErrorNs);
780
781    let mod_name: Ident = meta.name.clone();
782    let mod_visibility = meta.visibility.clone();
783
784    let mut mod_content = proc_macro2::TokenStream::new();
785    for item in &mut input.items {
786        if let syn::ImplItem::Fn(method) = item {
787            let func: ItemFn = syn::parse2(quote! {
788                #method
789            })
790            .unwrap();
791            for attr in &method.attrs {
792                if attr.path().segments.len() == 1 && attr.path().segments[0].ident == "smarterr" {
793                    if let Ok(smart_errors) = attr.parse_args::<SmartErrors>() {
794                        let r = _smarterr(func, smart_errors, Some(mod_name.clone()));
795                        let r0 = r.0;
796                        let func_out: ItemFn = syn::parse2(quote! {
797                            #r0
798                        })
799                        .unwrap();
800                        method.sig.output = func_out.sig.output;
801                        method.block = *func_out.block;
802                        mod_content.extend(r.1.into_iter());
803                        break;
804                    }
805                }
806            }
807            // remove only smarterr attributes
808            method
809                .attrs
810                .retain(|attr| !(attr.path().segments.len() == 1 && attr.path().segments[0].ident == "smarterr"));
811        } else if let syn::ImplItem::Macro(macros) = item {
812            // check it is `smarterr_fledged` macro
813            if macros.mac.path.segments.len() == 1 && macros.mac.path.segments[0].ident == "smarterr_fledged" {
814                // copy it to the mod
815                mod_content.extend(quote! {
816                    #macros
817                });
818            }
819        }
820    }
821
822    // remove `smarterr_fledged` macroses
823    input.items.retain(|item| {
824        if let syn::ImplItem::Macro(macros) = item {
825            // check it is `smarterr_fledged` macro
826            if macros.mac.path.segments.len() == 1 && macros.mac.path.segments[0].ident == "smarterr_fledged" {
827                return false;
828            }
829        }
830        true
831    });
832
833    let output: TokenStream = quote! {
834        #input
835        #mod_visibility mod #mod_name {
836            use smarterr_macro::smarterr_fledged;
837            #mod_content
838        }
839    }
840    .into();
841    output
842}
843
844#[proc_macro_attribute]
845pub fn smarterr(metadata: TokenStream, input: TokenStream) -> TokenStream {
846    let input = parse_macro_input!(input as ItemFn);
847    let meta = parse_macro_input!(metadata as SmartErrors);
848
849    let (mut output, ts) = _smarterr(input, meta, None);
850
851    output.extend(ts.into_iter());
852    output.into()
853}
854
855fn _smarterr(
856    mut input: ItemFn,
857    meta: SmartErrors,
858    module: Option<Ident>,
859) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
860    // if module is defined, visibility should be public
861    let visibility = if module.is_some() {
862        Visibility::Public(Pub::default())
863    } else {
864        input.vis.clone()
865    };
866    let err_enum: Ident = Ident::new(
867        &format!("{}Error", input.sig.ident).to_case(Case::Pascal),
868        input.sig.ident.span(),
869    );
870
871    input.sig.output = match input.sig.output {
872        ReturnType::Default => {
873            let spans = [input.sig.ident.span().clone(), input.sig.ident.span().clone()];
874            let rt = if let Some(mod_name) = &module {
875                syn::Type::Verbatim(quote! { std::result::Result<(), #mod_name::#err_enum> })
876            } else {
877                syn::Type::Verbatim(quote! { std::result::Result<(), #err_enum> })
878            };
879            ReturnType::Type(RArrow { spans }, Box::<Type>::new(rt))
880        }
881        ReturnType::Type(arrow, tt) => {
882            let spans = arrow.spans.clone();
883            let rt = if let Some(mod_name) = &module {
884                syn::Type::Verbatim(quote! { std::result::Result<#tt, #mod_name::#err_enum> })
885            } else {
886                syn::Type::Verbatim(quote! { std::result::Result<#tt, #err_enum> })
887            };
888            ReturnType::Type(RArrow { spans }, Box::<Type>::new(rt))
889        }
890    };
891
892    let mut dedup = BTreeSet::<String>::new();
893    let mut enum_errors = proc_macro2::TokenStream::new();
894    let mut errors_ctx = proc_macro2::TokenStream::new();
895    let mut errors_ctx_into_error_impl = proc_macro2::TokenStream::new();
896    let mut errors_sources = proc_macro2::TokenStream::new();
897    let mut errors_display = proc_macro2::TokenStream::new();
898
899    let mut handlers: Vec<syn::Stmt> = vec![syn::parse2(quote! {
900        trait ErrorHandler<T, EH, ER> {
901            fn handle<F: FnOnce(EH) -> Result<T, ER>>(self, handler: F) -> Result<T, ER>;
902        }
903    })
904    .unwrap()];
905
906    meta.errors.iter().flat_map(|p| p.iter()).for_each(|ed| match ed {
907        ErrorDef::Own(oe) => {
908            oe.to_enum_error_item(&mut enum_errors);
909            oe.to_ctx(&visibility, &mut errors_ctx);
910            oe.to_into_error_impl(&dedup, &err_enum, &mut errors_ctx_into_error_impl);
911            oe.to_errors_sources(&dedup, &err_enum, &mut errors_sources);
912            oe.to_display(&dedup, &err_enum, &mut errors_display);
913        }
914        ErrorDef::Inherited(ie) => {
915            let mut has_handled = false;
916            let mut handles = proc_macro2::TokenStream::new();
917            let mut unhandled_from = proc_macro2::TokenStream::new();
918            let mut handled_enum_errors = proc_macro2::TokenStream::new();
919
920            let source_err_enum = ie.source.clone();
921            let handled_err_enum: Ident = Ident::new(&format!("{}Handled", source_err_enum), ie.source.span());
922            ie.errors.iter().flat_map(|p| p.iter()).for_each(|ed| match ed {
923                InheritedErrorDef::Unhandled(ue) => {
924                    ue.to_unhandled_enum_error_item(&dedup, &mut enum_errors);
925                    ue.to_handler_action(&err_enum, &source_err_enum, &module, &mut unhandled_from, &mut handles);
926                    ue.to_into_error_impl(&dedup, &err_enum, &mut errors_ctx_into_error_impl);
927                    ue.to_errors_sources(&dedup, &err_enum, &mut errors_sources);
928                    ue.to_display(&dedup, &err_enum, &mut errors_display);
929                    dedup.insert(ue.name.to_string());
930                }
931                InheritedErrorDef::Handled(he) => {
932                    has_handled = true;
933                    he.to_handled_enum_error_item(&mut handled_enum_errors);
934                    he.to_handler_action(&source_err_enum, &handled_err_enum, &mut handles);
935                    for name in &he.names {
936                        dedup.insert(name.to_string());
937                    }
938                }
939            });
940
941            if has_handled {
942                let stmt = syn::parse2::<syn::Stmt>(quote! {
943                    enum #handled_err_enum {
944                        #handled_enum_errors
945                    }
946                });
947                handlers.push(stmt.unwrap());
948                let stmt = syn::parse2::<syn::Stmt>(quote! {
949                    impl<T> ErrorHandler<T, #handled_err_enum, #err_enum> for Result<T, #source_err_enum> {
950                        fn handle<F: FnOnce(#handled_err_enum) -> Result<T, #err_enum>>(
951                            self,
952                            handler: F,
953                        ) -> Result<T, #err_enum> {
954                            match self {
955                                Ok(v) => Ok(v),
956                                Err(e) => match e {
957                                    #handles
958                                },
959                            }
960                        }
961                    }
962                });
963                handlers.push(stmt.unwrap());
964            } else {
965                let stmt = syn::parse2::<syn::Stmt>(if let Some(mod_name) = &module {
966                    quote! {
967                        impl From<#mod_name::#source_err_enum> for #mod_name::#err_enum {
968                            fn from(source: #mod_name::#source_err_enum) -> Self {
969                                match source {
970                                    #unhandled_from
971                                }
972                            }
973                        }
974                    }
975                } else {
976                    quote! {
977                        impl From<#source_err_enum> for #err_enum {
978                            fn from(source: #source_err_enum) -> Self {
979                                match source {
980                                    #unhandled_from
981                                }
982                            }
983                        }
984                    }
985                });
986                handlers.push(stmt.unwrap());
987            }
988        }
989    });
990
991    if handlers.len() > 1 {
992        handlers.extend_from_slice(&input.block.stmts);
993        input.block.stmts = handlers;
994    }
995
996    let output = quote! { #input };
997
998    let ts = quote! {
999        #[derive(std::fmt::Debug)]
1000        #visibility enum #err_enum {
1001            #enum_errors
1002        }
1003        impl std::error::Error for #err_enum {
1004            fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1005                match self {
1006                    #errors_sources
1007                    _ => None,
1008                }
1009            }
1010        }
1011        impl std::fmt::Display for #err_enum {
1012            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1013                match self {
1014                    #errors_display
1015                }
1016                Ok(())
1017            }
1018        }
1019        #errors_ctx
1020        #errors_ctx_into_error_impl
1021    };
1022
1023    (output, ts)
1024}
1025
1026#[proc_macro]
1027pub fn smarterr_fledged(input: TokenStream) -> TokenStream {
1028    let input = parse_macro_input!(input as FledgedError);
1029
1030    let visibility = input.visibility.clone();
1031    let err_enum: Ident = input.name.clone();
1032
1033    let dedup = BTreeSet::<String>::new();
1034    let mut enum_errors = proc_macro2::TokenStream::new();
1035    let mut errors_ctx = proc_macro2::TokenStream::new();
1036    let mut errors_ctx_into_error_impl = proc_macro2::TokenStream::new();
1037    let mut errors_sources = proc_macro2::TokenStream::new();
1038    let mut errors_display = proc_macro2::TokenStream::new();
1039
1040    input.definition.iter().for_each(|oe| {
1041        oe.to_enum_error_item(&mut enum_errors);
1042        oe.to_ctx(&visibility, &mut errors_ctx);
1043        oe.to_into_error_impl(&dedup, &err_enum, &mut errors_ctx_into_error_impl);
1044        oe.to_errors_sources(&dedup, &err_enum, &mut errors_sources);
1045        oe.to_display(&dedup, &err_enum, &mut errors_display);
1046    });
1047
1048    quote! {
1049        #[derive(std::fmt::Debug)]
1050        #visibility enum #err_enum {
1051            #enum_errors
1052        }
1053        impl std::error::Error for #err_enum {
1054            fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1055                match self {
1056                    #errors_sources
1057                    _ => None,
1058                }
1059            }
1060        }
1061        impl std::fmt::Display for #err_enum {
1062            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1063                match self {
1064                    #errors_display
1065                }
1066                Ok(())
1067            }
1068        }
1069        #errors_ctx
1070        #errors_ctx_into_error_impl
1071    }
1072    .into()
1073}