1
use supp_macro::command;
2

            
3
// Test the command procedural macro with comprehensive coverage
4
// This tests the actual #[proc_macro] command macro from src/lib.rs
5

            
6
#[derive(Debug, Clone)]
7
pub enum Argument {
8
    String(String),
9
    Integer(i64),
10
    Boolean(bool),
11
}
12

            
13
#[derive(Debug)]
14
pub enum CmdError {
15
    Args(String),
16
}
17

            
18
impl std::fmt::Display for CmdError {
19
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20
        match self {
21
            CmdError::Args(msg) => write!(f, "Argument error: {msg}"),
22
        }
23
    }
24
}
25

            
26
impl std::error::Error for CmdError {}
27

            
28
#[derive(Debug)]
29
pub enum CmdResult {
30
    Success(String),
31
    Value(i64),
32
}
33

            
34
// TryFrom implementations for type conversion
35
impl TryFrom<Argument> for String {
36
    type Error = CmdError;
37

            
38
    fn try_from(arg: Argument) -> Result<Self, Self::Error> {
39
        match arg {
40
            Argument::String(s) => Ok(s),
41
            _ => Err(CmdError::Args(format!("Cannot convert {arg:?} to String"))),
42
        }
43
    }
44
}
45

            
46
impl TryFrom<Argument> for i64 {
47
    type Error = CmdError;
48

            
49
    fn try_from(arg: Argument) -> Result<Self, Self::Error> {
50
        match arg {
51
            Argument::Integer(i) => Ok(i),
52
            _ => Err(CmdError::Args(format!("Cannot convert {arg:?} to i64"))),
53
        }
54
    }
55
}
56

            
57
impl TryFrom<Argument> for bool {
58
    type Error = CmdError;
59

            
60
    fn try_from(arg: Argument) -> Result<Self, Self::Error> {
61
        match arg {
62
            Argument::Boolean(b) => Ok(b),
63
            _ => Err(CmdError::Args(format!("Cannot convert {arg:?} to bool"))),
64
        }
65
    }
66
}
67

            
68
// Progressive types approach - no unified CommandArgs struct needed!
69
// The macro generates command-specific runner types that provide
70
// compile-time validation and zero runtime overhead.
71

            
72
// Test commands demonstrating all macro capabilities
73

            
74
// 1. Command with no arguments
75
command! {
76
    SimpleCommand {
77
    } => {
78
        Ok(Some(CmdResult::Success("Simple command executed".to_string())))
79
    }
80
2
}
81

            
82
// 2. Command with required arguments only
83
command! {
84
    RequiredArgsCommand {
85
        #[required]
86
        name: String,
87
        #[required]
88
        count: i64,
89
    } => {
90
        Ok(Some(CmdResult::Success(format!("Name: {name}, Count: {count}"))))
91
    }
92
10
}
93

            
94
// 3. Command with optional arguments only
95
command! {
96
    OptionalArgsCommand {
97
        #[optional]
98
        flag: bool,
99
        #[optional]
100
        message: String,
101
    } => {
102
1
        let flag_str = flag.map_or("false".to_string(), |f| f.to_string());
103
1
        let message_str = message.map_or("default".to_string(), |s| s.clone());
104
        Ok(Some(CmdResult::Success(format!("Flag: {flag_str}, Message: {message_str}"))))
105
    }
106
7
}
107

            
108
// 4. Command with mixed required and optional arguments
109
command! {
110
    MixedArgsCommand {
111
        #[required]
112
        id: i64,
113
        #[required]
114
        name: String,
115
        #[optional]
116
        enabled: bool,
117
        #[optional]
118
        description: String,
119
    } => {
120
1
        let enabled_str = enabled.map_or("false".to_string(), |e| e.to_string());
121
1
        let desc = description.map_or("no description".to_string(), |s| s.clone());
122
        Ok(Some(CmdResult::Success(format!(
123
            "ID: {id}, Name: {name}, Enabled: {enabled_str}, Description: {desc}"
124
        ))))
125
    }
126
18
}
127

            
128
#[cfg(test)]
129
mod tests {
130
    use super::*;
131

            
132
    #[tokio::test]
133
1
    async fn test_simple_command_no_args() {
134
1
        let result = SimpleCommand::new().run().await.unwrap();
135
1
        assert!(result.is_some());
136

            
137
1
        if let Some(CmdResult::Success(msg)) = result {
138
1
            assert_eq!(msg, "Simple command executed");
139
1
        } else {
140
1
            panic!("Expected Success result");
141
1
        }
142
1
    }
143

            
144
    #[tokio::test]
145
1
    async fn test_required_args_command_success() {
146
1
        let result = RequiredArgsCommand::new()
147
1
            .name("test".to_string())
148
1
            .count(42)
149
1
            .run()
150
1
            .await
151
1
            .unwrap();
152
1
        assert!(result.is_some());
153

            
154
1
        if let Some(CmdResult::Success(msg)) = result {
155
1
            assert_eq!(msg, "Name: test, Count: 42");
156
1
        } else {
157
1
            panic!("Expected Success result");
158
1
        }
159
1
    }
160

            
161
    #[tokio::test]
162
1
    async fn test_optional_args_command_none() {
163
1
        let result = OptionalArgsCommand::new().run().await.unwrap();
164
1
        assert!(result.is_some());
165

            
166
1
        if let Some(CmdResult::Success(msg)) = result {
167
1
            assert_eq!(msg, "Flag: false, Message: default");
168
1
        } else {
169
1
            panic!("Expected Success result");
170
1
        }
171
1
    }
172

            
173
    #[tokio::test]
174
1
    async fn test_optional_args_command_with_values() {
175
1
        let result = OptionalArgsCommand::new()
176
1
            .flag(true)
177
1
            .message("hello".to_string())
178
1
            .run()
179
1
            .await
180
1
            .unwrap();
181
1
        assert!(result.is_some());
182

            
183
1
        if let Some(CmdResult::Success(msg)) = result {
184
1
            assert_eq!(msg, "Flag: true, Message: hello");
185
1
        } else {
186
1
            panic!("Expected Success result");
187
1
        }
188
1
    }
189

            
190
    #[tokio::test]
191
1
    async fn test_mixed_args_command_required_only() {
192
1
        let result = MixedArgsCommand::new()
193
1
            .id(123)
194
1
            .name("test_item".to_string())
195
1
            .run()
196
1
            .await
197
1
            .unwrap();
198
1
        assert!(result.is_some());
199

            
200
1
        if let Some(CmdResult::Success(msg)) = result {
201
1
            assert_eq!(
202
1
                msg,
203
1
                "ID: 123, Name: test_item, Enabled: false, Description: no description"
204
1
            );
205
1
        } else {
206
1
            panic!("Expected Success result");
207
1
        }
208
1
    }
209

            
210
    #[tokio::test]
211
1
    async fn test_mixed_args_command_all_args() {
212
1
        let result = MixedArgsCommand::new()
213
1
            .id(456)
214
1
            .name("full_item".to_string())
215
1
            .enabled(true)
216
1
            .description("A complete item".to_string())
217
1
            .run()
218
1
            .await
219
1
            .unwrap();
220
1
        assert!(result.is_some());
221

            
222
1
        if let Some(CmdResult::Success(msg)) = result {
223
1
            assert_eq!(
224
1
                msg,
225
1
                "ID: 456, Name: full_item, Enabled: true, Description: A complete item"
226
1
            );
227
1
        } else {
228
1
            panic!("Expected Success result");
229
1
        }
230
1
    }
231

            
232
    #[test]
233
1
    fn test_compile_time_validation() {
234
        // ✅ Progressive types approach with compile-time validation
235

            
236
        // 1. Required arguments - built using progressive types
237
1
        let _runner1 = RequiredArgsCommand::new()
238
1
            .name("test".to_string())
239
1
            .count(42);
240
        // .run() is available here because all required fields are set
241

            
242
        // 2. Optional arguments - no required fields needed
243
1
        let _runner2 = OptionalArgsCommand::new();
244
        // .run() is available immediately for optional-only commands
245

            
246
        // 3. Mixed required and optional arguments
247
1
        let _runner3 = MixedArgsCommand::new()
248
1
            .id(123)
249
1
            .name("test".to_string())
250
1
            .enabled(false)
251
1
            .description("desc".to_string());
252
        // .run() is available because all required fields are set
253

            
254
        // ✅ Progressive types provide compile-time safety
255
        // ✅ No Args type naming needed from user perspective
256
        // ✅ Direct field access without unwrap() in generated code
257
1
    }
258

            
259
    #[test]
260
1
    fn test_compile_time_errors() {
261
        // Test that compile-time errors occur with progressive types
262
1
        let t = trybuild::TestCases::new();
263

            
264
        // This test should fail to compile due to wrong argument type
265
1
        t.compile_fail("tests/compile-fail/wrong_argument_type.rs");
266

            
267
        // This test should fail to compile due to nonexistent method
268
1
        t.compile_fail("tests/compile-fail/missing_required_field.rs");
269

            
270
        // This test should fail to compile due to missing required field
271
1
        t.compile_fail("tests/compile-fail/missing_required_field_compile_time.rs");
272
1
    }
273

            
274
    #[test]
275
1
    fn test_compile_time_validation_demo() {
276
        // With progressive types, missing required fields are caught at compile time!
277
        // These examples would NOT compile:
278

            
279
        // ❌ This would NOT compile - missing required field:
280
        // let result = RequiredArgsCommand::new()
281
        //     .name("test".to_string())
282
        //     .run()  // ERROR: no method named `run` found for `RequiredArgsCommandRunner01`
283
        //     .await;
284

            
285
        // ❌ This would NOT compile - wrong type:
286
        // let runner = RequiredArgsCommand::new()
287
        //     .name("test".to_string())
288
        //     .count("not_a_number");  // ERROR: expected `i64`, found `&str`
289

            
290
        // ✅ This compiles fine - all required fields provided:
291
1
        let _runner = RequiredArgsCommand::new()
292
1
            .name("test".to_string())
293
1
            .count(42);
294
        // .run() is available because all required fields are set
295

            
296
        // ✅ No runtime validation needed - zero unwrap() calls in generated code!
297
1
        println!("Progressive types eliminate runtime validation entirely!");
298
1
    }
299

            
300
    #[test]
301
1
    fn test_zero_runtime_overhead() {
302
        // Progressive types provide zero runtime overhead!
303
        // The generated run() methods use direct field access:
304
        //
305
        // pub async fn run(self) -> Result<Option<CmdResult>, CmdError> {
306
        //     let name = self.name;     // ✅ Direct field access - no unwrap()!
307
        //     let count = self.count;   // ✅ Direct field access - no unwrap()!
308
        //
309
        //     // Original command body...
310
        // }
311
        //
312
        // This is possible because the type system guarantees that when run()
313
        // is available, all required fields are present in the struct.
314

            
315
1
        println!("✅ Zero runtime validation - fields accessed directly");
316
1
        println!("✅ Type system guarantees all required fields are present");
317
1
        println!("✅ No .unwrap(), .expect(), or panic!() calls needed");
318

            
319
        // ✅ Demonstrate the clean interface
320
1
        let _runner = MixedArgsCommand::new().id(123).name("test".to_string());
321
        // Optional fields can be omitted, run() still available
322
1
    }
323
}