use proc_macro2::TokenStream;
use quote::quote;
use syn::DeriveInput;
pub fn field_meta_derive(input: TokenStream) -> TokenStream {
let input = syn::parse2::<DeriveInput>(input).unwrap();
let name = &input.ident;
let fields_info = match input.data {
syn::Data::Struct(data) => match data.fields {
syn::Fields::Named(fields) => fields
.named
.iter()
.map(|f| {
let name = f.ident.as_ref().unwrap().to_string();
let ty = f.ty.clone();
let attrs = FieldAttrs::from_attrs(&f.attrs);
FieldInfo { name, ty, attrs }
})
.collect::<Vec<_>>(),
_ => panic!("#[derive(FieldMeta)] is only defined for structs with named fields."),
},
_ => panic!("#[derive(FieldMeta)] is only defined for structs."),
};
let field_names = fields_info
.iter()
.filter(|f| !f.attrs.skip)
.map(|f| &f.name)
.collect::<Vec<_>>();
let mut aliases = std::collections::HashMap::new();
for f in &fields_info {
if let Some(alias) = &f.attrs.alias {
if aliases.contains_key(alias) {
panic!("Duplicate alias '{alias}' found in struct '{name}'");
}
aliases.insert(alias.clone(), f.name.clone());
}
}
let field_count = field_names.len();
let aliases = aliases
.iter()
.map(|(alias, original)| {
quote! {
(#alias, #original)
}
})
.collect::<Vec<_>>();
let alisas_count = aliases.len();
quote! {
impl #name {
const FIELDS: [&'static str; #field_count] = [#(#field_names),*];
const FIELD_ALIASES: [(&'static str, &'static str); #alisas_count] = [#(#aliases),*];
pub fn fields() -> &'static [&'static str] {
&Self::FIELDS
}
pub fn contains_field(field: &str) -> bool {
if Self::FIELDS.contains(&field) {
true
} else {
Self::FIELD_ALIASES.iter().any(|(alias, _)| alias == &field)
}
}
pub fn field_count() -> usize {
#field_count
}
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
struct FieldInfo {
name: String,
ty: syn::Type,
attrs: FieldAttrs,
}
#[derive(Debug, Clone, Default)]
struct FieldAttrs {
skip: bool,
alias: Option<String>,
}
impl FieldAttrs {
fn from_attrs(attrs: &[syn::Attribute]) -> Self {
let mut ret = Self::default();
for attr in attrs {
if attr.path().is_ident("field_meta") {
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("skip") {
ret.skip = true;
}
if meta.path.is_ident("alias") {
let value: syn::LitStr = meta.value()?.parse()?;
ret.alias = Some(value.value());
}
Ok(())
});
}
}
ret
}
}