use crate::analysis::StructAnalysis;
use crate::generation::TokenGenerator;
use quote::quote;
use syn::Ident;
pub fn generate_regular_builder(
analysis: &StructAnalysis,
) -> syn::Result<proc_macro2::TokenStream> {
let token_generator = TokenGenerator::new(analysis);
generate_with_token_generator(&token_generator)
}
pub fn generate_with_token_generator(
token_generator: &TokenGenerator,
) -> syn::Result<proc_macro2::TokenStream> {
let builder_coordinator = RegularBuilderCoordinator::new(token_generator);
builder_coordinator.generate_complete_implementation()
}
struct RegularBuilderCoordinator<'a> {
token_generator: &'a TokenGenerator<'a>,
builder_name: String,
}
impl<'a> RegularBuilderCoordinator<'a> {
fn new(token_generator: &'a TokenGenerator<'a>) -> Self {
let struct_name = token_generator.analysis().struct_name();
let builder_name = format!("{struct_name}Builder");
Self {
token_generator,
builder_name,
}
}
fn generate_complete_implementation(&self) -> syn::Result<proc_macro2::TokenStream> {
let mut tokens = proc_macro2::TokenStream::new();
tokens.extend(self.generate_builder_struct()?);
tokens.extend(self.generate_struct_constructor_method()?);
tokens.extend(self.generate_default_implementation()?);
tokens.extend(self.generate_builder_implementation()?);
Ok(tokens)
}
fn generate_builder_struct(&self) -> syn::Result<proc_macro2::TokenStream> {
let builder_ident = syn::parse_str::<Ident>(&self.builder_name)?;
let analysis = self.token_generator.analysis();
let impl_generics = self.token_generator.impl_generics_tokens();
let type_generics = self.token_generator.type_generics_tokens();
let where_clause = self.token_generator.where_clause_tokens();
let field_declarations = self.generate_builder_field_declarations()?;
let doc = self.token_generator.generate_method_documentation(
&self.builder_name,
&{
let struct_name = analysis.struct_name();
format!("Builder struct for {struct_name} with optional field customization")
},
Some(&{
let build_method = analysis.struct_attributes().get_build_method_name();
format!("This builder provides a simple pattern for structs with only optional fields, allowing immediate construction via {build_method}() method.")
})
);
let debug_impl = self
.token_generator
.generate_debug_impl("e! { #builder_ident }, &type_generics);
let struct_visibility = self.token_generator.analysis().struct_visibility();
Ok(quote! {
#doc
#struct_visibility struct #builder_ident #impl_generics #where_clause {
#field_declarations
}
#debug_impl
})
}
fn generate_builder_field_declarations(&self) -> syn::Result<proc_macro2::TokenStream> {
let mut field_declarations = proc_macro2::TokenStream::new();
let analysis = self.token_generator.analysis();
for optional_field in analysis.optional_fields() {
let field_name = optional_field.name();
let field_type = optional_field.field_type();
let doc = self.token_generator.generate_field_documentation(
&optional_field.clean_name(),
"e! { #field_type }.to_string(),
false,
"Optional field",
);
field_declarations.extend(quote! {
#doc
#field_name: #field_type,
});
}
field_declarations.extend(self.token_generator.generate_phantom_data_field());
Ok(field_declarations)
}
fn generate_struct_constructor_method(&self) -> syn::Result<proc_macro2::TokenStream> {
let analysis = self.token_generator.analysis();
let struct_name = analysis.struct_name();
let builder_ident = syn::parse_str::<Ident>(&self.builder_name)?;
let impl_generics = self.token_generator.impl_generics_tokens();
let type_generics = self.token_generator.type_generics_tokens();
let where_clause = self.token_generator.where_clause_tokens();
let doc = self.token_generator.generate_method_documentation(
"builder",
"Creates a new builder for constructing an instance with optional field customization",
Some("All fields start with their default values and can be customized using setter methods.")
);
let const_kw = self.token_generator.const_keyword();
let is_const = self.token_generator.is_const_builder();
let builder_init = if is_const {
let default_field_init = self.generate_default_field_initializations()?;
quote! {
#builder_ident {
#default_field_init
}
}
} else {
quote! { #builder_ident::default() }
};
Ok(quote! {
impl #impl_generics #struct_name #type_generics #where_clause {
#doc
pub #const_kw fn builder() -> #builder_ident #type_generics {
#builder_init
}
}
})
}
fn generate_default_implementation(&self) -> syn::Result<proc_macro2::TokenStream> {
if self.token_generator.is_const_builder() {
return Ok(quote! {});
}
let builder_ident = syn::parse_str::<Ident>(&self.builder_name)?;
let impl_generics = self.token_generator.impl_generics_tokens();
let type_generics = self.token_generator.type_generics_tokens();
let where_clause = self.token_generator.where_clause_tokens();
let default_field_init = self.generate_default_field_initializations()?;
let doc = if self.token_generator.config().include_documentation {
quote! {
#[doc = "Creates a new builder with all fields initialized to their default values."]
}
} else {
quote! {}
};
Ok(quote! {
#doc
impl #impl_generics Default for #builder_ident #type_generics #where_clause {
fn default() -> Self {
Self {
#default_field_init
}
}
}
})
}
fn generate_default_field_initializations(&self) -> syn::Result<proc_macro2::TokenStream> {
let mut field_init = proc_macro2::TokenStream::new();
let analysis = self.token_generator.analysis();
for optional_field in analysis.optional_fields() {
let field_init_code = optional_field.generate_initialization(false)?;
field_init.extend(field_init_code);
}
field_init.extend(self.token_generator.generate_phantom_data_init());
Ok(field_init)
}
fn generate_builder_implementation(&self) -> syn::Result<proc_macro2::TokenStream> {
let builder_ident = syn::parse_str::<Ident>(&self.builder_name)?;
let impl_generics = self.token_generator.impl_generics_tokens();
let type_generics = self.token_generator.type_generics_tokens();
let where_clause = self.token_generator.where_clause_tokens();
let constructor_method = self.generate_constructor_method()?;
let setter_methods = self.generate_setter_methods()?;
let build_method = self.generate_build_method()?;
Ok(quote! {
impl #impl_generics #builder_ident #type_generics #where_clause {
#constructor_method
#setter_methods
#build_method
}
})
}
fn generate_constructor_method(&self) -> syn::Result<proc_macro2::TokenStream> {
let builder_ident = syn::parse_str::<Ident>(&self.builder_name)?;
let type_generics = self.token_generator.type_generics_tokens();
let doc = self.token_generator.generate_method_documentation(
"new",
"Creates a new builder with all fields at default values",
None,
);
let const_kw = self.token_generator.const_keyword();
let is_const = self.token_generator.is_const_builder();
let builder_init = if is_const {
let default_field_init = self.generate_default_field_initializations()?;
quote! {
#builder_ident {
#default_field_init
}
}
} else {
quote! { Self::default() }
};
Ok(quote! {
#doc
pub #const_kw fn new() -> #builder_ident #type_generics {
#builder_init
}
})
}
fn generate_setter_methods(&self) -> syn::Result<proc_macro2::TokenStream> {
let mut setter_methods = proc_macro2::TokenStream::new();
let analysis = self.token_generator.analysis();
let is_const = self.token_generator.is_const_builder();
let struct_setter_prefix = analysis.struct_attributes().get_setter_prefix();
let struct_impl_into = if is_const {
false
} else {
analysis.struct_attributes().get_impl_into()
};
for optional_field in analysis.optional_fields() {
if optional_field.should_generate_setter() {
let setter_method = optional_field.generate_setter_method(
&syn::parse_quote!(Self),
struct_setter_prefix,
struct_impl_into,
is_const,
)?;
setter_methods.extend(setter_method);
}
}
Ok(setter_methods)
}
fn generate_build_method(&self) -> syn::Result<proc_macro2::TokenStream> {
let analysis = self.token_generator.analysis();
let struct_name = analysis.struct_name();
let type_generics = self.token_generator.type_generics_tokens();
let struct_field_assignments = self.generate_struct_field_assignments()?;
let build_method_name = analysis.struct_attributes().get_build_method_name();
let build_method_ident = syn::parse_str::<Ident>(build_method_name)?;
let doc = self.token_generator.generate_method_documentation(
build_method_name,
"Builds the final instance",
Some("This method is immediately available since all fields are optional."),
);
let const_kw = self.token_generator.const_keyword();
Ok(quote! {
#doc
pub #const_kw fn #build_method_ident(self) -> #struct_name #type_generics {
#struct_name {
#struct_field_assignments
}
}
})
}
fn generate_struct_field_assignments(&self) -> syn::Result<proc_macro2::TokenStream> {
let mut assignments = proc_macro2::TokenStream::new();
let analysis = self.token_generator.analysis();
for optional_field in analysis.optional_fields() {
let field_name = optional_field.name();
assignments.extend(quote! {
#field_name: self.#field_name,
});
}
Ok(assignments)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::analysis::analyze_struct;
use syn::parse_quote;
#[test]
fn test_generate_regular_builder() {
let input = parse_quote! {
struct Example {
name: Option<String>,
count: i32,
active: bool,
}
};
let analysis = analyze_struct(&input).unwrap();
let result = generate_regular_builder(&analysis);
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains("struct"));
assert!(code.contains("Builder"));
assert!(code.contains("builder"));
assert!(code.contains("name"));
assert!(code.contains("count"));
assert!(code.contains("active"));
assert!(code.contains("build"));
assert!(code.contains("Default"));
}
#[test]
fn test_regular_builder_with_custom_defaults() {
let input = parse_quote! {
struct Example {
#[builder(default = "42")]
count: i32,
#[builder(default = "String::from(\"test\")")]
name: String,
}
};
let analysis = analyze_struct(&input).unwrap();
let result = generate_regular_builder(&analysis);
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains("42"));
assert!(code.contains("String") && code.contains("from"));
}
#[test]
fn test_regular_builder_with_skip_setter() {
let input = parse_quote! {
struct Example {
name: String,
#[builder(skip_setter, default = "Uuid::new_v4()")]
id: String,
}
};
let analysis = analyze_struct(&input).unwrap();
let result = generate_regular_builder(&analysis);
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains("pub fn name"));
assert!(!code.contains("pub fn id"));
assert!(code.contains("Uuid") && code.contains("new_v4"));
}
#[test]
fn test_regular_builder_with_generics() {
let input = parse_quote! {
struct Example<T: Clone> {
value: T,
name: String,
}
};
let analysis = analyze_struct(&input).unwrap();
let result = generate_regular_builder(&analysis);
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains("<"));
assert!(code.contains("T"));
assert!(code.contains("PhantomData"));
}
#[test]
fn test_regular_builder_with_custom_build_method() {
let input = parse_quote! {
#[builder(build_method = "create")]
struct Example {
name: String,
}
};
let analysis = analyze_struct(&input).unwrap();
let result = generate_regular_builder(&analysis);
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains("pub fn create"));
assert!(!code.contains("pub fn build("));
}
#[test]
fn test_coordinator_creation() {
let input = parse_quote! {
struct Example {
name: String,
}
};
let analysis = analyze_struct(&input).unwrap();
let token_generator = TokenGenerator::new(&analysis);
let coordinator = RegularBuilderCoordinator::new(&token_generator);
assert_eq!(coordinator.builder_name, "ExampleBuilder");
}
#[test]
fn test_regular_builder_empty_struct() {
let input = parse_quote! {
struct Empty {
}
};
if let Ok(analysis) = analyze_struct(&input) {
let result = generate_regular_builder(&analysis);
if let Ok(tokens) = result {
let code = tokens.to_string();
assert!(code.contains("struct"));
}
}
}
}