use darling::FromField;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{DataStruct, Fields, Ident};
use crate::{
CustomField, FieldAttr,
rapira_field_attrs::{extract_idx_attr, has_rapira_with_attr, skip_attr},
};
pub fn parse(data: &DataStruct, name: Ident, path: &TokenStream) -> TokenStream {
let string_name = name.to_string();
match &data.fields {
Fields::Named(fields) => {
let named = &fields.named;
let mut entries: Vec<(u32, TokenStream)> = Vec::new();
let mut seq = 0u32;
for field in named {
if skip_attr(&field.attrs) {
continue;
}
let attr = match FieldAttr::from_field(field) {
Ok(attr) => attr,
Err(err) => return err.write_errors(),
};
if has_rapira_with_attr(&field.attrs) && attr.with_type.is_none() {
return syn::Error::new_spanned(
field,
"#[rapira(with = ..)] field requires #[get_type(with_type = T)] under schema-aware GetType",
)
.to_compile_error();
}
let mut attr_count = 0u8;
if attr.with_type.is_some() {
attr_count += 1;
}
if attr.unimplemented.is_present() {
attr_count += 1;
}
if attr.custom.is_some() {
attr_count += 1;
}
if attr_count > 1 {
return syn::Error::new_spanned(
field,
"only one of `with_type`, `unimplemented`, or `custom` may be set per field",
)
.to_compile_error();
}
let ident = field.ident.as_ref().unwrap().to_string();
let ty = &field.ty;
let typ;
if let Some(with_type) = &attr.with_type {
typ = quote! {
<#with_type as #path::GetType>::TYPE
};
} else if attr.unimplemented.is_present() {
typ = quote! {
#path::Typ::Custom("unimplemented", &[])
};
} else if let Some(CustomField { name, types }) = &attr.custom {
typ = quote! {
#path::Typ::Custom(#name, #types)
};
} else {
typ = quote! {
<#ty as #path::GetType>::TYPE
};
}
let field_idx = extract_idx_attr(&field.attrs).unwrap_or_else(|| {
let current = seq;
seq += 1;
current
});
entries.push((
field_idx,
quote! {
(#ident, #typ),
},
));
}
entries.sort_by_key(|(idx, _)| *idx);
let fields_stream: Vec<TokenStream> = entries.into_iter().map(|(_, ts)| ts).collect();
quote! {
impl #path::GetType for #name {
const TYPE: #path::Typ = {
let fields = &[
#(#fields_stream)*
];
#path::Typ::Struct(#path::StructType {
name: #string_name,
fields: #path::Fields::Named(fields),
})
};
}
}
}
Fields::Unnamed(fields) => {
let unnamed = &fields.unnamed;
if unnamed.len() == 1 {
let field = &unnamed[0];
let attr = match FieldAttr::from_field(field) {
Ok(attr) => attr,
Err(err) => return err.write_errors(),
};
let is_flatten = attr.flatten.is_present();
if is_flatten {
let typ = &field.ty;
return quote! {
impl #path::GetType for #name {
const TYPE: #path::Typ = <#typ as #path::GetType>::TYPE;
}
};
}
}
let fields_stream: Vec<TokenStream> = unnamed
.iter()
.map(|field| {
let typ = &field.ty;
quote! {
<#typ as #path::GetType>::TYPE,
}
})
.collect();
quote! {
impl #path::GetType for #name {
const TYPE: #path::Typ = {
let fields = &[
#(#fields_stream)*
];
#path::Typ::Struct(#path::StructType {
name: #string_name,
fields: #path::Fields::Unnamed(fields),
})
};
}
}
}
Fields::Unit => quote! {
impl #path::GetType for #name {
const TYPE: #path::Typ = #path::Typ::Scalar(#path::ScalarTyp::Void);
}
},
}
}