Skip to main content

supp_macro/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::Lit;
4use syn::{Data, DeriveInput, Fields, ItemFn, Path, parse_macro_input};
5
6#[proc_macro_attribute]
7pub fn local_db_sqlx_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
8    let input = parse_macro_input!(item as ItemFn);
9    let fn_name = &input.sig.ident;
10    let block = &input.block;
11
12    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    TokenStream::from(expanded)
23}
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]
36pub fn shared_runtime_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
37    let input = parse_macro_input!(item as ItemFn);
38    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    }
43    let fn_name = &input.sig.ident;
44    let output = &input.sig.output;
45    let block = &input.block;
46    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    TokenStream::from(quote! {
51        #[test]
52        #(#attrs)*
53        fn #fn_name() #output {
54            crate::common::runtime().block_on(async #block)
55        }
56    })
57}
58
59#[proc_macro_derive(Builder, attributes(builder))]
60pub fn builder_macro(input: TokenStream) -> TokenStream {
61    // Parse the input tokens into a syntax tree
62    let input = parse_macro_input!(input as DeriveInput);
63    let name = &input.ident;
64    let generics = &input.generics; // Capture generics (including lifetimes)
65    let builder_name = syn::Ident::new(&format!("{name}Builder"), name.span());
66
67    // Check for custom error_kind attribute
68    let mut error_kind = None;
69
70    // Parse attributes
71    for attr in &input.attrs {
72        if attr.path().is_ident("builder") {
73            attr.parse_nested_meta(|meta| {
74                if meta.path.is_ident("error_kind")
75                    && let Ok(Lit::Str(lit_str)) = meta.value()?.parse()
76                {
77                    error_kind = Some(lit_str.parse::<Path>().unwrap());
78                }
79                Ok(())
80            })
81            .unwrap();
82        }
83    }
84
85    // Set a default error kind if none is provided
86    let error_kind = error_kind.expect(
87        "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    let custom_error_name = syn::Ident::new(&format!("{name}Error"), name.span());
92
93    let fields = if let Data::Struct(data) = &input.data {
94        if let Fields::Named(fields) = &data.fields {
95            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    let builder_fields = fields.iter().map(|field| {
105        let field_name = &field.ident;
106        let field_ty = &field.ty;
107        let builder_field_type = quote! { Option<#field_ty> };
108        quote! {
109            #field_name: #builder_field_type
110        }
111    });
112
113    // Generate initialization in new()
114    let builder_fields_init = fields.iter().map(|field| {
115        let field_name = &field.ident;
116        quote! {
117            #field_name: None
118        }
119    });
120
121    // Generate setter methods
122    let setters = fields.iter().map(|field| {
123        let field_name = &field.ident;
124        let field_type = &field.ty;
125
126        if is_option_type(field_type) {
127            let inner_type = get_inner_type(field_type);
128            if is_string_type(&inner_type) {
129                // For Option<String>, accept &str
130                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                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        } else if is_string_type(field_type) {
146            // For String, accept &str
147            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            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    });
163
164    // Generate code to check for missing required fields
165    let check_required_fields = fields
166        .iter()
167        .filter(|field| !is_option_type(&field.ty))
168        .map(|field| {
169            let field_name = &field.ident;
170            let field_name_str = field_name.as_ref().unwrap().to_string();
171            quote! {
172                if self.#field_name.is_none() {
173                    missing_fields.push(#field_name_str);
174                }
175            }
176        });
177
178    // Generate build_fields
179    let build_fields = fields.iter().map(|field| {
180        let field_name = &field.ident;
181        if is_option_type(&field.ty) {
182            quote! {
183                #field_name: self.#field_name.clone().unwrap_or(None)
184            }
185        } else {
186            quote! {
187                #field_name: self.#field_name.clone().unwrap()
188            }
189        }
190    });
191
192    // Extract the lifetime parameters from generics for use in the builder struct
193    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
194
195    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    TokenStream::from(expanded)
235}
236
237/// Helper function to determine if a type is an `Option<T>`
238fn is_option_type(ty: &syn::Type) -> bool {
239    matches!(ty, syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. }) if segments.iter().any(|segment| segment.ident == "Option"))
240}
241
242/// Helper function to get the inner type of an `Option<T>`
243fn get_inner_type(ty: &syn::Type) -> syn::Type {
244    if let syn::Type::Path(type_path) = ty
245        && let Some(segment) = type_path.path.segments.first()
246        && segment.ident == "Option"
247        && let syn::PathArguments::AngleBracketed(args) = &segment.arguments
248        && let Some(syn::GenericArgument::Type(inner_type)) = args.args.first()
249    {
250        return inner_type.clone();
251    }
252    ty.clone()
253}
254
255/// Helper function to check if the type is String
256fn is_string_type(ty: &syn::Type) -> bool {
257    if let syn::Type::Path(type_path) = ty
258        && let Some(segment) = type_path.path.segments.last()
259    {
260        return segment.ident == "String";
261    }
262    false
263}
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]
750pub fn command(input: TokenStream) -> TokenStream {
751    let input = parse_macro_input!(input as CommandInput);
752
753    let name = &input.name;
754    let required_args = &input.required_args;
755    let optional_args = &input.optional_args;
756    let body = &input.body;
757
758    // Generate progressive runner types for all combinations of required fields
759    let runner_types = generate_progressive_runner_types(name, required_args, optional_args, body);
760
761    // Generate the main command struct
762    let command_struct = quote! {
763        #[derive(Debug)]
764        pub struct #name;
765    };
766
767    // Generate the new() method that starts the builder chain
768    let new_method = generate_new_method(name, required_args.len(), optional_args);
769
770    let expanded = quote! {
771        #command_struct
772
773        #runner_types
774
775        #new_method
776    };
777
778    TokenStream::from(expanded)
779}
780
781/// Generate all possible runner type combinations for required fields
782fn generate_progressive_runner_types(
783    command_name: &syn::Ident,
784    required_args: &[(syn::Ident, syn::Type)],
785    optional_args: &[(syn::Ident, syn::Type)],
786    body: &syn::Block,
787) -> proc_macro2::TokenStream {
788    let num_required = required_args.len();
789    let total_combinations = 1 << num_required; // 2^num_required
790
791    let mut runner_types = Vec::new();
792
793    // Generate a runner type for each possible combination of set required fields
794    for combination in 0..total_combinations {
795        let runner_type = generate_single_runner_type(
796            command_name,
797            required_args,
798            optional_args,
799            combination,
800            num_required,
801            body,
802        );
803        runner_types.push(runner_type);
804    }
805
806    quote! {
807        #(#runner_types)*
808    }
809}
810
811/// Generate a single runner type for a specific combination of set fields
812fn generate_single_runner_type(
813    command_name: &syn::Ident,
814    required_args: &[(syn::Ident, syn::Type)],
815    optional_args: &[(syn::Ident, syn::Type)],
816    combination: usize,
817    num_required: usize,
818    body: &syn::Block,
819) -> proc_macro2::TokenStream {
820    // Create binary representation for the runner type name
821    let binary_suffix = format!("{:0width$b}", combination, width = num_required.max(1));
822    let runner_name = syn::Ident::new(
823        &format!("{command_name}Runner{binary_suffix}"),
824        command_name.span(),
825    );
826
827    // Determine which required fields are set in this combination
828    let mut struct_fields = Vec::new();
829    for (i, (field_name, field_type)) in required_args.iter().enumerate() {
830        if (combination >> i) & 1 == 1 {
831            // This required field is set in this combination
832            struct_fields.push(quote! {
833                pub #field_name: #field_type
834            });
835        }
836    }
837
838    // Always include optional fields in all runner types
839    for (field_name, field_type) in optional_args {
840        struct_fields.push(quote! {
841            pub #field_name: Option<#field_type>
842        });
843    }
844
845    // Generate the struct definition
846    let struct_def = if struct_fields.is_empty() {
847        quote! {
848            #[derive(Debug)]
849            pub struct #runner_name;
850        }
851    } else {
852        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    let transition_methods = generate_transition_methods(
862        command_name,
863        required_args,
864        optional_args,
865        combination,
866        num_required,
867    );
868
869    // Generate run method if this is the complete state (all required fields set)
870    let complete_mask = (1 << num_required) - 1;
871    let run_method = if combination == complete_mask {
872        generate_run_method(command_name, required_args, optional_args, body)
873    } else {
874        quote! {}
875    };
876
877    quote! {
878        #struct_def
879
880        impl #runner_name {
881            #transition_methods
882            #run_method
883        }
884    }
885}
886
887/// Generate transition methods for a runner type (field setters)
888fn generate_transition_methods(
889    command_name: &syn::Ident,
890    required_args: &[(syn::Ident, syn::Type)],
891    optional_args: &[(syn::Ident, syn::Type)],
892    current_combination: usize,
893    num_required: usize,
894) -> proc_macro2::TokenStream {
895    let mut methods = Vec::new();
896
897    // Generate setter methods for required fields not yet set
898    for (i, (field_name, field_type)) in required_args.iter().enumerate() {
899        if (current_combination >> i) & 1 == 0 {
900            // This required field is not set yet, generate a setter
901            let new_combination = current_combination | (1 << i);
902            let binary_suffix =
903                format!("{:0width$b}", new_combination, width = num_required.max(1));
904            let target_runner = syn::Ident::new(
905                &format!("{command_name}Runner{binary_suffix}"),
906                command_name.span(),
907            );
908
909            let method = generate_field_setter_method(
910                required_args,
911                optional_args,
912                field_name,
913                field_type,
914                current_combination,
915                &target_runner,
916            );
917            methods.push(method);
918        }
919    }
920
921    // Generate setter methods for optional fields (available on all runner types)
922    for (field_name, field_type) in optional_args {
923        let current_runner = syn::Ident::new(
924            &format!(
925                "{}Runner{:0width$b}",
926                command_name,
927                current_combination,
928                width = num_required.max(1)
929            ),
930            command_name.span(),
931        );
932
933        let method = generate_optional_field_setter(
934            field_name,
935            field_type,
936            &current_runner,
937            required_args,
938            optional_args,
939            current_combination,
940            num_required,
941        );
942        methods.push(method);
943    }
944
945    quote! {
946        #(#methods)*
947    }
948}
949
950/// Generate a setter method for a required field
951fn generate_field_setter_method(
952    required_args: &[(syn::Ident, syn::Type)],
953    optional_args: &[(syn::Ident, syn::Type)],
954    field_name: &syn::Ident,
955    field_type: &syn::Type,
956    current_combination: usize,
957    target_runner: &syn::Ident,
958) -> proc_macro2::TokenStream {
959    // Generate field assignments for the new state
960    let mut field_assignments = Vec::new();
961
962    // Handle required fields
963    for (i, (req_field_name, _)) in required_args.iter().enumerate() {
964        if req_field_name == field_name {
965            // This is the field being set
966            field_assignments.push(quote! {
967                #req_field_name: value
968            });
969        } else if (current_combination >> i) & 1 == 1 {
970            // This field was already set, move it from self
971            field_assignments.push(quote! {
972                #req_field_name: self.#req_field_name
973            });
974        }
975        // Fields not set in either state are omitted
976    }
977
978    // Handle optional fields (always present, move from self)
979    for (opt_field_name, _) in optional_args {
980        field_assignments.push(quote! {
981            #opt_field_name: self.#opt_field_name
982        });
983    }
984
985    // Generate the constructor call
986    let constructor = if field_assignments.is_empty() {
987        quote! { #target_runner }
988    } else {
989        quote! {
990            #target_runner {
991                #(#field_assignments),*
992            }
993        }
994    };
995
996    quote! {
997        pub fn #field_name(self, value: #field_type) -> #target_runner {
998            #constructor
999        }
1000    }
1001}
1002
1003/// Generate a setter method for an optional field
1004fn generate_optional_field_setter(
1005    field_name: &syn::Ident,
1006    field_type: &syn::Type,
1007    current_runner: &syn::Ident,
1008    required_args: &[(syn::Ident, syn::Type)],
1009    optional_args: &[(syn::Ident, syn::Type)],
1010    current_combination: usize,
1011    _num_required: usize,
1012) -> proc_macro2::TokenStream {
1013    // Generate field assignments (same state, but update the optional field)
1014    let mut field_assignments = Vec::new();
1015
1016    // Handle required fields (move from self if set)
1017    for (i, (req_field_name, _)) in required_args.iter().enumerate() {
1018        if (current_combination >> i) & 1 == 1 {
1019            field_assignments.push(quote! {
1020                #req_field_name: self.#req_field_name
1021            });
1022        }
1023    }
1024
1025    // Handle optional fields
1026    for (opt_field_name, _) in optional_args {
1027        if opt_field_name == field_name {
1028            // This is the field being set
1029            field_assignments.push(quote! {
1030                #opt_field_name: Some(value)
1031            });
1032        } else {
1033            // Move other optional fields from self
1034            field_assignments.push(quote! {
1035                #opt_field_name: self.#opt_field_name
1036            });
1037        }
1038    }
1039
1040    let constructor = if field_assignments.is_empty() {
1041        quote! { #current_runner }
1042    } else {
1043        quote! {
1044            #current_runner {
1045                #(#field_assignments),*
1046            }
1047        }
1048    };
1049
1050    quote! {
1051        pub fn #field_name(self, value: #field_type) -> #current_runner {
1052            #constructor
1053        }
1054    }
1055}
1056
1057/// Generate the run method for the complete runner state
1058fn generate_run_method(
1059    _command_name: &syn::Ident,
1060    required_args: &[(syn::Ident, syn::Type)],
1061    optional_args: &[(syn::Ident, syn::Type)],
1062    body: &syn::Block,
1063) -> proc_macro2::TokenStream {
1064    // Extract field values directly (no unwrap needed!)
1065    let mut variable_assignments = Vec::new();
1066
1067    // Required fields - direct field access
1068    for (field_name, _) in required_args {
1069        variable_assignments.push(quote! {
1070            let #field_name = self.#field_name;
1071        });
1072    }
1073
1074    // Optional fields - direct field access
1075    for (field_name, _) in optional_args {
1076        variable_assignments.push(quote! {
1077            let #field_name = self.#field_name;
1078        });
1079    }
1080
1081    quote! {
1082        pub async fn run(self) -> Result<Option<CmdResult>, CmdError> {
1083            // Zero runtime checks - direct field access!
1084            #(#variable_assignments)*
1085
1086            // Original command body
1087            #body
1088        }
1089    }
1090}
1091
1092/// Generate the `new()` method for the command
1093fn generate_new_method(
1094    command_name: &syn::Ident,
1095    num_required: usize,
1096    optional_args: &[(syn::Ident, syn::Type)],
1097) -> proc_macro2::TokenStream {
1098    let initial_runner = syn::Ident::new(
1099        &format!(
1100            "{}Runner{:0width$b}",
1101            command_name,
1102            0,
1103            width = num_required.max(1)
1104        ),
1105        command_name.span(),
1106    );
1107
1108    // Initial state has no required fields set, but has optional fields as None
1109    let constructor = if optional_args.is_empty() && num_required > 0 {
1110        // Unit struct (no fields at all in initial state)
1111        quote! { #initial_runner }
1112    } else {
1113        // Struct with optional fields initialized to None
1114        let optional_field_inits = optional_args.iter().map(|(field_name, _)| {
1115            quote! { #field_name: None }
1116        });
1117
1118        if optional_field_inits.len() > 0 {
1119            quote! {
1120                #initial_runner {
1121                    #(#optional_field_inits),*
1122                }
1123            }
1124        } else {
1125            quote! { #initial_runner }
1126        }
1127    };
1128
1129    quote! {
1130        impl #command_name {
1131            pub fn new() -> #initial_runner {
1132                #constructor
1133            }
1134        }
1135    }
1136}
1137
1138struct CommandInput {
1139    name: syn::Ident,
1140    required_args: Vec<(syn::Ident, syn::Type)>,
1141    optional_args: Vec<(syn::Ident, syn::Type)>,
1142    body: syn::Block,
1143}
1144
1145impl syn::parse::Parse for CommandInput {
1146    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
1147        let name: syn::Ident = input.parse()?;
1148
1149        let content;
1150        syn::braced!(content in input);
1151
1152        let mut required_args = Vec::new();
1153        let mut optional_args = Vec::new();
1154
1155        while !content.is_empty() {
1156            // Parse attributes
1157            let mut is_optional = false;
1158            let mut is_required = false;
1159
1160            while content.peek(syn::Token![#]) {
1161                content.parse::<syn::Token![#]>()?;
1162                let attr_content;
1163                syn::bracketed!(attr_content in content);
1164                let attr_name: syn::Ident = attr_content.parse()?;
1165
1166                if attr_name == "optional" {
1167                    is_optional = true;
1168                } else if attr_name == "required" {
1169                    is_required = true;
1170                } else {
1171                    return Err(syn::Error::new(
1172                        attr_name.span(),
1173                        "Unknown attribute. Use #[required] or #[optional]",
1174                    ));
1175                }
1176            }
1177
1178            // Parse the field
1179            let arg_name: syn::Ident = content.parse()?;
1180            content.parse::<syn::Token![:]>()?;
1181            let arg_type: syn::Type = content.parse()?;
1182
1183            if content.peek(syn::Token![,]) {
1184                content.parse::<syn::Token![,]>()?;
1185            }
1186
1187            // Determine if optional (default to required if no attribute specified)
1188            let is_optional_field = if is_required && is_optional {
1189                return Err(syn::Error::new(
1190                    arg_name.span(),
1191                    "Field cannot be both #[required] and #[optional]",
1192                ));
1193            } else if is_optional {
1194                true
1195            } else {
1196                false // Default to required
1197            };
1198
1199            if is_optional_field {
1200                optional_args.push((arg_name, arg_type));
1201            } else {
1202                required_args.push((arg_name, arg_type));
1203            }
1204        }
1205
1206        // The '=>' is outside the braces
1207        input.parse::<syn::Token![=>]>()?;
1208        let body: syn::Block = input.parse()?;
1209
1210        Ok(CommandInput {
1211            name,
1212            required_args,
1213            optional_args,
1214            body,
1215        })
1216    }
1217}