use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields, LitInt, LitStr};
#[proc_macro_derive(Searchable, attributes(searchable))]
pub fn derive_searchable(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let ident = &input.ident;
let mut index_name = ident.to_string().to_lowercase() + "s";
for attr in &input.attrs {
if attr.path().is_ident("searchable") {
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("index") {
let value: LitStr = meta.value()?.parse()?;
index_name = value.value();
}
Ok(())
});
}
}
let fields = match &input.data {
Data::Struct(s) => match &s.fields {
Fields::Named(f) => &f.named,
_ => {
return syn::Error::new(
Span::call_site(),
"#[derive(Searchable)] requires named fields",
)
.to_compile_error()
.into();
}
},
_ => {
return syn::Error::new(
Span::call_site(),
"#[derive(Searchable)] only supports structs",
)
.to_compile_error()
.into();
}
};
let mut field_entries = Vec::new();
for field in fields {
let field_name = field.ident.as_ref().unwrap().to_string();
let mut rank: Option<u8> = None;
for attr in &field.attrs {
if attr.path().is_ident("searchable") {
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("rank") {
let value: LitInt = meta.value()?.parse()?;
rank = Some(value.base10_parse::<u8>().map_err(|_| {
syn::Error::new(Span::call_site(), "rank must be a u8")
})?);
}
Ok(())
});
}
}
if let Some(r) = rank {
field_entries.push((field_name, r));
}
}
let searchable_fields_tokens: Vec<_> = field_entries
.iter()
.map(|(name, rank)| {
let rank_val = *rank;
quote! {
::rok_orm::search::SearchField {
name: #name.into(),
weight: ::rok_orm::search::RankWeight::from_rank(#rank_val),
}
}
})
.collect();
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let expanded = quote! {
impl #impl_generics ::rok_orm::search::Searchable for #ident #ty_generics #where_clause {
fn index_name() -> &'static str {
#index_name
}
fn searchable_fields() -> ::std::vec::Vec<::rok_orm::search::SearchField> {
vec![ #(#searchable_fields_tokens),* ]
}
}
};
TokenStream::from(expanded)
}