error_enum/
lib.rs

1//! To generate [Display](core::fmt::Display) implementation and
2//! documentation comments for error types.
3//!
4//! |     Concept      |            Example             |
5//! |:----------------:|:------------------------------:|
6//! |      Number      |             `1234`             |
7//! |       Code       |            `E1234`             |
8//! |     Category     |              `E`               |
9//! |       Kind       |         `error[E1234]`         |
10//! | Message Category |        `error[E1234]: `        |
11//! |   Description    |        `Access denied.`        |
12//! |     Message      | `error[E1234]: Access denied.` |
13
14#![warn(rust_2021_compatibility, rustdoc::all, missing_docs)]
15
16#[cfg(feature = "colored")]
17use ansi_term::{Color, Style};
18use proc_macro::TokenStream;
19use proc_macro2::TokenStream as TokenStream2;
20use quote::{format_ident, quote, ToTokens};
21use std::iter::once;
22use syn::{
23    braced,
24    parse::{self, Parse},
25    parse_macro_input, Attribute, Fields, Generics, Ident, LitInt, LitStr, Meta, Token, Variant,
26    Visibility,
27};
28
29/// Configuration for each variant.
30#[derive(Clone, Copy, Debug)]
31struct Config<'i> {
32    category: &'i str,
33    nested: bool,
34    #[cfg(feature = "colored")]
35    style_prefix: Style,
36    #[cfg(feature = "colored")]
37    style_message: Style,
38}
39impl<'i> Config<'i> {
40    pub fn new(category: &'i str) -> Self {
41        let nested = false;
42        #[cfg(feature = "colored")]
43        {
44            let style_prefix = Style::default();
45            let style_message = Style::default();
46            // Just give it a default color.
47            #[cfg(feature = "colored")]
48            let style_prefix = style_prefix.fg(Color::Red);
49            Self {
50                category,
51                nested,
52                style_prefix,
53                style_message,
54            }
55        }
56        #[cfg(not(feature = "colored"))]
57        {
58            Self { category, nested }
59        }
60    }
61    #[cfg(feature = "colored")]
62    pub fn prefix(&self) -> ansi_term::Prefix {
63        self.style_prefix.prefix()
64    }
65    #[cfg(feature = "colored")]
66    pub fn infix(&self) -> ansi_term::Infix {
67        self.style_prefix.infix(self.style_message)
68    }
69    #[cfg(feature = "colored")]
70    pub fn suffix(&self) -> ansi_term::Suffix {
71        self.style_message.suffix()
72    }
73    pub fn on_category(&mut self, _category: &Ident) {}
74    pub fn on_attrs(&mut self, attrs: &Vec<Attribute>) {
75        for attr in attrs {
76            self.on_attr(attr)
77        }
78    }
79    #[cfg(feature = "colored")]
80    fn lit_str_to_color(str: &LitStr) -> Color {
81        let str = str.value();
82        match str.as_str() {
83            "black" => Color::Black,
84            "red" => Color::Red,
85            "green" => Color::Green,
86            "yellow" => Color::Yellow,
87            "blue" => Color::Blue,
88            "purple" => Color::Purple,
89            "cyan" => Color::Cyan,
90            "white" => Color::White,
91            _ => panic!("Unexpected color `{}`.", str),
92        }
93    }
94    #[cfg(feature = "colored")]
95    fn rgb_tuple_to_color(tuple: &syn::ExprTuple) -> Color {
96        assert!(
97            tuple.elems.len() == 3,
98            "RGB color should have 3 componenets."
99        );
100        let mut iter = tuple.elems.iter();
101        let mut get_component = || -> u8 {
102            let component = iter.next().unwrap();
103            if let syn::Expr::Lit(syn::ExprLit {
104                lit: syn::Lit::Int(int),
105                attrs: _attrs,
106            }) = component
107            {
108                int.base10_parse().expect("Invalid RGB code")
109            } else {
110                panic!("Unsupported expression in RGB code.");
111            }
112        };
113        Color::RGB(get_component(), get_component(), get_component())
114    }
115    fn on_attr(&mut self, attr: &Attribute) {
116        let res = self;
117        match &attr.meta {
118            Meta::List(_list) => {
119                unimplemented!("Attribute list.");
120            }
121            Meta::NameValue(name_value) => {
122                if let Some(_ident) = name_value.path.get_ident() {
123                    #[cfg(feature = "colored")]
124                    {
125                        let ident = _ident;
126                        if ident == "color" || ident == "foreground" || ident == "fg" {
127                            match &name_value.value {
128                                syn::Expr::Lit(literal) => {
129                                    if !literal.attrs.is_empty() {
130                                        eprintln!("Attributes in literal is ignored.");
131                                    }
132                                    match &literal.lit {
133                                        syn::Lit::Int(int) => {
134                                            res.style_prefix = res.style_prefix.fg(Color::Fixed(
135                                                int.base10_parse().expect("Invalid color."),
136                                            ));
137                                        }
138                                        syn::Lit::Str(str) => {
139                                            res.style_prefix =
140                                                res.style_prefix.fg(Self::lit_str_to_color(str))
141                                        }
142                                        _ => {
143                                            unimplemented!("Unsupported literal in MetaNameValue.")
144                                        }
145                                    }
146                                }
147                                syn::Expr::Tuple(tuple) => {
148                                    res.style_prefix =
149                                        res.style_prefix.fg(Self::rgb_tuple_to_color(tuple))
150                                }
151                                _ => unimplemented!("Unsupported expression in MetaNameValue."),
152                            }
153                        } else if ident == "background" || ident == "bg" {
154                            match &name_value.value {
155                                syn::Expr::Lit(literal) => {
156                                    if !literal.attrs.is_empty() {
157                                        eprintln!("Attributes in literal is ignored.");
158                                    }
159                                    match &literal.lit {
160                                        syn::Lit::Int(int) => {
161                                            res.style_prefix = res.style_prefix.on(Color::Fixed(
162                                                int.base10_parse().expect("Invalid color."),
163                                            ));
164                                        }
165                                        syn::Lit::Str(str) => {
166                                            res.style_prefix =
167                                                res.style_prefix.on(Self::lit_str_to_color(str));
168                                        }
169                                        _ => {
170                                            unimplemented!("Unsupported literal in MetaNameValue.")
171                                        }
172                                    }
173                                }
174                                syn::Expr::Tuple(tuple) => {
175                                    res.style_prefix =
176                                        res.style_prefix.on(Self::rgb_tuple_to_color(tuple))
177                                }
178                                _ => unimplemented!("Unsupported expression in MetaNameValue."),
179                            }
180                        }
181                    }
182                    #[cfg(not(feature = "colored"))]
183                    unimplemented!("Path in MetaNameValue.");
184                } else {
185                    unimplemented!("Path in MetaNameValue.");
186                }
187            }
188            Meta::Path(path) => {
189                if let Some(ident) = path.get_ident() {
190                    if ident == "nested" {
191                        res.nested = true;
192                    } else {
193                        #[cfg(feature = "colored")]
194                        {
195                            macro_rules! set_config {
196                                ($ident:ident) => {
197                                    if ident == stringify!($ident) {
198                                        res.style_message = res.style_message.$ident();
199                                    }
200                                };
201                            }
202                            set_config!(bold);
203                            set_config!(dimmed);
204                            set_config!(italic);
205                            set_config!(underline);
206                            set_config!(blink);
207                            set_config!(reverse);
208                            set_config!(hidden);
209                            set_config!(strikethrough);
210                        }
211                        #[cfg(not(feature = "colored"))]
212                        unimplemented!("Path in MetaNameValue.");
213                    }
214                } else {
215                    unimplemented!("Path in attribute.");
216                }
217            }
218        }
219    }
220}
221
222struct ErrorVariant {
223    variant: Variant,
224    msg: LitStr,
225}
226
227impl Parse for ErrorVariant {
228    fn parse(input: parse::ParseStream) -> syn::Result<Self> {
229        let variant = input.parse()?;
230        let msg = input.parse()?;
231        Ok(Self { variant, msg })
232    }
233}
234
235impl ErrorVariant {
236    fn to_tokens(&self, code: &str, tokens: &mut TokenStream2) {
237        let variant = &self.variant;
238        let msg = &self.msg;
239        let code = format!("{}: ", code);
240        tokens.extend(quote! {
241            #[doc = #code]
242            #[doc = #msg]
243            #variant,
244        })
245    }
246}
247
248impl ErrorVariant {
249    fn fmt_desc(&self) -> TokenStream2 {
250        let name = &self.variant.ident;
251        let msg = &self.msg;
252        match &self.variant.fields {
253            Fields::Named(fields) => {
254                let fields = fields
255                    .named
256                    .iter()
257                    .map(|field| field.ident.as_ref().unwrap());
258                quote! {
259                    Self::#name { #(#fields, )* } => {
260                        ::core::write!{f, #msg}?;
261                        ::core::result::Result::Ok(())
262                    }
263                }
264            }
265            Fields::Unnamed(unnamed) => {
266                let elements = unnamed
267                    .unnamed
268                    .iter()
269                    .enumerate()
270                    .map(|(i, _)| format_ident!("_{i}"))
271                    .collect::<Vec<_>>();
272                quote! {
273                    Self::#name ( #(#elements, )* ) => {
274                        ::core::write!{f, #msg, #(#elements, )*}?;
275                        ::core::result::Result::Ok(())
276                    }
277                }
278            }
279            Fields::Unit => {
280                quote! {
281                    Self::#name => {
282                        ::core::write!{f, #msg}?;
283                        ::core::result::Result::Ok(())
284                    }
285                }
286            }
287        }
288    }
289    fn get_desc(&self) -> TokenStream2 {
290        let name = &self.variant.ident;
291        let msg = &self.msg;
292        match &self.variant.fields {
293            Fields::Named(fields) => {
294                let fields = fields
295                    .named
296                    .iter()
297                    .map(|field| field.ident.as_ref().unwrap());
298                quote! {
299                    Self::#name { #(#fields, )* } => {
300                        ::std::format!{#msg}
301                    }
302                }
303            }
304            Fields::Unnamed(unnamed) => {
305                let elements = unnamed
306                    .unnamed
307                    .iter()
308                    .enumerate()
309                    .map(|(i, _)| format_ident!("_{i}"))
310                    .collect::<Vec<_>>();
311                quote! {
312                    Self::#name ( #(#elements, )* ) => {
313                        ::std::format!{#msg, #(#elements, )*}
314                    }
315                }
316            }
317            Fields::Unit => {
318                quote! {
319                    Self::#name => {
320                        ::std::format!{#msg}
321                    }
322                }
323            }
324        }
325    }
326    fn get_category(&self, cfg: Config) -> TokenStream2 {
327        let name = &self.variant.ident;
328        if cfg.nested {
329            quote! {
330                Self::#name (nested) => nested.get_category(),
331            }
332        } else {
333            let category = cfg.category;
334            quote! {
335                Self::#name {..} => #category,
336            }
337        }
338    }
339    fn get_number(&self, cfg: Config, number: String) -> TokenStream2 {
340        let name = &self.variant.ident;
341        if cfg.nested {
342            quote! {
343                Self::#name (nested) => {
344                    let number = nested.get_number();
345                    ::std::borrow::Cow::Owned(::std::format!("{}{}", #number, number))
346                }
347            }
348        } else {
349            quote! {
350                Self::#name {..} => ::std::borrow::Cow::Borrowed(#number),
351            }
352        }
353    }
354    fn get_code(&self, cfg: Config, number: String) -> TokenStream2 {
355        let name = &self.variant.ident;
356        if cfg.nested {
357            quote! {
358                Self::#name (nested) => {
359                    let number = nested.get_number();
360                    let category = nested.get_category().chars().next().unwrap().to_uppercase();
361                    ::std::borrow::Cow::Owned(::std::format!("{}{}{}", category, #number, number))
362                }
363            }
364        } else {
365            let category = cfg.category.chars().next().unwrap().to_uppercase();
366            let code = format!("{category}{number}");
367            quote! {
368                Self::#name {..} => ::std::borrow::Cow::Borrowed(#code),
369            }
370        }
371    }
372    fn get_prefix(&self, cfg: Config, number: String) -> TokenStream2 {
373        let name = &self.variant.ident;
374        // eprintln!("{:?}", cfg);
375        if cfg.nested {
376            quote! {
377                Self::#name (nested) => {
378                    let category = nested.get_category();
379                    let short = category.chars().next().unwrap().to_uppercase();
380                    let prefix = ::std::format!("{}[{}{}{}]", category, short, #number, nested.get_number());
381                    ::std::borrow::Cow::Owned(prefix)
382                },
383            }
384        } else {
385            let category = cfg.category;
386            let short = category.chars().next().unwrap().to_uppercase();
387            let prefix = format!("{}[{}{}]", category, short, &number);
388            quote! {
389                Self::#name {..} => ::std::borrow::Cow::Borrowed(#prefix),
390            }
391        }
392    }
393    #[cfg(feature = "colored")]
394    fn write_str(s: impl std::fmt::Display) -> TokenStream2 {
395        let s = s.to_string();
396        quote! {f.write_str(#s)?;}
397    }
398    fn fmt(&self, number: String, cfg: Config) -> TokenStream2 {
399        let name = &self.variant.ident;
400        let msg = &self.msg;
401        #[cfg(feature = "colored")]
402        let prefix = Self::write_str(cfg.prefix());
403        #[cfg(feature = "colored")]
404        let infix = Self::write_str(cfg.infix());
405        #[cfg(feature = "colored")]
406        let suffix = Self::write_str(cfg.suffix());
407        #[cfg(not(feature = "colored"))]
408        let prefix = quote! {};
409        #[cfg(not(feature = "colored"))]
410        let infix = quote! {};
411        #[cfg(not(feature = "colored"))]
412        let suffix = quote! {};
413        let get_prefix = if cfg.nested {
414            quote! {&self.get_prefix()}
415        } else {
416            let prefix = format!(
417                "{}[{}{}]",
418                cfg.category,
419                cfg.category.chars().next().unwrap().to_uppercase(),
420                number,
421            );
422            quote! {#prefix}
423        };
424        match &self.variant.fields {
425            Fields::Named(fields) => {
426                assert!(!cfg.nested, "Named fields can't be nested error.");
427                let fields = fields
428                    .named
429                    .iter()
430                    .map(|field| field.ident.as_ref().unwrap());
431                quote! {
432                    Self::#name { #(#fields, )* } => {
433                        #prefix
434                        f.write_str(#get_prefix)?;
435                        f.write_str(": ")?;
436                        #infix
437                        ::core::write!{f, #msg}?;
438                        #suffix
439                        ::core::result::Result::Ok(())
440                    }
441                }
442            }
443            Fields::Unnamed(unnamed) => {
444                if cfg.nested {
445                    assert_eq!(
446                        unnamed.unnamed.len(),
447                        1,
448                        "Nested error can consists of one unnamed fields.",
449                    );
450                    quote! {
451                        Self::#name ( nested ) => {
452                            #prefix
453                            f.write_str(#get_prefix)?;
454                            f.write_str(": ")?;
455                            #infix
456                            ::core::write!{f, #msg, nested.get_desc()}?;
457                            #suffix
458                            ::core::result::Result::Ok(())
459                        }
460                    }
461                } else {
462                    let elements = unnamed
463                        .unnamed
464                        .iter()
465                        .enumerate()
466                        .map(|(i, _)| format_ident!("_{i}"))
467                        .collect::<Vec<_>>();
468                    quote! {
469                        Self::#name ( #(#elements, )* ) => {
470                            #prefix
471                            f.write_str(#get_prefix)?;
472                            f.write_str(": ")?;
473                            #infix
474                            ::core::write!{f, #msg, #(#elements, )*}?;
475                            #suffix
476                            ::core::result::Result::Ok(())
477                        }
478                    }
479                }
480            }
481            Fields::Unit => {
482                assert!(!cfg.nested, "Unit can't be nested error.");
483                quote! {
484                    Self::#name => {
485                        #prefix
486                        f.write_str(#get_prefix)?;
487                        f.write_str(": ")?;
488                        #infix
489                        ::core::write!{f, #msg}?;
490                        #suffix
491                        ::core::result::Result::Ok(())
492                    }
493                }
494            }
495        }
496    }
497}
498
499enum ErrorTree {
500    Prefix(Vec<Attribute>, LitInt, LitStr, Vec<ErrorTree>),
501    Variant(Vec<Attribute>, LitInt, ErrorVariant),
502}
503
504impl Parse for ErrorTree {
505    fn parse(input: parse::ParseStream) -> syn::Result<Self> {
506        if input.peek2(LitStr) {
507            let attrs = input.call(Attribute::parse_outer)?;
508            let code = input.parse()?;
509            let desc = input.parse()?;
510            let children;
511            braced!(children in input);
512            let mut nodes = Vec::new();
513            while !children.is_empty() {
514                let node = children.parse()?;
515                nodes.push(node);
516            }
517            Ok(ErrorTree::Prefix(attrs, code, desc, nodes))
518        } else {
519            let attrs = input.call(Attribute::parse_outer)?;
520            let code = input.parse()?;
521            let variant = input.parse()?;
522            let _comma: Token![,] = input.parse()?;
523            Ok(ErrorTree::Variant(attrs, code, variant))
524        }
525    }
526}
527
528impl ErrorTree {
529    /// - [Config].
530    /// - Code.
531    /// - [ErrorVariant].
532    fn get_variants<'s>(
533        &'s self,
534        config: Config<'s>,
535        prenumber: &'_ str,
536    ) -> impl Iterator<Item = (Config<'s>, String, &'s ErrorVariant)> {
537        match self {
538            Self::Prefix(attrs, postnumber, _desc, children) => children
539                .iter()
540                .flat_map(|node| {
541                    let mut config = config.clone();
542                    config.on_attrs(attrs);
543                    node.get_variants(config, &format!("{prenumber}{postnumber}"))
544                })
545                .collect::<Vec<_>>()
546                .into_iter(),
547            Self::Variant(attrs, postnumber, var) => {
548                let code = format!("{prenumber}{postnumber}");
549                let mut config = config;
550                config.on_attrs(attrs);
551                vec![(config, code, var)].into_iter()
552            }
553        }
554    }
555    /// - Depth.
556    /// - Prefix.
557    /// - Variant name.
558    /// - Message (for [Display](core::fmt::Display)).
559    fn get_nodes<'s>(
560        &'s self,
561        config: Config<'s>,
562        prenumber: &str,
563        depth: usize,
564    ) -> impl Iterator<Item = (Config<'s>, usize, String, Option<String>, String)> {
565        match self {
566            Self::Prefix(_attrs, postnumber, desc, children) => {
567                let number = format!("{}{}", prenumber, postnumber);
568                once((config.clone(), depth, number.clone(), None, desc.value()))
569                    .chain(
570                        children
571                            .iter()
572                            .flat_map(|node| node.get_nodes(config.clone(), &number, depth + 1)),
573                    )
574                    .collect::<Vec<_>>()
575                    .into_iter()
576            }
577            Self::Variant(_attrs, postnumber, var) => {
578                let number = format!("{}{}", prenumber, postnumber);
579                vec![(
580                    config,
581                    depth,
582                    number,
583                    Some(var.variant.ident.to_string()),
584                    var.msg.value(),
585                )]
586                .into_iter()
587            }
588        }
589    }
590}
591
592struct ErrorEnum {
593    attrs: Vec<Attribute>,
594    vis: Visibility,
595    name: Ident,
596    generics: Generics,
597    variants: Vec<(Vec<Attribute>, Ident, String, LitStr, Vec<ErrorTree>)>,
598}
599
600impl ErrorEnum {
601    /// - [Config].
602    /// - Code.
603    /// - [ErrorVariant].
604    fn get_variants<'s>(&'s self) -> impl Iterator<Item = (Config, String, &'s ErrorVariant)> {
605        self.variants
606            .iter()
607            .flat_map(|(attrs, category, category_string, _, tree)| {
608                let mut config = Config::new(&category_string);
609                config.on_attrs(attrs);
610                config.on_category(category);
611                tree.iter()
612                    .flat_map(move |node| node.get_variants(config, ""))
613            })
614    }
615    /// - [Config].
616    /// - Depth.
617    /// - Prefix.
618    /// - Variant name.
619    /// - Message (for [Display](core::fmt::Display)).
620    fn get_nodes<'s>(&'s self) -> Vec<(Config, usize, String, Option<String>, String)> {
621        self.variants
622            .iter()
623            .flat_map(|(attrs, category, category_string, msg, tree)| {
624                let mut config = Config::new(&category_string);
625                config.on_category(category);
626                config.on_attrs(attrs);
627                once((config, 0, String::new(), None, msg.value())).chain(
628                    tree.iter()
629                        .flat_map(|node| node.get_nodes(config, "", 1))
630                        .collect::<Vec<_>>()
631                        .into_iter(),
632                )
633            })
634            .collect()
635    }
636}
637
638impl Parse for ErrorEnum {
639    fn parse(input: parse::ParseStream) -> syn::Result<Self> {
640        let attrs = input.call(Attribute::parse_outer)?;
641        let vis = input.parse()?;
642        let name = input.parse()?;
643        let generics = input.parse()?;
644        let mut variants = Vec::new();
645        while !input.is_empty() {
646            let attrs = input.call(Attribute::parse_outer)?;
647            let category: syn::Ident = input.parse()?;
648            let category_string = category.to_string();
649            let msg = input.parse()?;
650            let inner;
651            braced!(inner in input);
652            let mut trees = Vec::new();
653            while !inner.is_empty() {
654                let tree = inner.parse()?;
655                trees.push(tree);
656            }
657            assert!(inner.is_empty());
658            variants.push((attrs, category, category_string, msg, trees));
659        }
660        Ok(Self {
661            attrs,
662            vis,
663            generics,
664            name,
665            variants,
666        })
667    }
668}
669
670impl ToTokens for ErrorEnum {
671    fn to_tokens(&self, tokens: &mut TokenStream2) {
672        let attrs = &self.attrs;
673        let vis = &self.vis;
674        let name = &self.name;
675        let generics = &self.generics;
676        let doc = self
677            .get_nodes()
678            .into_iter()
679            .map(|(_config, depth, code, name, desc)| {
680                let indent = "  ".repeat(depth);
681                match name {
682                    Some(name) => format!("{indent}- `{code}`(**{name}**): {desc}"),
683                    None => format!("{indent}- `{code}`: {desc}"),
684                }
685            });
686        let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
687
688        let error_variants = self.get_variants().collect::<Vec<_>>();
689
690        let variants = {
691            let mut tokens = TokenStream2::new();
692            error_variants.iter().for_each(|(cfg, number, var)| {
693                var.to_tokens(&format!("{}{}", cfg.category, number), &mut tokens)
694            });
695            tokens
696        };
697        tokens.extend(quote! {
698            #[doc = "List of error variants:"]
699            #(
700                #[doc = #doc]
701            )*
702            #(#attrs)*
703            #vis enum #name #generics {
704                #variants
705            }
706        });
707
708        let fmt = self
709            .get_variants()
710            .map(|(cfg, code, variant)| variant.fmt(code, cfg));
711        tokens.extend(quote! {
712            impl #impl_generics ::core::fmt::Display for #name #ty_generics #where_clause {
713                fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
714                    match self {
715                        #(#fmt)*
716                    }
717                }
718            }
719        });
720
721        let get_category = self
722            .get_variants()
723            .map(|(cfg, _number, variant)| variant.get_category(cfg));
724        let get_number = self
725            .get_variants()
726            .map(|(cfg, number, variant)| variant.get_number(cfg, number));
727        let get_code = self
728            .get_variants()
729            .map(|(cfg, number, variant)| variant.get_code(cfg, number));
730        let get_prefix = self
731            .get_variants()
732            .map(|(cfg, number, variant)| variant.get_prefix(cfg, number));
733        let fmt_desc = self
734            .get_variants()
735            .map(|(_cfg, _number, variant)| variant.fmt_desc());
736        let get_desc = self
737            .get_variants()
738            .map(|(_cfg, _number, variant)| variant.get_desc());
739        tokens.extend(quote! {
740            impl #impl_generics #name #ty_generics #where_clause {
741                /// Write error category like `E`.
742                pub fn get_category(&self) -> &'static ::core::primitive::str {
743                    match self {
744                        #(#get_category)*
745                    }
746                }
747                /// Write error code number like `0000`.
748                pub fn get_number(&self) -> ::std::borrow::Cow<'static, str> {
749                    match self {
750                        #(#get_number)*
751                    }
752                }
753                /// Write error code like `E0000`.
754                pub fn get_code(&self) -> ::std::borrow::Cow<'static, str> {
755                    match self {
756                        #(#get_code)*
757                    }
758                }
759                /// Write error message prefix like `error[E0000]: `.
760                pub fn get_prefix(&self) -> ::std::borrow::Cow<'static, str> {
761                    match self {
762                        #(#get_prefix)*
763                    }
764                }
765                fn fmt_desc(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
766                    match self {
767                        #(#fmt_desc)*
768                    }
769                }
770                /// Get error description.
771                pub fn get_desc(&self) -> String {
772                    match self {
773                        #(#get_desc)*
774                    }
775                }
776            }
777        });
778    }
779}
780
781/// Define a new error type.
782///
783/// First, provide the name of the type.
784/// Second, provide each variant of the error by following order:
785/// 1. Code.
786/// 2. Attributes (optional).
787///     - `#[bold]` for bold text.
788///     - `#[dimmed]` for dimmed text.
789///     - `#[italic]` for italic text.
790///     - `#[underline]` for text with an underline.
791///     - `#[blink]` for blinking text.
792///     - `#[reverse]` for text with reversed color.
793///     - `#[hidden]` for hidden text.
794///     - `#[strikethrough]` for text with strikethrough.
795///     - `#[color = "<color name>"]` for overriding default color. Only 8 colors below are supported:
796///
797///         - `black`.
798///         - `red`.
799///         - `green`.
800///         - `yellow`.
801///         - `blue`.
802///         - `purple`.
803///         - `cyan`.
804///         - `white`.
805///
806///     - `#[color = FIXED_COLOR]` for overriding default color with a color code.
807///         See [ansi_term::Color].
808///     - `#[color = (R, G, B)]` for overriding default color with RGB.
809///     - `#[nested]` if it's a nested error generated by [error_enum](crate).
810/// 3. Name.
811/// 4. Fields.
812/// 5. Message.
813/// 6. A trailing comma.
814#[proc_macro]
815pub fn error_type(token: TokenStream) -> TokenStream {
816    let error = parse_macro_input!(token as ErrorEnum);
817    error.to_token_stream().into()
818}
819
820#[cfg(test)]
821mod tests {
822    use crate::ErrorEnum;
823    use quote::{quote, ToTokens};
824
825    #[test]
826    fn basic() {
827        let output: ErrorEnum = syn::parse2(quote! {
828            FileSystemError
829                E "错误" {
830                    01 FileNotFound {path: std::path::Path}
831                    "{path} not found.",
832                }
833        })
834        .unwrap();
835        let output = output.into_token_stream();
836        eprintln!("{:#}", output);
837    }
838
839    #[test]
840    #[cfg(feature = "colored")]
841    fn colored() {
842        let output: ErrorEnum = syn::parse2(quote! {
843            FileSystemError
844                #[fg = (0xaf, 0, 0)]
845                #[bg = (0xa8, 0xa8, 0xa8)]
846                E "错误" {
847                    01 FileNotFound {path: std::path::Path}
848                    "{path} not found.",
849                }
850                #[fg = 214]
851                #[bg = 025]
852                W "警告" {
853                    01 FileTooLarge {path: std::path::Path}
854                    "{path} is too large.",
855                }
856                #[color = "blue"]
857                H "提示" {
858                    01 FileNameSuggestion (std::path::Path)
859                    "{0} may be what you want.",
860                }
861        })
862        .unwrap();
863        let output = output.into_token_stream();
864        eprintln!("{:#}", output);
865    }
866
867    #[test]
868    #[cfg(feature = "colored")]
869    fn colorful() {
870        let output: ErrorEnum = syn::parse2(quote! {
871            ColoredError
872                #[bold]
873                #[dimmer]
874                E "错误" {
875                    #[fg = "black"]
876                    0 BlackError (u8)
877                        "{0} is not black.",
878                    #[bg = "red"]
879                    1 RedError (u8, u8)
880                        "{0} and {1} is red.",
881                    #[fg = "green"]
882                    #[bg = "yellow"]
883                    2 GreenYellowError
884                        "Code is green, while description is yellow.",
885                    #[color = "blue"]
886                    3 BlueError
887                        "I'm blue.",
888                    #[foreground = "purple"]
889                    #[background = "cyan"]
890                    4 PurpleCyanError
891                        "Purpule and cyan.",
892                    #[color = "white"]
893                    5 WhiteError { white: String }
894                        "All in white.",
895                }
896        })
897        .unwrap();
898        let output = output.into_token_stream();
899        eprintln!("{:#}", output);
900    }
901
902    #[test]
903    fn deep() {
904        let output: ErrorEnum = syn::parse2(quote! {
905            FileSystemError
906                E "错误" {
907                    0 "文件错误" {
908                        0 AccessDenied
909                        "无权限。",
910                    }
911                }
912        })
913        .unwrap();
914        let output = output.into_token_stream();
915        eprintln!("{:#}", output);
916    }
917
918    #[test]
919    fn nested() {
920        let output: ErrorEnum = syn::parse2(quote! {
921            FileSystemError
922                E "错误" {
923                    #[nested]
924                    01 FileError (FileError)
925                    "{0}",
926                }
927        })
928        .unwrap();
929        let output = output.into_token_stream();
930        eprintln!("{:#}", output);
931    }
932
933    #[test]
934    fn check_config() {
935        let output: ErrorEnum = syn::parse2(quote! {
936            FileSystemError
937                Error "错误" {
938                    #[nested]
939                    01 FileError (FileError)
940                    "{0}",
941                }
942        })
943        .unwrap();
944        for (config, number, _variant) in output.get_variants() {
945            assert_eq!(config.category, "Error");
946            #[cfg(feature = "colored")]
947            assert_eq!(
948                format!("{config:?}"),
949                r#"Config { category: "Error", nested: true, style_prefix: Style { fg(Red) }, style_message: Style {} }"#,
950            );
951            #[cfg(not(feature = "colored"))]
952            assert_eq!(
953                format!("{config:?}"),
954                "Config { category: 'E', nested: true }",
955            );
956            assert_eq!(number, "01");
957        }
958    }
959
960    #[test]
961    #[cfg(feature = "colored")]
962    #[should_panic]
963    fn rgb_2() {
964        let output: ErrorEnum = syn::parse2(quote! {
965            FileSystemError
966                #[color = (0, 0)]
967                E "错误" {
968                    01 FileError (FileError)
969                    "{0}",
970                }
971        })
972        .unwrap();
973        let output = output.into_token_stream();
974        eprintln!("{:#}", output);
975    }
976
977    #[test]
978    #[cfg(feature = "colored")]
979    #[should_panic]
980    fn rgb_wrong() {
981        let output: ErrorEnum = syn::parse2(quote! {
982            FileSystemError
983                #[color = (4usize, -3, 2*5)]
984                E "错误" {
985                    01 FileError (FileError)
986                    "{0}",
987                }
988        })
989        .unwrap();
990        let output = output.into_token_stream();
991        eprintln!("{:#}", output);
992    }
993
994    #[test]
995    #[cfg(feature = "colored")]
996    #[should_panic]
997    fn color_bool() {
998        let output: ErrorEnum = syn::parse2(quote! {
999            FileSystemError
1000                #[color = true]
1001                E "错误" {
1002                    01 FileError (FileError)
1003                    "{0}",
1004                }
1005        })
1006        .unwrap();
1007        let output = output.into_token_stream();
1008        eprintln!("{:#}", output);
1009    }
1010
1011    #[test]
1012    #[should_panic]
1013    fn attribute_list() {
1014        let output: ErrorEnum = syn::parse2(quote! {
1015            FileSystemError
1016                #[nested(true)]
1017                E "错误" {
1018                    01 FileError (FileError)
1019                    "{0}",
1020                }
1021        })
1022        .unwrap();
1023        let output = output.into_token_stream();
1024        eprintln!("{:#}", output);
1025    }
1026
1027    #[test]
1028    #[should_panic]
1029    fn path() {
1030        let output: ErrorEnum = syn::parse2(quote! {
1031            FileSystemError
1032                #[nest::ed]
1033                E "错误" {
1034                    01 FileError (FileError)
1035                    "{0}",
1036                }
1037        })
1038        .unwrap();
1039        let output = output.into_token_stream();
1040        eprintln!("{:#}", output);
1041    }
1042
1043    #[test]
1044    #[should_panic]
1045    fn unsupported_value() {
1046        let output: ErrorEnum = syn::parse2(quote! {
1047            FileSystemError
1048                #[fg = true || false]
1049                E "错误" {
1050                    01 FileError (FileError)
1051                    "{0}",
1052                }
1053        })
1054        .unwrap();
1055        let output = output.into_token_stream();
1056        eprintln!("{:#}", output);
1057    }
1058
1059    #[test]
1060    #[cfg(feature = "colored")]
1061    #[should_panic]
1062    fn wrong_color() {
1063        let output: ErrorEnum = syn::parse2(quote! {
1064            FileSystemError
1065                #[color = "blite"]
1066                E "错误" {
1067                    01 FileError (FileError)
1068                    "{0}",
1069                }
1070        })
1071        .unwrap();
1072        let output = output.into_token_stream();
1073        eprintln!("{:#}", output);
1074    }
1075}