apidoc_expand/
api_model.rs

1use proc_macro2::TokenStream;
2use procmeta::prelude::*;
3use quote::quote;
4use roadblk_attr::{ConstraintType, Number};
5use roadblk_expand::{ConstraintTypeAttr, ValidateAttr};
6use syn::{Attribute, DeriveInput, LitStr};
7use syn::{Data, Fields, Result};
8
9use apidoc_attr::prop::ParsedApiDocAttr;
10use apidoc_attr::serde::ParsedSerdeAttr;
11
12#[derive(MetaParser)]
13pub enum NameAttr {
14    Name(LitStr),
15}
16
17#[derive(MetaParser)]
18pub enum ParsedApiUseAttr {
19    ApiUse(Type),
20}
21
22pub fn attrs_to_use_ty(attrs: &Vec<Attribute>) -> Result<Option<Type>> {
23    let mut result = None;
24    for attr in attrs {
25        let parsed = ParsedApiUseAttr::parse(&attr.meta);
26        if let Ok(api_use) = parsed {
27            if result.is_some() {
28                return Err(Error::new(Span::call_site(), "duplicate api_use"));
29            }
30            let ParsedApiUseAttr::ApiUse(ty) = api_use;
31            result = Some(ty);
32        }
33    }
34    Ok(result)
35}
36
37pub fn attrs_to_prop_token(attrs: &Vec<Attribute>) -> Result<TokenStream> {
38    let mut result = quote!();
39    for attr in attrs {
40        let parsed = ParsedApiDocAttr::parse(&attr.meta);
41        if let Ok(api_prop) = parsed {
42            result = api_prop.get_token_stream();
43        }
44    }
45    if result.is_empty() {
46        return Ok(quote!(None));
47    }
48    Ok(quote!(Some(#result)))
49}
50
51pub fn ident_to_name_token(ident: &Ident) -> TokenStream {
52    let name_str = ident.to_string();
53    quote!(#name_str.into())
54}
55
56fn get_constrait_token_stream(
57    new_models: &mut TokenStream,
58    attr_constraint_type: ConstraintTypeAttr,
59) -> Result<TokenStream> {
60    fn lit_to_number(lit: &Lit) -> Result<Number> {
61        let num = match lit {
62            Lit::Int(i) => Number::NegInt(i.base10_parse()?),
63            Lit::Float(f) => Number::Float(f.base10_parse()?),
64            _ => return Err(Error::new(Span::call_site(), "invalid number")),
65        };
66        Ok(num)
67    }
68
69    let result = match attr_constraint_type {
70        ConstraintTypeAttr::Length(min, max) => {
71            let min = min.base10_parse()?;
72            let max = max.base10_parse()?;
73            ConstraintType::Length(min, max).get_token_stream()
74        }
75        ConstraintTypeAttr::Range(min, max) => {
76            let min = lit_to_number(&min)?;
77            let max = lit_to_number(&max)?;
78            ConstraintType::Range(min, max).get_token_stream()
79        }
80        ConstraintTypeAttr::Validator => ConstraintType::SelfValidator.get_token_stream(),
81        ConstraintTypeAttr::SpecialValidator(ct) => {
82            quote!(#ct ::constraint_type())
83        }
84        ConstraintTypeAttr::Regex(r) => ConstraintType::Regex(r.value()).get_token_stream(),
85        ConstraintTypeAttr::Enumer(e) => {
86            *new_models = quote! {
87                #new_models
88                #e :: api_collect_ty(models);
89            };
90            quote!(ConstraintType::Enumer (#e :: api_model_id()))
91        }
92    };
93    Ok(result)
94}
95
96fn attrs_to_constraint_token(
97    new_models: &mut TokenStream,
98    field_ty: &Type,
99    attrs: &Vec<Attribute>,
100) -> Result<TokenStream> {
101    let mut result = quote!();
102    for attr in attrs {
103        let parsed = ValidateAttr::parse(&attr.meta);
104        if let Ok(parsed) = parsed {
105            let item_constraint = match parsed {
106                ValidateAttr::DefaultValidate | ValidateAttr::DefaultValidateWithMsg(_) => {
107                    quote!(#field_ty :: constraint_type())
108                }
109                ValidateAttr::Validate(ct) | ValidateAttr::ValidateWithMsg(ct, _) => {
110                    get_constrait_token_stream(new_models, ct)?
111                }
112            };
113            result = quote! {
114                #result
115                #item_constraint,
116            };
117        }
118    }
119    Ok(quote!(vec![#result]))
120}
121
122pub fn attrs_to_serde_token(attrs: &Vec<Attribute>) -> Result<TokenStream> {
123    let mut result = quote!();
124    for attr in attrs {
125        let parsed_attr = ParsedSerdeAttr::parse(&attr.meta);
126        if let Ok(parsed) = parsed_attr {
127            let serde_token_stream = parsed.get_token_stream();
128            result = quote! (#result #serde_token_stream,);
129        }
130    }
131    Ok(quote!(vec![#result]))
132}
133
134pub fn expand(input: DeriveInput) -> Result<TokenStream> {
135    let ty = &input.ident;
136    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
137    let impl_generics_token = impl_generics_join_trait(impl_generics, "ApiTyTrait")?;
138
139    let use_ty = attrs_to_use_ty(&input.attrs)?;
140    if let Some(ty) = use_ty {
141        return Ok(quote! {
142            impl #impl_generics_token  ApiTyTrait for #ty #ty_generics #where_clause {
143                fn api_collect_ty(models: &mut HashMap<String, Option<ApiModel>>) -> ApiTy {
144                    $ty :: api_collect_ty(models)
145                }
146            }
147        });
148    }
149    let name_str = ty.to_string();
150    let name_token = quote!(#name_str.into());
151    let desc_token = attrs_to_desc_token(&input.attrs)?;
152    let prop_token = attrs_to_prop_token(&input.attrs)?;
153    let serde_token = attrs_to_serde_token(&input.attrs)?;
154
155    let body_token = match &input.data {
156        Data::Struct(data) => impl_struct(name_token, desc_token, prop_token, serde_token, data)?,
157        Data::Enum(data) => impl_enum(name_token, desc_token, prop_token, serde_token, data)?,
158        Data::Union(_) => unimplemented!(),
159    };
160    let result = quote! {
161
162        impl #impl_generics_token  ApiTyTrait for #ty #ty_generics #where_clause {
163            fn api_collect_ty(models: &mut HashMap<String, Option<ApiModel>>) -> ApiTy {
164                #body_token
165            }
166        }
167
168    };
169    Ok(result)
170}
171
172fn impl_struct(
173    name_token: TokenStream,
174    desc_token: TokenStream,
175    prop_token: TokenStream,
176    serde_token: TokenStream,
177    data: &syn::DataStruct,
178) -> Result<TokenStream> {
179    let model_id_token = quote!(Self::api_model_id());
180    let model_token = get_model_token(
181        model_id_token.clone(),
182        name_token,
183        desc_token,
184        prop_token,
185        serde_token,
186        quote!(None),
187        &data.fields,
188    )?;
189    Ok(quote! {
190        #model_token
191        ApiTy::Struct {
192            model_id: #model_id_token
193        }
194    })
195}
196
197fn impl_enum(
198    name_token: TokenStream,
199    desc_token: TokenStream,
200    prop_token: TokenStream,
201    serde_token: TokenStream,
202    data: &syn::DataEnum,
203) -> Result<TokenStream> {
204    let mut variants_token = quote!();
205    let mut model_token = quote!();
206    for variant in data.variants.iter() {
207        let mut model_value_token = quote!(None);
208        if let Some((_, expr)) = &variant.discriminant {
209            model_value_token = quote!(Some(#expr));
210        }
211        let attrs = &variant.attrs;
212        let variant_name = variant.ident.to_string();
213        let model_name_token = quote!(#variant_name.into());
214        let model_prop_token = attrs_to_prop_token(attrs)?;
215        let model_serde_token = attrs_to_serde_token(attrs)?;
216        let fields = &variant.fields;
217        let model_id_token = quote!(format!("{}::{}", Self::api_model_id(), #variant_name));
218
219        let model_desc_token = attrs_to_desc_token(attrs)?;
220        let item_model_token = get_model_token(
221            model_id_token.clone(),
222            model_name_token,
223            model_desc_token,
224            model_prop_token,
225            model_serde_token,
226            model_value_token,
227            fields,
228        )?;
229        model_token = quote! {
230            #model_token
231            #item_model_token
232        };
233        variants_token = quote!(#variants_token #model_id_token,);
234    }
235    let result_token = quote! {
236        #model_token
237        ApiTy::Enumer(Box::new(
238            ApiEnumer {
239                ident: #name_token,
240                name: #desc_token,
241                prop: #prop_token,
242                serde: #serde_token,
243                variants: vec![#variants_token],
244            }
245        ))
246    };
247    Ok(result_token)
248}
249
250fn get_model_token(
251    model_id_token: TokenStream,
252    model_name_token: TokenStream,
253    model_desc_token: TokenStream,
254    model_prop_token: TokenStream,
255    model_serde_token: TokenStream,
256    model_value_token: TokenStream,
257    fields: &Fields,
258) -> Result<TokenStream> {
259    let mut fields_item_token = quote!();
260    let mut new_models_token = quote!();
261    for (index, field) in fields.iter().enumerate() {
262        let attrs = &field.attrs;
263        let field_prop_token = attrs_to_prop_token(attrs)?;
264        let field_serde_token = attrs_to_serde_token(attrs)?;
265        let use_ty = attrs_to_use_ty(attrs)?;
266        let mut field_ty = field.get_type_colon2();
267        if let Some(use_ty) = use_ty {
268            field_ty = use_ty;
269        }
270        let field_constraint_token =
271            attrs_to_constraint_token(&mut new_models_token, &field_ty, attrs)?;
272        let field_desc = attrs_to_desc_token(attrs)?;
273        match &field.ident {
274            Some(field_ident) => {
275                let field_name_token = ident_to_name_token(field_ident);
276                fields_item_token = quote! {
277                    #fields_item_token
278                    ApiNamedField {
279                        name: #field_desc,
280                        ident: #field_name_token,
281                        prop: #field_prop_token,
282                        option: #field_ty :: api_option(),
283                        ty: #field_ty :: api_collect_ty(models),
284                        constraint: #field_constraint_token,
285                        serde: #field_serde_token,
286                    },
287                };
288            }
289            None => {
290                fields_item_token = quote! {
291                    #fields_item_token
292                    ApiUnamedField {
293                        name: #field_desc,
294                        index: #index as i32,
295                        prop: #field_prop_token,
296                        option: #field_ty :: api_option(),
297                        ty: #field_ty :: api_collect_ty(models),
298                        constraint: #field_constraint_token,
299                        serde: #field_serde_token,
300                    },
301                };
302            }
303        }
304    }
305    let fields_token = match fields {
306        Fields::Named(_) => {
307            quote! {
308                ApiFields::Named(vec![#fields_item_token])
309            }
310        }
311        Fields::Unnamed(_) => {
312            quote! {
313                ApiFields::Unnamed(vec![#fields_item_token])
314            }
315        }
316        Fields::Unit => {
317            quote! {
318                ApiFields::Unit
319            }
320        }
321    };
322    let model_token = quote! {
323        ApiModel {
324            ident: #model_name_token,
325            name: #model_desc_token,
326            prop: #model_prop_token,
327            value: #model_value_token,
328            serde: #model_serde_token,
329            fields: #fields_token,
330        }
331    };
332    let result_token = quote! {
333        let model_id = #model_id_token;
334        if !models.contains_key(&model_id) {
335            models.insert(model_id.clone(), None);
336            let model = #model_token;
337            models.insert(model_id, Some(model));
338            #new_models_token
339        };
340    };
341    Ok(result_token)
342}
343
344fn attrs_to_desc_token(attrs: &[Attribute]) -> Result<TokenStream> {
345    let mut field_desc = None;
346    for attr in attrs {
347        let parsed_attr = NameAttr::try_from(attr);
348        if let Ok(parsed_attr) = parsed_attr {
349            if field_desc.is_some() {
350                return Err(Error::new(Span::call_site(), "desc attr duplicated"));
351            }
352            let NameAttr::Name(desc) = parsed_attr;
353            field_desc = Some(desc.value());
354        }
355    }
356    Ok(field_desc.get_token_stream())
357}