#![deny(missing_docs)]
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields, Ident, Type};
#[proc_macro_derive(Builder)]
pub fn builder_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let builder_name = Ident::new(&format!("{}Builder", name), Span::def_site());
let fields = match input.data {
Data::Struct(ref data_struct) => match data_struct.fields {
Fields::Named(ref fields_named) => &fields_named.named,
_ => {
return syn::Error::new_spanned(
&data_struct.fields,
"Builder can only be derived for structs with named fields",
)
.to_compile_error()
.into();
}
},
_ => {
return syn::Error::new_spanned(&name, "Builder can only be derived for structs")
.to_compile_error()
.into();
}
};
fn inner_type(ty: &Type) -> (bool, &Type) {
if let Type::Path(type_path) = ty {
if let Some(segment) = type_path.path.segments.first() {
if segment.ident == "Option" {
if let syn::PathArguments::AngleBracketed(ref angle_bracketed) =
segment.arguments
{
if let Some(syn::GenericArgument::Type(ref inner_ty)) =
angle_bracketed.args.first()
{
return (true, inner_ty);
}
}
}
}
}
(false, ty)
}
let builder_fields = fields.iter().map(|f| {
let name = &f.ident;
let ty = &f.ty;
let (_, inner_ty) = inner_type(ty);
quote! {
#name: std::option::Option<#inner_ty>,
}
});
let builder_init = fields.iter().map(|f| {
let name = &f.ident;
quote! {
#name: None,
}
});
let build_fields = fields.iter().map(|f| {
let name = &f.ident;
let ty = &f.ty;
let field_name_str = name.as_ref().unwrap().to_string();
let (is_option, _) = inner_type(ty);
if is_option {
quote! {
#name: self.#name.clone(),
}
} else {
quote! {
#name: self.#name.clone().ok_or_else(|| format!("Field '{}' is required", #field_name_str))?,
}
}
});
let builder_methods = fields.iter().map(|f| {
let name = &f.ident;
let ty = &f.ty;
let (_, inner_ty) = inner_type(ty);
let method_name = syn::Ident::new(
&format!("with_{}", name.as_ref().unwrap()),
Span::call_site(),
);
quote! {
pub fn #method_name(&mut self, #name: #inner_ty) -> &mut Self {
self.#name = std::option::Option::Some(#name);
self
}
}
});
let expanded = quote! {
#[derive(Debug)]
pub struct #builder_name {
#(#builder_fields)*
}
impl #builder_name {
#(#builder_methods)*
pub fn build(&self) -> Result<#name, std::boxed::Box<dyn std::error::Error>> {
Ok(#name {
#(#build_fields)*
})
}
}
impl #name {
pub fn builder() -> #builder_name {
#builder_name {
#(#builder_init)*
}
}
}
};
TokenStream::from(expanded)
}