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 if let Some(first_char) = handle.chars().next() {
68 if first_char == '-' || first_char == '_' {
69 return Err(HandleValidationError::StartsWithInvalidChar);
70 }
71 }
72
73 if let Some(last_char) = handle.chars().last() {
74 if last_char == '-' || last_char == '_' {
75 return Err(HandleValidationError::EndsWithInvalidChar);
76 }
77 }
78
79 for c in handle.chars() {
80 if !c.is_ascii_alphanumeric() && c != '-' && c != '_' {
81 return Err(HandleValidationError::InvalidCharacters);
82 }
83 }
84
85 Ok(handle.to_lowercase())
86}
87
88pub fn is_valid_email(email: &str) -> bool {
89 let email = email.trim();
90 if email.is_empty() || email.len() > MAX_EMAIL_LENGTH {
91 return false;
92 }
93 let parts: Vec<&str> = email.rsplitn(2, '@').collect();
94 if parts.len() != 2 {
95 return false;
96 }
97 let domain = parts[0];
98 let local = parts[1];
99 if local.is_empty() || local.len() > MAX_LOCAL_PART_LENGTH {
100 return false;
101 }
102 if local.starts_with('.') || local.ends_with('.') {
103 return false;
104 }
105 if local.contains("..") {
106 return false;
107 }
108 for c in local.chars() {
109 if !c.is_ascii_alphanumeric() && !EMAIL_LOCAL_SPECIAL_CHARS.contains(c) {
110 return false;
111 }
112 }
113 if domain.is_empty() || domain.len() > MAX_DOMAIN_LENGTH {
114 return false;
115 }
116 if !domain.contains('.') {
117 return false;
118 }
119 for label in domain.split('.') {
120 if label.is_empty() || label.len() > MAX_DOMAIN_LABEL_LENGTH {
121 return false;
122 }
123 if label.starts_with('-') || label.ends_with('-') {
124 return false;
125 }
126 for c in label.chars() {
127 if !c.is_ascii_alphanumeric() && c != '-' {
128 return false;
129 }
130 }
131 }
132 true
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn test_valid_handles() {
141 assert_eq!(validate_short_handle("alice"), Ok("alice".to_string()));
142 assert_eq!(validate_short_handle("bob123"), Ok("bob123".to_string()));
143 assert_eq!(
144 validate_short_handle("user-name"),
145 Ok("user-name".to_string())
146 );
147 assert_eq!(
148 validate_short_handle("user_name"),
149 Ok("user_name".to_string())
150 );
151 assert_eq!(
152 validate_short_handle("UPPERCASE"),
153 Ok("uppercase".to_string())
154 );
155 assert_eq!(
156 validate_short_handle("MixedCase123"),
157 Ok("mixedcase123".to_string())
158 );
159 assert_eq!(validate_short_handle("abc"), Ok("abc".to_string()));
160 }
161
162 #[test]
163 fn test_invalid_handles() {
164 assert_eq!(validate_short_handle(""), Err(HandleValidationError::Empty));
165 assert_eq!(
166 validate_short_handle(" "),
167 Err(HandleValidationError::Empty)
168 );
169 assert_eq!(
170 validate_short_handle("ab"),
171 Err(HandleValidationError::TooShort)
172 );
173 assert_eq!(
174 validate_short_handle("a"),
175 Err(HandleValidationError::TooShort)
176 );
177 assert_eq!(
178 validate_short_handle("test spaces"),
179 Err(HandleValidationError::ContainsSpaces)
180 );
181 assert_eq!(
182 validate_short_handle("test\ttab"),
183 Err(HandleValidationError::ContainsSpaces)
184 );
185 assert_eq!(
186 validate_short_handle("-starts"),
187 Err(HandleValidationError::StartsWithInvalidChar)
188 );
189 assert_eq!(
190 validate_short_handle("_starts"),
191 Err(HandleValidationError::StartsWithInvalidChar)
192 );
193 assert_eq!(
194 validate_short_handle("ends-"),
195 Err(HandleValidationError::EndsWithInvalidChar)
196 );
197 assert_eq!(
198 validate_short_handle("ends_"),
199 Err(HandleValidationError::EndsWithInvalidChar)
200 );
201 assert_eq!(
202 validate_short_handle("test@user"),
203 Err(HandleValidationError::InvalidCharacters)
204 );
205 assert_eq!(
206 validate_short_handle("test!user"),
207 Err(HandleValidationError::InvalidCharacters)
208 );
209 assert_eq!(
210 validate_short_handle("test.user"),
211 Err(HandleValidationError::InvalidCharacters)
212 );
213 }
214
215 #[test]
216 fn test_handle_trimming() {
217 assert_eq!(validate_short_handle(" alice "), Ok("alice".to_string()));
218 }
219
220 #[test]
221 fn test_valid_emails() {
222 assert!(is_valid_email("user@example.com"));
223 assert!(is_valid_email("user.name@example.com"));
224 assert!(is_valid_email("user+tag@example.com"));
225 assert!(is_valid_email("user@sub.example.com"));
226 assert!(is_valid_email("USER@EXAMPLE.COM"));
227 assert!(is_valid_email("user123@example123.com"));
228 assert!(is_valid_email("a@b.co"));
229 }
230 #[test]
231 fn test_invalid_emails() {
232 assert!(!is_valid_email(""));
233 assert!(!is_valid_email("user"));
234 assert!(!is_valid_email("user@"));
235 assert!(!is_valid_email("@example.com"));
236 assert!(!is_valid_email("user@example"));
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@example.com"));
241 assert!(!is_valid_email("user.@example.com"));
242 assert!(!is_valid_email("user..name@example.com"));
243 assert!(!is_valid_email("user@-example.com"));
244 assert!(!is_valid_email("user@example-.com"));
245 }
246 #[test]
247 fn test_trimmed_whitespace() {
248 assert!(is_valid_email(" user@example.com "));
249 }
250}