apidoc_expand/
api_error.rs

1use procmeta::prelude::*;
2use syn::{DeriveInput, LitInt, LitStr};
3
4#[derive(MetaParser)]
5pub enum ParsedErrorFieldAttr {
6    #[name("error")]
7    Error(LitStr),
8}
9
10#[derive(MetaParser)]
11pub enum ParsedApiFieldAttr {
12    #[name("api_error")]
13    AssignApiError {
14        status: LitInt,
15        code: LitStr,
16        cause: LitStr,
17    },
18
19    // code, cause
20    #[name("api_error")]
21    BusinessCodeError(LitStr),
22
23    // code, cause
24    #[name("api_error")]
25    BusinessCodeCauseError(LitStr, LitStr),
26
27    #[name("api_error")]
28    FromError(FromError),
29}
30
31#[derive(MetaParser)]
32pub enum FromError {
33    #[name("from")]
34    Item(Expr),
35
36    #[name("from")]
37    AssignCauseItem(Expr, LitStr),
38}
39
40pub fn expand(input: &DeriveInput) -> Result<TokenStream> {
41    let data = &input.data;
42    let ty = &input.ident;
43    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
44
45    let mut collect_item_token = quote!();
46    let mut extend_token = quote!();
47    match data {
48        syn::Data::Struct(_) => unimplemented!(),
49        syn::Data::Enum(data) => {
50            for var in &data.variants {
51                let mut this_error: Option<ParsedErrorFieldAttr> = None;
52                let mut api_error: Option<ParsedApiFieldAttr> = None;
53                let attrs = &var.attrs;
54                for attr in attrs {
55                    let parsed_attr = ParsedErrorFieldAttr::try_from(attr);
56                    if let Ok(parsed_attr) = parsed_attr {
57                        this_error = Some(parsed_attr);
58                        continue;
59                    }
60
61                    let parsed_attr = ParsedApiFieldAttr::try_from(attr)?;
62                    if api_error.is_some() {
63                        return Err(Error::new(Span::call_site(), "dumplicate api_error"));
64                    }
65                    api_error = Some(parsed_attr);
66                }
67                if let Some(parsed_attr) = api_error {
68                    match parsed_attr {
69                        ParsedApiFieldAttr::AssignApiError {
70                            status,
71                            code,
72                            cause,
73                        } => {
74                            collect_item_token = quote! {
75                                #collect_item_token
76                                ApiErrorItem {
77                                    status: #status,
78                                    code: #code.into(),
79                                    cause: #cause.into(),
80                                },
81                            };
82                        }
83                        ParsedApiFieldAttr::BusinessCodeCauseError(code, cause) => {
84                            collect_item_token = quote! {
85                                #collect_item_token
86                                ApiErrorItem {
87                                    status: 200,
88                                    code: #code.into(),
89                                    cause: #cause.into(),
90                                },
91                            };
92                        }
93                        ParsedApiFieldAttr::FromError(inner_err) => match inner_err {
94                            FromError::Item(item) => {
95                                collect_item_token = quote! {
96                                    #collect_item_token
97                                    #item.into(),
98                                };
99                            }
100                            FromError::AssignCauseItem(item, cause) => {
101                                collect_item_token = quote! {
102                                    #collect_item_token
103                                    {
104                                        let item: ApiErrorItem = #item.into();
105                                        ApiErrorItem {
106                                            status: item.status,
107                                            code: item.code,
108                                            cause: #cause.into(),
109                                        }
110                                    },
111                                };
112                            }
113                        },
114                        ParsedApiFieldAttr::BusinessCodeError(code) => {
115                            let cause = this_error
116                                .ok_or(Error::new(Span::call_site(), "api_error miss cause"))?;
117                            let ParsedErrorFieldAttr::Error(cause) = cause;
118                            collect_item_token = quote! {
119                                #collect_item_token
120                                ApiErrorItem {
121                                    status: 200,
122                                    code: #code.into(),
123                                    cause: #cause.into(),
124                                },
125                            };
126                        }
127                    }
128                } else {
129                    let fields = &var.fields;
130                    if let syn::Fields::Unnamed(unnamed) = fields {
131                        if unnamed.unnamed.len() == 1 {
132                            let unnamed_ty = &unnamed.unnamed[0].ty;
133
134                            extend_token = quote! {
135                                #extend_token
136                                result.extend(<#unnamed_ty as ApiErrors>::api_errors());
137                            };
138                        }
139                    }
140                }
141            }
142        }
143        syn::Data::Union(_) => unimplemented!(),
144    }
145    let body_token = if extend_token.is_empty() {
146        quote!(vec![#collect_item_token])
147    } else {
148        quote! {
149            let mut result = vec![#collect_item_token];
150            #extend_token
151            result
152        }
153    };
154    let result = quote! {
155        impl #impl_generics  ApiErrors for #ty #ty_generics #where_clause {
156            fn api_errors() -> Vec<ApiErrorItem> {
157                #body_token
158            }
159        }
160    };
161    Ok(result)
162}