1
use supp_macro::command;
2

            
3
#[derive(Debug)]
4
pub enum CmdError {
5
    Args(String),
6
}
7

            
8
impl std::fmt::Display for CmdError {
9
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
10
        match self {
11
            CmdError::Args(msg) => write!(f, "Argument error: {}", msg),
12
        }
13
    }
14
}
15

            
16
impl std::error::Error for CmdError {}
17

            
18
#[derive(Debug)]
19
pub enum CmdResult {
20
    Success(String),
21
    Value(i64),
22
}
23

            
24
// Test commands demonstrating the progressive types approach
25

            
26
// 1. Command with no arguments (generates 1 runner type)
27
command! {
28
    SimpleCommand {
29
    } => {
30
        Ok(Some(CmdResult::Success("Simple command executed".to_string())))
31
    }
32
4
}
33

            
34
// 2. Command with required arguments only (generates 4 runner types: 00, 01, 10, 11)
35
command! {
36
    RequiredArgsCommand {
37
        #[required]
38
        name: String,
39
        #[required]
40
        count: i64,
41
    } => {
42
        Ok(Some(CmdResult::Success(format!("Name: {}, Count: {}", name, count))))
43
    }
44
14
}
45

            
46
// 3. Command with optional arguments only (generates 1 runner type)
47
command! {
48
    OptionalArgsCommand {
49
        #[optional]
50
        flag: bool,
51
        #[optional]
52
        message: String,
53
    } => {
54
2
        let flag_str = flag.map_or("false".to_string(), |f| f.to_string());
55
2
        let message_str = message.map_or("default".to_string(), |s| s.to_string());
56
        Ok(Some(CmdResult::Success(format!("Flag: {}, Message: {}", flag_str, message_str))))
57
    }
58
8
}
59

            
60
// 4. Command with mixed required and optional arguments (generates 4 runner types: 00, 01, 10, 11)
61
command! {
62
    MixedArgsCommand {
63
        #[required]
64
        id: i64,
65
        #[required]
66
        name: String,
67
        #[optional]
68
        enabled: bool,
69
        #[optional]
70
        description: String,
71
    } => {
72
2
        let enabled_str = enabled.map_or("false".to_string(), |e| e.to_string());
73
2
        let desc = description.map_or("no description".to_string(), |s| s.to_string());
74
        Ok(Some(CmdResult::Success(format!(
75
            "ID: {}, Name: {}, Enabled: {}, Description: {}",
76
            id, name, enabled_str, desc
77
        ))))
78
    }
79
18
}
80

            
81
#[cfg(test)]
82
mod tests {
83
    use super::*;
84

            
85
    #[tokio::test]
86
3
    async fn test_simple_interface_usage() {
87
        // ✅ CLEAN INTERFACE - Users never see or name Args types!
88

            
89
        // 1. Simple command with no arguments
90
2
        let result = SimpleCommand::new().run().await.unwrap();
91

            
92
2
        if let Some(CmdResult::Success(msg)) = result {
93
2
            assert_eq!(msg, "Simple command executed");
94
        }
95

            
96
        // 2. Required arguments - compile-time validated, zero runtime checks
97
2
        let result = RequiredArgsCommand::new()
98
2
            .name("test".to_string()) // ✅ Type validated at compile time
99
2
            .count(42) // ✅ Type validated at compile time
100
2
            .run() // ✅ Only available when all required fields set
101
2
            .await
102
2
            .unwrap();
103

            
104
2
        if let Some(CmdResult::Success(msg)) = result {
105
2
            assert_eq!(msg, "Name: test, Count: 42");
106
        }
107

            
108
        // 3. Optional arguments
109
2
        let result = OptionalArgsCommand::new()
110
2
            .flag(true) // ✅ Optional field
111
2
            .message("hello".to_string()) // ✅ Optional field
112
2
            .run()
113
2
            .await
114
2
            .unwrap();
115

            
116
2
        if let Some(CmdResult::Success(msg)) = result {
117
2
            assert_eq!(msg, "Flag: true, Message: hello");
118
        }
119

            
120
        // 4. Mixed required and optional - most realistic scenario
121
2
        let result = MixedArgsCommand::new()
122
2
            .id(123) // ✅ Required
123
2
            .name("test_item".to_string()) // ✅ Required
124
2
            .enabled(true) // ✅ Optional
125
2
            .description("A test item".to_string()) // ✅ Optional
126
2
            .run()
127
2
            .await
128
2
            .unwrap();
129

            
130
3
        if let Some(CmdResult::Success(msg)) = result {
131
3
            assert_eq!(
132
2
                msg,
133
2
                "ID: 123, Name: test_item, Enabled: true, Description: A test item"
134
2
            );
135
2
        }
136
2
    }
137

            
138
    #[test]
139
2
    fn test_compile_time_validation_demo() {
140
        // These examples demonstrate compile-time validation:
141

            
142
        // ✅ Correct usage compiles
143
2
        let _runner1 = RequiredArgsCommand::new()
144
2
            .name("test".to_string())
145
2
            .count(42);
146
        // .run() is available here because all required fields are set
147

            
148
        // ❌ This would NOT compile - missing required field:
149
        // let result = RequiredArgsCommand::new()
150
        //     .name("test".to_string())
151
        //     .run()  // ERROR: no method named `run` found for `RequiredArgsCommandRunner01`
152
        //     .await;
153

            
154
        // ❌ This would NOT compile - wrong type:
155
        // let runner = RequiredArgsCommand::new()
156
        //     .name("test".to_string())
157
        //     .count("not_a_number");  // ERROR: expected `i64`, found `&str`
158

            
159
        // ❌ This would NOT compile - method doesn't exist:
160
        // let runner = RequiredArgsCommand::new()
161
        //     .invalid_method("test");  // ERROR: no method named `invalid_method`
162

            
163
        // ✅ Optional fields can be omitted
164
2
        let _runner2 = MixedArgsCommand::new().id(456).name("minimal".to_string());
165
        // .run() is available because all required fields are set
166
2
    }
167

            
168
    #[test]
169
2
    fn test_zero_runtime_checks_demo() {
170
        // The generated run() methods use direct field access:
171
        //
172
        // pub async fn run(self) -> Result<Option<CmdResult>, CmdError> {
173
        //     let name = self.name;     // ✅ Direct field access - no unwrap() needed!
174
        //     let count = self.count;   // ✅ Direct field access - no unwrap() needed!
175
        //
176
        //     // Original command body...
177
        // }
178
        //
179
        // This is possible because the type system guarantees that when run()
180
        // is available, all required fields are set in the struct.
181

            
182
2
        println!("✅ Zero runtime validation - fields are accessed directly");
183
2
        println!("✅ Type system guarantees all required fields are present");
184
2
        println!("✅ No .unwrap(), .expect(), or panic!() calls in generated code");
185
2
    }
186
}