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}