auto_builder_core/
lib.rs

1use proc_macro2::TokenStream;
2use syn::{punctuated::Punctuated, token::Comma, DeriveInput, Field};
3
4pub struct BuilderBuilder {
5    /// The AST of the struct we're deriving the builder for
6    ast: DeriveInput,
7    // If `true`, the builder will set all fields to their default values. The deriving struct must implement `Default`.
8    should_default: bool,
9}
10
11impl BuilderBuilder {
12    pub fn new(ast: DeriveInput) -> Self {
13        let should_default = ast
14            .attrs
15            .iter()
16            .filter(|attr| attr.path().is_ident("builder"))
17            .any(|attr| {
18                if let Ok(value) = attr.parse_args::<TokenStream>() {
19                    value.to_string() == "default"
20                } else {
21                    false
22                }
23            });
24
25        Self {
26            ast,
27            should_default,
28        }
29    }
30
31    fn ident(&self) -> &syn::Ident {
32        &self.ast.ident
33    }
34
35    fn builder_name(&self) -> syn::Ident {
36        quote::format_ident!("{}Builder", self.ident())
37    }
38
39    fn builder_fields(&self) -> Vec<TokenStream> {
40        self.target_fields()
41            .iter()
42            .map(|field| {
43                let field_name = field.ident.as_ref().expect("Expected field name");
44                let field_type = &field.ty;
45                quote::quote!(
46                    #field_name: Option::<#field_type>,
47                )
48            })
49            .collect::<Vec<_>>()
50    }
51
52    fn builder_field_values(&self) -> Vec<TokenStream> {
53        match self.should_default {
54            true => self
55                .target_fields()
56                .iter()
57                .map(|field| {
58                    let field_name = field.ident.as_ref().expect("Expected field name");
59                    quote::quote!(
60                        #field_name: Some(Default::default()),
61                    )
62                })
63                .collect(),
64            false => self
65                .target_fields()
66                .iter()
67                .map(|field| {
68                    let field_name = field.ident.as_ref().expect("Expected field name");
69                    quote::quote!(
70                        #field_name: None,
71                    )
72                })
73                .collect(),
74        }
75    }
76
77    fn builder_methods(&self) -> Vec<TokenStream> {
78        self.target_fields()
79            .iter()
80            .map(|field| {
81                let field_name = field.ident.as_ref().expect("Expected field name");
82                let field_type = &field.ty;
83                quote::quote!(
84                    fn #field_name(&mut self, value: #field_type) -> &mut Self {
85                        self.#field_name = Some(value);
86                        self
87                    }
88                )
89            })
90            .collect()
91    }
92
93    fn builder_build_checks(&self) -> Vec<TokenStream> {
94        self.target_fields().iter().map(|field| {
95            let field_name = field.ident.as_ref().expect("Expected field name");
96            quote::quote!(
97                if self.#field_name.is_none() {
98                    return Err(format!("Expected field to be set: {}", stringify!(#field_name).to_string()));
99                }
100            )
101        }).collect::<Vec<_>>()
102    }
103
104    fn target_fields(&self) -> &Punctuated<Field, Comma> {
105        match self.ast.data {
106            syn::Data::Struct(syn::DataStruct {
107                fields: syn::Fields::Named(syn::FieldsNamed { ref named, .. }),
108                ..
109            }) => named,
110            _ => panic!("Builder macro only supports structs with named fields"),
111        }
112    }
113
114    fn target_field_values(&self) -> Vec<TokenStream> {
115        self.target_fields().iter().map(|field| {
116            let field_name = field.ident.as_ref().expect("Expected field name");
117            quote::quote!(
118                #field_name: self.#field_name.clone().expect(format!("Expected field to be set: {}", stringify!(#field_name).to_string()).as_str()),
119            )
120        }).collect()
121    }
122
123    pub fn build_builder(&self) -> TokenStream {
124        let ident = &self.ast.ident;
125
126        // Generate builder struct
127        let builder_struct_name = self.builder_name();
128        let builder_fields = self.builder_fields();
129        let builder_field_values = self.builder_field_values();
130        let builder_methods = self.builder_methods();
131        let instance_field_values = self.target_field_values();
132        let checks_for_builder = self.builder_build_checks();
133
134        quote::quote!(
135            #[derive(Clone, Debug)]
136            struct #builder_struct_name {
137                #(#builder_fields)*
138            }
139
140            #[allow(dead_code)]
141            impl #builder_struct_name {
142                fn new() -> #builder_struct_name {
143                    #builder_struct_name {
144                        #(#builder_field_values)*
145                    }
146                }
147
148                #(#builder_methods)*
149
150                fn build(&self) -> Result<#ident, String> {
151                    #(#checks_for_builder)*
152                    Ok(#ident {
153                        #(#instance_field_values)*
154                    })
155                }
156            }
157
158        )
159    }
160}