gpui_form_num_regex/
lib.rs

1use regex::Regex;
2use std::sync::OnceLock;
3
4pub trait NumRegex {
5    fn validation_regex() -> &'static Regex;
6}
7
8macro_rules! static_regex {
9    ($pattern:expr) => {{
10        static REGEX: OnceLock<Regex> = OnceLock::new();
11        REGEX.get_or_init(|| Regex::new($pattern).expect("Invalid regex pattern"))
12    }};
13}
14
15macro_rules! impl_numregex_signed {
16    ($($t:ty),*) => {
17        $(
18            impl NumRegex for $t {
19                fn validation_regex() -> &'static Regex {
20                    static_regex!(r"^[+-]?(?:0|[1-9]\d*)$")
21                }
22            }
23        )*
24    };
25}
26
27macro_rules! impl_numregex_unsigned {
28    ($($t:ty),*) => {
29        $(
30            impl NumRegex for $t {
31                fn validation_regex() -> &'static Regex {
32                    static_regex!(r"^(?:0|[1-9]\d*)$")
33                }
34            }
35        )*
36    };
37}
38
39macro_rules! impl_numregex_float {
40    ($($t:ty),*) => {
41        $(
42            impl NumRegex for $t {
43                fn validation_regex() -> &'static Regex {
44                    // Matches: integers, decimals, scientific notation, infinity, NaN
45                    static_regex!(r"^[+-]?(?:(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?|inf|infinity|nan)$")
46                }
47            }
48        )*
49    };
50}
51
52impl_numregex_signed!(i8, i16, i32, i64, i128, isize);
53impl_numregex_unsigned!(u8, u16, u32, u64, u128, usize);
54impl_numregex_float!(f32, f64);
55
56#[cfg(feature = "rust_decimal")]
57impl NumRegex for rust_decimal::Decimal {
58    fn validation_regex() -> &'static Regex {
59        // Matches decimal numbers with optional sign and decimal point
60        // Supports scientific notation as rust_decimal can parse it
61        static_regex!(r"^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$")
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn test_signed_integers() {
71        assert!(i32::validation_regex().is_match("123"));
72        assert!(i32::validation_regex().is_match("-123"));
73        assert!(i32::validation_regex().is_match("+123"));
74        assert!(i32::validation_regex().is_match("0"));
75        assert!(!i32::validation_regex().is_match("123.45"));
76        assert!(!i32::validation_regex().is_match("abc"));
77        assert!(!i32::validation_regex().is_match(""));
78    }
79
80    #[test]
81    fn test_unsigned_integers() {
82        assert!(u32::validation_regex().is_match("123"));
83        assert!(u32::validation_regex().is_match("0"));
84        assert!(!u32::validation_regex().is_match("-123"));
85        assert!(!u32::validation_regex().is_match("+123"));
86        assert!(!u32::validation_regex().is_match("123.45"));
87    }
88
89    #[test]
90    fn test_floats() {
91        assert!(f64::validation_regex().is_match("123.45"));
92        assert!(f64::validation_regex().is_match("-123.45"));
93        assert!(f64::validation_regex().is_match("1.23e10"));
94        assert!(f64::validation_regex().is_match("1.23E-10"));
95        assert!(f64::validation_regex().is_match("inf"));
96        assert!(f64::validation_regex().is_match("nan"));
97        assert!(f64::validation_regex().is_match("123"));
98        assert!(f64::validation_regex().is_match(".5"));
99        assert!(!f64::validation_regex().is_match("123."));
100    }
101
102    #[cfg(feature = "rust_decimal")]
103    #[test]
104    fn test_decimal() {
105        use rust_decimal::Decimal;
106
107        assert!(Decimal::validation_regex().is_match("123.45"));
108        assert!(Decimal::validation_regex().is_match("-123.45"));
109        assert!(Decimal::validation_regex().is_match("1.23e10"));
110        assert!(Decimal::validation_regex().is_match("0"));
111        assert!(!Decimal::validation_regex().is_match("inf"));
112        assert!(!Decimal::validation_regex().is_match("nan"));
113    }
114
115    #[test]
116    fn test_regex_compilation() {
117        let _ = i32::validation_regex();
118        let _ = u32::validation_regex();
119        let _ = f64::validation_regex();
120
121        #[cfg(feature = "rust_decimal")]
122        {
123            use rust_decimal::Decimal;
124            let _ = Decimal::validation_regex();
125        }
126    }
127}