use crate::parse::OptionsInput;
use crate::Result;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
pub fn generate_builder(input: &OptionsInput) -> Result<TokenStream> {
let struct_name = &input.name;
let builder_name = format_ident!("{}Builder", input.name);
let vis = &input.vis;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let struct_fields = input.fields.iter().map(|field| {
let field_name = &field.ident;
let full_type = &field.full_type;
quote! { #field_name: #full_type }
});
let setters = input.fields.iter().map(|field| {
let field_name = &field.ident;
let setter_name = format_ident!("with_{}", field_name);
let inner_type = &field.inner_type;
quote! {
#vis fn #setter_name(mut self, value: #inner_type) -> Self {
self.#field_name = Some(value);
self
}
}
});
let build_fields = input.fields.iter().map(|field| {
let field_name = &field.ident;
quote! { #field_name: self.#field_name }
});
let default_fields = input.fields.iter().map(|field| {
let field_name = &field.ident;
quote! { #field_name: None }
});
Ok(quote! {
#[automatically_derived]
#vis struct #builder_name #ty_generics #where_clause {
#(#struct_fields),*
}
#[automatically_derived]
impl #impl_generics #builder_name #ty_generics #where_clause {
#vis fn new() -> Self {
Self {
#(#default_fields),*
}
}
#(#setters)*
#[must_use]
#vis fn build(self) -> #struct_name #ty_generics {
#struct_name {
#(#build_fields),*
}
}
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parse::OptionsInput;
use quote::quote;
#[test]
fn builder_generated() {
let input: syn::DeriveInput = syn::parse_quote! {
#[options(layers(runtime, account))]
pub struct TestOptions {
pub field_a: Option<String>,
pub field_b: Option<u32>,
}
};
let parsed = OptionsInput::from_derive_input(&input).unwrap();
let tokens = generate_builder(&parsed).unwrap();
let expected = quote! {
#[automatically_derived]
pub struct TestOptionsBuilder {
field_a: Option<String>,
field_b: Option<u32>
}
#[automatically_derived]
impl TestOptionsBuilder {
pub fn new() -> Self {
Self {
field_a: None,
field_b: None
}
}
pub fn with_field_a(mut self, value: String) -> Self {
self.field_a = Some(value);
self
}
pub fn with_field_b(mut self, value: u32) -> Self {
self.field_b = Some(value);
self
}
#[must_use]
pub fn build(self) -> TestOptions {
TestOptions {
field_a: self.field_a,
field_b: self.field_b
}
}
}
};
assert_eq!(expected.to_string(), tokens.to_string());
}
}