1use regex::Regex;
4use std::sync::OnceLock;
5
6pub struct Valid;
11
12impl Valid {
13 fn email_re() -> &'static Regex {
14 static RE: OnceLock<Regex> = OnceLock::new();
15 RE.get_or_init(|| Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap())
16 }
17
18 fn mobile_re() -> &'static Regex {
19 static RE: OnceLock<Regex> = OnceLock::new();
20 RE.get_or_init(|| Regex::new(r"^1[3-9]\d{9}$").unwrap())
21 }
22
23 fn url_re() -> &'static Regex {
24 static RE: OnceLock<Regex> = OnceLock::new();
25 RE.get_or_init(|| Regex::new(r"^https?://[^\s/$.?#].[^\s]*$").unwrap())
26 }
27
28 pub fn is_email(s: &str) -> bool { Self::email_re().is_match(s) }
30
31 pub fn is_mobile(s: &str) -> bool { Self::mobile_re().is_match(s) }
33
34 pub fn is_url(s: &str) -> bool { Self::url_re().is_match(s) }
36
37 pub fn is_digits(s: &str) -> bool { !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()) }
39
40 pub fn is_alphanumeric(s: &str) -> bool { !s.is_empty() && s.chars().all(|c| c.is_ascii_alphanumeric()) }
42
43 pub fn len_between(s: &str, min: usize, max: usize) -> bool {
45 let len = s.chars().count();
46 len >= min && len <= max
47 }
48
49 pub fn is_strong_password(s: &str) -> bool {
51 if s.len() < 8 { return false; }
52 let has_lower = s.chars().any(|c| c.is_ascii_lowercase());
53 let has_upper = s.chars().any(|c| c.is_ascii_uppercase());
54 let has_digit = s.chars().any(|c| c.is_ascii_digit());
55 has_lower && has_upper && has_digit
56 }
57
58 pub fn is_ipv4(s: &str) -> bool {
60 s.parse::<std::net::Ipv4Addr>().is_ok()
61 }
62
63 pub fn is_base64(s: &str) -> bool {
65 base64::Engine::decode(&base64::engine::general_purpose::STANDARD, s).is_ok() && s.len() % 4 == 0
66 }
67}
68
69#[cfg(feature = "validator")]
73impl Valid {
74 pub fn format_validation_errors(errors: &validator::ValidationErrors) -> String {
75 let mut messages = Vec::new();
76 for (field, field_errors) in errors.field_errors() {
77 for error in field_errors {
78 if let Some(msg) = &error.message {
79 messages.push(format!("{}: {}", field, msg));
80 } else {
81 messages.push(format!("{}: {}", field, error.code));
82 }
83 }
84 }
85 messages.join("; ")
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 #[test]
93 fn test_email() { assert!(Valid::is_email("a@b.com")); assert!(!Valid::is_email("not-email")); }
94 #[test]
95 fn test_mobile() { assert!(Valid::is_mobile("13812345678")); assert!(!Valid::is_mobile("1234")); }
96 #[test]
97 fn test_password() { assert!(Valid::is_strong_password("Abcdefg1")); assert!(!Valid::is_strong_password("123456")); }
98}