1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
use proc_macro2::TokenStream as TokenStream2;
use darling::{ast, util, FromDeriveInput, FromField, FromMeta};
use quote::{quote, ToTokens};
use syn::{Generics, Ident, Type};
// Step 1: Parsing attributes into intermediate structs.
// `FieldConfig` defines special configurations that can be applied to a field
// using the `#[config(...)]` attribute.
#[derive(Debug, FromMeta)]
enum FieldConfig {
// `#[config(optional)]`: Marks a field as optional in the builder.
// When building the config struct, if this field is None in the builder,
// it will default to `None` in the `Arc<Mutex<Option<T>>>`.
#[darling(rename = "optional")]
Optional,
// `#[config(iterator(dtype = Type, add_fn = "function_name"))]`:
// Marks a field as a collection and generates an `add_` method.
// `dtype`: Specifies the type of elements in the collection.
// `add_fn`: Optionally specifies the method name to add elements (e.g., "push", "insert"). Defaults to "push".
#[darling(rename = "iterator")]
Iterator {
dtype: Box<Type>,
add_fn: Option<String>,
},
}
// `ConfigField` represents a single field from the input struct.
// It's derived using `darling::FromField` to parse field-level attributes.
#[derive(Debug, FromField)]
#[darling(attributes(config))] // Specifies that attributes for this field are under `#[config(...)]`
struct ConfigField {
ident: Option<Ident>, // The identifier (name) of the field.
ty: Type, // The type of the field.
// `extra`: Captures any `FieldConfig` applied to this field via `#[config(...)]`.
extra: Option<FieldConfig>,
}
// `Config` represents the entire struct to which the `#[derive(Config)]` macro is applied.
// It's derived using `darling::FromDeriveInput`.
#[derive(Debug, FromDeriveInput)]
#[darling(attributes(config), supports(struct_named))] // Specifies struct-level attributes and that it only works on named structs.
pub struct Config {
ident: Ident, // The identifier (name) of the struct.
// `data`: Contains the fields of the struct, parsed into `ConfigField`.
// `ast::Data<util::Ignored, ConfigField>` means we only care about struct fields,
// and those fields are parsed into `ConfigField`.
data: ast::Data<util::Ignored, ConfigField>,
generics: Generics, // Generics of the input struct (e.g., `<T>`).
}
// Step 2: Generating Rust code (TokenStream2) from the parsed intermediate structs.
// `impl ToTokens for Config` is the main entry point for code generation for the entire struct.
impl ToTokens for Config {
fn to_tokens(&self, tokens: &mut TokenStream2) {
// Extract the fields from the parsed struct data.
// `take_struct()` ensures we are dealing with a struct with named fields.
let fields = &self
.data
.as_ref()
.take_struct()
.expect("Only available for structs");
// `name`: The original struct's identifier.
let name = &self.ident;
// `new_name`: The identifier for the generated config struct.
// If the original struct name starts with `_`, it's removed. Otherwise, "Config" is appended.
// e.g., `MyStruct` -> `MyStructConfig`, `_Internal` -> `InternalConfig`.
let new_name = match format!("{name}") {
n if n.starts_with("_") => Ident::new(&n[1..], name.span()),
n => Ident::new(&format!("{n}Config"), name.span()),
};
// `builder_name`: The identifier for the generated builder struct.
// e.g., `MyStructConfig` -> `MyStructConfigBuilder`.
let builder_name = Ident::new(&format!("{new_name}Builder"), new_name.span());
// --- Preparing iterators for code generation ---
// `fields_builders`: Generates the builder methods for each field (e.g., `fn field_name(self, value: Type) -> Self`).
let fields_builders = fields.iter().map(|f| f.builder());
// `fn_iter`: Generates getter/setter/adder methods for each field in the config struct.
// This delegates to `ConfigField::to_tokens`.
let fn_iter = fields.iter();
// `field_names*`: Iterators over the field identifiers, used in various parts of the generated code
// for defining struct fields, initializing them, etc.
let field_names = fields.iter().filter_map(|f| f.ident.as_ref());
let field_names2 = field_names.clone();
let field_names3 = field_names.clone();
let field_names4 = field_names.clone();
let field_names5 = field_names.clone();
// `ok_or_error`: Generates the logic for initializing fields in the config struct from the builder.
// Handles required fields (panic if None), optional fields, and iterator fields (default if None).
let ok_or_error = fields.iter().map(|f| f.ok_panic_default());
// `field_none`: Generates `field_name: None::<Type>` for initializing builder fields to `None`.
let field_none = fields.iter().map(|f| f.field_none());
// `field_type*`: Iterators over field types.
let field_type = fields.iter().map(|f| &f.ty);
let field_type2 = field_type.clone();
// `generics`: Original struct's generics.
let generics = &self.generics;
// `split_for_impl`: Splits generics into parts needed for `impl` blocks (e.g., `impl<T>`, ` <T>`, `where T: Clone`).
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
// --- Code Generation using `quote!` ---
// The `quote!` macro takes Rust-like syntax and generates `TokenStream2`.
// Variables prefixed with `#` are interpolated. `#(...)*` repeats for each item in an iterator.
tokens.extend(quote! {
// Define the generated Config struct (e.g., `MyStructConfig`).
// Each field is wrapped in `Arc<Mutex<T>>` to allow shared mutable access
// across different parts of an application, ensuring thread safety.
#[derive(Clone)] // Clone is derived to allow cloning the config (which clones the Arcs).
pub struct #new_name #generics {
#(#field_names: ::std::sync::Arc<::std::sync::Mutex<#field_type>>),*
}
// Define the generated Builder struct (e.g., `MyStructConfigBuilder`).
// Each field is an `Option<T>`, allowing for partial construction.
// Fields are set individually, and then `build()` is called.
pub struct #builder_name #generics {
#(#field_names2: ::std::option::Option<#field_type2>),*
}
// Implement a `builder()` method on the original struct.
// This allows transitioning from an instance of the original struct to its builder.
// e.g., `let my_struct_builder = my_struct_instance.builder();`
impl #impl_generics #name #ty_generics #where_clause {
pub fn builder(self) -> #builder_name #ty_generics {
#builder_name::from(self) // Delegates to `From<OriginalStruct> for BuilderStruct`
}
}
// Implement methods (getters, setters, adders) on the generated Config struct.
// This iterates through `fn_iter` which calls `ConfigField::to_tokens` for each field.
impl #impl_generics #new_name #ty_generics #where_clause {
#(#fn_iter)*
}
// Implement methods on the generated Builder struct.
impl #impl_generics #builder_name #ty_generics #where_clause {
// Field setter methods for the builder (fluent interface).
// e.g., `builder.field1(value1).field2(value2)`
#(#fields_builders)*
// `new()`: Constructor for the builder, initializing all fields to `None`.
pub fn new() -> #builder_name #ty_generics {
Self {
#(#field_none),* // Initializes each field_name: Option::None::<FieldType>
}
}
// `build()`: Consumes the builder and attempts to create an instance of the Config struct.
// Returns `anyhow::Result` to handle potential errors (e.g., a required field not set).
pub fn build(self) -> ::anyhow::Result<#new_name #ty_generics> {
#new_name::try_from(self) // Delegates to `TryFrom<BuilderStruct> for ConfigStruct`
}
}
// Implement `Default` for the Builder struct, making `Builder::new()` the default.
impl #impl_generics ::std::default::Default for #builder_name #ty_generics #where_clause {
fn default() -> Self {
Self::new()
}
}
// Implement `From<OriginalStruct> for ConfigStruct`.
// Converts an instance of the original struct directly into a Config struct.
// Each field from the original struct is wrapped in `Arc::new(Mutex::new(...))`.
impl #impl_generics From<#name #ty_generics> for #new_name #ty_generics #where_clause {
fn from(value: #name #ty_generics) -> Self {
Self {
#(#field_names3: ::std::sync::Arc::new(::std::sync::Mutex::new(value.#field_names3))),*
}
}
}
// Implement `From<OriginalStruct> for BuilderStruct`.
// Converts an instance of the original struct into a Builder struct.
// Each field from the original struct is wrapped in `Some(...)`.
impl #impl_generics From<#name #ty_generics> for #builder_name #ty_generics #where_clause {
fn from(value: #name #ty_generics) -> Self {
Self {
#(#field_names4: ::std::option::Option::Some(value.#field_names4)),*
}
}
}
// Implement `TryFrom<ConfigStruct> for OriginalStruct`.
// Converts a Config struct back into an instance of the original struct.
// This involves locking each Mutex and cloning the inner value.
// Returns `Result` because locking a Mutex can fail (if poisoned).
impl #impl_generics TryFrom<#new_name #ty_generics> for #name #ty_generics #where_clause {
type Error = ::anyhow::Error;
fn try_from(value: #new_name #ty_generics) -> ::std::result::Result<Self, Self::Error> {
Ok(
Self {
// For each field, lock the mutex, handle potential poison error, and clone the value.
#(#field_names5: value.#field_names5.lock().map_err(|e| ::anyhow::anyhow!("Poison error {e}"))?.clone()),*
}
)
}
}
// Implement `TryFrom<BuilderStruct> for ConfigStruct`.
// This is the core logic for building the Config struct from the Builder.
// It uses `ok_or_error` (which calls `ConfigField::ok_panic_default`) to handle
// how each field is initialized based on its configuration (required, optional, iterator).
impl #impl_generics TryFrom<#builder_name #ty_generics> for #new_name #ty_generics #where_clause {
type Error = ::anyhow::Error;
fn try_from(value: #builder_name #ty_generics) -> ::std::result::Result<Self, Self::Error> {
Ok(
Self {
// `ok_or_error` generates the initialization logic for each field.
// e.g., for a required field: `Arc::new(Mutex::new(value.field_name.ok_or("error")?))`
// e.g., for an optional field: `Arc::new(Mutex::new(value.field_name.unwrap_or(None)))`
// e.g., for an iterator field: `Arc::new(Mutex::new(value.field_name.unwrap_or_default())))`
#(#ok_or_error),*
}
)
}
}
});
}
}
// `impl ToTokens for ConfigField` generates the methods for a single field
// within the `impl ConfigStruct { ... }` block.
impl ToTokens for ConfigField {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = self.ident.as_ref().expect("Only fields with ident allowed");
let dtype = &self.ty; // The type of the field, e.g., `String`, `Vec<u32>`, `Option<i32>`.
// Generate `set_field_name` method.
let set_name = Ident::new(&format!("set_{name}"), name.span());
// Generate `get_field_name` method.
let get_name = Ident::new(&format!("get_{name}"), name.span());
// `extra`: Handles special code generation for `Iterator` fields.
let extra = if let Some(FieldConfig::Iterator {
dtype: iterator_item_type,
add_fn,
}) = &self.extra
{
// If the field is configured as an iterator `#[config(iterator(dtype = ...))]`
// `add_name`: Name of the method to add items, e.g., `add_my_vec`.
let add_name = Ident::new(&format!("add_{name}"), name.span());
// `add_fn_ident`: The actual function to call on the collection, e.g., `push`, `insert`.
// Defaults to `push` if not specified in `#[config(iterator(add_fn = "..."))]`.
let add_fn_ident = if let Some(add) = add_fn {
Ident::new(add, name.span())
} else {
Ident::new("push", name.span())
};
// Generate the `add_field_name` method.
// It locks the Mutex, calls the specified `add_fn_ident` on the collection, and returns `Result`.
quote! {
pub fn #add_name(&self, value: #iterator_item_type) -> ::anyhow::Result<()> {
let mut field = self.#name.lock().map_err(|e| ::anyhow::anyhow!("Poison error {e}"))?;
field.#add_fn_ident(value); // e.g., field.push(value)
Ok(())
}
}
} else {
// If not an iterator field, no extra methods are generated here.
quote! {}
};
tokens.extend(quote! {
// Append the `add_` method if generated.
#extra
// Generate the `set_field_name` method.
// It locks the Mutex and replaces the entire value.
// `value` here is of `dtype` (the full type of the field, e.g., `Vec<String>`).
pub fn #set_name(&self, value: #dtype) -> ::anyhow::Result<()> {
let mut field = self.#name.lock().map_err(|e| ::anyhow::anyhow!("Poison error {e}"))?;
*field = value;
Ok(())
}
// Generate the `get_field_name` method.
// It locks the Mutex and clones the inner value.
// Returns `Result<FieldType>` to handle potential Mutex poison errors.
pub fn #get_name(&self) -> ::anyhow::Result<#dtype> {
Ok(self.#name.lock().map_err(|e| ::anyhow::anyhow!("Poison error {e}"))?.clone())
}
});
}
}
// Helper methods for `ConfigField` used during token generation by `Config::to_tokens`.
impl ConfigField {
// `builder()`: Generates the fluent setter method for this field in the Builder struct.
// e.g., `pub fn field_name(mut self, value: FieldType) -> Self { self.field_name = Some(value); self }`
fn builder(&self) -> TokenStream2 {
let name = self.ident.as_ref().expect("should have a name");
let dtype = &self.ty;
quote! {
pub fn #name(mut self, value: #dtype) -> Self {
self.#name = Some(value);
self
}
}
}
// `field_none()`: Generates the initialization for this field in the Builder's `new()` method.
// e.g., `field_name: ::std::option::Option::None::<FieldType>`
fn field_none(&self) -> TokenStream2 {
let name = self.ident.as_ref().expect("should have a name");
let dtype = &self.ty; // Note: This `dtype` is the full type of the field.
quote! {
#name: ::std::option::Option::None::<#dtype>
}
}
// `ok_panic_default()`: Generates the logic for initializing this field in the Config struct
// when converting `TryFrom<BuilderStruct>`. This is a crucial part that handles
// different field configurations (`extra: Option<FieldConfig>`).
fn ok_panic_default(&self) -> TokenStream2 {
let name = self.ident.as_ref().expect("should have a name");
let name_str = format!("{name}"); // Field name as a string for error messages.
if let Some(extra_config) = &self.extra {
match extra_config {
// If `#[config(iterator(...))]`:
// The field in the builder is `Option<CollectionType>`.
// If `Some(collection)`, use it. If `None`, use `Default::default()` for the collection type.
// This assumes the collection type implements `Default` (e.g., `Vec::new()`).
FieldConfig::Iterator { .. } => {
quote! {
#name: ::std::sync::Arc::new(::std::sync::Mutex::new(value.#name.unwrap_or_else(::std::default::Default::default)))
}
}
// If `#[config(optional)]`:
// The field type itself is `Option<InnerType>`. The builder field is `Option<Option<InnerType>>`.
// `value.#name` is `Option<Option<InnerType>>`.
// `unwrap_or(Option::None)` means if the builder had `Some(Some(val))` -> `Some(val)`,
// if `Some(None)` -> `None`, if `None` (builder field not set) -> `None`.
// The resulting `Arc<Mutex<Option<InnerType>>>` will hold `None` if the builder didn't provide a value.
FieldConfig::Optional => {
// The field's type `self.ty` is expected to be `Option<T>`.
// `value.#name` from the builder is `Option<Option<T>>`.
// We want `Arc<Mutex<Option<T>>>`.
// `value.#name.unwrap_or(::std::option::Option::None)` handles the outer Option from the builder.
// If builder's `value.#name` is `None` (field not set), it becomes `Arc<Mutex<None>>`.
// If builder's `value.#name` is `Some(actual_option_value)`, it becomes `Arc<Mutex<actual_option_value>>`.
quote! {
#name: ::std::sync::Arc::new(::std::sync::Mutex::new(value.#name.unwrap_or(::std::option::Option::None)))
}
}
}
} else {
// If no special `#[config(...)]` attribute (i.e., it's a required field):
// The field in the builder is `Option<FieldType>`.
// `value.#name.ok_or(...)` ensures that if the builder has `None` for this field,
// an error is returned, effectively making the field mandatory.
quote! {
#name: ::std::sync::Arc::new(::std::sync::Mutex::new(value.#name.ok_or(::anyhow::anyhow!("Option for field '{}' was None", #name_str))?))
}
}
}
}