use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::{quote, ToTokens};
use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields};
pub fn is_supported_map(type_string: &str) -> bool {
const ALL: [&str; 2] = ["BTreeMap", "HashMap"];
ALL.iter().any(|m| type_string.contains(&m.to_lowercase()))
}
fn struct_info_section(struct_name: Ident, struct_data: DataStruct) -> TokenStream {
let fields = match struct_data.fields {
Fields::Named(f) => f,
_ => {
return quote! {compile_error!("The InfoSection can only be derived for structs with named fields.")}.into()
}
};
let fields = fields
.named
.into_iter()
.map(|v| {
let is_dictionary =
is_supported_map(&v.ty.clone().into_token_stream().to_string().to_lowercase());
let name = v.ident.ok_or(
"Structs with unnamed fields are not supported by the InfoSection.".to_owned(),
)?;
Ok((is_dictionary, name))
})
.collect::<Result<Vec<_>, String>>();
let section_key_fields: Vec<_> = match fields {
Ok(ref f) => f.iter().filter(|i| !i.0).map(|i| i.1.clone()).collect(),
Err(e) => return quote! {compile_error!(#e)}.into(),
};
let section_dictionary_fields: Vec<_> = match fields {
Ok(f) => f.iter().filter(|i| i.0).map(|i| i.1.clone()).collect(),
Err(e) => return quote! {compile_error!(#e)}.into(),
};
let key_fields_names: Vec<_> = section_key_fields.iter().map(|v| v.to_string()).collect();
let dictionary_fields_names: Vec<_> = section_dictionary_fields
.iter()
.map(|v| v.to_string())
.collect();
quote! {
impl From<#struct_name> for valkey_module::OneInfoSectionData {
fn from(val: #struct_name) -> valkey_module::OneInfoSectionData {
let section_name = stringify!(#struct_name).to_owned();
let fields = vec![
#((
#key_fields_names.to_owned(),
valkey_module::InfoContextBuilderFieldTopLevelValue::from(val.#section_key_fields)
), )*
#((
#dictionary_fields_names.to_owned(),
valkey_module::InfoContextBuilderFieldTopLevelValue::Dictionary {
name: #dictionary_fields_names.to_owned(),
fields: valkey_module::InfoContextFieldBottomLevelData(
val.#section_dictionary_fields
.into_iter()
.map(|d| d.into())
.collect()),
}
), )*
];
(section_name, fields)
}
}
}
.into()
}
pub fn info_section(item: TokenStream) -> TokenStream {
let input: DeriveInput = parse_macro_input!(item);
let ident = input.ident;
match input.data {
Data::Struct(s) => struct_info_section(ident, s),
_ => {
quote! {compile_error!("The InfoSection derive can only be used with structs.")}.into()
}
}
}