Skip to main content

runi_cli/launcher/
types.rs

1use std::fmt;
2use std::str::FromStr;
3
4/// Convert a raw command-line argument string into a typed value.
5///
6/// A blanket implementation covers every `T: FromStr` with a `Display` error,
7/// so any type that already implements `FromStr` works automatically. Implement
8/// this trait directly only for types that cannot use `FromStr` (for example,
9/// to support a custom syntax).
10pub trait FromArg: Sized {
11    fn from_arg(raw: &str) -> Result<Self, String>;
12
13    /// Human-readable name of the expected type, used in error messages.
14    fn type_name() -> &'static str {
15        std::any::type_name::<Self>()
16    }
17}
18
19impl<T> FromArg for T
20where
21    T: FromStr,
22    T::Err: fmt::Display,
23{
24    fn from_arg(raw: &str) -> Result<Self, String> {
25        raw.parse::<T>().map_err(|e| e.to_string())
26    }
27}
28
29/// Sanity checks for the commonly used built-in types. These exist so regressions
30/// in the blanket impl surface immediately.
31#[cfg(test)]
32mod tests {
33    use super::*;
34    use runi_test::pretty_assertions::assert_eq;
35    use std::path::PathBuf;
36
37    #[test]
38    fn parses_strings_and_paths() {
39        assert_eq!(String::from_arg("hello").unwrap(), "hello".to_string());
40        assert_eq!(
41            PathBuf::from_arg("/tmp/x").unwrap(),
42            PathBuf::from("/tmp/x")
43        );
44    }
45
46    #[test]
47    fn parses_numbers() {
48        assert_eq!(i32::from_arg("-7").unwrap(), -7);
49        assert_eq!(u64::from_arg("42").unwrap(), 42u64);
50        assert!((f64::from_arg("2.5").unwrap() - 2.5).abs() < 1e-9);
51    }
52
53    #[test]
54    fn parses_booleans() {
55        assert!(bool::from_arg("true").unwrap());
56        assert!(!bool::from_arg("false").unwrap());
57    }
58
59    #[test]
60    fn reports_error_for_invalid_input() {
61        let err = i32::from_arg("not-a-number").unwrap_err();
62        assert!(!err.is_empty());
63    }
64}