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}