use proc_macro2::TokenStream;
use procmeta::prelude::*;
use quote::{quote, ToTokens};
use roadblk_attr::{ConstraintType, Number};
use roadblk_expand::{ConstraintTypeAttr, ValidateAttr};
use syn::{Attribute, DeriveInput};
use syn::{Data, Fields, Result};
use apidoc_attr::prop::ParsedApiDocAttr;
use apidoc_attr::serde::ParsedSerdeAttr;
pub fn attrs_to_prop_token(attrs: &Vec<Attribute>) -> Result<TokenStream> {
let mut result = quote!();
for attr in attrs {
let parsed = ParsedApiDocAttr::parse(&attr.meta);
if let Ok(api_prop) = parsed {
result = api_prop.get_token_stream();
}
}
if result.is_empty() {
return Ok(quote!(None));
}
Ok(quote!(Some(#result)))
}
pub fn ident_to_name_token(ident: &Ident) -> TokenStream {
let name_str = ident.to_string();
quote!(#name_str.into())
}
fn get_constrait_token_stream(
new_models: &mut TokenStream,
attr_constraint_type: ConstraintTypeAttr,
) -> Result<TokenStream> {
fn lit_to_number(lit: &Lit) -> Result<Number> {
let num = match lit {
Lit::Int(i) => Number::NegInt(i.base10_parse()?),
Lit::Float(f) => Number::Float(f.base10_parse()?),
_ => return Err(Error::new(Span::call_site(), "invalid number")),
};
Ok(num)
}
let result = match attr_constraint_type {
ConstraintTypeAttr::Length(min, max) => {
let min = min.base10_parse()?;
let max = max.base10_parse()?;
ConstraintType::Length(min, max).get_token_stream()
}
ConstraintTypeAttr::Range(min, max) => {
let min = lit_to_number(&min)?;
let max = lit_to_number(&max)?;
ConstraintType::Range(min, max).get_token_stream()
}
ConstraintTypeAttr::Validator => ConstraintType::SelfValidator.get_token_stream(),
ConstraintTypeAttr::SpecialValidator(ct) => {
quote!(#ct ::constraint_type())
}
ConstraintTypeAttr::Regex(r) => ConstraintType::Regex(r.value()).get_token_stream(),
ConstraintTypeAttr::Enumer(e) => {
*new_models = quote! {
#new_models
#e :: api_collect_ty(models);
};
quote!(ConstraintType::Enumer (#e :: api_model_id()))
}
};
Ok(result)
}
fn attrs_to_constraint_token(
new_models: &mut TokenStream,
field_ty: &Type,
attrs: &Vec<Attribute>,
) -> Result<TokenStream> {
let mut result = quote!();
for attr in attrs {
let parsed = ValidateAttr::parse(&attr.meta);
if let Ok(parsed) = parsed {
let item_constraint = match parsed {
ValidateAttr::DefaultValidate | ValidateAttr::DefaultValidateWithMsg(_) => {
quote!(#field_ty :: constraint_type())
}
ValidateAttr::Validate(ct) | ValidateAttr::ValidateWithMsg(ct, _) => {
get_constrait_token_stream(new_models, ct)?
}
};
result = quote! {
#result
#item_constraint,
};
}
}
Ok(quote!(vec![#result]))
}
pub fn attrs_to_serde_token(attrs: &Vec<Attribute>) -> Result<TokenStream> {
let mut result = quote!();
for attr in attrs {
let parsed_attr = ParsedSerdeAttr::parse(&attr.meta);
if let Ok(parsed) = parsed_attr {
let serde_token_stream = parsed.get_token_stream();
result = quote! (#result #serde_token_stream,);
}
}
Ok(quote!(vec![#result]))
}
pub fn expand(input: DeriveInput) -> Result<TokenStream> {
let ty = &input.ident;
let name_str = ty.to_string();
let name_token = quote!(#name_str.into());
let prop_token = attrs_to_prop_token(&input.attrs)?;
let serde_token = attrs_to_serde_token(&input.attrs)?;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let mut generics_token = impl_generics.into_token_stream();
if !generics_token.is_empty() {
let generics_token_str = generics_token.to_string();
let mut generics_token_str = generics_token_str.trim().to_string();
generics_token_str.remove(0);
generics_token_str.remove(generics_token_str.len() - 1);
let mut split_generics: Vec<String> = generics_token_str
.split(',')
.map(|t| t.to_string())
.collect();
for item_ty in split_generics.iter_mut() {
if item_ty.contains('\'') {
continue;
}
if item_ty.contains(':') {
item_ty.push_str("+ApiTyTrait");
} else {
item_ty.push_str(":ApiTyTrait");
}
}
let mut generics_token_str = split_generics.join(",");
generics_token_str.insert(0, '<');
generics_token_str.push('>');
generics_token = generics_token_str.parse()?;
}
let body_token = match &input.data {
Data::Struct(data) => impl_struct(name_token, prop_token, serde_token, data)?,
Data::Enum(data) => impl_enum(name_token, prop_token, serde_token, data)?,
Data::Union(_) => unimplemented!(),
};
let result = quote! {
impl #generics_token ApiTyTrait for #ty #ty_generics #where_clause {
fn api_collect_ty(models: &mut HashMap<String, Option<ApiModel>>) -> ApiTy {
#body_token
}
}
};
Ok(result)
}
fn impl_struct(
name_token: TokenStream,
prop_token: TokenStream,
serde_token: TokenStream,
data: &syn::DataStruct,
) -> Result<TokenStream> {
let model_id_token = quote!(Self::api_model_id());
let model_token = get_model_token(
model_id_token.clone(),
name_token,
prop_token,
serde_token,
quote!(None),
&data.fields,
)?;
Ok(quote! {
#model_token
ApiTy::Struct {
model_id: #model_id_token
}
})
}
fn impl_enum(
name_token: TokenStream,
prop_token: TokenStream,
serde_token: TokenStream,
data: &syn::DataEnum,
) -> Result<TokenStream> {
let mut variants_token = quote!();
let mut model_token = quote!();
for variant in data.variants.iter() {
let mut model_value_token = quote!(None);
if let Some((_, expr)) = &variant.discriminant {
model_value_token = quote!(Some(#expr));
}
let attrs = &variant.attrs;
let variant_name = variant.ident.to_string();
let model_name_token = quote!(#variant_name.into());
let model_prop_token = attrs_to_prop_token(attrs)?;
let model_serde_token = attrs_to_serde_token(attrs)?;
let fields = &variant.fields;
let model_id_token = quote!(format!("{}::{}", Self::api_model_id(), #variant_name));
let item_model_token = get_model_token(
model_id_token.clone(),
model_name_token,
model_prop_token,
model_serde_token,
model_value_token,
fields,
)?;
model_token = quote! {
#model_token
#item_model_token
};
variants_token = quote!(#variants_token #model_id_token,);
}
let result_token = quote! {
#model_token
ApiTy::Enumer(Box::new(
ApiEnumer {
name: #name_token,
prop: #prop_token,
serde: #serde_token,
variants: vec![#variants_token],
}
))
};
Ok(result_token)
}
fn get_model_token(
model_id_token: TokenStream,
model_name_token: TokenStream,
model_prop_token: TokenStream,
model_serde_token: TokenStream,
model_value_token: TokenStream,
fields: &Fields,
) -> Result<TokenStream> {
let mut fields_item_token = quote!();
let mut new_models_token = quote!();
for (index, field) in fields.iter().enumerate() {
let attrs = &field.attrs;
let field_prop_token = attrs_to_prop_token(attrs)?;
let field_serde_token = attrs_to_serde_token(attrs)?;
let field_ty = field.get_type_colon2();
let field_constraint_token =
attrs_to_constraint_token(&mut new_models_token, &field_ty, attrs)?;
let mut ty = field.ty.clone();
type_add_colon2(&mut ty);
match &field.ident {
Some(field_ident) => {
let field_name_token = ident_to_name_token(field_ident);
fields_item_token = quote! {
#fields_item_token
ApiNamedField {
name: #field_name_token,
prop: #field_prop_token,
option: #ty :: api_option(),
ty: #ty :: api_collect_ty(models),
constraint: #field_constraint_token,
serde: #field_serde_token,
},
};
}
None => {
fields_item_token = quote! {
#fields_item_token
ApiUnamedField {
index: #index as i32,
prop: #field_prop_token,
option: #ty :: api_option(),
ty: #ty :: api_collect_ty(models),
constraint: #field_constraint_token,
serde: #field_serde_token,
},
};
}
}
}
let fields_token = match fields {
Fields::Named(_) => {
quote! {
ApiFields::Named(vec![#fields_item_token])
}
}
Fields::Unnamed(_) => {
quote! {
ApiFields::Unnamed(vec![#fields_item_token])
}
}
Fields::Unit => {
quote! {
ApiFields::Unit
}
}
};
let model_token = quote! {
ApiModel {
name: #model_name_token,
prop: #model_prop_token,
value: #model_value_token,
serde: #model_serde_token,
fields: #fields_token,
}
};
let result_token = quote! {
let model_id = #model_id_token;
if !models.contains_key(&model_id) {
models.insert(model_id.clone(), None);
let model = #model_token;
models.insert(model_id, Some(model));
#new_models_token
};
};
Ok(result_token)
}