sui_default_config/
lib.rsuse proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{
parse_macro_input, Attribute, Data, DataStruct, DeriveInput, Fields, FieldsNamed, Meta,
MetaList, MetaNameValue, NestedMeta,
};
#[allow(non_snake_case)]
#[proc_macro_attribute]
pub fn DefaultConfig(_attr: TokenStream, input: TokenStream) -> TokenStream {
let DeriveInput {
attrs,
vis,
ident,
generics,
data,
} = parse_macro_input!(input as DeriveInput);
let Data::Struct(DataStruct {
struct_token,
fields,
semi_token,
}) = data
else {
panic!("Default configs must be structs.");
};
let Fields::Named(FieldsNamed {
brace_token: _,
named,
}) = fields
else {
panic!("Default configs must have named fields.");
};
let fields_with_names: Vec<_> = named
.iter()
.map(|field| {
let Some(ident) = &field.ident else {
panic!("All fields must have an identifier.");
};
(ident, field)
})
.collect();
let fields = fields_with_names.iter().map(|(name, field)| {
let default = format!("{ident}::__default_{name}");
quote! { #[serde(default = #default)] #field }
});
let defaults = fields_with_names.iter().map(|(name, field)| {
let ty = &field.ty;
let fn_name = format_ident!("__default_{}", name);
let cfg = extract_cfg(&field.attrs);
quote! {
#[doc(hidden)] #cfg
fn #fn_name() -> #ty {
<Self as std::default::Default>::default().#name
}
}
});
let has_rename_all = attrs.iter().any(|attr| {
if !attr.path.is_ident("serde") {
return false;
};
let Ok(Meta::List(MetaList { nested, .. })) = attr.parse_meta() else {
return false;
};
nested.iter().any(|nested| {
if let NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, .. })) = nested {
path.is_ident("rename_all")
} else {
false
}
})
});
let rename_all = if !has_rename_all {
quote! { #[serde(rename_all = "kebab-case")] }
} else {
quote! {}
};
TokenStream::from(quote! {
#[derive(serde::Serialize, serde::Deserialize)]
#rename_all
#(#attrs)* #vis #struct_token #ident #generics {
#(#fields),*
} #semi_token
impl #ident {
#(#defaults)*
}
})
}
fn extract_cfg(attrs: &[Attribute]) -> Option<&Attribute> {
attrs.iter().find(|attr| {
let meta = attr.parse_meta().ok();
meta.is_some_and(|m| m.path().is_ident("cfg"))
})
}