use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::DeriveInput;
use synext::*;
const BUILDER_SUFFIX: &str = "Builder";
const BUILDER_ATTR_NAME: &str = "builder";
const BUILDER_ATTR_CUSTOM_METHOD: &str = "method";
pub fn derive_builder_fields(derive_input: &DeriveInput) -> Vec<proc_macro2::TokenStream> {
let mut builder_fields = Vec::new();
let fields = try_parse_named_fields(&derive_input);
fields.iter().for_each(|field| {
let field_name = field.ident.clone().unwrap();
let field_type = &field.ty;
if try_predicate_is_option(field_type) {
let inner_type = try_unwrap_option(field_type);
let builder_field = quote! {
#field_name: ::std::option::Option<#inner_type>
};
builder_fields.push(builder_field);
} else if try_predicate_is_vec(field_type) {
let builder_field = quote! {
#field_name: #field_type
};
builder_fields.push(builder_field);
} else {
let builder_field = quote! {
#field_name: ::std::option::Option<#field_type>
};
builder_fields.push(builder_field);
}
});
builder_fields
}
pub fn derive_builder_setters(derive_input: &DeriveInput) -> Vec<proc_macro2::TokenStream> {
let mut builder_setters = Vec::new();
let fields = try_parse_named_fields(&derive_input);
fields.iter().for_each(|field| {
let field_name = field.ident.clone().unwrap();
let field_type = &field.ty;
if try_predicate_is_option(field_type) {
let inner_type = try_unwrap_option(field_type);
let builder_setter = quote! {
pub fn #field_name(mut self, #field_name: #inner_type) -> Self {
self.#field_name = ::std::option::Option::Some(#field_name);
self
}
};
builder_setters.push(builder_setter);
} else if try_predicate_is_vec(field_type) {
let builder_setter = quote! {
pub fn #field_name(mut self, #field_name: #field_type) -> Self {
self.#field_name.extend(#field_name);
self
}
};
builder_setters.push(builder_setter);
if let Ok(builder_method_opt) = try_extract_field_attribute_path_attribute(
BUILDER_ATTR_NAME,
BUILDER_ATTR_CUSTOM_METHOD,
field,
) {
if let Some(builder_method) = builder_method_opt {
let inner_type = try_unwrap_vec(field_type);
let builder_method_setter = quote! {
pub fn #builder_method(mut self, #builder_method: #inner_type) -> Self {
self.#field_name.push(#builder_method);
self
}
};
builder_setters.push(builder_method_setter);
}
}
} else {
let builder_setter = quote! {
pub fn #field_name(mut self, #field_name: #field_type) -> Self {
self.#field_name = ::std::option::Option::Some(#field_name);
self
}
};
builder_setters.push(builder_setter);
}
});
builder_setters
}
pub fn derive_builder_defaults(derive_input: &DeriveInput) -> Vec<proc_macro2::TokenStream> {
let mut builder_defaults = Vec::new();
let fields = try_parse_named_fields(&derive_input);
fields.iter().for_each(|field| {
let field_name = field.ident.clone().unwrap();
let field_type = &field.ty;
if try_predicate_is_vec(field_type) {
let builder_default = quote! {
#field_name: ::std::vec::Vec::new()
};
builder_defaults.push(builder_default);
} else {
let builder_default = quote! {
#field_name: ::std::option::Option::None
};
builder_defaults.push(builder_default);
}
});
builder_defaults
}
pub fn derive_build_field_checker(derive_input: &DeriveInput) -> Vec<proc_macro2::TokenStream> {
let mut build_field_checkers = Vec::new();
let fields = try_parse_named_fields(&derive_input);
fields.iter().for_each(|field| {
let field_name = field.ident.clone().unwrap();
let field_type = &field.ty;
if try_predicate_is_not_option_and_vec(field_type) {
let field_checker = quote! {
if self.#field_name.is_none() {
let err = format!("Missing field: `{}`!", stringify!(#field_name));
return ::std::result::Result::Err(err.into())
}
};
build_field_checkers.push(field_checker);
}
});
build_field_checkers
}
pub fn derive_build_fields(derive_input: &DeriveInput) -> Vec<proc_macro2::TokenStream> {
let mut build_fields = Vec::new();
let fields = try_parse_named_fields(&derive_input);
fields.iter().for_each(|field| {
let field_name = field.ident.clone().unwrap();
let field_type = &field.ty;
if try_predicate_is_option(field_type) {
let build_field = quote! {
#field_name: self.#field_name.clone().unwrap()
};
build_fields.push(build_field);
} else if try_predicate_is_vec(field_type) {
let build_field = quote! {
#field_name: self.#field_name.clone()
};
build_fields.push(build_field);
} else {
let build_field = quote! {
#field_name: self.#field_name.clone().unwrap()
};
build_fields.push(build_field);
}
});
build_fields
}
pub fn derive_builder(input: TokenStream) -> TokenStream {
let derive_input = try_derive_input(input);
let struct_name = &derive_input.ident;
let (impl_generics, ty_generics, where_clause) = &derive_input.generics.split_for_impl();
let visibility = derive_input.vis.clone();
let builder_name = format_ident!("{}{}", struct_name, BUILDER_SUFFIX);
let builder_fields = derive_builder_fields(&derive_input);
let builder_setters = derive_builder_setters(&derive_input);
let builder_defaults = derive_builder_defaults(&derive_input);
let build_field_checkers = derive_build_field_checker(&derive_input);
let build_fields = derive_build_fields(&derive_input);
let expanded = quote! {
#visibility struct #builder_name #ty_generics #where_clause {
#(
#builder_fields
),*
}
impl #impl_generics #struct_name #ty_generics #where_clause {
pub fn builder() -> #builder_name #ty_generics {
#builder_name {
#(
#builder_defaults
),*
}
}
}
impl #impl_generics #builder_name #ty_generics #where_clause {
#(
#builder_setters
)*
pub fn build(self) -> ::std::result::Result<#struct_name #ty_generics, ::std::boxed::Box<dyn std::error::Error + 'static>> {
#(
#build_field_checkers
)*
::std::result::Result::Ok(
#struct_name {
#(
#build_fields
),*
}
)
}
}
};
TokenStream::from(expanded)
}