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
210
pub fn local_db_sqlx_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
8
210
    let input = parse_macro_input!(item as ItemFn);
9
210
    let fn_name = &input.sig.ident;
10
210
    let block = &input.block;
11

            
12
210
    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(Some(pool.clone()));
17
            #block
18
        Ok(())
19
        }
20
    };
21

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

            
25
/// Runs an async test body on ONE runtime shared by the whole test binary,
26
/// obtained from `crate::common::runtime()` (as [`local_db_sqlx_test`] expects
27
/// `setup()`).
28
///
29
/// `#[tokio::test]` builds a runtime per test and drops it when the test ends.
30
/// `server::db`'s production pool is a process-wide `OnceCell`, so connections
31
/// opened inside the first test's runtime are still pooled after that runtime is
32
/// gone, and a later acquire fails with "A Tokio 1.x context was found, but it
33
/// is being shutdown" — inside whatever test happened to run next, which is why
34
/// it reads as a bug in unrelated code.
35
#[proc_macro_attribute]
36
113
pub fn shared_runtime_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
37
113
    let input = parse_macro_input!(item as ItemFn);
38
113
    if !input.sig.inputs.is_empty() {
39
        return syn::Error::new_spanned(&input.sig, "shared_runtime_test takes no arguments")
40
            .to_compile_error()
41
            .into();
42
113
    }
43
113
    let fn_name = &input.sig.ident;
44
113
    let output = &input.sig.output;
45
113
    let block = &input.block;
46
113
    let attrs = &input.attrs;
47

            
48
    // The return type is preserved and the value returned: erasing it would let
49
    // a test that returns `Err` pass with only an unused-`Result` warning.
50
113
    TokenStream::from(quote! {
51
        #[test]
52
        #(#attrs)*
53
        fn #fn_name() #output {
54
            crate::common::runtime().block_on(async #block)
55
        }
56
    })
57
113
}
58

            
59
#[proc_macro_derive(Builder, attributes(builder))]
60
24
pub fn builder_macro(input: TokenStream) -> TokenStream {
61
    // Parse the input tokens into a syntax tree
62
24
    let input = parse_macro_input!(input as DeriveInput);
63
24
    let name = &input.ident;
64
24
    let generics = &input.generics; // Capture generics (including lifetimes)
65
24
    let builder_name = syn::Ident::new(&format!("{name}Builder"), name.span());
66

            
67
    // Check for custom error_kind attribute
68
24
    let mut error_kind = None;
69

            
70
    // Parse attributes
71
72
    for attr in &input.attrs {
72
72
        if attr.path().is_ident("builder") {
73
24
            attr.parse_nested_meta(|meta| {
74
24
                if meta.path.is_ident("error_kind")
75
24
                    && let Ok(Lit::Str(lit_str)) = meta.value()?.parse()
76
24
                {
77
24
                    error_kind = Some(lit_str.parse::<Path>().unwrap());
78
24
                }
79
24
                Ok(())
80
24
            })
81
24
            .unwrap();
82
48
        }
83
    }
84

            
85
    // Set a default error kind if none is provided
86
24
    let error_kind = error_kind.expect(
87
24
        "Error kind (e.g., FinanceError) must be specified with #[builder(error_kind = \"...\")]",
88
    );
89

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

            
93
24
    let fields = if let Data::Struct(data) = &input.data {
94
24
        if let Fields::Named(fields) = &data.fields {
95
24
            fields.named.iter().collect::<Vec<_>>()
96
        } else {
97
            panic!("Builder macro only supports structs with named fields");
98
        }
99
    } else {
100
        panic!("Builder macro only supports structs");
101
    };
102

            
103
    // Generate builder struct fields with the same generics (including lifetimes)
104
108
    let builder_fields = fields.iter().map(|field| {
105
108
        let field_name = &field.ident;
106
108
        let field_ty = &field.ty;
107
108
        let builder_field_type = quote! { Option<#field_ty> };
108
108
        quote! {
109
            #field_name: #builder_field_type
110
        }
111
108
    });
112

            
113
    // Generate initialization in new()
114
108
    let builder_fields_init = fields.iter().map(|field| {
115
108
        let field_name = &field.ident;
116
108
        quote! {
117
            #field_name: None
118
        }
119
108
    });
120

            
121
    // Generate setter methods
122
108
    let setters = fields.iter().map(|field| {
123
108
        let field_name = &field.ident;
124
108
        let field_type = &field.ty;
125

            
126
108
        if is_option_type(field_type) {
127
28
            let inner_type = get_inner_type(field_type);
128
28
            if is_string_type(&inner_type) {
129
                // For Option<String>, accept &str
130
4
                quote! {
131
                    pub fn #field_name(&mut self, value: &str) -> &mut Self {
132
                        self.#field_name = Some(Some(value.to_string()));
133
                        self
134
                    }
135
                }
136
            } else {
137
                // For Option<T>, accept T directly
138
24
                quote! {
139
                    pub fn #field_name(&mut self, value: #inner_type) -> &mut Self {
140
                        self.#field_name = Some(Some(value));
141
                        self
142
                    }
143
                }
144
            }
145
80
        } else if is_string_type(field_type) {
146
            // For String, accept &str
147
8
            quote! {
148
                pub fn #field_name(&mut self, value: &str) -> &mut Self {
149
                    self.#field_name = Some(value.to_string());
150
                    self
151
                }
152
            }
153
        } else {
154
            // For non-Option<T> and non-String fields, accept T directly
155
72
            quote! {
156
                pub fn #field_name(&mut self, value: #field_type) -> &mut Self {
157
                    self.#field_name = Some(value);
158
                    self
159
                }
160
            }
161
        }
162
108
    });
163

            
164
    // Generate code to check for missing required fields
165
24
    let check_required_fields = fields
166
24
        .iter()
167
108
        .filter(|field| !is_option_type(&field.ty))
168
80
        .map(|field| {
169
80
            let field_name = &field.ident;
170
80
            let field_name_str = field_name.as_ref().unwrap().to_string();
171
80
            quote! {
172
                if self.#field_name.is_none() {
173
                    missing_fields.push(#field_name_str);
174
                }
175
            }
176
80
        });
177

            
178
    // Generate build_fields
179
108
    let build_fields = fields.iter().map(|field| {
180
108
        let field_name = &field.ident;
181
108
        if is_option_type(&field.ty) {
182
28
            quote! {
183
                #field_name: self.#field_name.clone().unwrap_or(None)
184
            }
185
        } else {
186
80
            quote! {
187
                #field_name: self.#field_name.clone().unwrap()
188
            }
189
        }
190
108
    });
191

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

            
195
24
    let expanded = quote! {
196
        pub struct #builder_name #impl_generics #where_clause {
197
            #(#builder_fields),*
198
        }
199

            
200
        impl #impl_generics #builder_name #ty_generics #where_clause {
201
            pub fn new() -> Self {
202
                Self {
203
                    #(#builder_fields_init),*
204
                }
205
            }
206

            
207
            #(#setters)*
208

            
209
            pub fn build(&self) -> Result<#name #ty_generics, #error_kind> {
210
                let mut missing_fields = Vec::new();
211
                #(#check_required_fields)*
212

            
213
                if !missing_fields.is_empty() {
214
                    return Err(#error_kind::from(#custom_error_name::Build(format!(
215
                        "{} fields are missing: {}",
216
                        stringify!(#name),
217
                        missing_fields.join(", ")
218
                    ))));
219
                }
220

            
221
                Ok(#name {
222
                    #(#build_fields),*
223
                })
224
            }
225
        }
226

            
227
        impl #impl_generics #name #ty_generics #where_clause {
228
            pub fn builder() -> #builder_name #ty_generics {
229
                #builder_name::new()
230
            }
231
        }
232
    };
233

            
234
24
    TokenStream::from(expanded)
235
24
}
236

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

            
242
/// Helper function to get the inner type of an `Option<T>`
243
28
fn get_inner_type(ty: &syn::Type) -> syn::Type {
244
28
    if let syn::Type::Path(type_path) = ty
245
28
        && let Some(segment) = type_path.path.segments.first()
246
28
        && segment.ident == "Option"
247
28
        && let syn::PathArguments::AngleBracketed(args) = &segment.arguments
248
28
        && let Some(syn::GenericArgument::Type(inner_type)) = args.args.first()
249
    {
250
28
        return inner_type.clone();
251
    }
252
    ty.clone()
253
28
}
254

            
255
/// Helper function to check if the type is String
256
108
fn is_string_type(ty: &syn::Type) -> bool {
257
108
    if let syn::Type::Path(type_path) = ty
258
108
        && let Some(segment) = type_path.path.segments.last()
259
    {
260
108
        return segment.ident == "String";
261
    }
262
    false
263
108
}
264

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

            
753
166
    let name = &input.name;
754
166
    let required_args = &input.required_args;
755
166
    let optional_args = &input.optional_args;
756
166
    let body = &input.body;
757

            
758
    // Generate progressive runner types for all combinations of required fields
759
166
    let runner_types = generate_progressive_runner_types(name, required_args, optional_args, body);
760

            
761
    // Generate the main command struct
762
166
    let command_struct = quote! {
763
        #[derive(Debug)]
764
        pub struct #name;
765
    };
766

            
767
    // Generate the new() method that starts the builder chain
768
166
    let new_method = generate_new_method(name, required_args.len(), optional_args);
769

            
770
166
    let expanded = quote! {
771
        #command_struct
772

            
773
        #runner_types
774

            
775
        #new_method
776
    };
777

            
778
166
    TokenStream::from(expanded)
779
166
}
780

            
781
/// Generate all possible runner type combinations for required fields
782
166
fn generate_progressive_runner_types(
783
166
    command_name: &syn::Ident,
784
166
    required_args: &[(syn::Ident, syn::Type)],
785
166
    optional_args: &[(syn::Ident, syn::Type)],
786
166
    body: &syn::Block,
787
166
) -> proc_macro2::TokenStream {
788
166
    let num_required = required_args.len();
789
166
    let total_combinations = 1 << num_required; // 2^num_required
790

            
791
166
    let mut runner_types = Vec::new();
792

            
793
    // Generate a runner type for each possible combination of set required fields
794
1047
    for combination in 0..total_combinations {
795
1047
        let runner_type = generate_single_runner_type(
796
1047
            command_name,
797
1047
            required_args,
798
1047
            optional_args,
799
1047
            combination,
800
1047
            num_required,
801
1047
            body,
802
1047
        );
803
1047
        runner_types.push(runner_type);
804
1047
    }
805

            
806
166
    quote! {
807
        #(#runner_types)*
808
    }
809
166
}
810

            
811
/// Generate a single runner type for a specific combination of set fields
812
1047
fn generate_single_runner_type(
813
1047
    command_name: &syn::Ident,
814
1047
    required_args: &[(syn::Ident, syn::Type)],
815
1047
    optional_args: &[(syn::Ident, syn::Type)],
816
1047
    combination: usize,
817
1047
    num_required: usize,
818
1047
    body: &syn::Block,
819
1047
) -> proc_macro2::TokenStream {
820
    // Create binary representation for the runner type name
821
1047
    let binary_suffix = format!("{:0width$b}", combination, width = num_required.max(1));
822
1047
    let runner_name = syn::Ident::new(
823
1047
        &format!("{command_name}Runner{binary_suffix}"),
824
1047
        command_name.span(),
825
    );
826

            
827
    // Determine which required fields are set in this combination
828
1047
    let mut struct_fields = Vec::new();
829
3458
    for (i, (field_name, field_type)) in required_args.iter().enumerate() {
830
3458
        if (combination >> i) & 1 == 1 {
831
1729
            // This required field is set in this combination
832
1729
            struct_fields.push(quote! {
833
1729
                pub #field_name: #field_type
834
1729
            });
835
1729
        }
836
    }
837

            
838
    // Always include optional fields in all runner types
839
1274
    for (field_name, field_type) in optional_args {
840
1274
        struct_fields.push(quote! {
841
1274
            pub #field_name: Option<#field_type>
842
1274
        });
843
1274
    }
844

            
845
    // Generate the struct definition
846
1047
    let struct_def = if struct_fields.is_empty() {
847
101
        quote! {
848
            #[derive(Debug)]
849
            pub struct #runner_name;
850
        }
851
    } else {
852
946
        quote! {
853
            #[derive(Debug)]
854
            pub struct #runner_name {
855
                #(#struct_fields),*
856
            }
857
        }
858
    };
859

            
860
    // Generate transition methods for this runner type
861
1047
    let transition_methods = generate_transition_methods(
862
1047
        command_name,
863
1047
        required_args,
864
1047
        optional_args,
865
1047
        combination,
866
1047
        num_required,
867
    );
868

            
869
    // Generate run method if this is the complete state (all required fields set)
870
1047
    let complete_mask = (1 << num_required) - 1;
871
1047
    let run_method = if combination == complete_mask {
872
166
        generate_run_method(command_name, required_args, optional_args, body)
873
    } else {
874
881
        quote! {}
875
    };
876

            
877
1047
    quote! {
878
        #struct_def
879

            
880
        impl #runner_name {
881
            #transition_methods
882
            #run_method
883
        }
884
    }
885
1047
}
886

            
887
/// Generate transition methods for a runner type (field setters)
888
1047
fn generate_transition_methods(
889
1047
    command_name: &syn::Ident,
890
1047
    required_args: &[(syn::Ident, syn::Type)],
891
1047
    optional_args: &[(syn::Ident, syn::Type)],
892
1047
    current_combination: usize,
893
1047
    num_required: usize,
894
1047
) -> proc_macro2::TokenStream {
895
1047
    let mut methods = Vec::new();
896

            
897
    // Generate setter methods for required fields not yet set
898
3458
    for (i, (field_name, field_type)) in required_args.iter().enumerate() {
899
3458
        if (current_combination >> i) & 1 == 0 {
900
1729
            // This required field is not set yet, generate a setter
901
1729
            let new_combination = current_combination | (1 << i);
902
1729
            let binary_suffix =
903
1729
                format!("{:0width$b}", new_combination, width = num_required.max(1));
904
1729
            let target_runner = syn::Ident::new(
905
1729
                &format!("{command_name}Runner{binary_suffix}"),
906
1729
                command_name.span(),
907
1729
            );
908
1729

            
909
1729
            let method = generate_field_setter_method(
910
1729
                required_args,
911
1729
                optional_args,
912
1729
                field_name,
913
1729
                field_type,
914
1729
                current_combination,
915
1729
                &target_runner,
916
1729
            );
917
1729
            methods.push(method);
918
1729
        }
919
    }
920

            
921
    // Generate setter methods for optional fields (available on all runner types)
922
1274
    for (field_name, field_type) in optional_args {
923
1274
        let current_runner = syn::Ident::new(
924
1274
            &format!(
925
1274
                "{}Runner{:0width$b}",
926
1274
                command_name,
927
1274
                current_combination,
928
1274
                width = num_required.max(1)
929
1274
            ),
930
1274
            command_name.span(),
931
1274
        );
932
1274

            
933
1274
        let method = generate_optional_field_setter(
934
1274
            field_name,
935
1274
            field_type,
936
1274
            &current_runner,
937
1274
            required_args,
938
1274
            optional_args,
939
1274
            current_combination,
940
1274
            num_required,
941
1274
        );
942
1274
        methods.push(method);
943
1274
    }
944

            
945
1047
    quote! {
946
        #(#methods)*
947
    }
948
1047
}
949

            
950
/// Generate a setter method for a required field
951
1729
fn generate_field_setter_method(
952
1729
    required_args: &[(syn::Ident, syn::Type)],
953
1729
    optional_args: &[(syn::Ident, syn::Type)],
954
1729
    field_name: &syn::Ident,
955
1729
    field_type: &syn::Type,
956
1729
    current_combination: usize,
957
1729
    target_runner: &syn::Ident,
958
1729
) -> proc_macro2::TokenStream {
959
    // Generate field assignments for the new state
960
1729
    let mut field_assignments = Vec::new();
961

            
962
    // Handle required fields
963
6661
    for (i, (req_field_name, _)) in required_args.iter().enumerate() {
964
6661
        if req_field_name == field_name {
965
1729
            // This is the field being set
966
1729
            field_assignments.push(quote! {
967
1729
                #req_field_name: value
968
1729
            });
969
4932
        } else if (current_combination >> i) & 1 == 1 {
970
2466
            // This field was already set, move it from self
971
2466
            field_assignments.push(quote! {
972
2466
                #req_field_name: self.#req_field_name
973
2466
            });
974
2466
        }
975
        // Fields not set in either state are omitted
976
    }
977

            
978
    // Handle optional fields (always present, move from self)
979
2225
    for (opt_field_name, _) in optional_args {
980
2225
        field_assignments.push(quote! {
981
2225
            #opt_field_name: self.#opt_field_name
982
2225
        });
983
2225
    }
984

            
985
    // Generate the constructor call
986
1729
    let constructor = if field_assignments.is_empty() {
987
        quote! { #target_runner }
988
    } else {
989
1729
        quote! {
990
            #target_runner {
991
                #(#field_assignments),*
992
            }
993
        }
994
    };
995

            
996
1729
    quote! {
997
        pub fn #field_name(self, value: #field_type) -> #target_runner {
998
            #constructor
999
        }
    }
1729
}
/// Generate a setter method for an optional field
1274
fn generate_optional_field_setter(
1274
    field_name: &syn::Ident,
1274
    field_type: &syn::Type,
1274
    current_runner: &syn::Ident,
1274
    required_args: &[(syn::Ident, syn::Type)],
1274
    optional_args: &[(syn::Ident, syn::Type)],
1274
    current_combination: usize,
1274
    _num_required: usize,
1274
) -> proc_macro2::TokenStream {
    // Generate field assignments (same state, but update the optional field)
1274
    let mut field_assignments = Vec::new();
    // Handle required fields (move from self if set)
4450
    for (i, (req_field_name, _)) in required_args.iter().enumerate() {
4450
        if (current_combination >> i) & 1 == 1 {
2225
            field_assignments.push(quote! {
2225
                #req_field_name: self.#req_field_name
2225
            });
2225
        }
    }
    // Handle optional fields
4582
    for (opt_field_name, _) in optional_args {
4582
        if opt_field_name == field_name {
1274
            // This is the field being set
1274
            field_assignments.push(quote! {
1274
                #opt_field_name: Some(value)
1274
            });
3308
        } else {
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
166
fn generate_run_method(
166
    _command_name: &syn::Ident,
166
    required_args: &[(syn::Ident, syn::Type)],
166
    optional_args: &[(syn::Ident, syn::Type)],
166
    body: &syn::Block,
166
) -> proc_macro2::TokenStream {
    // Extract field values directly (no unwrap needed!)
166
    let mut variable_assignments = Vec::new();
    // Required fields - direct field access
338
    for (field_name, _) in required_args {
338
        variable_assignments.push(quote! {
338
            let #field_name = self.#field_name;
338
        });
338
    }
    // 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
    }
166
    quote! {
        pub async fn run(self) -> Result<Option<CmdResult>, CmdError> {
            // Zero runtime checks - direct field access!
            #(#variable_assignments)*
            // Original command body
            #body
        }
    }
166
}
/// Generate the `new()` method for the command
166
fn generate_new_method(
166
    command_name: &syn::Ident,
166
    num_required: usize,
166
    optional_args: &[(syn::Ident, syn::Type)],
166
) -> proc_macro2::TokenStream {
166
    let initial_runner = syn::Ident::new(
166
        &format!(
166
            "{}Runner{:0width$b}",
166
            command_name,
166
            0,
166
            width = num_required.max(1)
166
        ),
166
        command_name.span(),
    );
    // Initial state has no required fields set, but has optional fields as None
166
    let constructor = if optional_args.is_empty() && num_required > 0 {
        // Unit struct (no fields at all in initial state)
86
        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 }
        }
    };
166
    quote! {
        impl #command_name {
            pub fn new() -> #initial_runner {
                #constructor
            }
        }
    }
166
}
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 {
166
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
166
        let name: syn::Ident = input.parse()?;
        let content;
166
        syn::braced!(content in input);
166
        let mut required_args = Vec::new();
166
        let mut optional_args = Vec::new();
676
        while !content.is_empty() {
            // Parse attributes
510
            let mut is_optional = false;
510
            let mut is_required = false;
1020
            while content.peek(syn::Token![#]) {
510
                content.parse::<syn::Token![#]>()?;
                let attr_content;
510
                syn::bracketed!(attr_content in content);
510
                let attr_name: syn::Ident = attr_content.parse()?;
510
                if attr_name == "optional" {
172
                    is_optional = true;
338
                } else if attr_name == "required" {
338
                    is_required = true;
338
                } else {
                    return Err(syn::Error::new(
                        attr_name.span(),
                        "Unknown attribute. Use #[required] or #[optional]",
                    ));
                }
            }
            // Parse the field
510
            let arg_name: syn::Ident = content.parse()?;
510
            content.parse::<syn::Token![:]>()?;
510
            let arg_type: syn::Type = content.parse()?;
510
            if content.peek(syn::Token![,]) {
510
                content.parse::<syn::Token![,]>()?;
            }
            // Determine if optional (default to required if no attribute specified)
510
            let is_optional_field = if is_required && is_optional {
                return Err(syn::Error::new(
                    arg_name.span(),
                    "Field cannot be both #[required] and #[optional]",
                ));
510
            } else if is_optional {
172
                true
            } else {
338
                false // Default to required
            };
510
            if is_optional_field {
172
                optional_args.push((arg_name, arg_type));
338
            } else {
338
                required_args.push((arg_name, arg_type));
338
            }
        }
        // The '=>' is outside the braces
166
        input.parse::<syn::Token![=>]>()?;
166
        let body: syn::Block = input.parse()?;
166
        Ok(CommandInput {
166
            name,
166
            required_args,
166
            optional_args,
166
            body,
166
        })
166
    }
}