formars_core/
validators.rs1pub(crate) fn is_plausible_email(s: &str) -> bool {
6 let Some((local, domain)) = s.split_once('@') else {
7 return false;
8 };
9 if s.matches('@').count() != 1 || local.is_empty() || !domain.contains('.') {
10 return false;
11 }
12 if s.chars().any(char::is_whitespace) {
13 return false;
14 }
15 let labels: Vec<&str> = domain.split('.').collect();
16 labels.len() >= 2
17 && labels
18 .iter()
19 .all(|l| !l.is_empty() && l.chars().all(|c| c.is_ascii_alphanumeric() || c == '-'))
20}
21
22pub(crate) fn is_plausible_url(s: &str) -> bool {
26 let Some((scheme, rest)) = s.split_once("://") else {
27 return false;
28 };
29 if scheme.is_empty()
30 || !scheme
31 .chars()
32 .next()
33 .is_some_and(|c| c.is_ascii_alphabetic())
34 {
35 return false;
36 }
37 if !scheme
38 .chars()
39 .all(|c| c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.')
40 {
41 return false;
42 }
43 let authority = rest.split(['/', '?', '#']).next().unwrap_or("");
44 let host = authority.split(':').next().unwrap_or(authority);
45 !host.is_empty() && !s.chars().any(char::is_whitespace)
46}
47
48pub(crate) fn is_plausible_uuid(s: &str) -> bool {
51 let groups: Vec<&str> = s.split('-').collect();
52 if groups.len() != 5 || !groups.iter().map(|g| g.len()).eq([8usize, 4, 4, 4, 12]) {
53 return false;
54 }
55 groups
56 .iter()
57 .all(|g| g.chars().all(|c| c.is_ascii_hexdigit()))
58}
59
60#[cfg(test)]
61mod tests {
62 use crate::validators::{is_plausible_email, is_plausible_url, is_plausible_uuid};
63
64 #[test]
65 fn email_fixture_table() {
66 let accept = ["user@example.com", "a.b+tag@sub.domain.org", "x@y.co"];
67 let reject = [
68 "",
69 "not-an-email",
70 "@example.com",
71 "user@",
72 "user@@example.com",
73 "user@example..com",
74 "us er@example.com",
75 "user@exa mple.com",
76 "user@example.",
77 "user@ex\u{e4}mple.com",
78 ];
79 for s in accept {
80 assert!(is_plausible_email(s), "expected accept: {s}");
81 }
82 for s in reject {
83 assert!(!is_plausible_email(s), "expected reject: {s}");
84 }
85 }
86
87 #[test]
88 fn url_fixture_table() {
89 let accept = [
90 "https://example.com",
91 "http://example.com/path?query=1#frag",
92 "https://sub.domain.org:8443/x",
93 "ftp://files.example.org",
94 ];
95 let reject = [
96 "",
97 "example.com",
98 "https://",
99 "http:///path",
100 "://example.com",
101 "https://:8080/x",
102 ];
103 for s in accept {
104 assert!(is_plausible_url(s), "expected accept: {s}");
105 }
106 for s in reject {
107 assert!(!is_plausible_url(s), "expected reject: {s}");
108 }
109 }
110
111 #[test]
112 fn uuid_fixture_table() {
113 let accept = [
114 "123e4567-e89b-12d3-a456-426614174000",
115 "00000000-0000-0000-0000-000000000000",
116 "123E4567-E89B-12D3-A456-426614174000",
117 ];
118 let reject = [
119 "",
120 "123e4567e89b12d3a456426614174000",
121 "123e4567-e89b-12d3-a456-42661417400g",
122 "123e4567-e89b-12d3-a456",
123 "zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz",
124 ];
125 for s in accept {
126 assert!(is_plausible_uuid(s), "expected accept: {s}");
127 }
128 for s in reject {
129 assert!(!is_plausible_uuid(s), "expected reject: {s}");
130 }
131 }
132}