Lines
84.4 %
Functions
72 %
Branches
100 %
use supp_macro::command;
// Test the command procedural macro with comprehensive coverage
// This tests the actual #[proc_macro] command macro from src/lib.rs
#[derive(Debug, Clone)]
pub enum Argument {
String(String),
Integer(i64),
Boolean(bool),
}
#[derive(Debug)]
pub enum CmdError {
Args(String),
impl std::fmt::Display for CmdError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CmdError::Args(msg) => write!(f, "Argument error: {}", msg),
impl std::error::Error for CmdError {}
pub enum CmdResult {
Success(String),
Value(i64),
// TryFrom implementations for type conversion
impl TryFrom<Argument> for String {
type Error = CmdError;
fn try_from(arg: Argument) -> Result<Self, Self::Error> {
match arg {
Argument::String(s) => Ok(s),
_ => Err(CmdError::Args(format!(
"Cannot convert {:?} to String",
arg
))),
impl TryFrom<Argument> for i64 {
Argument::Integer(i) => Ok(i),
_ => Err(CmdError::Args(format!("Cannot convert {:?} to i64", arg))),
impl TryFrom<Argument> for bool {
Argument::Boolean(b) => Ok(b),
_ => Err(CmdError::Args(format!("Cannot convert {:?} to bool", arg))),
// Progressive types approach - no unified CommandArgs struct needed!
// The macro generates command-specific runner types that provide
// compile-time validation and zero runtime overhead.
// Test commands demonstrating all macro capabilities
// 1. Command with no arguments
command! {
SimpleCommand {
} => {
Ok(Some(CmdResult::Success("Simple command executed".to_string())))
// 2. Command with required arguments only
RequiredArgsCommand {
#[required]
name: String,
count: i64,
Ok(Some(CmdResult::Success(format!("Name: {}, Count: {}", name, count))))
// 3. Command with optional arguments only
OptionalArgsCommand {
#[optional]
flag: bool,
message: String,
let flag_str = flag.map_or("false".to_string(), |f| f.to_string());
let message_str = message.map_or("default".to_string(), |s| s.to_string());
Ok(Some(CmdResult::Success(format!("Flag: {}, Message: {}", flag_str, message_str))))
// 4. Command with mixed required and optional arguments
MixedArgsCommand {
id: i64,
enabled: bool,
description: String,
let enabled_str = enabled.map_or("false".to_string(), |e| e.to_string());
let desc = description.map_or("no description".to_string(), |s| s.to_string());
Ok(Some(CmdResult::Success(format!(
"ID: {}, Name: {}, Enabled: {}, Description: {}",
id, name, enabled_str, desc
))))
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_simple_command_no_args() {
let result = SimpleCommand::new().run().await.unwrap();
assert!(result.is_some());
if let Some(CmdResult::Success(msg)) = result {
assert_eq!(msg, "Simple command executed");
} else {
panic!("Expected Success result");
async fn test_required_args_command_success() {
let result = RequiredArgsCommand::new()
.name("test".to_string())
.count(42)
.run()
.await
.unwrap();
assert_eq!(msg, "Name: test, Count: 42");
async fn test_optional_args_command_none() {
let result = OptionalArgsCommand::new().run().await.unwrap();
assert_eq!(msg, "Flag: false, Message: default");
async fn test_optional_args_command_with_values() {
let result = OptionalArgsCommand::new()
.flag(true)
.message("hello".to_string())
assert_eq!(msg, "Flag: true, Message: hello");
async fn test_mixed_args_command_required_only() {
let result = MixedArgsCommand::new()
.id(123)
.name("test_item".to_string())
assert_eq!(
msg,
"ID: 123, Name: test_item, Enabled: false, Description: no description"
);
async fn test_mixed_args_command_all_args() {
.id(456)
.name("full_item".to_string())
.enabled(true)
.description("A complete item".to_string())
"ID: 456, Name: full_item, Enabled: true, Description: A complete item"
#[test]
fn test_compile_time_validation() {
// ✅ Progressive types approach with compile-time validation
// 1. Required arguments - built using progressive types
let _runner1 = RequiredArgsCommand::new()
.count(42);
// .run() is available here because all required fields are set
// 2. Optional arguments - no required fields needed
let _runner2 = OptionalArgsCommand::new();
// .run() is available immediately for optional-only commands
// 3. Mixed required and optional arguments
let _runner3 = MixedArgsCommand::new()
.enabled(false)
.description("desc".to_string());
// .run() is available because all required fields are set
// ✅ Progressive types provide compile-time safety
// ✅ No Args type naming needed from user perspective
// ✅ Direct field access without unwrap() in generated code
fn test_compile_time_errors() {
// Test that compile-time errors occur with progressive types
let t = trybuild::TestCases::new();
// This test should fail to compile due to wrong argument type
t.compile_fail("tests/compile-fail/wrong_argument_type.rs");
// This test should fail to compile due to nonexistent method
t.compile_fail("tests/compile-fail/missing_required_field.rs");
// This test should fail to compile due to missing required field
t.compile_fail("tests/compile-fail/missing_required_field_compile_time.rs");
fn test_compile_time_validation_demo() {
// With progressive types, missing required fields are caught at compile time!
// These examples would NOT compile:
// ❌ This would NOT compile - missing required field:
// let result = RequiredArgsCommand::new()
// .name("test".to_string())
// .run() // ERROR: no method named `run` found for `RequiredArgsCommandRunner01`
// .await;
// ❌ This would NOT compile - wrong type:
// let runner = RequiredArgsCommand::new()
// .count("not_a_number"); // ERROR: expected `i64`, found `&str`
// ✅ This compiles fine - all required fields provided:
let _runner = RequiredArgsCommand::new()
// ✅ No runtime validation needed - zero unwrap() calls in generated code!
println!("Progressive types eliminate runtime validation entirely!");
fn test_zero_runtime_overhead() {
// Progressive types provide zero runtime overhead!
// The generated run() methods use direct field access:
//
// pub async fn run(self) -> Result<Option<CmdResult>, CmdError> {
// let name = self.name; // ✅ Direct field access - no unwrap()!
// let count = self.count; // ✅ Direct field access - no unwrap()!
// // Original command body...
// }
// This is possible because the type system guarantees that when run()
// is available, all required fields are present in the struct.
println!("✅ Zero runtime validation - fields accessed directly");
println!("✅ Type system guarantees all required fields are present");
println!("✅ No .unwrap(), .expect(), or panic!() calls needed");
// ✅ Demonstrate the clean interface
let _runner = MixedArgsCommand::new().id(123).name("test".to_string());
// Optional fields can be omitted, run() still available