aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/func/pack_err.rs
blob: 8b224b0d2bec92091ca33b540afda9d1a8ae3b25 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
use just_fmt::snake_case;
use proc_macro::TokenStream;
use quote::quote;
use syn::{Ident, Token, Type, parse_macro_input};

enum PackErrInput {
    /// pack_err!(ErrorNotFound)
    Simple { type_name: Ident },
    /// pack_err!(ErrorNotDir = PathBuf)
    Typed {
        type_name: Ident,
        inner_type: Box<Type>,
    },
}

impl syn::parse::Parse for PackErrInput {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let type_name: Ident = input.parse()?;

        if input.peek(Token![=]) {
            input.parse::<Token![=]>()?;
            let inner_type: Type = input.parse()?;
            Ok(PackErrInput::Typed {
                type_name,
                inner_type: Box::new(inner_type),
            })
        } else {
            Ok(PackErrInput::Simple { type_name })
        }
    }
}

#[allow(clippy::too_many_lines)]
pub(crate) fn pack_err(input: TokenStream) -> TokenStream {
    let parsed = parse_macro_input!(input as PackErrInput);

    match parsed {
        PackErrInput::Simple { type_name } => {
            let name_str = type_name.to_string();
            let snake_name = snake_case!(&name_str);

            // Note: No longer derives Serialize under structural_renderer.
            // Use pack_err_structural for structured output support.
            let derive = quote! {
                #[derive(::mingling::Grouped)]
            };

            let expanded = quote! {
                #derive
                pub struct #type_name {
                    /// The snake_case name of this error, automatically set at compile time.
                    pub name: String,
                }

                impl ::std::default::Default for #type_name {
                    fn default() -> Self {
                        Self {
                            name: #snake_name.into(),
                        }
                    }
                }

                ::mingling::macros::register_type!(#type_name);
            };

            expanded.into()
        }
        PackErrInput::Typed {
            type_name,
            inner_type,
        } => {
            let name_str = type_name.to_string();
            let snake_name = snake_case!(&name_str);

            // Note: No longer derives Serialize under structural_renderer.
            // Use pack_err_structural for structured output support.
            let derive = quote! {
                #[derive(::mingling::Grouped)]
            };

            let expanded = quote! {
                #derive
                pub struct #type_name {
                    /// The snake_case name of this error, automatically set at compile time.
                    pub name: String,
                    /// Additional context info for this error.
                    pub info: #inner_type,
                }

                impl #type_name {
                    /// Creates a new error with the given info.
                    /// The `name` field is automatically set to the snake_case of the struct name.
                    pub fn new(info: #inner_type) -> Self {
                        Self {
                            name: #snake_name.into(),
                            info,
                        }
                    }
                }

                ::mingling::macros::register_type!(#type_name);
            };

            expanded.into()
        }
    }
}