1
use proc_macro::TokenStream;
2
use quote::quote;
3
use syn::Lit;
4
use syn::{Data, DeriveInput, Fields, ItemFn, Path, parse_macro_input};
5

            
6
#[proc_macro_attribute]
7
196
pub fn local_db_sqlx_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
8
196
    let input = parse_macro_input!(item as ItemFn);
9
196
    let fn_name = &input.sig.ident;
10
196
    let block = &input.block;
11

            
12
196
    let expanded = quote! {
13
        #[sqlx::test(migrator = "server::db::MIGRATOR")]
14
        async fn #fn_name(pool: PgPool) -> Result<(), anyhow::Error> {
15
            setup().await;
16
            DB_POOL.set(&pool);
17
            #block
18
        Ok(())
19
        }
20
    };
21

            
22
196
    TokenStream::from(expanded)
23
196
}
24

            
25
#[proc_macro_derive(Builder, attributes(builder))]
26
24
pub fn builder_macro(input: TokenStream) -> TokenStream {
27
    // Parse the input tokens into a syntax tree
28
24
    let input = parse_macro_input!(input as DeriveInput);
29
24
    let name = &input.ident;
30
24
    let generics = &input.generics; // Capture generics (including lifetimes)
31
24
    let builder_name = syn::Ident::new(&format!("{name}Builder"), name.span());
32

            
33
    // Check for custom error_kind attribute
34
24
    let mut error_kind = None;
35

            
36
    // Parse attributes
37
72
    for attr in &input.attrs {
38
72
        if attr.path().is_ident("builder") {
39
24
            attr.parse_nested_meta(|meta| {
40
24
                if meta.path.is_ident("error_kind")
41
24
                    && let Ok(Lit::Str(lit_str)) = meta.value()?.parse()
42
24
                {
43
24
                    error_kind = Some(lit_str.parse::<Path>().unwrap());
44
24
                }
45
24
                Ok(())
46
24
            })
47
24
            .unwrap();
48
48
        }
49
    }
50

            
51
    // Set a default error kind if none is provided
52
24
    let error_kind = error_kind.expect(
53
24
        "Error kind (e.g., FinanceError) must be specified with #[builder(error_kind = \"...\")]",
54
    );
55

            
56
    // Define a custom error type based on the struct name, e.g., CommodityError for Commodity
57
24
    let custom_error_name = syn::Ident::new(&format!("{name}Error"), name.span());
58

            
59
24
    let fields = if let Data::Struct(data) = &input.data {
60
24
        if let Fields::Named(fields) = &data.fields {
61
24
            fields.named.iter().collect::<Vec<_>>()
62
        } else {
63
            panic!("Builder macro only supports structs with named fields");
64
        }
65
    } else {
66
        panic!("Builder macro only supports structs");
67
    };
68

            
69
    // Generate builder struct fields with the same generics (including lifetimes)
70
108
    let builder_fields = fields.iter().map(|field| {
71
108
        let field_name = &field.ident;
72
108
        let field_ty = &field.ty;
73
108
        let builder_field_type = quote! { Option<#field_ty> };
74
108
        quote! {
75
            #field_name: #builder_field_type
76
        }
77
108
    });
78

            
79
    // Generate initialization in new()
80
108
    let builder_fields_init = fields.iter().map(|field| {
81
108
        let field_name = &field.ident;
82
108
        quote! {
83
            #field_name: None
84
        }
85
108
    });
86

            
87
    // Generate setter methods
88
108
    let setters = fields.iter().map(|field| {
89
108
        let field_name = &field.ident;
90
108
        let field_type = &field.ty;
91

            
92
108
        if is_option_type(field_type) {
93
28
            let inner_type = get_inner_type(field_type);
94
28
            if is_string_type(&inner_type) {
95
                // For Option<String>, accept &str
96
4
                quote! {
97
                    pub fn #field_name(&mut self, value: &str) -> &mut Self {
98
                        self.#field_name = Some(Some(value.to_string()));
99
                        self
100
                    }
101
                }
102
            } else {
103
                // For Option<T>, accept T directly
104
24
                quote! {
105
                    pub fn #field_name(&mut self, value: #inner_type) -> &mut Self {
106
                        self.#field_name = Some(Some(value));
107
                        self
108
                    }
109
                }
110
            }
111
80
        } else if is_string_type(field_type) {
112
            // For String, accept &str
113
8
            quote! {
114
                pub fn #field_name(&mut self, value: &str) -> &mut Self {
115
                    self.#field_name = Some(value.to_string());
116
                    self
117
                }
118
            }
119
        } else {
120
            // For non-Option<T> and non-String fields, accept T directly
121
72
            quote! {
122
                pub fn #field_name(&mut self, value: #field_type) -> &mut Self {
123
                    self.#field_name = Some(value);
124
                    self
125
                }
126
            }
127
        }
128
108
    });
129

            
130
    // Generate code to check for missing required fields
131
24
    let check_required_fields = fields
132
24
        .iter()
133
108
        .filter(|field| !is_option_type(&field.ty))
134
80
        .map(|field| {
135
80
            let field_name = &field.ident;
136
80
            let field_name_str = field_name.as_ref().unwrap().to_string();
137
80
            quote! {
138
                if self.#field_name.is_none() {
139
                    missing_fields.push(#field_name_str);
140
                }
141
            }
142
80
        });
143

            
144
    // Generate build_fields
145
108
    let build_fields = fields.iter().map(|field| {
146
108
        let field_name = &field.ident;
147
108
        if is_option_type(&field.ty) {
148
28
            quote! {
149
                #field_name: self.#field_name.clone().unwrap_or(None)
150
            }
151
        } else {
152
80
            quote! {
153
                #field_name: self.#field_name.clone().unwrap()
154
            }
155
        }
156
108
    });
157

            
158
    // Extract the lifetime parameters from generics for use in the builder struct
159
24
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
160

            
161
24
    let expanded = quote! {
162
        pub struct #builder_name #impl_generics #where_clause {
163
            #(#builder_fields),*
164
        }
165

            
166
        impl #impl_generics #builder_name #ty_generics #where_clause {
167
            pub fn new() -> Self {
168
                Self {
169
                    #(#builder_fields_init),*
170
                }
171
            }
172

            
173
            #(#setters)*
174

            
175
            pub fn build(&self) -> Result<#name #ty_generics, #error_kind> {
176
                let mut missing_fields = Vec::new();
177
                #(#check_required_fields)*
178

            
179
                if !missing_fields.is_empty() {
180
                    return Err(#error_kind::from(#custom_error_name::Build(format!(
181
                        "{} fields are missing: {}",
182
                        stringify!(#name),
183
                        missing_fields.join(", ")
184
                    ))));
185
                }
186

            
187
                Ok(#name {
188
                    #(#build_fields),*
189
                })
190
            }
191
        }
192

            
193
        impl #impl_generics #name #ty_generics #where_clause {
194
            pub fn builder() -> #builder_name #ty_generics {
195
                #builder_name::new()
196
            }
197
        }
198
    };
199

            
200
24
    TokenStream::from(expanded)
201
24
}
202

            
203
/// Helper function to determine if a type is an `Option<T>`
204
324
fn is_option_type(ty: &syn::Type) -> bool {
205
324
    matches!(ty, syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. }) if segments.iter().any(|segment| segment.ident == "Option"))
206
324
}
207

            
208
/// Helper function to get the inner type of an `Option<T>`
209
28
fn get_inner_type(ty: &syn::Type) -> syn::Type {
210
28
    if let syn::Type::Path(type_path) = ty
211
28
        && let Some(segment) = type_path.path.segments.first()
212
28
        && segment.ident == "Option"
213
28
        && let syn::PathArguments::AngleBracketed(args) = &segment.arguments
214
28
        && let Some(syn::GenericArgument::Type(inner_type)) = args.args.first()
215
    {
216
28
        return inner_type.clone();
217
    }
218
    ty.clone()
219
28
}
220

            
221
/// Helper function to check if the type is String
222
108
fn is_string_type(ty: &syn::Type) -> bool {
223
108
    if let syn::Type::Path(type_path) = ty
224
108
        && let Some(segment) = type_path.path.segments.last()
225
    {
226
108
        return segment.ident == "String";
227
    }
228
    false
229
108
}
230

            
231
/// A procedural macro for generating typed Command implementations with compile-time validation.
232
///
233
/// This macro provides pure value-based argument passing with compile-time type safety by generating:
234
/// - Typed Args structs with proper field types passed by value only
235
/// - Commands that accept Args structs directly (no `HashMap` usage)
236
/// - Individual typed variables available directly in command scope
237
/// - Compile-time validation of argument types and required/optional fields
238
/// - Zero runtime argument parsing or validation overhead
239
///
240
/// # Syntax
241
///
242
/// ```ignore
243
/// command! {
244
///     CommandName {
245
///         #[required]
246
///         arg_name: Type,
247
///         #[optional]
248
///         opt_name: Type,
249
///     } => {
250
///         // Command implementation body
251
///         // Individual typed variables are available in scope
252
///     }
253
/// }
254
/// ```
255
///
256
/// # Generated Code
257
///
258
/// The macro generates:
259
/// - A `CommandNameArgs` struct with typed fields (required fields as `Type`, optional as `Option<Type>`)
260
/// - A `CommandName` struct implementing `Command` trait with typed `run(args: CommandNameArgs)` method
261
/// - Individual typed variables extracted from the Args struct and available in the command body
262
/// - Pure compile-time type validation with no runtime overhead
263
///
264
/// # Examples
265
///
266
/// ## Simple command with no arguments
267
///
268
/// ```rust
269
/// # use supp_macro::command;
270
/// # use async_trait::async_trait;
271
/// #
272
/// # #[derive(Debug)]
273
/// # pub enum CmdError {
274
/// #     Args(String),
275
/// # }
276
/// #
277
/// # impl std::fmt::Display for CmdError {
278
/// #     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279
/// #         match self {
280
/// #             CmdError::Args(msg) => write!(f, "Argument error: {}", msg),
281
/// #         }
282
/// #     }
283
/// # }
284
/// #
285
/// # impl std::error::Error for CmdError {}
286
/// #
287
/// # #[derive(Debug)]
288
/// # pub enum CmdResult {
289
/// #     String(String),
290
/// # }
291
/// #
292
/// # #[derive(Debug, Default)]
293
/// # pub struct CommandArgs {}
294
/// # impl CommandArgs { pub fn new() -> Self { Self::default() } }
295
/// #
296
/// # #[async_trait]
297
/// # pub trait Command: std::fmt::Debug {
298
/// #     type Args;
299
/// #     async fn run(&self, args: Self::Args) -> Result<Option<CmdResult>, CmdError>;
300
/// # }
301
///
302
/// command! {
303
///     GetVersion {
304
///     } => {
305
///         Ok(Some(CmdResult::String("1.0.0".to_string())))
306
///     }
307
/// }
308
///
309
/// # #[tokio::main]
310
/// # async fn main() {
311
/// let result = GetVersion::new().run().await.unwrap();
312
/// # }
313
/// ```
314
///
315
/// ## Command with required arguments (server-compatible types)
316
///
317
/// ```rust
318
/// # use supp_macro::command;
319
/// # use async_trait::async_trait;
320
/// # use uuid::Uuid;
321
/// # use num_rational::Rational64;
322
/// #
323
/// # #[derive(Debug, Clone)]
324
/// # pub enum Argument {
325
/// #     String(String),
326
/// #     Uuid(Uuid),
327
/// #     Rational(Rational64),
328
/// # }
329
/// #
330
/// # #[derive(Debug)]
331
/// # pub enum CmdError {
332
/// #     Args(String),
333
/// # }
334
/// #
335
/// # impl std::fmt::Display for CmdError {
336
/// #     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
337
/// #         match self {
338
/// #             CmdError::Args(msg) => write!(f, "Argument error: {}", msg),
339
/// #         }
340
/// #     }
341
/// # }
342
/// #
343
/// # impl std::error::Error for CmdError {}
344
/// #
345
/// # #[derive(Debug)]
346
/// # pub enum CmdResult {
347
/// #     String(String),
348
/// # }
349
/// #
350
/// # #[derive(Debug, Default)]
351
/// # pub struct CommandArgs {
352
/// #     pub symbol: Option<String>,
353
/// #     pub name: Option<String>,
354
/// #     pub user_id: Option<uuid::Uuid>,
355
/// # }
356
/// # impl CommandArgs {
357
/// #     pub fn new() -> Self { Self::default() }
358
/// #     pub fn symbol(mut self, v: String) -> Self { self.symbol = Some(v); self }
359
/// #     pub fn name(mut self, v: String) -> Self { self.name = Some(v); self }
360
/// #     pub fn user_id(mut self, v: uuid::Uuid) -> Self { self.user_id = Some(v); self }
361
/// # }
362
/// #
363
/// # #[async_trait]
364
/// # pub trait Command: std::fmt::Debug {
365
/// #     type Args;
366
/// #     async fn run(&self, args: Self::Args) -> Result<Option<CmdResult>, CmdError>;
367
/// # }
368
///
369
/// // This creates a commodity in the financial system
370
/// command! {
371
///     CreateCommodity {
372
///         #[required]
373
///         symbol: String,
374
///         #[required]
375
///         name: String,
376
///         #[required]
377
///         user_id: Uuid,
378
///     } => {
379
///         // Individual typed variables are automatically available
380
///         Ok(Some(CmdResult::String(format!(
381
///             "Created commodity {} ({}) for user {}",
382
///             name, symbol, user_id
383
///         ))))
384
///     }
385
/// }
386
///
387
/// # #[tokio::main]
388
/// # async fn main() {
389
/// let result = CreateCommodity::new()
390
///     .symbol("USD".to_string())
391
///     .name("US Dollar".to_string())
392
///     .user_id(uuid::Uuid::new_v4())
393
///     .run()
394
///     .await
395
///     .unwrap();
396
/// # }
397
/// ```
398
///
399
/// ## Command with optional arguments
400
///
401
/// ```rust
402
/// # use supp_macro::command;
403
/// # use async_trait::async_trait;
404
/// # use uuid::Uuid;
405
/// #
406
/// # #[derive(Debug, Clone)]
407
/// # pub enum Argument {
408
/// #     String(String),
409
/// #     Uuid(Uuid),
410
/// # }
411
/// #
412
/// # #[derive(Debug)]
413
/// # pub enum CmdError {
414
/// #     Args(String),
415
/// # }
416
/// #
417
/// # impl std::fmt::Display for CmdError {
418
/// #     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
419
/// #         match self {
420
/// #             CmdError::Args(msg) => write!(f, "Argument error: {}", msg),
421
/// #         }
422
/// #     }
423
/// # }
424
/// #
425
/// # impl std::error::Error for CmdError {}
426
/// #
427
/// # #[derive(Debug)]
428
/// # pub enum CmdResult {
429
/// #     String(String),
430
/// # }
431
/// #
432
/// # #[derive(Debug, Default)]
433
/// # pub struct CommandArgs {
434
/// #     pub user_id: Option<uuid::Uuid>,
435
/// #     pub account: Option<String>,
436
/// # }
437
/// # impl CommandArgs {
438
/// #     pub fn new() -> Self { Self::default() }
439
/// #     pub fn user_id(mut self, v: uuid::Uuid) -> Self { self.user_id = Some(v); self }
440
/// #     pub fn account(mut self, v: String) -> Self { self.account = Some(v); self }
441
/// # }
442
/// #
443
/// # #[async_trait]
444
/// # pub trait Command: std::fmt::Debug {
445
/// #     type Args;
446
/// #     async fn run(&self, args: Self::Args) -> Result<Option<CmdResult>, CmdError>;
447
/// # }
448
///
449
/// command! {
450
///     ListTransactions {
451
///         #[required]
452
///         user_id: Uuid,
453
///         #[optional]
454
///         account: String,
455
///     } => {
456
///         let filter = if let Some(account) = account {
457
///             format!(" for account {}", account)
458
///         } else {
459
///             String::new()
460
///         };
461
///         Ok(Some(CmdResult::String(format!("Listing transactions for user {}{}", user_id, filter))))
462
///     }
463
/// }
464
/// ```
465
///
466
/// ## Command with mixed required and optional arguments
467
///
468
/// ```rust
469
/// # use supp_macro::command;
470
/// # use async_trait::async_trait;
471
/// #
472
/// # #[derive(Debug, Clone)]
473
/// # pub enum Argument {
474
/// #     String(String),
475
/// #     Integer(i64),
476
/// #     Boolean(bool),
477
/// # }
478
/// #
479
/// # #[derive(Debug)]
480
/// # pub enum CmdError {
481
/// #     Args(String),
482
/// # }
483
/// #
484
/// # impl std::fmt::Display for CmdError {
485
/// #     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
486
/// #         match self {
487
/// #             CmdError::Args(msg) => write!(f, "Argument error: {}", msg),
488
/// #         }
489
/// #     }
490
/// # }
491
/// #
492
/// # impl std::error::Error for CmdError {}
493
/// #
494
/// # #[derive(Debug)]
495
/// # pub enum CmdResult {
496
/// #     Success(String),
497
/// # }
498
/// #
499
/// # impl TryFrom<Argument> for String {
500
/// #     type Error = CmdError;
501
/// #     fn try_from(arg: Argument) -> Result<Self, Self::Error> {
502
/// #         match arg {
503
/// #             Argument::String(s) => Ok(s),
504
/// #             _ => Err(CmdError::Args(format!("Cannot convert {:?} to String", arg))),
505
/// #         }
506
/// #     }
507
/// # }
508
/// #
509
/// # impl TryFrom<Argument> for i64 {
510
/// #     type Error = CmdError;
511
/// #     fn try_from(arg: Argument) -> Result<Self, Self::Error> {
512
/// #         match arg {
513
/// #             Argument::Integer(i) => Ok(i),
514
/// #             _ => Err(CmdError::Args(format!("Cannot convert {:?} to i64", arg))),
515
/// #         }
516
/// #     }
517
/// # }
518
/// #
519
/// # impl TryFrom<Argument> for bool {
520
/// #     type Error = CmdError;
521
/// #     fn try_from(arg: Argument) -> Result<Self, Self::Error> {
522
/// #         match arg {
523
/// #             Argument::Boolean(b) => Ok(b),
524
/// #             _ => Err(CmdError::Args(format!("Cannot convert {:?} to bool", arg))),
525
/// #         }
526
/// #     }
527
/// # }
528
/// #
529
/// # #[async_trait]
530
/// # pub trait TypedCommand {
531
/// #     type Args;
532
/// #     async fn run_typed(&self, args: Self::Args) -> Result<Option<CmdResult>, CmdError>;
533
/// # }
534
/// #
535
/// # #[derive(Debug, Default)]
536
/// # pub struct CommandArgs {
537
/// #     pub user_id: Option<i64>,
538
/// #     pub username: Option<String>,
539
/// #     pub email: Option<String>,
540
/// #     pub is_admin: Option<bool>,
541
/// # }
542
/// # impl CommandArgs {
543
/// #     pub fn new() -> Self { Self::default() }
544
/// #     pub fn user_id(mut self, v: i64) -> Self { self.user_id = Some(v); self }
545
/// #     pub fn username(mut self, v: String) -> Self { self.username = Some(v); self }
546
/// #     pub fn email(mut self, v: String) -> Self { self.email = Some(v); self }
547
/// #     pub fn is_admin(mut self, v: bool) -> Self { self.is_admin = Some(v); self }
548
/// # }
549
/// #
550
/// # #[async_trait]
551
/// # pub trait Command {
552
/// #     type Args;
553
/// #     async fn run(&self, args: Self::Args) -> Result<Option<CmdResult>, CmdError>;
554
/// # }
555
///
556
/// command! {
557
///     CreateUserCommand {
558
///         #[required]
559
///         user_id: i64,
560
///         #[required]
561
///         username: String,
562
///         #[optional]
563
///         email: String,
564
///         #[optional]
565
///         is_admin: bool,
566
///     } => {
567
///         let email_str = email.map_or_else(|| format!("{}@example.com", username), |s| s.to_string());
568
///         let admin_status = is_admin.unwrap_or(false);
569
///
570
///         let message = format!(
571
///             "Created user {} (ID: {}, Email: {}, Admin: {})",
572
///             username, user_id, email_str, admin_status
573
///         );
574
///         Ok(Some(CmdResult::Success(message)))
575
///     }
576
/// }
577
///
578
/// # #[tokio::main]
579
/// # async fn main() {
580
/// let result = CreateUserCommand::new()
581
///     .user_id(123)
582
///     .username("alice".to_string())
583
///     .is_admin(true)
584
///     .run()
585
///     .await
586
///     .unwrap();
587
/// # }
588
/// ```
589
///
590
/// ## Server-compatible Command implementation
591
///
592
/// ```rust
593
/// # use supp_macro::command;
594
/// # use async_trait::async_trait;
595
/// #
596
/// # #[derive(Debug, Clone)]
597
/// # pub enum Argument {
598
/// #     String(String),
599
/// #     Integer(i64),
600
/// #     Boolean(bool),
601
/// # }
602
/// #
603
/// # #[derive(Debug)]
604
/// # pub enum CmdError {
605
/// #     Args(String),
606
/// # }
607
/// #
608
/// # impl std::fmt::Display for CmdError {
609
/// #     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
610
/// #         match self {
611
/// #             CmdError::Args(msg) => write!(f, "Argument error: {}", msg),
612
/// #         }
613
/// #     }
614
/// # }
615
/// #
616
/// # impl std::error::Error for CmdError {}
617
/// #
618
/// # #[derive(Debug)]
619
/// # pub enum CmdResult {
620
/// #     Success(String),
621
/// # }
622
/// #
623
/// # #[derive(Debug, Default)]
624
/// # pub struct CommandArgs {
625
/// #     pub a: Option<i64>,
626
/// #     pub b: Option<i64>,
627
/// # }
628
/// # impl CommandArgs {
629
/// #     pub fn new() -> Self { Self::default() }
630
/// #     pub fn a(mut self, v: i64) -> Self { self.a = Some(v); self }
631
/// #     pub fn b(mut self, v: i64) -> Self { self.b = Some(v); self }
632
/// # }
633
/// #
634
/// # #[async_trait]
635
/// # pub trait Command {
636
/// #     type Args;
637
/// #     async fn run(&self, args: Self::Args) -> Result<Option<CmdResult>, CmdError>;
638
/// # }
639
///
640
/// command! {
641
///     CalculateCommand {
642
///         #[required]
643
///         a: i64,
644
///         #[required]
645
///         b: i64,
646
///     } => {
647
///         let result = a + b;
648
///         Ok(Some(CmdResult::Success(format!("{} + {} = {}", a, b, result))))
649
///     }
650
/// }
651
///
652
/// # #[tokio::main]
653
/// # async fn main() {
654
/// let result = CalculateCommand::new()
655
///     .a(10)
656
///     .b(20)
657
///     .run()
658
///     .await
659
///     .unwrap();
660
/// # }
661
/// ```
662
///
663
/// ## Migration from Manual Commands
664
///
665
/// The macro makes it easy to migrate from manual Command implementations:
666
///
667
/// ```rust,ignore
668
/// // BEFORE: Manual implementation
669
/// #[derive(Debug)]
670
/// pub struct GetConfig;
671
///
672
/// #[async_trait]
673
/// impl Command for GetConfig {
674
///     async fn run<'a>(&self, args: &'a HashMap<&'a str, &'a Argument>) -> Result<Option<CmdResult>, CmdError> {
675
///         if let Some(Argument::String(name)) = args.get("name") {
676
///             Ok(config(name).await?.map(|v| CmdResult::String(v)))
677
///         } else {
678
///             Err(CmdError::Args("No field name provided".to_string()))
679
///         }
680
///     }
681
/// }
682
///
683
/// // AFTER: Using the macro
684
/// command! {
685
///     GetConfig {
686
///         #[required]
687
///         name: String,
688
///     } => {
689
///         Ok(config(name).await?.map(|v| CmdResult::String(v)))
690
///     }
691
/// }
692
/// ```
693
///
694
/// # Error Handling
695
///
696
/// The new pure typed system provides compile-time error prevention:
697
///
698
/// - Missing required arguments are compile-time errors (cannot compile without them)
699
/// - Invalid argument types are compile-time errors (type checking at build time)
700
/// - Runtime errors only occur in the command body logic itself
701
/// - No argument validation overhead at runtime
702
///
703
/// # Supported Argument Types
704
///
705
/// The macro supports any Rust type for arguments:
706
/// - `String` - Text arguments
707
/// - `i64`, `u64`, etc. - Integer arguments
708
/// - `bool` - Boolean arguments
709
/// - `Rational64` - Rational number arguments (for financial precision)
710
/// - `Uuid` - UUID arguments
711
/// - `Vec<u8>` - Binary data arguments
712
/// - `DateTime<Utc>` - `DateTime` arguments
713
/// - Custom types - Any type can be used as an argument
714
/// - `Option<T>` - Automatically applied for optional arguments
715
#[proc_macro]
716
170
pub fn command(input: TokenStream) -> TokenStream {
717
170
    let input = parse_macro_input!(input as CommandInput);
718

            
719
170
    let name = &input.name;
720
170
    let required_args = &input.required_args;
721
170
    let optional_args = &input.optional_args;
722
170
    let body = &input.body;
723

            
724
    // Generate progressive runner types for all combinations of required fields
725
170
    let runner_types = generate_progressive_runner_types(name, required_args, optional_args, body);
726

            
727
    // Generate the main command struct
728
170
    let command_struct = quote! {
729
        #[derive(Debug)]
730
        pub struct #name;
731
    };
732

            
733
    // Generate the new() method that starts the builder chain
734
170
    let new_method = generate_new_method(name, required_args.len(), optional_args);
735

            
736
170
    let expanded = quote! {
737
        #command_struct
738

            
739
        #runner_types
740

            
741
        #new_method
742
    };
743

            
744
170
    TokenStream::from(expanded)
745
170
}
746

            
747
/// Generate all possible runner type combinations for required fields
748
170
fn generate_progressive_runner_types(
749
170
    command_name: &syn::Ident,
750
170
    required_args: &[(syn::Ident, syn::Type)],
751
170
    optional_args: &[(syn::Ident, syn::Type)],
752
170
    body: &syn::Block,
753
170
) -> proc_macro2::TokenStream {
754
170
    let num_required = required_args.len();
755
170
    let total_combinations = 1 << num_required; // 2^num_required
756

            
757
170
    let mut runner_types = Vec::new();
758

            
759
    // Generate a runner type for each possible combination of set required fields
760
1063
    for combination in 0..total_combinations {
761
1063
        let runner_type = generate_single_runner_type(
762
1063
            command_name,
763
1063
            required_args,
764
1063
            optional_args,
765
1063
            combination,
766
1063
            num_required,
767
1063
            body,
768
1063
        );
769
1063
        runner_types.push(runner_type);
770
1063
    }
771

            
772
170
    quote! {
773
        #(#runner_types)*
774
    }
775
170
}
776

            
777
/// Generate a single runner type for a specific combination of set fields
778
1063
fn generate_single_runner_type(
779
1063
    command_name: &syn::Ident,
780
1063
    required_args: &[(syn::Ident, syn::Type)],
781
1063
    optional_args: &[(syn::Ident, syn::Type)],
782
1063
    combination: usize,
783
1063
    num_required: usize,
784
1063
    body: &syn::Block,
785
1063
) -> proc_macro2::TokenStream {
786
    // Create binary representation for the runner type name
787
1063
    let binary_suffix = format!("{:0width$b}", combination, width = num_required.max(1));
788
1063
    let runner_name = syn::Ident::new(
789
1063
        &format!("{command_name}Runner{binary_suffix}"),
790
1063
        command_name.span(),
791
    );
792

            
793
    // Determine which required fields are set in this combination
794
1063
    let mut struct_fields = Vec::new();
795
3490
    for (i, (field_name, field_type)) in required_args.iter().enumerate() {
796
3490
        if (combination >> i) & 1 == 1 {
797
1745
            // This required field is set in this combination
798
1745
            struct_fields.push(quote! {
799
1745
                pub #field_name: #field_type
800
1745
            });
801
1745
        }
802
    }
803

            
804
    // Always include optional fields in all runner types
805
1274
    for (field_name, field_type) in optional_args {
806
1274
        struct_fields.push(quote! {
807
1274
            pub #field_name: Option<#field_type>
808
1274
        });
809
1274
    }
810

            
811
    // Generate the struct definition
812
1063
    let struct_def = if struct_fields.is_empty() {
813
105
        quote! {
814
            #[derive(Debug)]
815
            pub struct #runner_name;
816
        }
817
    } else {
818
958
        quote! {
819
            #[derive(Debug)]
820
            pub struct #runner_name {
821
                #(#struct_fields),*
822
            }
823
        }
824
    };
825

            
826
    // Generate transition methods for this runner type
827
1063
    let transition_methods = generate_transition_methods(
828
1063
        command_name,
829
1063
        required_args,
830
1063
        optional_args,
831
1063
        combination,
832
1063
        num_required,
833
    );
834

            
835
    // Generate run method if this is the complete state (all required fields set)
836
1063
    let complete_mask = (1 << num_required) - 1;
837
1063
    let run_method = if combination == complete_mask {
838
170
        generate_run_method(command_name, required_args, optional_args, body)
839
    } else {
840
893
        quote! {}
841
    };
842

            
843
1063
    quote! {
844
        #struct_def
845

            
846
        impl #runner_name {
847
            #transition_methods
848
            #run_method
849
        }
850
    }
851
1063
}
852

            
853
/// Generate transition methods for a runner type (field setters)
854
1063
fn generate_transition_methods(
855
1063
    command_name: &syn::Ident,
856
1063
    required_args: &[(syn::Ident, syn::Type)],
857
1063
    optional_args: &[(syn::Ident, syn::Type)],
858
1063
    current_combination: usize,
859
1063
    num_required: usize,
860
1063
) -> proc_macro2::TokenStream {
861
1063
    let mut methods = Vec::new();
862

            
863
    // Generate setter methods for required fields not yet set
864
3490
    for (i, (field_name, field_type)) in required_args.iter().enumerate() {
865
3490
        if (current_combination >> i) & 1 == 0 {
866
1745
            // This required field is not set yet, generate a setter
867
1745
            let new_combination = current_combination | (1 << i);
868
1745
            let binary_suffix =
869
1745
                format!("{:0width$b}", new_combination, width = num_required.max(1));
870
1745
            let target_runner = syn::Ident::new(
871
1745
                &format!("{command_name}Runner{binary_suffix}"),
872
1745
                command_name.span(),
873
1745
            );
874
1745

            
875
1745
            let method = generate_field_setter_method(
876
1745
                required_args,
877
1745
                optional_args,
878
1745
                field_name,
879
1745
                field_type,
880
1745
                current_combination,
881
1745
                &target_runner,
882
1745
            );
883
1745
            methods.push(method);
884
1745
        }
885
    }
886

            
887
    // Generate setter methods for optional fields (available on all runner types)
888
1274
    for (field_name, field_type) in optional_args {
889
1274
        let current_runner = syn::Ident::new(
890
1274
            &format!(
891
1274
                "{}Runner{:0width$b}",
892
1274
                command_name,
893
1274
                current_combination,
894
1274
                width = num_required.max(1)
895
1274
            ),
896
1274
            command_name.span(),
897
1274
        );
898
1274

            
899
1274
        let method = generate_optional_field_setter(
900
1274
            field_name,
901
1274
            field_type,
902
1274
            &current_runner,
903
1274
            required_args,
904
1274
            optional_args,
905
1274
            current_combination,
906
1274
            num_required,
907
1274
        );
908
1274
        methods.push(method);
909
1274
    }
910

            
911
1063
    quote! {
912
        #(#methods)*
913
    }
914
1063
}
915

            
916
/// Generate a setter method for a required field
917
1745
fn generate_field_setter_method(
918
1745
    required_args: &[(syn::Ident, syn::Type)],
919
1745
    optional_args: &[(syn::Ident, syn::Type)],
920
1745
    field_name: &syn::Ident,
921
1745
    field_type: &syn::Type,
922
1745
    current_combination: usize,
923
1745
    target_runner: &syn::Ident,
924
1745
) -> proc_macro2::TokenStream {
925
    // Generate field assignments for the new state
926
1745
    let mut field_assignments = Vec::new();
927

            
928
    // Handle required fields
929
6693
    for (i, (req_field_name, _)) in required_args.iter().enumerate() {
930
6693
        if req_field_name == field_name {
931
1745
            // This is the field being set
932
1745
            field_assignments.push(quote! {
933
1745
                #req_field_name: value
934
1745
            });
935
4948
        } else if (current_combination >> i) & 1 == 1 {
936
2474
            // This field was already set, move it from self
937
2474
            field_assignments.push(quote! {
938
2474
                #req_field_name: self.#req_field_name
939
2474
            });
940
2474
        }
941
        // Fields not set in either state are omitted
942
    }
943

            
944
    // Handle optional fields (always present, move from self)
945
2225
    for (opt_field_name, _) in optional_args {
946
2225
        field_assignments.push(quote! {
947
2225
            #opt_field_name: self.#opt_field_name
948
2225
        });
949
2225
    }
950

            
951
    // Generate the constructor call
952
1745
    let constructor = if field_assignments.is_empty() {
953
        quote! { #target_runner }
954
    } else {
955
1745
        quote! {
956
            #target_runner {
957
                #(#field_assignments),*
958
            }
959
        }
960
    };
961

            
962
1745
    quote! {
963
        pub fn #field_name(self, value: #field_type) -> #target_runner {
964
            #constructor
965
        }
966
    }
967
1745
}
968

            
969
/// Generate a setter method for an optional field
970
1274
fn generate_optional_field_setter(
971
1274
    field_name: &syn::Ident,
972
1274
    field_type: &syn::Type,
973
1274
    current_runner: &syn::Ident,
974
1274
    required_args: &[(syn::Ident, syn::Type)],
975
1274
    optional_args: &[(syn::Ident, syn::Type)],
976
1274
    current_combination: usize,
977
1274
    _num_required: usize,
978
1274
) -> proc_macro2::TokenStream {
979
    // Generate field assignments (same state, but update the optional field)
980
1274
    let mut field_assignments = Vec::new();
981

            
982
    // Handle required fields (move from self if set)
983
4450
    for (i, (req_field_name, _)) in required_args.iter().enumerate() {
984
4450
        if (current_combination >> i) & 1 == 1 {
985
2225
            field_assignments.push(quote! {
986
2225
                #req_field_name: self.#req_field_name
987
2225
            });
988
2225
        }
989
    }
990

            
991
    // Handle optional fields
992
4582
    for (opt_field_name, _) in optional_args {
993
4582
        if opt_field_name == field_name {
994
1274
            // This is the field being set
995
1274
            field_assignments.push(quote! {
996
1274
                #opt_field_name: Some(value)
997
1274
            });
998
3308
        } else {
999
3308
            // Move other optional fields from self
3308
            field_assignments.push(quote! {
3308
                #opt_field_name: self.#opt_field_name
3308
            });
3308
        }
    }
1274
    let constructor = if field_assignments.is_empty() {
        quote! { #current_runner }
    } else {
1274
        quote! {
            #current_runner {
                #(#field_assignments),*
            }
        }
    };
1274
    quote! {
        pub fn #field_name(self, value: #field_type) -> #current_runner {
            #constructor
        }
    }
1274
}
/// Generate the run method for the complete runner state
170
fn generate_run_method(
170
    _command_name: &syn::Ident,
170
    required_args: &[(syn::Ident, syn::Type)],
170
    optional_args: &[(syn::Ident, syn::Type)],
170
    body: &syn::Block,
170
) -> proc_macro2::TokenStream {
    // Extract field values directly (no unwrap needed!)
170
    let mut variable_assignments = Vec::new();
    // Required fields - direct field access
346
    for (field_name, _) in required_args {
346
        variable_assignments.push(quote! {
346
            let #field_name = self.#field_name;
346
        });
346
    }
    // Optional fields - direct field access
172
    for (field_name, _) in optional_args {
172
        variable_assignments.push(quote! {
172
            let #field_name = self.#field_name;
172
        });
172
    }
170
    quote! {
        pub async fn run(self) -> Result<Option<CmdResult>, CmdError> {
            // Zero runtime checks - direct field access!
            #(#variable_assignments)*
            // Original command body
            #body
        }
    }
170
}
/// Generate the `new()` method for the command
170
fn generate_new_method(
170
    command_name: &syn::Ident,
170
    num_required: usize,
170
    optional_args: &[(syn::Ident, syn::Type)],
170
) -> proc_macro2::TokenStream {
170
    let initial_runner = syn::Ident::new(
170
        &format!(
170
            "{}Runner{:0width$b}",
170
            command_name,
170
            0,
170
            width = num_required.max(1)
170
        ),
170
        command_name.span(),
    );
    // Initial state has no required fields set, but has optional fields as None
170
    let constructor = if optional_args.is_empty() && num_required > 0 {
        // Unit struct (no fields at all in initial state)
90
        quote! { #initial_runner }
    } else {
        // Struct with optional fields initialized to None
172
        let optional_field_inits = optional_args.iter().map(|(field_name, _)| {
172
            quote! { #field_name: None }
172
        });
80
        if optional_field_inits.len() > 0 {
65
            quote! {
                #initial_runner {
                    #(#optional_field_inits),*
                }
            }
        } else {
15
            quote! { #initial_runner }
        }
    };
170
    quote! {
        impl #command_name {
            pub fn new() -> #initial_runner {
                #constructor
            }
        }
    }
170
}
struct CommandInput {
    name: syn::Ident,
    required_args: Vec<(syn::Ident, syn::Type)>,
    optional_args: Vec<(syn::Ident, syn::Type)>,
    body: syn::Block,
}
impl syn::parse::Parse for CommandInput {
170
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
170
        let name: syn::Ident = input.parse()?;
        let content;
170
        syn::braced!(content in input);
170
        let mut required_args = Vec::new();
170
        let mut optional_args = Vec::new();
688
        while !content.is_empty() {
            // Parse attributes
518
            let mut is_optional = false;
518
            let mut is_required = false;
1036
            while content.peek(syn::Token![#]) {
518
                content.parse::<syn::Token![#]>()?;
                let attr_content;
518
                syn::bracketed!(attr_content in content);
518
                let attr_name: syn::Ident = attr_content.parse()?;
518
                if attr_name == "optional" {
172
                    is_optional = true;
346
                } else if attr_name == "required" {
346
                    is_required = true;
346
                } else {
                    return Err(syn::Error::new(
                        attr_name.span(),
                        "Unknown attribute. Use #[required] or #[optional]",
                    ));
                }
            }
            // Parse the field
518
            let arg_name: syn::Ident = content.parse()?;
518
            content.parse::<syn::Token![:]>()?;
518
            let arg_type: syn::Type = content.parse()?;
518
            if content.peek(syn::Token![,]) {
518
                content.parse::<syn::Token![,]>()?;
            }
            // Determine if optional (default to required if no attribute specified)
518
            let is_optional_field = if is_required && is_optional {
                return Err(syn::Error::new(
                    arg_name.span(),
                    "Field cannot be both #[required] and #[optional]",
                ));
518
            } else if is_optional {
172
                true
            } else {
346
                false // Default to required
            };
518
            if is_optional_field {
172
                optional_args.push((arg_name, arg_type));
346
            } else {
346
                required_args.push((arg_name, arg_type));
346
            }
        }
        // The '=>' is outside the braces
170
        input.parse::<syn::Token![=>]>()?;
170
        let body: syn::Block = input.parse()?;
170
        Ok(CommandInput {
170
            name,
170
            required_args,
170
            optional_args,
170
            body,
170
        })
170
    }
}