litellm_rs/utils/data/validation/
data_validator.rs1use crate::utils::error::gateway_error::{GatewayError, Result};
4use regex::Regex;
5use std::collections::HashSet;
6
7pub struct DataValidator;
9
10impl DataValidator {
11 pub fn validate_username(username: &str) -> Result<()> {
13 if username.trim().is_empty() {
14 return Err(GatewayError::Validation(
15 "Username cannot be empty".to_string(),
16 ));
17 }
18
19 if username.len() < 3 {
20 return Err(GatewayError::Validation(
21 "Username must be at least 3 characters".to_string(),
22 ));
23 }
24
25 if username.len() > 50 {
26 return Err(GatewayError::Validation(
27 "Username cannot exceed 50 characters".to_string(),
28 ));
29 }
30
31 let username_regex = Regex::new(r"^[a-zA-Z0-9_-]+$")
32 .map_err(|e| GatewayError::Internal(format!("Regex error: {}", e)))?;
33
34 if !username_regex.is_match(username) {
35 return Err(GatewayError::Validation(
36 "Username can only contain letters, numbers, underscores, and hyphens".to_string(),
37 ));
38 }
39
40 Ok(())
41 }
42
43 pub fn validate_password(password: &str) -> Result<()> {
45 if password.len() < 8 {
46 return Err(GatewayError::Validation(
47 "Password must be at least 8 characters".to_string(),
48 ));
49 }
50
51 if password.len() > 128 {
52 return Err(GatewayError::Validation(
53 "Password cannot exceed 128 characters".to_string(),
54 ));
55 }
56
57 let has_lowercase = password.chars().any(|c| c.is_lowercase());
58 let has_uppercase = password.chars().any(|c| c.is_uppercase());
59 let has_digit = password.chars().any(|c| c.is_ascii_digit());
60 let has_special = password
61 .chars()
62 .any(|c| "!@#$%^&*()_+-=[]{}|;:,.<>?".contains(c));
63
64 let strength_count = [has_lowercase, has_uppercase, has_digit, has_special]
65 .iter()
66 .filter(|&&x| x)
67 .count();
68
69 if strength_count < 3 {
70 return Err(GatewayError::Validation(
71 "Password must contain at least 3 of: lowercase, uppercase, digit, special character".to_string()
72 ));
73 }
74
75 Ok(())
76 }
77
78 pub fn validate_team_name(name: &str) -> Result<()> {
80 if name.trim().is_empty() {
81 return Err(GatewayError::Validation(
82 "Team name cannot be empty".to_string(),
83 ));
84 }
85
86 if name.len() < 2 {
87 return Err(GatewayError::Validation(
88 "Team name must be at least 2 characters".to_string(),
89 ));
90 }
91
92 if name.len() > 100 {
93 return Err(GatewayError::Validation(
94 "Team name cannot exceed 100 characters".to_string(),
95 ));
96 }
97
98 Ok(())
99 }
100
101 pub fn validate_tags(tags: &[String]) -> Result<()> {
103 if tags.len() > 20 {
104 return Err(GatewayError::Validation(
105 "Cannot have more than 20 tags".to_string(),
106 ));
107 }
108
109 let mut unique_tags = HashSet::new();
110 for tag in tags {
111 if tag.trim().is_empty() {
112 return Err(GatewayError::Validation("Tag cannot be empty".to_string()));
113 }
114
115 if tag.len() > 50 {
116 return Err(GatewayError::Validation(
117 "Tag cannot exceed 50 characters".to_string(),
118 ));
119 }
120
121 if !unique_tags.insert(tag.to_lowercase()) {
122 return Err(GatewayError::Validation(format!("Duplicate tag: {}", tag)));
123 }
124 }
125
126 Ok(())
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
137 fn test_validate_username_valid() {
138 assert!(DataValidator::validate_username("john_doe").is_ok());
139 assert!(DataValidator::validate_username("user123").is_ok());
140 assert!(DataValidator::validate_username("test-user").is_ok());
141 assert!(DataValidator::validate_username("ABC").is_ok());
142 }
143
144 #[test]
145 fn test_validate_username_empty() {
146 let result = DataValidator::validate_username("");
147 assert!(result.is_err());
148 assert!(result.unwrap_err().to_string().contains("empty"));
149 }
150
151 #[test]
152 fn test_validate_username_whitespace_only() {
153 let result = DataValidator::validate_username(" ");
154 assert!(result.is_err());
155 assert!(result.unwrap_err().to_string().contains("empty"));
156 }
157
158 #[test]
159 fn test_validate_username_too_short() {
160 let result = DataValidator::validate_username("ab");
161 assert!(result.is_err());
162 assert!(result.unwrap_err().to_string().contains("at least 3"));
163 }
164
165 #[test]
166 fn test_validate_username_exactly_3_chars() {
167 assert!(DataValidator::validate_username("abc").is_ok());
168 }
169
170 #[test]
171 fn test_validate_username_too_long() {
172 let long_name = "a".repeat(51);
173 let result = DataValidator::validate_username(&long_name);
174 assert!(result.is_err());
175 assert!(result.unwrap_err().to_string().contains("50"));
176 }
177
178 #[test]
179 fn test_validate_username_exactly_50_chars() {
180 let name = "a".repeat(50);
181 assert!(DataValidator::validate_username(&name).is_ok());
182 }
183
184 #[test]
185 fn test_validate_username_invalid_chars() {
186 let result = DataValidator::validate_username("user@name");
187 assert!(result.is_err());
188 assert!(
189 result
190 .unwrap_err()
191 .to_string()
192 .contains("letters, numbers, underscores")
193 );
194 }
195
196 #[test]
197 fn test_validate_username_space_in_middle() {
198 let result = DataValidator::validate_username("user name");
199 assert!(result.is_err());
200 }
201
202 #[test]
203 fn test_validate_username_special_chars() {
204 assert!(DataValidator::validate_username("user!name").is_err());
205 assert!(DataValidator::validate_username("user.name").is_err());
206 assert!(DataValidator::validate_username("user#name").is_err());
207 }
208
209 #[test]
210 fn test_validate_username_allowed_chars() {
211 assert!(DataValidator::validate_username("User_Name-123").is_ok());
212 }
213
214 #[test]
217 fn test_validate_password_valid() {
218 assert!(DataValidator::validate_password("Password1!").is_ok());
219 assert!(DataValidator::validate_password("Secure@Pass123").is_ok());
220 assert!(DataValidator::validate_password("MyP@ssw0rd").is_ok());
221 }
222
223 #[test]
224 fn test_validate_password_too_short() {
225 let result = DataValidator::validate_password("Pass1!");
226 assert!(result.is_err());
227 assert!(result.unwrap_err().to_string().contains("at least 8"));
228 }
229
230 #[test]
231 fn test_validate_password_exactly_8_chars() {
232 assert!(DataValidator::validate_password("Pass1!ab").is_ok());
233 }
234
235 #[test]
236 fn test_validate_password_too_long() {
237 let long_pass = "a".repeat(129);
238 let result = DataValidator::validate_password(&long_pass);
239 assert!(result.is_err());
240 assert!(result.unwrap_err().to_string().contains("128"));
241 }
242
243 #[test]
244 fn test_validate_password_exactly_128_chars() {
245 let pass = format!("Aa1!{}", "a".repeat(124));
247 assert!(DataValidator::validate_password(&pass).is_ok());
248 }
249
250 #[test]
251 fn test_validate_password_weak_lowercase_only() {
252 let result = DataValidator::validate_password("abcdefgh");
253 assert!(result.is_err());
254 assert!(result.unwrap_err().to_string().contains("at least 3"));
255 }
256
257 #[test]
258 fn test_validate_password_weak_no_special() {
259 let result = DataValidator::validate_password("AbCdEfGh");
261 assert!(result.is_err());
262 }
263
264 #[test]
265 fn test_validate_password_three_strength() {
266 assert!(DataValidator::validate_password("Password1").is_ok());
268 }
269
270 #[test]
271 fn test_validate_password_four_strength() {
272 assert!(DataValidator::validate_password("Password1!").is_ok());
274 }
275
276 #[test]
277 fn test_validate_password_digit_special_lowercase() {
278 assert!(DataValidator::validate_password("password1!").is_ok());
279 }
280
281 #[test]
282 fn test_validate_password_various_special_chars() {
283 assert!(DataValidator::validate_password("Password@1").is_ok());
284 assert!(DataValidator::validate_password("Password#1").is_ok());
285 assert!(DataValidator::validate_password("Password$1").is_ok());
286 assert!(DataValidator::validate_password("Password!1").is_ok());
287 }
288
289 #[test]
292 fn test_validate_team_name_valid() {
293 assert!(DataValidator::validate_team_name("My Team").is_ok());
294 assert!(DataValidator::validate_team_name("Engineering").is_ok());
295 assert!(DataValidator::validate_team_name("AI Research Team").is_ok());
296 }
297
298 #[test]
299 fn test_validate_team_name_empty() {
300 let result = DataValidator::validate_team_name("");
301 assert!(result.is_err());
302 assert!(result.unwrap_err().to_string().contains("empty"));
303 }
304
305 #[test]
306 fn test_validate_team_name_whitespace_only() {
307 let result = DataValidator::validate_team_name(" ");
308 assert!(result.is_err());
309 assert!(result.unwrap_err().to_string().contains("empty"));
310 }
311
312 #[test]
313 fn test_validate_team_name_too_short() {
314 let result = DataValidator::validate_team_name("A");
315 assert!(result.is_err());
316 assert!(result.unwrap_err().to_string().contains("at least 2"));
317 }
318
319 #[test]
320 fn test_validate_team_name_exactly_2_chars() {
321 assert!(DataValidator::validate_team_name("AB").is_ok());
322 }
323
324 #[test]
325 fn test_validate_team_name_too_long() {
326 let long_name = "a".repeat(101);
327 let result = DataValidator::validate_team_name(&long_name);
328 assert!(result.is_err());
329 assert!(result.unwrap_err().to_string().contains("100"));
330 }
331
332 #[test]
333 fn test_validate_team_name_exactly_100_chars() {
334 let name = "a".repeat(100);
335 assert!(DataValidator::validate_team_name(&name).is_ok());
336 }
337
338 #[test]
339 fn test_validate_team_name_special_chars_allowed() {
340 assert!(DataValidator::validate_team_name("Team #1").is_ok());
342 assert!(DataValidator::validate_team_name("R&D Team").is_ok());
343 assert!(DataValidator::validate_team_name("Frontend/Backend").is_ok());
344 }
345
346 #[test]
349 fn test_validate_tags_empty_list() {
350 let tags: Vec<String> = vec![];
351 assert!(DataValidator::validate_tags(&tags).is_ok());
352 }
353
354 #[test]
355 fn test_validate_tags_valid() {
356 let tags = vec!["api".to_string(), "backend".to_string(), "v2".to_string()];
357 assert!(DataValidator::validate_tags(&tags).is_ok());
358 }
359
360 #[test]
361 fn test_validate_tags_too_many() {
362 let tags: Vec<String> = (0..21).map(|i| format!("tag{}", i)).collect();
363 let result = DataValidator::validate_tags(&tags);
364 assert!(result.is_err());
365 assert!(result.unwrap_err().to_string().contains("20"));
366 }
367
368 #[test]
369 fn test_validate_tags_exactly_20() {
370 let tags: Vec<String> = (0..20).map(|i| format!("tag{}", i)).collect();
371 assert!(DataValidator::validate_tags(&tags).is_ok());
372 }
373
374 #[test]
375 fn test_validate_tags_empty_tag() {
376 let tags = vec!["valid".to_string(), "".to_string()];
377 let result = DataValidator::validate_tags(&tags);
378 assert!(result.is_err());
379 assert!(result.unwrap_err().to_string().contains("empty"));
380 }
381
382 #[test]
383 fn test_validate_tags_whitespace_only_tag() {
384 let tags = vec!["valid".to_string(), " ".to_string()];
385 let result = DataValidator::validate_tags(&tags);
386 assert!(result.is_err());
387 assert!(result.unwrap_err().to_string().contains("empty"));
388 }
389
390 #[test]
391 fn test_validate_tags_too_long_tag() {
392 let long_tag = "a".repeat(51);
393 let tags = vec![long_tag];
394 let result = DataValidator::validate_tags(&tags);
395 assert!(result.is_err());
396 assert!(result.unwrap_err().to_string().contains("50"));
397 }
398
399 #[test]
400 fn test_validate_tags_exactly_50_char_tag() {
401 let tag = "a".repeat(50);
402 let tags = vec![tag];
403 assert!(DataValidator::validate_tags(&tags).is_ok());
404 }
405
406 #[test]
407 fn test_validate_tags_duplicate() {
408 let tags = vec!["api".to_string(), "API".to_string()];
409 let result = DataValidator::validate_tags(&tags);
410 assert!(result.is_err());
411 assert!(result.unwrap_err().to_string().contains("Duplicate"));
412 }
413
414 #[test]
415 fn test_validate_tags_case_insensitive_duplicate() {
416 let tags = vec!["Backend".to_string(), "BACKEND".to_string()];
417 let result = DataValidator::validate_tags(&tags);
418 assert!(result.is_err());
419 assert!(result.unwrap_err().to_string().contains("Duplicate"));
420 }
421
422 #[test]
423 fn test_validate_tags_similar_but_different() {
424 let tags = vec!["api".to_string(), "api2".to_string(), "api-v2".to_string()];
425 assert!(DataValidator::validate_tags(&tags).is_ok());
426 }
427
428 #[test]
429 fn test_validate_tags_single_tag() {
430 let tags = vec!["single".to_string()];
431 assert!(DataValidator::validate_tags(&tags).is_ok());
432 }
433}