this repo has no description
1pub const MAX_EMAIL_LENGTH: usize = 254;
2pub const MAX_LOCAL_PART_LENGTH: usize = 64;
3pub const MAX_DOMAIN_LENGTH: usize = 253;
4pub const MAX_DOMAIN_LABEL_LENGTH: usize = 63;
5const EMAIL_LOCAL_SPECIAL_CHARS: &str = ".!#$%&'*+/=?^_`{|}~-";
6
7pub const MIN_HANDLE_LENGTH: usize = 3;
8pub const MAX_HANDLE_LENGTH: usize = 253;
9
10#[derive(Debug, PartialEq)]
11pub enum HandleValidationError {
12 Empty,
13 TooShort,
14 TooLong,
15 InvalidCharacters,
16 StartsWithInvalidChar,
17 EndsWithInvalidChar,
18 ContainsSpaces,
19}
20
21impl std::fmt::Display for HandleValidationError {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 match self {
24 Self::Empty => write!(f, "Handle cannot be empty"),
25 Self::TooShort => write!(
26 f,
27 "Handle must be at least {} characters",
28 MIN_HANDLE_LENGTH
29 ),
30 Self::TooLong => write!(
31 f,
32 "Handle exceeds maximum length of {} characters",
33 MAX_HANDLE_LENGTH
34 ),
35 Self::InvalidCharacters => write!(
36 f,
37 "Handle contains invalid characters. Only alphanumeric, hyphens, and underscores are allowed"
38 ),
39 Self::StartsWithInvalidChar => {
40 write!(f, "Handle cannot start with a hyphen or underscore")
41 }
42 Self::EndsWithInvalidChar => write!(f, "Handle cannot end with a hyphen or underscore"),
43 Self::ContainsSpaces => write!(f, "Handle cannot contain spaces"),
44 }
45 }
46}
47
48pub fn validate_short_handle(handle: &str) -> Result<String, HandleValidationError> {
49 let handle = handle.trim();
50
51 if handle.is_empty() {
52 return Err(HandleValidationError::Empty);
53 }
54
55 if handle.contains(' ') || handle.contains('\t') || handle.contains('\n') {
56 return Err(HandleValidationError::ContainsSpaces);
57 }
58
59 if handle.len() < MIN_HANDLE_LENGTH {
60 return Err(HandleValidationError::TooShort);
61 }
62
63 if handle.len() > MAX_HANDLE_LENGTH {
64 return Err(HandleValidationError::TooLong);
65 }
66
67 let first_char = handle.chars().next().unwrap();
68 if first_char == '-' || first_char == '_' {
69 return Err(HandleValidationError::StartsWithInvalidChar);
70 }
71
72 let last_char = handle.chars().last().unwrap();
73 if last_char == '-' || last_char == '_' {
74 return Err(HandleValidationError::EndsWithInvalidChar);
75 }
76
77 for c in handle.chars() {
78 if !c.is_ascii_alphanumeric() && c != '-' && c != '_' {
79 return Err(HandleValidationError::InvalidCharacters);
80 }
81 }
82
83 Ok(handle.to_lowercase())
84}
85
86pub fn is_valid_email(email: &str) -> bool {
87 let email = email.trim();
88 if email.is_empty() || email.len() > MAX_EMAIL_LENGTH {
89 return false;
90 }
91 let parts: Vec<&str> = email.rsplitn(2, '@').collect();
92 if parts.len() != 2 {
93 return false;
94 }
95 let domain = parts[0];
96 let local = parts[1];
97 if local.is_empty() || local.len() > MAX_LOCAL_PART_LENGTH {
98 return false;
99 }
100 if local.starts_with('.') || local.ends_with('.') {
101 return false;
102 }
103 if local.contains("..") {
104 return false;
105 }
106 for c in local.chars() {
107 if !c.is_ascii_alphanumeric() && !EMAIL_LOCAL_SPECIAL_CHARS.contains(c) {
108 return false;
109 }
110 }
111 if domain.is_empty() || domain.len() > MAX_DOMAIN_LENGTH {
112 return false;
113 }
114 if !domain.contains('.') {
115 return false;
116 }
117 for label in domain.split('.') {
118 if label.is_empty() || label.len() > MAX_DOMAIN_LABEL_LENGTH {
119 return false;
120 }
121 if label.starts_with('-') || label.ends_with('-') {
122 return false;
123 }
124 for c in label.chars() {
125 if !c.is_ascii_alphanumeric() && c != '-' {
126 return false;
127 }
128 }
129 }
130 true
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn test_valid_handles() {
139 assert_eq!(validate_short_handle("alice"), Ok("alice".to_string()));
140 assert_eq!(validate_short_handle("bob123"), Ok("bob123".to_string()));
141 assert_eq!(
142 validate_short_handle("user-name"),
143 Ok("user-name".to_string())
144 );
145 assert_eq!(
146 validate_short_handle("user_name"),
147 Ok("user_name".to_string())
148 );
149 assert_eq!(
150 validate_short_handle("UPPERCASE"),
151 Ok("uppercase".to_string())
152 );
153 assert_eq!(
154 validate_short_handle("MixedCase123"),
155 Ok("mixedcase123".to_string())
156 );
157 assert_eq!(validate_short_handle("abc"), Ok("abc".to_string()));
158 }
159
160 #[test]
161 fn test_invalid_handles() {
162 assert_eq!(validate_short_handle(""), Err(HandleValidationError::Empty));
163 assert_eq!(
164 validate_short_handle(" "),
165 Err(HandleValidationError::Empty)
166 );
167 assert_eq!(
168 validate_short_handle("ab"),
169 Err(HandleValidationError::TooShort)
170 );
171 assert_eq!(
172 validate_short_handle("a"),
173 Err(HandleValidationError::TooShort)
174 );
175 assert_eq!(
176 validate_short_handle("test spaces"),
177 Err(HandleValidationError::ContainsSpaces)
178 );
179 assert_eq!(
180 validate_short_handle("test\ttab"),
181 Err(HandleValidationError::ContainsSpaces)
182 );
183 assert_eq!(
184 validate_short_handle("-starts"),
185 Err(HandleValidationError::StartsWithInvalidChar)
186 );
187 assert_eq!(
188 validate_short_handle("_starts"),
189 Err(HandleValidationError::StartsWithInvalidChar)
190 );
191 assert_eq!(
192 validate_short_handle("ends-"),
193 Err(HandleValidationError::EndsWithInvalidChar)
194 );
195 assert_eq!(
196 validate_short_handle("ends_"),
197 Err(HandleValidationError::EndsWithInvalidChar)
198 );
199 assert_eq!(
200 validate_short_handle("test@user"),
201 Err(HandleValidationError::InvalidCharacters)
202 );
203 assert_eq!(
204 validate_short_handle("test!user"),
205 Err(HandleValidationError::InvalidCharacters)
206 );
207 assert_eq!(
208 validate_short_handle("test.user"),
209 Err(HandleValidationError::InvalidCharacters)
210 );
211 }
212
213 #[test]
214 fn test_handle_trimming() {
215 assert_eq!(validate_short_handle(" alice "), Ok("alice".to_string()));
216 }
217
218 #[test]
219 fn test_valid_emails() {
220 assert!(is_valid_email("user@example.com"));
221 assert!(is_valid_email("user.name@example.com"));
222 assert!(is_valid_email("user+tag@example.com"));
223 assert!(is_valid_email("user@sub.example.com"));
224 assert!(is_valid_email("USER@EXAMPLE.COM"));
225 assert!(is_valid_email("user123@example123.com"));
226 assert!(is_valid_email("a@b.co"));
227 }
228 #[test]
229 fn test_invalid_emails() {
230 assert!(!is_valid_email(""));
231 assert!(!is_valid_email("user"));
232 assert!(!is_valid_email("user@"));
233 assert!(!is_valid_email("@example.com"));
234 assert!(!is_valid_email("user@example"));
235 assert!(!is_valid_email("user@@example.com"));
236 assert!(!is_valid_email("user@.example.com"));
237 assert!(!is_valid_email("user@example..com"));
238 assert!(!is_valid_email(".user@example.com"));
239 assert!(!is_valid_email("user.@example.com"));
240 assert!(!is_valid_email("user..name@example.com"));
241 assert!(!is_valid_email("user@-example.com"));
242 assert!(!is_valid_email("user@example-.com"));
243 }
244 #[test]
245 fn test_trimmed_whitespace() {
246 assert!(is_valid_email(" user@example.com "));
247 }
248}