Skip to main content

command

Macro command 

Source
command!() { /* proc-macro */ }
Expand description

A procedural macro for generating typed Command implementations with compile-time validation.

This macro provides pure value-based argument passing with compile-time type safety by generating:

  • Typed Args structs with proper field types passed by value only
  • Commands that accept Args structs directly (no HashMap usage)
  • Individual typed variables available directly in command scope
  • Compile-time validation of argument types and required/optional fields
  • Zero runtime argument parsing or validation overhead

§Syntax

command! {
    CommandName {
        #[required]
        arg_name: Type,
        #[optional]
        opt_name: Type,
    } => {
        // Command implementation body
        // Individual typed variables are available in scope
    }
}

§Generated Code

The macro generates:

  • A CommandNameArgs struct with typed fields (required fields as Type, optional as Option<Type>)
  • A CommandName struct implementing Command trait with typed run(args: CommandNameArgs) method
  • Individual typed variables extracted from the Args struct and available in the command body
  • Pure compile-time type validation with no runtime overhead

§Examples

§Simple command with no arguments


command! {
    GetVersion {
    } => {
        Ok(Some(CmdResult::String("1.0.0".to_string())))
    }
}

let result = GetVersion::new().run().await.unwrap();

§Command with required arguments (server-compatible types)


// This creates a commodity in the financial system
command! {
    CreateCommodity {
        #[required]
        symbol: String,
        #[required]
        name: String,
        #[required]
        user_id: Uuid,
    } => {
        // Individual typed variables are automatically available
        Ok(Some(CmdResult::String(format!(
            "Created commodity {} ({}) for user {}",
            name, symbol, user_id
        ))))
    }
}

let result = CreateCommodity::new()
    .symbol("USD".to_string())
    .name("US Dollar".to_string())
    .user_id(uuid::Uuid::new_v4())
    .run()
    .await
    .unwrap();

§Command with optional arguments


command! {
    ListTransactions {
        #[required]
        user_id: Uuid,
        #[optional]
        account: String,
    } => {
        let filter = if let Some(account) = account {
            format!(" for account {}", account)
        } else {
            String::new()
        };
        Ok(Some(CmdResult::String(format!("Listing transactions for user {}{}", user_id, filter))))
    }
}

§Command with mixed required and optional arguments


command! {
    CreateUserCommand {
        #[required]
        user_id: i64,
        #[required]
        username: String,
        #[optional]
        email: String,
        #[optional]
        is_admin: bool,
    } => {
        let email_str = email.map_or_else(|| format!("{}@example.com", username), |s| s.to_string());
        let admin_status = is_admin.unwrap_or(false);

        let message = format!(
            "Created user {} (ID: {}, Email: {}, Admin: {})",
            username, user_id, email_str, admin_status
        );
        Ok(Some(CmdResult::Success(message)))
    }
}

let result = CreateUserCommand::new()
    .user_id(123)
    .username("alice".to_string())
    .is_admin(true)
    .run()
    .await
    .unwrap();

§Server-compatible Command implementation


command! {
    CalculateCommand {
        #[required]
        a: i64,
        #[required]
        b: i64,
    } => {
        let result = a + b;
        Ok(Some(CmdResult::Success(format!("{} + {} = {}", a, b, result))))
    }
}

let result = CalculateCommand::new()
    .a(10)
    .b(20)
    .run()
    .await
    .unwrap();

§Migration from Manual Commands

The macro makes it easy to migrate from manual Command implementations:

// BEFORE: Manual implementation
#[derive(Debug)]
pub struct GetConfig;

#[async_trait]
impl Command for GetConfig {
    async fn run<'a>(&self, args: &'a HashMap<&'a str, &'a Argument>) -> Result<Option<CmdResult>, CmdError> {
        if let Some(Argument::String(name)) = args.get("name") {
            Ok(config(name).await?.map(|v| CmdResult::String(v)))
        } else {
            Err(CmdError::Args("No field name provided".to_string()))
        }
    }
}

// AFTER: Using the macro
command! {
    GetConfig {
        #[required]
        name: String,
    } => {
        Ok(config(name).await?.map(|v| CmdResult::String(v)))
    }
}

§Error Handling

The new pure typed system provides compile-time error prevention:

  • Missing required arguments are compile-time errors (cannot compile without them)
  • Invalid argument types are compile-time errors (type checking at build time)
  • Runtime errors only occur in the command body logic itself
  • No argument validation overhead at runtime

§Supported Argument Types

The macro supports any Rust type for arguments:

  • String - Text arguments
  • i64, u64, etc. - Integer arguments
  • bool - Boolean arguments
  • Rational64 - Rational number arguments (for financial precision)
  • Uuid - UUID arguments
  • Vec<u8> - Binary data arguments
  • DateTime<Utc> - DateTime arguments
  • Custom types - Any type can be used as an argument
  • Option<T> - Automatically applied for optional arguments