cli_command/
command.rs

1use crate::cli_error::{CliError, CliErrorKind};
2use std::collections::HashMap;
3
4/// Represents a parsed command with its name and arguments.
5///
6/// The `Command` struct holds the parsed command-line arguments in a structured format.
7/// It provides convenient methods for accessing arguments with type conversion and error handling.
8///
9/// # Examples
10///
11/// ```rust
12/// use cli_command::{Command, parse_command_string};
13///
14/// let cmd = parse_command_string("serve --port 8080 --host localhost").unwrap();
15/// assert_eq!(cmd.name, "serve");
16/// assert_eq!(cmd.get_argument("port"), Some("8080"));
17/// assert_eq!(cmd.get_argument("host"), Some("localhost"));
18/// ```
19#[derive(Debug, Clone)]
20pub struct Command {
21    /// The command name (first non-flag argument)
22    pub name: String,
23    /// Parsed arguments as a map of argument names to their values
24    pub arguments: HashMap<String, Box<[String]>>,
25}
26
27impl Command {
28    /// Gets the nth parameter of an argument as a `usize`, if it exists and can be parsed.
29    ///
30    /// # Arguments
31    /// * `argument` - The name of the argument
32    /// * `nth` - The zero-based index of the parameter
33    ///
34    /// # Returns
35    /// * `Some(usize)` - If the parameter exists and can be parsed as a usize
36    /// * `None` - If the argument doesn't exist or the parameter can't be parsed
37    ///
38    /// # Examples
39    /// ```rust
40    /// use cli_command::{Command, parse_command_string};
41    ///
42    /// let cmd = parse_command_string("--numbers 1 2 3").unwrap();
43    /// assert_eq!(cmd.get_argument_nth_parameter_usize("numbers", 0), Some(1));
44    /// assert_eq!(cmd.get_argument_nth_parameter_usize("numbers", 1), Some(2));
45    /// assert_eq!(cmd.get_argument_nth_parameter_usize("numbers", 5), None);
46    /// ```
47    pub fn get_argument_nth_parameter_usize(&self, argument: &str, nth: usize) -> Option<usize> {
48        match self.arguments.get(argument) {
49            Some(params) if params.len() > nth => usize::from_str_radix(&params[nth], 10).ok(),
50            _ => None,
51        }
52    }
53
54    /// Gets the nth parameter of an argument as a string slice, if it exists.
55    ///
56    /// # Arguments
57    /// * `argument` - The name of the argument
58    /// * `nth` - The zero-based index of the parameter
59    ///
60    /// # Returns
61    /// * `Some(&str)` - If the parameter exists
62    /// * `None` - If the argument doesn't exist or the parameter is out of bounds
63    ///
64    /// # Examples
65    /// ```rust
66    /// use cli_command::{Command, parse_command_string};
67    ///
68    /// let cmd = parse_command_string("--files a.txt b.txt c.txt").unwrap();
69    /// assert_eq!(cmd.get_argument_nth_parameter("files", 0), Some("a.txt"));
70    /// assert_eq!(cmd.get_argument_nth_parameter("files", 1), Some("b.txt"));
71    /// assert_eq!(cmd.get_argument_nth_parameter("files", 5), None);
72    /// ```
73    pub fn get_argument_nth_parameter(&self, argument: &str, nth: usize) -> Option<&str> {
74        match self.arguments.get(argument) {
75            Some(params) if params.len() > nth => Some(&params[nth]),
76            _ => None,
77        }
78    }
79
80    /// Gets the nth parameter of an argument as a string slice, returning an error if missing.
81    ///
82    /// # Arguments
83    /// * `argument` - The name of the argument
84    /// * `nth` - The zero-based index of the parameter
85    ///
86    /// # Returns
87    /// * `Ok(&str)` - If the parameter exists
88    /// * `Err(CliError)` - If the argument or parameter is missing
89    ///
90    /// # Examples
91    /// ```rust
92    /// use cli_command::{Command, parse_command_string};
93    ///
94    /// let cmd = parse_command_string("--files a.txt b.txt").unwrap();
95    /// assert_eq!(cmd.get_argument_nth_param_mandatory("files", 0).unwrap(), "a.txt");
96    /// assert_eq!(cmd.get_argument_nth_param_mandatory("files", 1).unwrap(), "b.txt");
97    /// ```
98    pub fn get_argument_nth_param_mandatory(
99        &self,
100        argument: &str,
101        nth: usize,
102    ) -> Result<&str, CliError> {
103        let params = self.get_argument_mandatory_all(argument)?;
104        if params.len() > nth {
105            Ok(&params[nth])
106        } else if nth > 0 {
107            Err(CliErrorKind::MissingParameter(argument.to_string(), nth).into())
108        } else {
109            Err(CliErrorKind::MissingArgument(argument.to_string()).into())
110        }
111    }
112
113    /// Gets the nth parameter of an argument as a `usize`, returning an error if missing or invalid.
114    ///
115    /// # Arguments
116    /// * `argument` - The name of the argument
117    /// * `nth` - The zero-based index of the parameter
118    ///
119    /// # Returns
120    /// * `Ok(usize)` - If the parameter exists and can be parsed
121    /// * `Err(CliError)` - If the argument is missing or the parameter can't be parsed
122    ///
123    /// # Examples
124    /// ```rust
125    /// use cli_command::{Command, parse_command_string};
126    ///
127    /// let cmd = parse_command_string("--numbers 1 2 3").unwrap();
128    /// assert_eq!(cmd.get_argument_nth_param_mandatory_usize("numbers", 0).unwrap(), 1);
129    /// assert_eq!(cmd.get_argument_nth_param_mandatory_usize("numbers", 1).unwrap(), 2);
130    /// ```
131    pub fn get_argument_nth_param_mandatory_usize(
132        &self,
133        argument: &str,
134        nth: usize,
135    ) -> Result<usize, CliError> {
136        let param = self.get_argument_nth_param_mandatory(argument, nth)?;
137        Ok(usize::from_str_radix(param, 10)?)
138    }
139
140    /// Checks if an argument exists (regardless of whether it has values).
141    ///
142    /// # Arguments
143    /// * `argument` - The name of the argument to check
144    ///
145    /// # Returns
146    /// * `true` - If the argument exists
147    /// * `false` - If the argument doesn't exist
148    ///
149    /// # Examples
150    /// ```rust
151    /// use cli_command::{Command, parse_command_string};
152    ///
153    /// let cmd = parse_command_string("--verbose --port 8080").unwrap();
154    /// assert!(cmd.contains_argument("verbose"));
155    /// assert!(cmd.contains_argument("port"));
156    /// assert!(!cmd.contains_argument("debug"));
157    /// ```
158    pub fn contains_argument(&self, argument: &str) -> bool {
159        self.arguments.contains_key(argument)
160    }
161
162    /// Gets the first parameter of an argument as a string slice, if it exists.
163    ///
164    /// # Arguments
165    /// * `argument` - The name of the argument
166    ///
167    /// # Returns
168    /// * `Some(&str)` - If the argument exists and has at least one parameter
169    /// * `None` - If the argument doesn't exist or has no parameters
170    ///
171    /// # Examples
172    /// ```rust
173    /// use cli_command::{Command, parse_command_string};
174    ///
175    /// let cmd = parse_command_string("--host localhost --port 8080").unwrap();
176    /// assert_eq!(cmd.get_argument("host"), Some("localhost"));
177    /// assert_eq!(cmd.get_argument("port"), Some("8080"));
178    /// assert_eq!(cmd.get_argument("debug"), None);
179    /// ```
180    pub fn get_argument(&self, argument: &str) -> Option<&str> {
181        self.get_argument_nth_parameter(argument, 0)
182    }
183
184    /// Gets the first parameter of an argument as a string slice, returning an error if missing.
185    ///
186    /// # Arguments
187    /// * `argument` - The name of the argument
188    ///
189    /// # Returns
190    /// * `Ok(&str)` - If the argument exists and has at least one parameter
191    /// * `Err(CliError)` - If the argument is missing
192    ///
193    /// # Examples
194    /// ```rust
195    /// use cli_command::{Command, parse_command_string};
196    ///
197    /// let cmd = parse_command_string("--host localhost").unwrap();
198    /// assert_eq!(cmd.get_argument_mandatory("host").unwrap(), "localhost");
199    /// ```
200    pub fn get_argument_mandatory(&self, argument: &str) -> Result<&str, CliError> {
201        self.get_argument_nth_param_mandatory(argument, 0)
202    }
203
204    /// Gets the first parameter of an argument as a `usize`, returning an error if missing or invalid.
205    ///
206    /// # Arguments
207    /// * `argument` - The name of the argument
208    ///
209    /// # Returns
210    /// * `Ok(usize)` - If the argument exists and can be parsed as a usize
211    /// * `Err(CliError)` - If the argument is missing or can't be parsed
212    ///
213    /// # Examples
214    /// ```rust
215    /// use cli_command::{Command, parse_command_string};
216    ///
217    /// let cmd = parse_command_string("--port 8080").unwrap();
218    /// assert_eq!(cmd.get_argument_mandatory_usize("port").unwrap(), 8080);
219    /// ```
220    pub fn get_argument_mandatory_usize(&self, argument: &str) -> Result<usize, CliError> {
221        let value = self.get_argument_mandatory(argument)?;
222        Ok(usize::from_str_radix(value, 10)?)
223    }
224
225    /// Gets the first parameter of an argument as a `usize`, if it exists and can be parsed.
226    ///
227    /// # Arguments
228    /// * `argument` - The name of the argument
229    ///
230    /// # Returns
231    /// * `Some(usize)` - If the argument exists and can be parsed as a usize
232    /// * `None` - If the argument doesn't exist or can't be parsed
233    ///
234    /// # Examples
235    /// ```rust
236    /// use cli_command::{Command, parse_command_string};
237    ///
238    /// let cmd = parse_command_string("--port 8080 --workers 4").unwrap();
239    /// assert_eq!(cmd.get_argument_usize("port"), Some(8080));
240    /// assert_eq!(cmd.get_argument_usize("workers"), Some(4));
241    /// assert_eq!(cmd.get_argument_usize("debug"), None);
242    /// ```
243    pub fn get_argument_usize(&self, argument: &str) -> Option<usize> {
244        match self.get_argument(argument) {
245            Some(v) => match usize::from_str_radix(v, 10) {
246                Ok(v) => Some(v),
247                Err(_) => None,
248            },
249            None => None,
250        }
251    }
252
253    /// Gets the first parameter of an argument as an `i32`, if it exists and can be parsed.
254    ///
255    /// # Arguments
256    /// * `argument` - The name of the argument
257    ///
258    /// # Returns
259    /// * `Some(i32)` - If the argument exists and can be parsed as an i32
260    /// * `None` - If the argument doesn't exist or can't be parsed
261    ///
262    /// # Examples
263    /// ```rust
264    /// use cli_command::{Command, parse_command_string};
265    ///
266    /// let cmd = parse_command_string("--port 8080 --timeout 30").unwrap();
267    /// assert_eq!(cmd.get_argument_i32("port"), Some(8080));
268    /// assert_eq!(cmd.get_argument_i32("timeout"), Some(30));
269    /// assert_eq!(cmd.get_argument_i32("debug"), None);
270    /// ```
271    pub fn get_argument_i32(&self, argument: &str) -> Option<i32> {
272        match self.get_argument(argument) {
273            Some(v) => v.parse().ok(),
274            None => None,
275        }
276    }
277
278    /// Gets the first parameter of an argument as a `f64`, if it exists and can be parsed.
279    ///
280    /// # Arguments
281    /// * `argument` - The name of the argument
282    ///
283    /// # Returns
284    /// * `Some(f64)` - If the argument exists and can be parsed as an f64
285    /// * `None` - If the argument doesn't exist or can't be parsed
286    ///
287    /// # Examples
288    /// ```rust
289    /// use cli_command::{Command, parse_command_string};
290    ///
291    /// let cmd = parse_command_string("--ratio 0.5 --threshold 1.23").unwrap();
292    /// assert_eq!(cmd.get_argument_f64("ratio"), Some(0.5));
293    /// assert_eq!(cmd.get_argument_f64("threshold"), Some(1.23));
294    /// assert_eq!(cmd.get_argument_f64("debug"), None);
295    /// ```
296    pub fn get_argument_f64(&self, argument: &str) -> Option<f64> {
297        match self.get_argument(argument) {
298            Some(v) => v.parse().ok(),
299            None => None,
300        }
301    }
302
303    /// Gets the first parameter of an argument as a `bool`, if it exists and can be parsed.
304    ///
305    /// # Arguments
306    /// * `argument` - The name of the argument
307    ///
308    /// # Returns
309    /// * `Some(bool)` - If the argument exists and can be parsed as a bool
310    /// * `None` - If the argument doesn't exist or can't be parsed
311    ///
312    /// # Examples
313    /// ```rust
314    /// use cli_command::{Command, parse_command_string};
315    ///
316    /// let cmd = parse_command_string("--enabled true --disabled false").unwrap();
317    /// assert_eq!(cmd.get_argument_bool("enabled"), Some(true));
318    /// assert_eq!(cmd.get_argument_bool("disabled"), Some(false));
319    /// assert_eq!(cmd.get_argument_bool("debug"), None);
320    /// ```
321    pub fn get_argument_bool(&self, argument: &str) -> Option<bool> {
322        match self.get_argument(argument) {
323            Some(v) => v.parse().ok(),
324            None => None,
325        }
326    }
327
328    /// Gets all parameters of an argument as a slice of strings, if it exists.
329    ///
330    /// # Arguments
331    /// * `argument` - The name of the argument
332    ///
333    /// # Returns
334    /// * `Some(&Box<[String]>)` - If the argument exists
335    /// * `None` - If the argument doesn't exist
336    ///
337    /// # Examples
338    /// ```rust
339    /// use cli_command::{Command, parse_command_string};
340    ///
341    /// let cmd = parse_command_string("--files a.txt b.txt c.txt").unwrap();
342    /// let files = cmd.get_argument_all("files").unwrap();
343    /// assert_eq!(files.len(), 3);
344    /// assert_eq!(&files[0], "a.txt");
345    /// assert_eq!(&files[1], "b.txt");
346    /// assert_eq!(&files[2], "c.txt");
347    /// ```
348    pub fn get_argument_all(&self, argument: &str) -> Option<&Box<[String]>> {
349        self.arguments.get(argument)
350    }
351
352    /// Gets all parameters of an argument as a slice of strings, returning an error if missing.
353    ///
354    /// # Arguments
355    /// * `argument` - The name of the argument
356    ///
357    /// # Returns
358    /// * `Ok(&Box<[String]>)` - If the argument exists
359    /// * `Err(CliError)` - If the argument is missing
360    ///
361    /// # Examples
362    /// ```rust
363    /// use cli_command::{Command, parse_command_string};
364    ///
365    /// let cmd = parse_command_string("--files a.txt b.txt").unwrap();
366    /// let files = cmd.get_argument_mandatory_all("files").unwrap();
367    /// assert_eq!(files.len(), 2);
368    /// ```
369    pub fn get_argument_mandatory_all(&self, argument: &str) -> Result<&Box<[String]>, CliError> {
370        self.get_argument_all(argument)
371            .ok_or(CliErrorKind::MissingArgument(argument.to_string()).into())
372    }
373
374    /// Gets the first parameter of an argument with type conversion, or returns a default value if missing.
375    ///
376    /// This method attempts to parse the argument value as the specified type `T`. If the argument
377    /// is missing, it returns the provided default value instead of an error.
378    ///
379    /// # Arguments
380    /// * `argument` - The name of the argument
381    /// * `default` - The default value to return if the argument is missing
382    ///
383    /// # Returns
384    /// * `Ok(T)` - If the argument exists and can be parsed, or if missing (returns default)
385    /// * `Err(CliError)` - If the argument exists but can't be parsed as the target type
386    ///
387    /// # Examples
388    /// ```rust
389    /// use cli_command::{Command, parse_command_string};
390    ///
391    /// let cmd = parse_command_string("--port 8080").unwrap();
392    /// let port: u16 = cmd.get_argument_or_default("port", 3000).unwrap();
393    /// assert_eq!(port, 8080);
394    ///
395    /// let timeout: u64 = cmd.get_argument_or_default("timeout", 30).unwrap();
396    /// assert_eq!(timeout, 30); // Uses default since timeout not provided
397    /// ```
398    pub fn get_argument_or_default<T>(&self, argument: &str, default: T) -> Result<T, CliError>
399    where
400        T: std::str::FromStr,
401        T::Err: std::error::Error + Send + Sync + 'static,
402    {
403        match self.get_argument_mandatory(argument) {
404            Ok(value) => T::from_str(value).map_err(|e| CliError::new_inner(Box::new(e))),
405            Err(_) => Ok(default),
406        }
407    }
408}
409
410#[cfg(test)]
411mod tests {
412    use crate::cli_error::{CliError, CliErrorKind};
413    use crate::parse::parse_command_string;
414    use std::net::SocketAddr;
415    use std::str::FromStr;
416
417    #[test]
418    fn test_get_argument_first_value_or_default() -> Result<(), CliError> {
419        let cmd = parse_command_string("--socket 172.20.3.1:7618")?;
420        assert_eq!(&cmd.arguments.get("socket").unwrap()[0], "172.20.3.1:7618");
421
422        let socket =
423            cmd.get_argument_or_default("socket", SocketAddr::from_str("127.0.0.1:1000")?)?;
424        assert_eq!(socket, SocketAddr::from_str("172.20.3.1:7618")?);
425
426        let cmd = parse_command_string("--streams 2")?;
427        let streams = cmd.get_argument_or_default("streams", 5)?;
428        assert_eq!(streams, 2);
429        assert_eq!(cmd.get_argument_or_default("components", 5)?, 5);
430
431        Ok(())
432    }
433
434    #[test]
435    fn test_basic_argument_parsing() -> Result<(), CliError> {
436        let cmd = parse_command_string("serve --port 8080 --host localhost --verbose")?;
437
438        assert_eq!(cmd.name, "serve");
439        assert_eq!(cmd.get_argument("port"), Some("8080"));
440        assert_eq!(cmd.get_argument("host"), Some("localhost"));
441        assert!(cmd.contains_argument("verbose"));
442        assert!(!cmd.contains_argument("debug"));
443
444        Ok(())
445    }
446
447    #[test]
448    fn test_multiple_values() -> Result<(), CliError> {
449        let cmd = parse_command_string("build --files a.txt b.txt c.txt --output dist/")?;
450
451        assert_eq!(cmd.name, "build");
452        let files = cmd.get_argument_all("files").unwrap();
453        assert_eq!(files.len(), 3);
454        assert_eq!(&files[0], "a.txt");
455        assert_eq!(&files[1], "b.txt");
456        assert_eq!(&files[2], "c.txt");
457        assert_eq!(cmd.get_argument("output"), Some("dist/"));
458
459        Ok(())
460    }
461
462    #[test]
463    fn test_usize_conversion() -> Result<(), CliError> {
464        let cmd = parse_command_string("--port 8080 --workers 4 --timeout 30")?;
465
466        assert_eq!(cmd.get_argument_usize("port"), Some(8080));
467        assert_eq!(cmd.get_argument_usize("workers"), Some(4));
468        assert_eq!(cmd.get_argument_usize("timeout"), Some(30));
469        assert_eq!(cmd.get_argument_usize("missing"), None);
470
471        Ok(())
472    }
473
474    #[test]
475    fn test_mandatory_arguments() -> Result<(), CliError> {
476        let cmd = parse_command_string("--required value")?;
477
478        assert_eq!(cmd.get_argument_mandatory("required")?, "value");
479
480        let cmd = parse_command_string("")?;
481        let result = cmd.get_argument_mandatory("missing");
482        assert!(result.is_err());
483
484        if let Err(CliError {
485            kind: CliErrorKind::MissingArgument(arg),
486            ..
487        }) = result
488        {
489            assert_eq!(arg, "missing");
490        } else {
491            panic!("Expected MissingArgument error");
492        }
493
494        Ok(())
495    }
496
497    #[test]
498    fn test_nth_parameter_access() -> Result<(), CliError> {
499        let cmd = parse_command_string("--numbers 1 2 3 4 5")?;
500
501        assert_eq!(cmd.get_argument_nth_parameter("numbers", 0), Some("1"));
502        assert_eq!(cmd.get_argument_nth_parameter("numbers", 2), Some("3"));
503        assert_eq!(cmd.get_argument_nth_parameter("numbers", 4), Some("5"));
504        assert_eq!(cmd.get_argument_nth_parameter("numbers", 5), None);
505        assert_eq!(cmd.get_argument_nth_parameter("missing", 0), None);
506
507        Ok(())
508    }
509
510    #[test]
511    fn test_nth_parameter_usize() -> Result<(), CliError> {
512        let cmd = parse_command_string("--values 10 20 30")?;
513
514        assert_eq!(cmd.get_argument_nth_parameter_usize("values", 0), Some(10));
515        assert_eq!(cmd.get_argument_nth_parameter_usize("values", 1), Some(20));
516        assert_eq!(cmd.get_argument_nth_parameter_usize("values", 2), Some(30));
517        assert_eq!(cmd.get_argument_nth_parameter_usize("values", 3), None);
518        assert_eq!(cmd.get_argument_nth_parameter_usize("missing", 0), None);
519
520        Ok(())
521    }
522
523    #[test]
524    fn test_mandatory_nth_parameter() -> Result<(), CliError> {
525        let cmd = parse_command_string("--files a.txt b.txt c.txt")?;
526
527        assert_eq!(cmd.get_argument_nth_param_mandatory("files", 0)?, "a.txt");
528        assert_eq!(cmd.get_argument_nth_param_mandatory("files", 1)?, "b.txt");
529        assert_eq!(cmd.get_argument_nth_param_mandatory("files", 2)?, "c.txt");
530
531        let result = cmd.get_argument_nth_param_mandatory("files", 5);
532        assert!(result.is_err());
533
534        if let Err(CliError {
535            kind: CliErrorKind::MissingParameter(arg, pos),
536            ..
537        }) = result
538        {
539            assert_eq!(arg, "files");
540            assert_eq!(pos, 5);
541        } else {
542            panic!("Expected MissingParameter error");
543        }
544
545        Ok(())
546    }
547
548    #[test]
549    fn test_mandatory_nth_parameter_usize() -> Result<(), CliError> {
550        let cmd = parse_command_string("--numbers 1 2 3")?;
551
552        assert_eq!(cmd.get_argument_nth_param_mandatory_usize("numbers", 0)?, 1);
553        assert_eq!(cmd.get_argument_nth_param_mandatory_usize("numbers", 1)?, 2);
554        assert_eq!(cmd.get_argument_nth_param_mandatory_usize("numbers", 2)?, 3);
555
556        Ok(())
557    }
558
559    #[test]
560    fn test_type_conversion_with_defaults() -> Result<(), CliError> {
561        let cmd = parse_command_string("--port 8080")?;
562
563        let port: u16 = cmd.get_argument_or_default("port", 3000)?;
564        assert_eq!(port, 8080);
565
566        let timeout: u64 = cmd.get_argument_or_default("timeout", 30)?;
567        assert_eq!(timeout, 30); // Uses default
568
569        let host: String = cmd.get_argument_or_default("host", "localhost".to_string())?;
570        assert_eq!(host, "localhost"); // Uses default
571
572        Ok(())
573    }
574
575    #[test]
576    fn test_short_and_long_arguments() -> Result<(), CliError> {
577        let cmd = parse_command_string("-v --verbose --port 8080 -h localhost")?;
578
579        assert!(cmd.contains_argument("v"));
580        assert!(cmd.contains_argument("verbose"));
581        assert_eq!(cmd.get_argument("port"), Some("8080"));
582        assert_eq!(cmd.get_argument("h"), Some("localhost"));
583
584        Ok(())
585    }
586
587    #[test]
588    fn test_boolean_flags() -> Result<(), CliError> {
589        let cmd = parse_command_string("--enable-feature --no-cache --debug")?;
590
591        assert!(cmd.contains_argument("enable-feature"));
592        assert!(cmd.contains_argument("no-cache"));
593        assert!(cmd.contains_argument("debug"));
594        assert!(!cmd.contains_argument("missing"));
595
596        Ok(())
597    }
598
599    #[test]
600    fn test_empty_command() -> Result<(), CliError> {
601        let cmd = parse_command_string("")?;
602
603        assert_eq!(cmd.name, "");
604        assert!(cmd.arguments.is_empty());
605
606        Ok(())
607    }
608
609    #[test]
610    fn test_command_without_arguments() -> Result<(), CliError> {
611        let cmd = parse_command_string("help")?;
612
613        assert_eq!(cmd.name, "help");
614        assert!(cmd.arguments.is_empty());
615
616        Ok(())
617    }
618
619    #[test]
620    fn test_invalid_usize_parsing() -> Result<(), CliError> {
621        let cmd = parse_command_string("--port abc --workers 123")?;
622
623        assert_eq!(cmd.get_argument_usize("port"), None); // Invalid number
624        assert_eq!(cmd.get_argument_usize("workers"), Some(123)); // Valid number
625        assert_eq!(cmd.get_argument_usize("missing"), None); // Missing argument
626
627        Ok(())
628    }
629
630    #[test]
631    fn test_error_handling() -> Result<(), CliError> {
632        let cmd = parse_command_string("--port 8080")?;
633
634        let result = cmd.get_argument_mandatory("missing");
635        assert!(result.is_err());
636
637        let result = cmd.get_argument_nth_param_mandatory("port", 5);
638        assert!(result.is_err());
639
640        Ok(())
641    }
642}