use darling::FromField;
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::{
Data, DataStruct, DeriveInput, Field, Fields, FieldsNamed, GenericArgument, Path, Type,
TypePath,
};
#[derive(Debug, Default, FromField)]
#[darling(default, attributes(builder))]
struct Opts {
each: Option<String>,
default: Option<String>,
}
struct Fd {
name: Ident,
ty: Type,
optional: bool,
opts: Opts,
}
pub struct BuilderContext {
name: Ident,
fields: Vec<Fd>,
}
impl From<Field> for Fd {
fn from(f: Field) -> Self {
let (optional, ty) = get_option_inner(&f.ty);
let opts = Opts::from_field(&f).unwrap_or_default();
Self {
opts,
name: f.ident.unwrap(),
optional,
ty: ty.to_owned(),
}
}
}
impl From<DeriveInput> for BuilderContext {
fn from(input: DeriveInput) -> Self {
let name = input.ident;
let fields = if let Data::Struct(DataStruct {
fields: Fields::Named(FieldsNamed { named, .. }),
..
}) = input.data
{
named
} else {
panic!("Unsupported data type");
};
let fds = fields.into_iter().map(Fd::from).collect();
Self { name, fields: fds }
}
}
impl BuilderContext {
pub fn render(&self) -> TokenStream {
let name = &self.name;
let builder_name = Ident::new(&format!("{}Builder", name), name.span());
let optionized_fields = self.gen_optionized_fields();
let methods = self.gen_methods();
let assigns = self.gen_assigns();
quote! {
#[derive(Debug, Default)]
pub struct #builder_name {
#(#optionized_fields,)*
}
impl #builder_name {
#(#methods)*
pub fn build(mut self) -> Result<#name, &'static str> {
Ok(#name {
#(#assigns,)*
})
}
}
impl #name {
pub fn builder() -> #builder_name {
Default::default()
}
}
}
}
fn gen_optionized_fields(&self) -> Vec<TokenStream> {
self.fields
.iter()
.map(|Fd { name, ty, .. }| quote! { #name: std::option::Option<#ty> })
.collect()
}
fn gen_methods(&self) -> Vec<TokenStream> {
self.fields
.iter()
.map(|f| {
let name = &f.name;
let ty = &f.ty;
if !f.optional && f.opts.each.is_some() {
let each = Ident::new(f.opts.each.as_deref().unwrap(), name.span());
let (is_vec, ty) = get_vec_inner(ty);
if is_vec {
return quote! {
pub fn #each(mut self, v: impl Into<#ty>) -> Self {
let mut data = self.#name.take().unwrap_or_default();
data.push(v.into());
self.#name = Some(data);
self
}
};
}
}
quote! {
pub fn #name(mut self, v: impl Into<#ty>) -> Self {
self.#name = Some(v.into());
self
}
}
})
.collect()
}
fn gen_assigns(&self) -> Vec<TokenStream> {
self.fields
.iter()
.map(|Fd { name, optional, opts, .. }| {
if *optional {
return quote! {
#name: self.#name.take()
};
}
if let Some(default) = opts.default.as_ref() {
let ast: TokenStream = default.parse().unwrap();
return quote! {
#name: self.#name.take().unwrap_or_else(|| #ast)
};
}
quote! {
#name: self.#name.take().ok_or(concat!(stringify!(#name), " needs to be set!"))?
}
})
.collect()
}
}
fn get_option_inner(ty: &Type) -> (bool, &Type) {
get_type_inner(ty, "Option")
}
fn get_vec_inner(ty: &Type) -> (bool, &Type) {
get_type_inner(ty, "Vec")
}
fn get_type_inner<'a>(ty: &'a Type, name: &str) -> (bool, &'a Type) {
if let Type::Path(TypePath {
path: Path { segments, .. },
..
}) = ty
{
if let Some(v) = segments.iter().next() {
if v.ident == name {
let t = match &v.arguments {
syn::PathArguments::AngleBracketed(a) => match a.args.iter().next() {
Some(GenericArgument::Type(t)) => t,
_ => panic!("Not sure what to do with other GenericArgument"),
},
_ => panic!("Not sure what to do with other PathArguments"),
};
return (true, t);
}
}
}
(false, ty)
}