build_it/lib.rs
1//! This crate provides a derive-macro to generate the builder pattern for a struct.
2//! The builder implementation contains a method for each field of the struct, ignoring fields with
3//! the #[skip] attribute.
4//! Each field to generate a method for must be of type Option<T>. If any field is not of type
5//! Option<T>, and doesn't have the #[skip] attribute, the macro will panic.
6//!
7//!
8//! # Examples
9//! ```
10//! use build_it::Builder;
11//! #[derive(Default, Builder)]
12//! struct MyAwesomeStruct {
13//! name: Option<String>,
14//! pub age: Option<u32>,
15//! #[build_it(skip)]
16//! address: String,
17//!
18//! /// The `#[build_it(rename = "new_name")]` attribute can be used to rename the builder
19//! /// method. In this case, the builder method will be called `new_name` instead of
20//! /// `renamed`:
21//! /// `let builder = MyAwesomeStruct::default().new_name("Alice".to_string());`
22//! #[build_it(rename = "new_name")]
23//! renamed: Option<String>,
24//!
25//! /// The `#[build_it(into)]` attribute can be used to allow the builder method to accept
26//! /// types that can be converted into the field type. In this case, the builder method will
27//! /// accept a `&str` instead of a `String`:
28//! /// `let builder = MyAwesomeStruct::default().name_into("Alice");`
29//! #[build_it(into)]
30//! name_into: Option<String>,
31//!
32//! #[build_it(skip)]
33//! // NOTE: While the `#[skip]` attribute is still supported, it is deprecated in favor of
34//! // the `#[build_it(skip)]` attribute.
35//! pub phone: Option<String>,
36//! }
37//! let builder = MyAwesomeStruct::default()
38//! .name("Alice".to_string())
39//! .age(42);
40//! // Note that `address` and `phone` do not have builder methods because of the #[skip]
41//! // attribute.
42//! assert_eq!(builder.name, Some("Alice".to_string()));
43//! assert_eq!(builder.age, Some(42));
44//!
45//! // These fields are skipped, so they're value will still be the default value.
46//! assert_eq!(builder.address, String::default());
47//! assert_eq!(builder.phone, None);
48//!```
49//!
50//! The generated builder methods will also display the field's documentation:
51//! ```
52//! use build_it::Builder;
53//! #[derive(Default, Builder)]
54//! struct MyAwesomeStruct {
55//! /// Name of the person
56//! name: Option<String>,
57//! /// Age of the person
58//! age: Option<u32>,
59//! }
60//! ```
61//! This will generate the following builder methods:
62//! ```
63//! # struct MyAwesomeStruct {
64//! # name: Option<String>,
65//! # age: Option<u32>,
66//! # }
67//! impl MyAwesomeStruct {
68//! /// Name of the person
69//! pub fn name(mut self, name: String) -> Self {
70//! self.name = Some(name);
71//! self
72//! }
73//! /// Age of the person
74//! pub fn age(mut self, age: u32) -> Self {
75//! self.age = Some(age);
76//! self
77//! }
78//! }
79//!
80
81use proc_macro::TokenStream;
82use proc_macro2::Span;
83use quote::{quote, ToTokens};
84use syn::{parse_macro_input, spanned::Spanned, DeriveInput};
85
86type Fields = syn::punctuated::Punctuated<syn::Field, syn::token::Comma>;
87
88#[proc_macro_derive(Builder, attributes(build_it, skip))]
89/// Derive the builder pattern for a struct.
90///
91/// The builder implementation contains a method for each field of the struct, ignoring fields with
92/// a #[skip] attribute.
93/// Each field to generate a method for must be of type Option<T>.
94///
95/// # Example
96///
97/// The following struct:
98/// ```
99/// # use build_it::Builder;
100/// #[derive(Builder)]
101/// struct SimpleStruct {
102/// name: Option<String>,
103/// age: Option<u32>,
104/// #[build_it(skip)]
105/// address: String,
106/// }
107/// ```
108/// will generate the following implementation:
109/// ```
110/// # struct SimpleStruct {
111/// # name: Option<String>,
112/// # age: Option<u32>,
113/// # }
114/// impl SimpleStruct {
115/// pub fn name(mut self, name: String) -> Self {
116/// self.name = Some(name);
117/// self
118/// }
119/// pub fn age(mut self, age: u32) -> Self {
120/// self.age = Some(age);
121/// self
122/// }
123/// }
124/// ```
125pub fn derive_builder(input: TokenStream) -> TokenStream {
126 // Parse the input tokens into a syntax tree
127 let input = parse_macro_input!(input as DeriveInput);
128 let global_attr = parse_global_attr(&input);
129 let data = match input.data {
130 syn::Data::Struct(ref data) => Ok(data),
131 syn::Data::Enum(ref data) => Err(syn::Error::new(
132 data.enum_token.span(),
133 "Builder derive does not work on enums",
134 )),
135 syn::Data::Union(ref data) => Err(syn::Error::new(
136 data.union_token.span(),
137 "Builder derive does not work on unions",
138 )),
139 };
140 if let Err(err) = data {
141 return err.to_compile_error().into();
142 }
143 let data = data.expect("data is a struct");
144
145 let fields = match data.fields {
146 syn::Fields::Named(ref fields) => &fields.named,
147 syn::Fields::Unit => return quote! {}.into(),
148 syn::Fields::Unnamed(ref fields) => {
149 return syn::Error::new(
150 fields.span(),
151 "Builder derive only works on structs with named fields",
152 )
153 .to_compile_error()
154 .into();
155 }
156 };
157
158 generate_builder_impl(&input, &global_attr, fields).into()
159}
160
161/// Generate the builder implementation for a struct.
162/// The builder implementation contains a method for each field of the struct, ignoring fields with
163/// a #[build_it(skip)] attribute.
164///
165/// # Example
166///
167/// For a struct with fields `name: Option<String>` and `age: Option<u32>`, the generated
168/// implementation is:
169/// ```
170/// # struct SimpleStruct {
171/// # name: Option<String>,
172/// # age: Option<u32>,
173/// # }
174/// impl SimpleStruct {
175/// pub fn name(mut self, name: String) -> Self {
176/// self.name = Some(name);
177/// self
178/// }
179/// pub fn age(mut self, age: u32) -> Self {
180/// self.age = Some(age);
181/// self
182/// }
183/// }
184/// ```
185fn generate_builder_impl(
186 input: &DeriveInput,
187 global_attr: &GlobalAttr,
188 fields: &Fields,
189) -> proc_macro2::TokenStream {
190 let name = &input.ident;
191 let generics = &input.generics;
192 let methods = fields
193 .iter()
194 .map(|f| generate_builder_method(f, global_attr));
195 quote! {
196 impl #generics #name #generics {
197 #(#methods)*
198 }
199 }
200}
201
202/// Generate the builder method for a field.
203/// The method has the same name as the field and takes the field type by value.
204///
205/// # Example
206///
207/// For a field `name: Option<String>`, the generated method is:
208/// ```
209/// # struct SimpleStruct {
210/// # name: Option<String>,
211/// # }
212/// # impl SimpleStruct {
213/// pub fn name(mut self, name: String) -> Self {
214/// self.name = Some(name);
215/// self
216/// }
217/// # }
218/// ```
219fn generate_builder_method(
220 field: &syn::Field,
221 global_attr: &GlobalAttr,
222) -> proc_macro2::TokenStream {
223 // Skip fields with a #[skip] attribute
224 // NOTE: This is deprecated in favor of the `build_it` attribute
225 if field.attrs.iter().any(|attr| attr.path().is_ident("skip")) {
226 return quote! {};
227 }
228
229 let attr = parse_attr(field);
230 if attr.skip {
231 return quote! {};
232 }
233
234 let field_name = field.ident.as_ref().unwrap();
235 let fn_name = syn::Ident::new(
236 &attr.rename.unwrap_or(field_name.to_string()),
237 Span::call_site(),
238 );
239 let field_ty = get_inner_type(&field.ty);
240 if field_ty.is_none() {
241 return syn::Error::new(
242 field.span(),
243 "Builder only works on Option<T> fields. Consider using #[skip] to skip fields that should not be optional.",
244 )
245 .to_compile_error();
246 }
247 let field_ty = field_ty.expect("field type is an Option<T>");
248
249 let docs = field.attrs.iter().filter_map(|attr| {
250 if attr.path().is_ident("doc") {
251 Some(attr.clone())
252 } else {
253 None
254 }
255 });
256 if attr.into || global_attr.into {
257 quote! {
258 #(#docs)*
259 pub fn #fn_name(mut self, #field_name: impl core::convert::Into<#field_ty>) -> Self {
260 self.#field_name = Some(#field_name.into());
261 self
262 }
263 }
264 } else {
265 quote! {
266 #(#docs)*
267 pub fn #fn_name(mut self, #field_name: #field_ty) -> Self {
268 self.#field_name = Some(#field_name);
269 self
270 }
271 }
272 }
273}
274
275#[derive(Default)]
276struct GlobalAttr {
277 into: bool,
278}
279
280fn parse_global_attr(input: &DeriveInput) -> GlobalAttr {
281 let mut result = GlobalAttr::default();
282 let attr = input
283 .attrs
284 .iter()
285 .find(|attr| attr.path().is_ident("build_it"));
286 if let Some(attr) = attr {
287 attr.parse_nested_meta(|meta| {
288 if meta.path.is_ident("into") {
289 result.into = true;
290 }
291 Ok(())
292 })
293 .expect("Failed to parse global build_it attribute");
294 }
295 result
296}
297
298#[derive(Default)]
299struct Attr {
300 skip: bool,
301 into: bool,
302 rename: Option<String>,
303}
304
305fn parse_attr(field: &syn::Field) -> Attr {
306 let attr = field
307 .attrs
308 .iter()
309 .find(|attr| attr.path().is_ident("build_it"));
310 let mut result = Attr::default();
311 if let Some(attr) = attr {
312 attr.parse_nested_meta(|meta| {
313 if meta.path.is_ident("skip") {
314 result.skip = true;
315 } else if meta.path.is_ident("into") {
316 result.into = true;
317 } else if meta.path.is_ident("rename") {
318 let content = meta.value().expect("Expected a value");
319 let lit: syn::LitStr = content.parse()?;
320 result.rename = Some(lit.value());
321 }
322 Ok(())
323 })
324 .expect("Failed to parse build_it attribute");
325 }
326 result
327}
328
329/// Get the inner type of an Option<T> type.
330fn get_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
331 if let syn::Type::Path(ref type_path) = ty {
332 if let Some(segment) = type_path.path.segments.first() {
333 // Check if the type is an Option
334 if segment.ident == "Option" {
335 // Get the type inside the Option: the first generic argument
336 if let syn::PathArguments::AngleBracketed(ref args) = segment.arguments {
337 if let Some(syn::GenericArgument::Type(ref ty)) = args.args.first() {
338 return Some(ty);
339 }
340 }
341 }
342 }
343 }
344 None
345}