Skip to main content

oxidite_utils/
validation.rs

1//! Validation utilities
2
3use regex::Regex;
4use std::sync::OnceLock;
5
6fn email_regex() -> &'static Regex {
7    static EMAIL: OnceLock<Regex> = OnceLock::new();
8    EMAIL.get_or_init(|| {
9        Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
10            .expect("email regex must compile")
11    })
12}
13
14fn url_regex() -> &'static Regex {
15    static URL: OnceLock<Regex> = OnceLock::new();
16    URL.get_or_init(|| {
17        Regex::new(r"^https?://[a-zA-Z0-9][-a-zA-Z0-9]*(\.[a-zA-Z0-9][-a-zA-Z0-9]*)*(:\d+)?(/.*)?$")
18            .expect("url regex must compile")
19    })
20}
21
22fn phone_regex() -> &'static Regex {
23    static PHONE: OnceLock<Regex> = OnceLock::new();
24    PHONE
25        .get_or_init(|| Regex::new(r"^\+?[0-9]{10,15}$").expect("phone regex must compile"))
26}
27
28/// Check if a string is a valid email address
29pub fn is_email(s: &str) -> bool {
30    email_regex().is_match(s)
31}
32
33/// Check if a string is a valid URL
34pub fn is_url(s: &str) -> bool {
35    url_regex().is_match(s)
36}
37
38/// Check if a string is a valid phone number (basic international format)
39pub fn is_phone(s: &str) -> bool {
40    phone_regex().is_match(s.replace(['-', ' ', '(', ')'], "").as_str())
41}
42
43/// Check if a string is alphanumeric
44pub fn is_alphanumeric(s: &str) -> bool {
45    !s.is_empty() && s.chars().all(|c| c.is_alphanumeric())
46}
47
48/// Check if a string is numeric
49pub fn is_numeric(s: &str) -> bool {
50    !s.is_empty() && s.chars().all(|c| c.is_ascii_digit())
51}
52
53/// Check if a string has minimum length
54pub fn min_length(s: &str, min: usize) -> bool {
55    s.len() >= min
56}
57
58/// Check if a string has maximum length
59pub fn max_length(s: &str, max: usize) -> bool {
60    s.len() <= max
61}
62
63/// Check if a string is within length bounds
64pub fn length_between(s: &str, min: usize, max: usize) -> bool {
65    min_length(s, min) && max_length(s, max)
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_is_email() {
74        assert!(is_email("test@example.com"));
75        assert!(is_email("user.name+tag@domain.co.uk"));
76        assert!(!is_email("invalid"));
77        assert!(!is_email("@example.com"));
78    }
79
80    #[test]
81    fn test_is_url() {
82        assert!(is_url("https://example.com"));
83        assert!(is_url("http://localhost:3000/path"));
84        assert!(!is_url("not-a-url"));
85        assert!(!is_url("ftp://example.com"));
86    }
87
88    #[test]
89    fn test_is_phone() {
90        assert!(is_phone("+1234567890"));
91        assert!(is_phone("123-456-7890"));
92        assert!(is_phone("(123) 456-7890"));
93        assert!(!is_phone("12345"));
94    }
95
96    #[test]
97    fn test_length_validators() {
98        assert!(min_length("hello", 3));
99        assert!(!min_length("hi", 3));
100        assert!(max_length("hi", 5));
101        assert!(!max_length("hello world", 5));
102        assert!(length_between("hello", 3, 10));
103    }
104
105    #[test]
106    fn test_numeric_is_ascii_only() {
107        assert!(is_numeric("12345"));
108        assert!(!is_numeric("١٢٣"));
109    }
110}