open_lark/core/
validation.rs

1/// 验证工具模块
2///
3/// 提供通用的验证功能,用于构建器和其他需要数据验证的场景
4use log::error;
5
6/// 检查字符是否为中文字符
7///
8/// 使用 Unicode 范围检查来识别中文字符
9///
10/// # 参数
11/// - `c`: 要检查的字符
12///
13/// # 返回
14/// true 如果是中文字符,false 否则
15fn is_chinese_char(c: char) -> bool {
16    // CJK Unified Ideographs (扩展A-F区)
17    let ranges = [
18        (0x4E00, 0x9FFF),   // CJK Unified Ideographs
19        (0x3400, 0x4DBF),   // CJK Unified Ideographs Extension A
20        (0x20000, 0x2A6DF), // CJK Unified Ideographs Extension B
21        (0x2A700, 0x2B73F), // CJK Unified Ideographs Extension C
22        (0x2B740, 0x2B81F), // CJK Unified Ideographs Extension D
23        (0x2B820, 0x2CEAF), // CJK Unified Ideographs Extension E
24        (0x2CEB0, 0x2EBEF), // CJK Unified Ideographs Extension F
25        (0x3000, 0x303F),   // CJK Symbols and Punctuation
26        (0x31C0, 0x31EF),   // CJK Strokes
27        (0x2F00, 0x2FD5),   // Kangxi Radicals
28        (0x2E80, 0x2EFF),   // CJK Radicals Supplement
29        (0xF900, 0xFAFF),   // CJK Compatibility Ideographs
30        (0x2F800, 0x2FA1F), // CJK Compatibility Ideographs Supplement
31    ];
32
33    let code = c as u32;
34    ranges
35        .iter()
36        .any(|&(start, end)| code >= start && code <= end)
37}
38
39/// 验证字符串长度,如果超过最大长度则截断
40///
41/// # 参数
42/// - `input`: 输入字符串
43/// - `max_len`: 最大长度
44/// - `field_name`: 字段名称(用于日志)
45///
46/// # 返回
47/// 验证后的字符串(可能被截断)
48pub fn validate_string_length(input: String, max_len: usize, field_name: &str) -> String {
49    if input.len() > max_len {
50        error!(
51            "{} exceeds maximum length of {} characters: {}",
52            field_name,
53            max_len,
54            input.len()
55        );
56        input[..max_len].to_string()
57    } else {
58        input
59    }
60}
61
62/// 验证必填字段不为空
63///
64/// # 参数
65/// - `value`: 字段值
66/// - `field_name`: 字段名称(用于日志)
67///
68/// # 返回
69/// true 如果字段有效,false 如果字段为空
70pub fn validate_required(value: &str, field_name: &str) -> bool {
71    if value.is_empty() {
72        error!("{} is required but empty", field_name);
73        false
74    } else {
75        true
76    }
77}
78
79/// 验证内容大小
80///
81/// # 参数
82/// - `content`: 内容字符串
83/// - `max_size`: 最大大小(字节)
84/// - `content_type`: 内容类型(用于日志)
85///
86/// # 返回
87/// true 如果内容大小有效,false 如果超过限制
88pub fn validate_content_size(content: &str, max_size: usize, content_type: &str) -> bool {
89    if content.len() > max_size {
90        error!(
91            "{} content exceeds maximum size of {} bytes: {}",
92            content_type,
93            max_size,
94            content.len()
95        );
96        false
97    } else {
98        true
99    }
100}
101
102/// 验证结果枚举
103#[derive(Debug, Clone, PartialEq)]
104pub enum ValidationResult {
105    /// 验证通过
106    Valid,
107    /// 验证失败,但有默认值或修复值
108    Warning(String),
109    /// 验证失败,无法继续
110    Invalid(String),
111}
112
113impl ValidationResult {
114    /// 检查验证是否通过(包括警告)
115    pub fn is_valid(&self) -> bool {
116        matches!(self, ValidationResult::Valid | ValidationResult::Warning(_))
117    }
118
119    /// 检查验证是否严格通过(无警告)
120    pub fn is_strictly_valid(&self) -> bool {
121        matches!(self, ValidationResult::Valid)
122    }
123
124    /// 获取错误信息(如果有)
125    pub fn error(&self) -> Option<&str> {
126        match self {
127            ValidationResult::Invalid(msg) | ValidationResult::Warning(msg) => Some(msg),
128            ValidationResult::Valid => None,
129        }
130    }
131}
132
133/// 验证构建器 trait
134///
135/// 可以为构建器实现此 trait 以提供统一的验证接口
136pub trait ValidateBuilder {
137    /// 验证构建器状态
138    ///
139    /// # 返回
140    /// 验证结果
141    fn validate(&self) -> ValidationResult;
142
143    /// 验证并报告错误
144    ///
145    /// 执行验证并记录任何错误到日志
146    ///
147    /// # 返回
148    /// true 如果验证通过或只有警告,false 如果有严重错误
149    fn validate_and_log(&self) -> bool {
150        match self.validate() {
151            ValidationResult::Valid => true,
152            ValidationResult::Warning(msg) => {
153                error!("Builder validation warning: {}", msg);
154                true
155            }
156            ValidationResult::Invalid(msg) => {
157                error!("Builder validation failed: {}", msg);
158                false
159            }
160        }
161    }
162}
163
164/// 消息内容大小限制(字节)
165pub mod message_limits {
166    /// 文本消息最大大小
167    pub const TEXT_MESSAGE_MAX_SIZE: usize = 150 * 1024; // 150KB
168    /// 富文本消息最大大小
169    pub const RICH_MESSAGE_MAX_SIZE: usize = 30 * 1024; // 30KB
170}
171
172/// UUID 验证常量
173pub mod uuid_limits {
174    /// UUID 最大长度
175    pub const MAX_LENGTH: usize = 50;
176}
177
178/// 密码验证常量
179pub mod password_limits {
180    /// 密码最小长度
181    pub const MIN_LENGTH: usize = 8;
182    /// 密码最大长度
183    pub const MAX_LENGTH: usize = 128;
184    /// 密码必须包含的字符类型
185    pub const REQUIRE_UPPERCASE: bool = true;
186    pub const REQUIRE_LOWERCASE: bool = true;
187    pub const REQUIRE_DIGIT: bool = true;
188    pub const REQUIRE_SPECIAL: bool = true;
189}
190
191/// 文件上传验证常量
192pub mod file_limits {
193    /// 文件上传最大大小 (100MB)
194    pub const MAX_FILE_SIZE: usize = 100 * 1024 * 1024;
195    /// IM 文件上传最大大小 (50MB)
196    pub const IM_MAX_FILE_SIZE: usize = 50 * 1024 * 1024;
197    /// 图片上传最大大小 (20MB)
198    pub const MAX_IMAGE_SIZE: usize = 20 * 1024 * 1024;
199    /// 文件名最大长度
200    pub const MAX_FILENAME_LENGTH: usize = 255;
201    /// 文件扩展名最大长度
202    pub const MAX_EXTENSION_LENGTH: usize = 10;
203    /// 允许的文件类型
204    pub const ALLOWED_FILE_TYPES: &[&str] = &[
205        "pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "csv", "zip", "rar", "7z",
206        "tar", "gz", "jpg", "jpeg", "png", "gif", "bmp", "svg", "mp4", "avi", "mov", "wmv", "flv",
207        "mkv", "mp3", "wav", "flac", "aac", "ogg", "json", "xml", "html", "css", "js", "py", "rs",
208        "go",
209    ];
210    /// 允许的图片类型
211    pub const ALLOWED_IMAGE_TYPES: &[&str] = &["jpg", "jpeg", "png", "gif", "bmp", "svg", "webp"];
212}
213
214/// 验证密码强度
215///
216/// # 参数
217/// - `password`: 密码字符串
218///
219/// # 返回
220/// 验证结果
221pub fn validate_password_strength(password: &str) -> ValidationResult {
222    // 检查长度
223    if password.len() < password_limits::MIN_LENGTH {
224        return ValidationResult::Invalid(format!(
225            "Password must be at least {} characters long",
226            password_limits::MIN_LENGTH
227        ));
228    }
229
230    if password.len() > password_limits::MAX_LENGTH {
231        return ValidationResult::Invalid(format!(
232            "Password must not exceed {} characters",
233            password_limits::MAX_LENGTH
234        ));
235    }
236
237    let mut has_uppercase = false;
238    let mut has_lowercase = false;
239    let mut has_digit = false;
240    let mut has_special = false;
241
242    for ch in password.chars() {
243        if ch.is_uppercase() {
244            has_uppercase = true;
245        } else if ch.is_lowercase() {
246            has_lowercase = true;
247        } else if ch.is_ascii_digit() {
248            has_digit = true;
249        } else if ch.is_ascii_punctuation() || ch.is_ascii_whitespace() {
250            has_special = true;
251        }
252    }
253
254    let mut missing_requirements = Vec::new();
255
256    if password_limits::REQUIRE_UPPERCASE && !has_uppercase {
257        missing_requirements.push("uppercase letter");
258    }
259
260    if password_limits::REQUIRE_LOWERCASE && !has_lowercase {
261        missing_requirements.push("lowercase letter");
262    }
263
264    if password_limits::REQUIRE_DIGIT && !has_digit {
265        missing_requirements.push("digit");
266    }
267
268    if password_limits::REQUIRE_SPECIAL && !has_special {
269        missing_requirements.push("special character");
270    }
271
272    if !missing_requirements.is_empty() {
273        return ValidationResult::Invalid(format!(
274            "Password is missing required character types: {}",
275            missing_requirements.join(", ")
276        ));
277    }
278
279    ValidationResult::Valid
280}
281
282/// 验证并规范化密码
283///
284/// # 参数
285/// - `password`: 密码字符串
286/// - `field_name`: 字段名称(用于日志)
287///
288/// # 返回
289/// 验证后的密码字符串和验证结果
290pub fn validate_and_sanitize_password(
291    password: String,
292    field_name: &str,
293) -> (String, ValidationResult) {
294    // 去除首尾空白
295    let password = password.trim().to_string();
296
297    // 验证强度
298    let result = validate_password_strength(&password);
299
300    if let ValidationResult::Invalid(msg) = &result {
301        error!("{} validation failed: {}", field_name, msg);
302    }
303
304    (password, result)
305}
306
307/// 验证文件大小
308///
309/// # 参数
310/// - `file_size`: 文件大小(字节)
311/// - `max_size`: 最大允许大小(字节)
312/// - `file_name`: 文件名(用于日志)
313///
314/// # 返回
315/// 验证结果
316pub fn validate_file_size(file_size: usize, max_size: usize, file_name: &str) -> ValidationResult {
317    if file_size == 0 {
318        return ValidationResult::Invalid("File size cannot be zero".to_string());
319    }
320
321    if file_size > max_size {
322        return ValidationResult::Invalid(format!(
323            "File '{}' exceeds maximum size of {} bytes (actual: {} bytes)",
324            file_name, max_size, file_size
325        ));
326    }
327
328    ValidationResult::Valid
329}
330
331/// 验证文件名
332///
333/// # 参数
334/// - `file_name`: 文件名
335///
336/// # 返回
337/// 验证结果和清理后的文件名
338pub fn validate_file_name(file_name: &str) -> (String, ValidationResult) {
339    let cleaned_name = file_name.trim();
340
341    // 检查是否为空
342    if cleaned_name.is_empty() {
343        return (
344            String::new(),
345            ValidationResult::Invalid("File name cannot be empty".to_string()),
346        );
347    }
348
349    // 检查长度
350    if cleaned_name.len() > file_limits::MAX_FILENAME_LENGTH {
351        return (
352            String::new(),
353            ValidationResult::Invalid(format!(
354                "File name exceeds maximum length of {} characters",
355                file_limits::MAX_FILENAME_LENGTH
356            )),
357        );
358    }
359
360    // 检查非法字符
361    let invalid_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|'];
362    for ch in cleaned_name.chars() {
363        if invalid_chars.contains(&ch) {
364            return (
365                String::new(),
366                ValidationResult::Invalid(format!(
367                    "File name contains invalid character: '{}'",
368                    ch
369                )),
370            );
371        }
372    }
373
374    // 检查保留文件名
375    let reserved_names = [
376        "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8",
377        "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
378    ];
379
380    let upper_name = cleaned_name.to_uppercase();
381    let name_without_ext = if let Some(dot_pos) = upper_name.find('.') {
382        &upper_name[..dot_pos]
383    } else {
384        &upper_name
385    };
386
387    if reserved_names.contains(&name_without_ext) {
388        return (
389            String::new(),
390            ValidationResult::Invalid(format!("'{}' is a reserved file name", cleaned_name)),
391        );
392    }
393
394    (cleaned_name.to_string(), ValidationResult::Valid)
395}
396
397/// 验证文件扩展名
398///
399/// # 参数
400/// - `file_name`: 文件名
401/// - `allowed_types`: 允许的扩展名列表
402///
403/// # 返回
404/// 验证结果和扩展名
405pub fn validate_file_extension(
406    file_name: &str,
407    allowed_types: &[&str],
408) -> (Option<String>, ValidationResult) {
409    let (_, validation_result) = validate_file_name(file_name);
410    if !validation_result.is_valid() {
411        return (None, validation_result);
412    }
413
414    // 提取扩展名
415    let extension = if let Some(dot_pos) = file_name.rfind('.') {
416        let ext = &file_name[dot_pos + 1..];
417        if ext.is_empty() {
418            return (
419                None,
420                ValidationResult::Invalid("File extension cannot be empty".to_string()),
421            );
422        }
423
424        // 检查扩展名长度
425        if ext.len() > file_limits::MAX_EXTENSION_LENGTH {
426            return (
427                None,
428                ValidationResult::Invalid(format!(
429                    "File extension exceeds maximum length of {} characters",
430                    file_limits::MAX_EXTENSION_LENGTH
431                )),
432            );
433        }
434
435        Some(ext.to_lowercase())
436    } else {
437        return (
438            None,
439            ValidationResult::Invalid("File must have an extension".to_string()),
440        );
441    };
442
443    // 检查扩展名是否在允许列表中
444    if let Some(ref ext) = extension {
445        if !allowed_types.contains(&ext.as_str()) {
446            return (
447                None,
448                ValidationResult::Invalid(format!(
449                    "File extension '.{}' is not allowed. Allowed types: {}",
450                    ext,
451                    allowed_types.join(", ")
452                )),
453            );
454        }
455    }
456
457    (extension, ValidationResult::Valid)
458}
459
460/// 验证图片文件
461///
462/// # 参数
463/// - `file_data`: 图片文件数据
464/// - `file_name`: 文件名
465///
466/// # 返回
467/// 验证结果
468pub fn validate_image_file(file_data: &[u8], file_name: &str) -> ValidationResult {
469    // 验证文件大小
470    let size_result = validate_file_size(file_data.len(), file_limits::MAX_IMAGE_SIZE, file_name);
471    if !size_result.is_valid() {
472        return size_result;
473    }
474
475    // 验证扩展名
476    let (_, ext_result) = validate_file_extension(file_name, file_limits::ALLOWED_IMAGE_TYPES);
477    if !ext_result.is_valid() {
478        return ext_result;
479    }
480
481    // ENHANCEMENT: 可增强图片文件头验证(Magic Number 检查)
482    // 通过检查文件签名防止文件扩展名伪造攻击,增强安全性
483    // 可考虑使用 `infer` crate 或手动实现常见格式的文件头检查
484
485    ValidationResult::Valid
486}
487
488/// 验证上传文件
489///
490/// # 参数
491/// - `file_data`: 文件数据
492/// - `file_name`: 文件名
493/// - `is_im_upload`: 是否为IM上传(有更小的文件大小限制)
494///
495/// # 返回
496/// 验证结果
497pub fn validate_upload_file(
498    file_data: &[u8],
499    file_name: &str,
500    is_im_upload: bool,
501) -> ValidationResult {
502    // 验证文件大小
503    let max_size = if is_im_upload {
504        file_limits::IM_MAX_FILE_SIZE
505    } else {
506        file_limits::MAX_FILE_SIZE
507    };
508
509    let size_result = validate_file_size(file_data.len(), max_size, file_name);
510    if !size_result.is_valid() {
511        return size_result;
512    }
513
514    // 验证文件名
515    let (_, name_result) = validate_file_name(file_name);
516    if !name_result.is_valid() {
517        return name_result;
518    }
519
520    // 验证扩展名
521    let (_, ext_result) = validate_file_extension(file_name, file_limits::ALLOWED_FILE_TYPES);
522    if !ext_result.is_valid() {
523        return ext_result;
524    }
525
526    ValidationResult::Valid
527}
528
529#[cfg(test)]
530mod tests {
531    use super::*;
532
533    #[test]
534    fn test_validate_string_length() {
535        // 测试正常情况
536        let input = "hello".to_string();
537        let result = validate_string_length(input, 10, "test_field");
538        assert_eq!(result, "hello");
539
540        // 测试截断
541        let input = "hello world".to_string();
542        let result = validate_string_length(input, 5, "test_field");
543        assert_eq!(result, "hello");
544    }
545
546    #[test]
547    fn test_validate_required() {
548        // 测试有效值
549        assert!(validate_required("hello", "test_field"));
550
551        // 测试空值
552        assert!(!validate_required("", "test_field"));
553    }
554
555    #[test]
556    fn test_validate_content_size() {
557        // 测试有效内容
558        assert!(validate_content_size("hello", 10, "test_content"));
559
560        // 测试过大内容
561        assert!(!validate_content_size("hello world", 5, "test_content"));
562    }
563
564    #[test]
565    fn test_validation_result() {
566        let valid = ValidationResult::Valid;
567        assert!(valid.is_valid());
568        assert!(valid.is_strictly_valid());
569        assert!(valid.error().is_none());
570
571        let warning = ValidationResult::Warning("test warning".to_string());
572        assert!(warning.is_valid());
573        assert!(!warning.is_strictly_valid());
574        assert_eq!(warning.error(), Some("test warning"));
575
576        let invalid = ValidationResult::Invalid("test error".to_string());
577        assert!(!invalid.is_valid());
578        assert!(!invalid.is_strictly_valid());
579        assert_eq!(invalid.error(), Some("test error"));
580    }
581
582    #[test]
583    fn test_validate_password_strength() {
584        // 测试有效密码
585        let valid_password = "SecurePass123!";
586        assert!(matches!(
587            validate_password_strength(valid_password),
588            ValidationResult::Valid
589        ));
590
591        // 测试太短的密码
592        let short_password = "Short1!";
593        assert!(matches!(
594            validate_password_strength(short_password),
595            ValidationResult::Invalid(_)
596        ));
597
598        // 测试太长的密码
599        let long_password = "a".repeat(password_limits::MAX_LENGTH + 1);
600        assert!(matches!(
601            validate_password_strength(&long_password),
602            ValidationResult::Invalid(_)
603        ));
604
605        // 测试缺少大写字母
606        let no_upper = "lowercase123!";
607        assert!(matches!(
608            validate_password_strength(no_upper),
609            ValidationResult::Invalid(_)
610        ));
611
612        // 测试缺少小写字母
613        let no_lower = "UPPERCASE123!";
614        assert!(matches!(
615            validate_password_strength(no_lower),
616            ValidationResult::Invalid(_)
617        ));
618
619        // 测试缺少数字
620        let no_digit = "NoDigitsHere!";
621        assert!(matches!(
622            validate_password_strength(no_digit),
623            ValidationResult::Invalid(_)
624        ));
625
626        // 测试缺少特殊字符
627        let no_special = "NoSpecialChars123";
628        assert!(matches!(
629            validate_password_strength(no_special),
630            ValidationResult::Invalid(_)
631        ));
632    }
633
634    #[test]
635    fn test_validate_and_sanitize_password() {
636        // 测试正常密码
637        let (password, result) =
638            validate_and_sanitize_password("  GoodPass123!  ".to_string(), "test_password");
639        assert_eq!(password, "GoodPass123!");
640        assert!(matches!(result, ValidationResult::Valid));
641
642        // 测试无效密码
643        let (password, result) =
644            validate_and_sanitize_password("  weak  ".to_string(), "test_password");
645        assert_eq!(password, "weak");
646        assert!(matches!(result, ValidationResult::Invalid(_)));
647    }
648
649    #[test]
650    fn test_validate_file_size() {
651        // 测试有效文件大小
652        assert!(matches!(
653            validate_file_size(1024, 2048, "test.txt"),
654            ValidationResult::Valid
655        ));
656
657        // 测试零大小文件
658        assert!(matches!(
659            validate_file_size(0, 2048, "test.txt"),
660            ValidationResult::Invalid(_)
661        ));
662
663        // 测试过大文件
664        assert!(matches!(
665            validate_file_size(3000, 2048, "test.txt"),
666            ValidationResult::Invalid(_)
667        ));
668    }
669
670    #[test]
671    fn test_validate_file_name() {
672        // 测试有效文件名
673        let (name, result) = validate_file_name("document.pdf");
674        assert_eq!(name, "document.pdf");
675        assert!(matches!(result, ValidationResult::Valid));
676
677        // 测试前后空格
678        let (name, result) = validate_file_name("  document.pdf  ");
679        assert_eq!(name, "document.pdf");
680        assert!(matches!(result, ValidationResult::Valid));
681
682        // 测试空文件名
683        let (name, result) = validate_file_name("");
684        assert_eq!(name, "");
685        assert!(matches!(result, ValidationResult::Invalid(_)));
686
687        // 测试过长文件名
688        let long_name = "a".repeat(file_limits::MAX_FILENAME_LENGTH + 1);
689        let (name, result) = validate_file_name(&long_name);
690        assert_eq!(name, "");
691        assert!(matches!(result, ValidationResult::Invalid(_)));
692
693        // 测试非法字符
694        let invalid_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|'];
695        for ch in invalid_chars {
696            let (name, result) = validate_file_name(&format!("test{}.txt", ch));
697            assert_eq!(name, "");
698            assert!(matches!(result, ValidationResult::Invalid(_)));
699        }
700
701        // 测试保留文件名
702        let reserved_names = ["CON", "PRN", "AUX", "NUL", "COM1", "LPT1"];
703        for name in reserved_names {
704            let (cleaned, result) = validate_file_name(name);
705            assert_eq!(cleaned, "");
706            assert!(matches!(result, ValidationResult::Invalid(_)));
707        }
708    }
709
710    #[test]
711    fn test_validate_file_extension() {
712        // 测试有效扩展名
713        let (ext, result) =
714            validate_file_extension("document.pdf", file_limits::ALLOWED_FILE_TYPES);
715        assert_eq!(ext, Some("pdf".to_string()));
716        assert!(matches!(result, ValidationResult::Valid));
717
718        // 测试无扩展名
719        let (ext, result) = validate_file_extension("document", file_limits::ALLOWED_FILE_TYPES);
720        assert_eq!(ext, None);
721        assert!(matches!(result, ValidationResult::Invalid(_)));
722
723        // 测试空扩展名
724        let (ext, result) = validate_file_extension("document.", file_limits::ALLOWED_FILE_TYPES);
725        assert_eq!(ext, None);
726        assert!(matches!(result, ValidationResult::Invalid(_)));
727
728        // 测试不允许的扩展名
729        let (ext, result) =
730            validate_file_extension("document.exe", file_limits::ALLOWED_FILE_TYPES);
731        assert_eq!(ext, None);
732        assert!(matches!(result, ValidationResult::Invalid(_)));
733
734        // 测试大小写不敏感
735        let (ext, result) =
736            validate_file_extension("document.PDF", file_limits::ALLOWED_FILE_TYPES);
737        assert_eq!(ext, Some("pdf".to_string()));
738        assert!(matches!(result, ValidationResult::Valid));
739    }
740
741    #[test]
742    fn test_validate_image_file() {
743        // 测试有效图片
744        let image_data = vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]; // PNG header
745        assert!(matches!(
746            validate_image_file(&image_data, "image.png"),
747            ValidationResult::Valid
748        ));
749
750        // 测试过大图片
751        let large_image_data = vec![0u8; file_limits::MAX_IMAGE_SIZE + 1];
752        assert!(matches!(
753            validate_image_file(&large_image_data, "large.png"),
754            ValidationResult::Invalid(_)
755        ));
756
757        // 测试不允许的图片类型
758        let image_data = vec![0x89, 0x50, 0x4E, 0x47];
759        assert!(matches!(
760            validate_image_file(&image_data, "image.tiff"),
761            ValidationResult::Invalid(_)
762        ));
763    }
764
765    #[test]
766    fn test_validate_upload_file() {
767        // 测试有效文件上传
768        let file_data = vec![0u8; 1024];
769        assert!(matches!(
770            validate_upload_file(&file_data, "document.pdf", false),
771            ValidationResult::Valid
772        ));
773
774        // 测试IM文件上传(较小的限制)
775        let im_file_data = vec![0u8; file_limits::IM_MAX_FILE_SIZE + 1];
776        assert!(matches!(
777            validate_upload_file(&im_file_data, "large.pdf", true),
778            ValidationResult::Invalid(_)
779        ));
780
781        // 测试普通文件上传(较大的限制)
782        let normal_file_data = vec![0u8; file_limits::MAX_FILE_SIZE + 1];
783        assert!(matches!(
784            validate_upload_file(&normal_file_data, "large.pdf", false),
785            ValidationResult::Invalid(_)
786        ));
787    }
788
789    #[test]
790    fn test_is_chinese_char() {
791        // 测试中文字符
792        assert!(is_chinese_char('中'));
793        assert!(is_chinese_char('文'));
794        assert!(is_chinese_char('字'));
795        assert!(is_chinese_char('符'));
796
797        // 测试CJK符号和标点(注意:这些符号在CJK范围内)
798        // 中文句号在CJK范围内
799        assert!(is_chinese_char('。')); // Unicode U+3002
800
801        // 测试一些不在CJK范围内的全角符号
802        assert!(!is_chinese_char(',')); // Unicode U+FF0C - 在全角符号区,不在CJK范围
803
804        // 测试非中文字符
805        assert!(!is_chinese_char('a'));
806        assert!(!is_chinese_char('1'));
807        assert!(!is_chinese_char('!'));
808        assert!(!is_chinese_char(' '));
809    }
810
811    #[test]
812    fn test_validate_name() {
813        // 测试有效姓名
814        assert!(matches!(
815            validate_name("张三", "姓名"),
816            ValidationResult::Valid
817        ));
818
819        assert!(matches!(
820            validate_name("John Smith", "姓名"),
821            ValidationResult::Valid
822        ));
823
824        // 测试空姓名
825        assert!(matches!(
826            validate_name("", "姓名"),
827            ValidationResult::Invalid(_)
828        ));
829
830        // 测试过短姓名
831        assert!(matches!(
832            validate_name("A", "姓名"),
833            ValidationResult::Invalid(_)
834        ));
835
836        // 测试过长姓名
837        let long_name = "A".repeat(employee_limits::NAME_MAX_LENGTH + 1);
838        assert!(matches!(
839            validate_name(&long_name, "姓名"),
840            ValidationResult::Invalid(_)
841        ));
842    }
843
844    #[test]
845    fn test_validate_email() {
846        // 测试有效邮箱
847        assert!(matches!(
848            validate_email("test@example.com", "邮箱"),
849            ValidationResult::Valid
850        ));
851
852        assert!(matches!(
853            validate_email("user.name+tag@domain.co.uk", "邮箱"),
854            ValidationResult::Valid
855        ));
856
857        // 测试无效邮箱
858        assert!(matches!(
859            validate_email("invalid-email", "邮箱"),
860            ValidationResult::Invalid(_)
861        ));
862
863        assert!(matches!(
864            validate_email("@example.com", "邮箱"),
865            ValidationResult::Invalid(_)
866        ));
867
868        assert!(matches!(
869            validate_email("test@", "邮箱"),
870            ValidationResult::Invalid(_)
871        ));
872
873        // 测试空邮箱
874        assert!(matches!(
875            validate_email("", "邮箱"),
876            ValidationResult::Invalid(_)
877        ));
878
879        // 测试过长邮箱
880        let long_email = format!(
881            "{}@example.com",
882            "a".repeat(employee_limits::EMAIL_MAX_LENGTH)
883        );
884        assert!(matches!(
885            validate_email(&long_email, "邮箱"),
886            ValidationResult::Invalid(_)
887        ));
888    }
889
890    #[test]
891    fn test_validate_phone() {
892        // 测试有效电话号码
893        assert!(matches!(
894            validate_phone("13812345678", "电话"),
895            ValidationResult::Valid
896        ));
897
898        assert!(matches!(
899            validate_phone("+86-138-1234-5678", "电话"),
900            ValidationResult::Valid
901        ));
902
903        assert!(matches!(
904            validate_phone("021-12345678", "电话"),
905            ValidationResult::Valid
906        ));
907
908        // 测试无效电话号码
909        assert!(matches!(
910            validate_phone("123", "电话"),
911            ValidationResult::Invalid(_)
912        ));
913
914        assert!(matches!(
915            validate_phone("abc123def", "电话"),
916            ValidationResult::Invalid(_)
917        ));
918
919        // 测试空电话号码(电话是可选的,所以空值是有效的)
920        assert!(matches!(
921            validate_phone("", "电话"),
922            ValidationResult::Valid
923        ));
924
925        // 测试过长电话号码
926        let long_phone = "1".repeat(employee_limits::PHONE_MAX_LENGTH + 1);
927        assert!(matches!(
928            validate_phone(&long_phone, "电话"),
929            ValidationResult::Invalid(_)
930        ));
931    }
932
933    #[test]
934    fn test_validate_work_experience() {
935        // 测试有效工作年限
936        assert!(matches!(
937            validate_work_experience(5, "工作年限"),
938            ValidationResult::Valid
939        ));
940
941        assert!(matches!(
942            validate_work_experience(0, "工作年限"),
943            ValidationResult::Valid
944        ));
945
946        assert!(matches!(
947            validate_work_experience(employee_limits::WORK_EXPERIENCE_MAX, "工作年限"),
948            ValidationResult::Valid
949        ));
950
951        // 测试无效工作年限
952        assert!(matches!(
953            validate_work_experience(employee_limits::WORK_EXPERIENCE_MAX + 1, "工作年限"),
954            ValidationResult::Invalid(_)
955        ));
956    }
957
958    #[test]
959    fn test_validate_birthday() {
960        // 测试有效生日
961        assert!(matches!(
962            validate_birthday(&Some("1990-01-01".to_string()), "生日"),
963            ValidationResult::Valid
964        ));
965
966        assert!(matches!(
967            validate_birthday(&Some("2000-12-31".to_string()), "生日"),
968            ValidationResult::Valid
969        ));
970
971        // 测试空生日(可选字段)
972        assert!(matches!(
973            validate_birthday(&None, "生日"),
974            ValidationResult::Valid
975        ));
976
977        // 测试无效生日格式
978        assert!(matches!(
979            validate_birthday(&Some("invalid-date".to_string()), "生日"),
980            ValidationResult::Invalid(_)
981        ));
982
983        assert!(matches!(
984            validate_birthday(&Some("1990/01/01".to_string()), "生日"),
985            ValidationResult::Invalid(_)
986        ));
987
988        assert!(matches!(
989            validate_birthday(&Some("1990-13-01".to_string()), "生日"),
990            ValidationResult::Invalid(_)
991        ));
992    }
993
994    #[test]
995    fn test_validate_expected_salary() {
996        // 测试有效期望薪资
997        assert!(matches!(
998            validate_expected_salary(&Some("10000-15000".to_string()), "期望薪资"),
999            ValidationResult::Valid
1000        ));
1001
1002        assert!(matches!(
1003            validate_expected_salary(&Some("面议".to_string()), "期望薪资"),
1004            ValidationResult::Valid
1005        ));
1006
1007        // 测试空期望薪资(可选字段)
1008        assert!(matches!(
1009            validate_expected_salary(&None, "期望薪资"),
1010            ValidationResult::Valid
1011        ));
1012
1013        // 测试过长期望薪资
1014        let long_salary = "1".repeat(employee_limits::EXPECTED_SALARY_MAX_LENGTH + 1);
1015        assert!(matches!(
1016            validate_expected_salary(&Some(long_salary), "期望薪资"),
1017            ValidationResult::Invalid(_)
1018        ));
1019    }
1020
1021    #[test]
1022    fn test_validate_tags() {
1023        // 测试有效标签
1024        let valid_tags = vec![
1025            "Java".to_string(),
1026            "Python".to_string(),
1027            "React".to_string(),
1028        ];
1029        assert!(matches!(
1030            validate_tags(&valid_tags, "技能标签"),
1031            ValidationResult::Valid
1032        ));
1033
1034        // 测试空标签列表
1035        assert!(matches!(
1036            validate_tags(&[], "技能标签"),
1037            ValidationResult::Valid
1038        ));
1039
1040        // 测试过多标签
1041        let too_many_tags: Vec<String> = (0..employee_limits::MAX_TALENT_TAGS + 1)
1042            .map(|i| format!("tag{}", i))
1043            .collect();
1044        assert!(matches!(
1045            validate_tags(&too_many_tags, "技能标签"),
1046            ValidationResult::Invalid(_)
1047        ));
1048
1049        // 测试空标签
1050        let tags_with_empty = vec!["Java".to_string(), "".to_string()];
1051        assert!(matches!(
1052            validate_tags(&tags_with_empty, "技能标签"),
1053            ValidationResult::Invalid(_)
1054        ));
1055
1056        // 测试过长标签
1057        let long_tag = "a".repeat(employee_limits::TAG_MAX_LENGTH + 1);
1058        let tags_with_long = vec![long_tag];
1059        assert!(matches!(
1060            validate_tags(&tags_with_long, "技能标签"),
1061            ValidationResult::Invalid(_)
1062        ));
1063    }
1064
1065    #[test]
1066    fn test_sanitize_name() {
1067        // 测试去除首尾空白
1068        assert_eq!(sanitize_name("  张三  "), "张三");
1069
1070        // 测试去除多余空格
1071        assert_eq!(sanitize_name("张  三"), "张 三");
1072
1073        // 测试正常名称
1074        assert_eq!(sanitize_name("John Smith"), "John Smith");
1075
1076        // 测试空字符串
1077        assert_eq!(sanitize_name(""), "");
1078    }
1079
1080    #[test]
1081    fn test_sanitize_tags() {
1082        let input_tags = vec![
1083            "  Java  ".to_string(),
1084            "Python".to_string(),
1085            "  ".to_string(),
1086            "React JS".to_string(),
1087        ];
1088
1089        let sanitized = sanitize_tags(&input_tags);
1090        assert_eq!(sanitized, vec!["java", "python", "react js"]);
1091    }
1092
1093    #[test]
1094    fn test_sanitize_tag() {
1095        // 测试去除空白和转换
1096        assert_eq!(sanitize_tag("  Java-Script  "), "java_script");
1097        assert_eq!(sanitize_tag("Node.js"), "node.js");
1098        assert_eq!(sanitize_tag("C++"), "c++");
1099        assert_eq!(sanitize_tag("React_Native"), "react_native");
1100    }
1101
1102    #[test]
1103    fn test_validate_page_size() {
1104        // 测试有效页面大小
1105        assert!(matches!(
1106            validate_page_size(10, "页面大小"),
1107            ValidationResult::Valid
1108        ));
1109
1110        assert!(matches!(
1111            validate_page_size(pagination_limits::MAX_PAGE_SIZE, "页面大小"),
1112            ValidationResult::Valid
1113        ));
1114
1115        // 测试无效页面大小
1116        assert!(matches!(
1117            validate_page_size(0, "页面大小"),
1118            ValidationResult::Invalid(_)
1119        ));
1120
1121        assert!(matches!(
1122            validate_page_size(pagination_limits::MAX_PAGE_SIZE + 1, "页面大小"),
1123            ValidationResult::Invalid(_)
1124        ));
1125    }
1126
1127    #[test]
1128    fn test_validate_page_token() {
1129        // 测试有效页面令牌
1130        assert!(matches!(
1131            validate_page_token("valid_token_123", "页面令牌"),
1132            ValidationResult::Valid
1133        ));
1134
1135        // 测试空页面令牌(空令牌是有效的,表示第一页)
1136        assert!(matches!(
1137            validate_page_token("", "页面令牌"),
1138            ValidationResult::Valid
1139        ));
1140
1141        // 测试过长页面令牌
1142        let long_token = "a".repeat(pagination_limits::MAX_PAGE_TOKEN_LENGTH + 1);
1143        assert!(matches!(
1144            validate_page_token(&long_token, "页面令牌"),
1145            ValidationResult::Invalid(_)
1146        ));
1147    }
1148
1149    #[test]
1150    fn test_validate_pagination_params() {
1151        // 测试有效分页参数
1152        assert!(matches!(
1153            validate_pagination_params(Some(10), Some("token123"), "test"),
1154            ValidationResult::Valid
1155        ));
1156
1157        // 测试只有页面大小
1158        assert!(matches!(
1159            validate_pagination_params(Some(10), None, "test"),
1160            ValidationResult::Valid
1161        ));
1162
1163        // 测试只有页面令牌(会产生警告但仍然有效)
1164        assert!(matches!(
1165            validate_pagination_params(None, Some("token123"), "test"),
1166            ValidationResult::Valid
1167        ));
1168
1169        // 测试都为空
1170        assert!(matches!(
1171            validate_pagination_params(None, None, "test"),
1172            ValidationResult::Valid
1173        ));
1174
1175        // 测试无效页面大小
1176        assert!(matches!(
1177            validate_pagination_params(Some(0), Some("token123"), "test"),
1178            ValidationResult::Invalid(_)
1179        ));
1180
1181        // 测试空页面令牌(空令牌是有效的)
1182        assert!(matches!(
1183            validate_pagination_params(Some(10), Some(""), "test"),
1184            ValidationResult::Valid
1185        ));
1186    }
1187
1188    #[test]
1189    fn test_validate_custom_fields() {
1190        use serde_json::Value;
1191        use std::collections::HashMap;
1192
1193        // 测试空自定义字段
1194        assert!(matches!(
1195            validate_custom_fields(&None, "自定义字段"),
1196            ValidationResult::Valid
1197        ));
1198
1199        // 测试有效自定义字段
1200        let mut valid_fields = HashMap::new();
1201        valid_fields.insert(
1202            "skill_level".to_string(),
1203            Value::String("advanced".to_string()),
1204        );
1205        valid_fields.insert(
1206            "years_exp".to_string(),
1207            Value::Number(serde_json::Number::from(5)),
1208        );
1209        valid_fields.insert("is_certified".to_string(), Value::Bool(true));
1210        valid_fields.insert(
1211            "tags".to_string(),
1212            Value::Array(vec![Value::String("rust".to_string())]),
1213        );
1214        valid_fields.insert("nullable_field".to_string(), Value::Null);
1215
1216        assert!(matches!(
1217            validate_custom_fields(&Some(valid_fields), "自定义字段"),
1218            ValidationResult::Valid
1219        ));
1220
1221        // 测试过多字段(超过50个)
1222        let mut too_many_fields = HashMap::new();
1223        for i in 0..51 {
1224            too_many_fields.insert(format!("field_{}", i), Value::String("value".to_string()));
1225        }
1226        assert!(matches!(
1227            validate_custom_fields(&Some(too_many_fields), "自定义字段"),
1228            ValidationResult::Invalid(_)
1229        ));
1230
1231        // 测试空键
1232        let mut empty_key_fields = HashMap::new();
1233        empty_key_fields.insert("".to_string(), Value::String("value".to_string()));
1234        assert!(matches!(
1235            validate_custom_fields(&Some(empty_key_fields), "自定义字段"),
1236            ValidationResult::Invalid(_)
1237        ));
1238
1239        // 测试过长键
1240        let mut long_key_fields = HashMap::new();
1241        let long_key = "a".repeat(employee_limits::CUSTOM_FIELD_KEY_MAX_LENGTH + 1);
1242        long_key_fields.insert(long_key, Value::String("value".to_string()));
1243        assert!(matches!(
1244            validate_custom_fields(&Some(long_key_fields), "自定义字段"),
1245            ValidationResult::Invalid(_)
1246        ));
1247
1248        // 测试过长字符串值
1249        let mut long_value_fields = HashMap::new();
1250        let long_value = "a".repeat(employee_limits::CUSTOM_FIELD_VALUE_MAX_LENGTH + 1);
1251        long_value_fields.insert("key".to_string(), Value::String(long_value));
1252        assert!(matches!(
1253            validate_custom_fields(&Some(long_value_fields), "自定义字段"),
1254            ValidationResult::Invalid(_)
1255        ));
1256
1257        // 测试过大数组
1258        let mut large_array_fields = HashMap::new();
1259        let large_array = (0..101)
1260            .map(|i| Value::Number(serde_json::Number::from(i)))
1261            .collect();
1262        large_array_fields.insert("large_array".to_string(), Value::Array(large_array));
1263        assert!(matches!(
1264            validate_custom_fields(&Some(large_array_fields), "自定义字段"),
1265            ValidationResult::Invalid(_)
1266        ));
1267
1268        // 测试对象值(不允许)
1269        let mut object_fields = HashMap::new();
1270        let mut nested_object = HashMap::new();
1271        nested_object.insert("nested".to_string(), Value::String("value".to_string()));
1272        object_fields.insert(
1273            "object_field".to_string(),
1274            Value::Object(serde_json::Map::from_iter(nested_object)),
1275        );
1276        assert!(matches!(
1277            validate_custom_fields(&Some(object_fields), "自定义字段"),
1278            ValidationResult::Invalid(_)
1279        ));
1280    }
1281
1282    #[test]
1283    fn test_validate_resume_attachment_ids() {
1284        // 测试有效附件ID列表
1285        let valid_ids = vec!["attachment_1".to_string(), "attachment_2".to_string()];
1286        assert!(matches!(
1287            validate_resume_attachment_ids(&valid_ids, "简历附件"),
1288            ValidationResult::Valid
1289        ));
1290
1291        // 测试空列表
1292        assert!(matches!(
1293            validate_resume_attachment_ids(&[], "简历附件"),
1294            ValidationResult::Valid
1295        ));
1296
1297        // 测试过多附件
1298        let too_many_ids: Vec<String> = (0..employee_limits::MAX_RESUME_ATTACHMENTS + 1)
1299            .map(|i| format!("attachment_{}", i))
1300            .collect();
1301        assert!(matches!(
1302            validate_resume_attachment_ids(&too_many_ids, "简历附件"),
1303            ValidationResult::Invalid(_)
1304        ));
1305
1306        // 测试空ID
1307        let empty_id_list = vec!["valid_id".to_string(), "".to_string()];
1308        assert!(matches!(
1309            validate_resume_attachment_ids(&empty_id_list, "简历附件"),
1310            ValidationResult::Invalid(_)
1311        ));
1312
1313        // 测试ID长度不合法
1314        let short_id_list = vec!["short".to_string()];
1315        assert!(matches!(
1316            validate_resume_attachment_ids(&short_id_list, "简历附件"),
1317            ValidationResult::Invalid(_)
1318        ));
1319
1320        let long_id_list = vec!["a".repeat(101)];
1321        assert!(matches!(
1322            validate_resume_attachment_ids(&long_id_list, "简历附件"),
1323            ValidationResult::Invalid(_)
1324        ));
1325    }
1326
1327    #[test]
1328    fn test_validate_talent_tag() {
1329        // 测试有效标签
1330        assert!(matches!(
1331            validate_talent_tag("rust", "技能标签"),
1332            ValidationResult::Valid
1333        ));
1334
1335        assert!(matches!(
1336            validate_talent_tag("Java_Spring", "技能标签"),
1337            ValidationResult::Valid
1338        ));
1339
1340        assert!(matches!(
1341            validate_talent_tag("前端开发", "技能标签"),
1342            ValidationResult::Valid
1343        ));
1344
1345        assert!(matches!(
1346            validate_talent_tag("Node_js", "技能标签"),
1347            ValidationResult::Valid
1348        ));
1349
1350        // 测试空标签
1351        assert!(matches!(
1352            validate_talent_tag("", "技能标签"),
1353            ValidationResult::Invalid(_)
1354        ));
1355
1356        // 测试过长标签
1357        let long_tag = "a".repeat(51);
1358        assert!(matches!(
1359            validate_talent_tag(&long_tag, "技能标签"),
1360            ValidationResult::Invalid(_)
1361        ));
1362
1363        // 测试非法字符
1364        assert!(matches!(
1365            validate_talent_tag("tag@domain", "技能标签"),
1366            ValidationResult::Invalid(_)
1367        ));
1368
1369        assert!(matches!(
1370            validate_talent_tag("tag*wildcard", "技能标签"),
1371            ValidationResult::Invalid(_)
1372        ));
1373    }
1374
1375    #[test]
1376    fn test_validate_talent_tags() {
1377        // 测试有效标签列表
1378        let valid_tags = vec![
1379            "rust".to_string(),
1380            "javascript".to_string(),
1381            "前端开发".to_string(),
1382        ];
1383        assert!(matches!(
1384            validate_talent_tags(&valid_tags),
1385            ValidationResult::Valid
1386        ));
1387
1388        // 测试空标签列表
1389        assert!(matches!(validate_talent_tags(&[]), ValidationResult::Valid));
1390
1391        // 测试过多标签
1392        let too_many_tags: Vec<String> = (0..21).map(|i| format!("tag_{}", i)).collect();
1393        assert!(matches!(
1394            validate_talent_tags(&too_many_tags),
1395            ValidationResult::Invalid(_)
1396        ));
1397
1398        // 测试包含无效标签
1399        let invalid_tags = vec!["valid_tag".to_string(), "invalid@tag".to_string()];
1400        assert!(matches!(
1401            validate_talent_tags(&invalid_tags),
1402            ValidationResult::Invalid(_)
1403        ));
1404    }
1405
1406    #[test]
1407    fn test_validate_resume_attachment() {
1408        // 测试有效附件ID
1409        assert!(matches!(
1410            validate_resume_attachment("valid_attachment_123", "附件ID"),
1411            ValidationResult::Valid
1412        ));
1413
1414        assert!(matches!(
1415            validate_resume_attachment("attachment-with-hyphens", "附件ID"),
1416            ValidationResult::Valid
1417        ));
1418
1419        assert!(matches!(
1420            validate_resume_attachment("attachment_with_underscores", "附件ID"),
1421            ValidationResult::Valid
1422        ));
1423
1424        // 测试空附件ID
1425        assert!(matches!(
1426            validate_resume_attachment("", "附件ID"),
1427            ValidationResult::Invalid(_)
1428        ));
1429
1430        // 测试过长附件ID
1431        let long_id = "a".repeat(101);
1432        assert!(matches!(
1433            validate_resume_attachment(&long_id, "附件ID"),
1434            ValidationResult::Invalid(_)
1435        ));
1436
1437        // 测试非法字符
1438        assert!(matches!(
1439            validate_resume_attachment("attachment@domain", "附件ID"),
1440            ValidationResult::Invalid(_)
1441        ));
1442
1443        assert!(matches!(
1444            validate_resume_attachment("attachment with spaces", "附件ID"),
1445            ValidationResult::Invalid(_)
1446        ));
1447
1448        assert!(matches!(
1449            validate_resume_attachment("attachment/with/slashes", "附件ID"),
1450            ValidationResult::Invalid(_)
1451        ));
1452    }
1453
1454    #[test]
1455    fn test_sanitize_name_edge_cases() {
1456        // 测试只有空格的字符串
1457        assert_eq!(sanitize_name("   "), "");
1458
1459        // 测试多种空白字符
1460        assert_eq!(sanitize_name("  张\t三  \n"), "张 三");
1461
1462        // 测试连续多个空格
1463        assert_eq!(sanitize_name("John     Smith"), "John Smith");
1464
1465        // 测试开头和结尾的空格
1466        assert_eq!(sanitize_name("  Mary Jane  "), "Mary Jane");
1467
1468        // 测试混合空白字符
1469        assert_eq!(sanitize_name("Test\t\tName\n\n"), "Test Name");
1470
1471        // 测试单个字符
1472        assert_eq!(sanitize_name("A"), "A");
1473
1474        // 测试中文姓名
1475        assert_eq!(sanitize_name("  李  小  明  "), "李 小 明");
1476    }
1477
1478    #[test]
1479    fn test_sanitize_tags_edge_cases() {
1480        // 测试重复标签去重
1481        let input_tags = vec!["java".to_string(), "JAVA".to_string(), "Java".to_string()];
1482        let sanitized = sanitize_tags(&input_tags);
1483        assert_eq!(sanitized, vec!["java"]);
1484
1485        // 测试空标签过滤
1486        let input_tags = vec![
1487            "valid".to_string(),
1488            "".to_string(),
1489            "   ".to_string(),
1490            "another".to_string(),
1491        ];
1492        let sanitized = sanitize_tags(&input_tags);
1493        assert_eq!(sanitized, vec!["valid", "another"]);
1494
1495        // 测试特殊字符处理
1496        let input_tags = vec![
1497            "node-js".to_string(),
1498            "react_native".to_string(),
1499            "vue.js".to_string(),
1500        ];
1501        let sanitized = sanitize_tags(&input_tags);
1502        assert_eq!(sanitized, vec!["node_js", "react_native", "vue.js"]);
1503
1504        // 测试空列表
1505        let sanitized = sanitize_tags(&[]);
1506        assert!(sanitized.is_empty());
1507    }
1508
1509    #[test]
1510    fn test_sanitize_tag_individual() {
1511        // 测试基本清理
1512        assert_eq!(sanitize_tag("  JavaScript  "), "javascript");
1513
1514        // 测试连字符替换
1515        assert_eq!(sanitize_tag("Node-JS"), "node_js");
1516
1517        // 测试下划线保持
1518        assert_eq!(sanitize_tag("React_Native"), "react_native");
1519
1520        // 测试混合替换
1521        assert_eq!(sanitize_tag("Vue-Router_Plugin"), "vue_router_plugin");
1522
1523        // 测试空字符串
1524        assert_eq!(sanitize_tag(""), "");
1525
1526        // 测试只有空格
1527        assert_eq!(sanitize_tag("   "), "");
1528
1529        // 测试已经是小写
1530        assert_eq!(sanitize_tag("lowercase"), "lowercase");
1531    }
1532
1533    #[test]
1534    fn test_validate_page_size_warnings() {
1535        // 测试边界值
1536        assert!(matches!(
1537            validate_page_size(pagination_limits::MIN_PAGE_SIZE, "页面大小"),
1538            ValidationResult::Valid
1539        ));
1540
1541        assert!(matches!(
1542            validate_page_size(pagination_limits::MAX_PAGE_SIZE, "页面大小"),
1543            ValidationResult::Valid
1544        ));
1545
1546        assert!(matches!(
1547            validate_page_size(pagination_limits::RECOMMENDED_PAGE_SIZE, "页面大小"),
1548            ValidationResult::Valid
1549        ));
1550
1551        // 测试推荐值以上(应该有警告但仍然有效)
1552        assert!(matches!(
1553            validate_page_size(pagination_limits::RECOMMENDED_PAGE_SIZE + 1, "页面大小"),
1554            ValidationResult::Valid
1555        ));
1556    }
1557
1558    #[test]
1559    fn test_validate_page_token_formats() {
1560        // 测试各种有效的base64格式
1561        assert!(matches!(
1562            validate_page_token("dGVzdA==", "页面令牌"),
1563            ValidationResult::Valid
1564        ));
1565
1566        assert!(matches!(
1567            validate_page_token("YWJjZGVmZw", "页面令牌"),
1568            ValidationResult::Valid
1569        ));
1570
1571        // 测试URL安全的base64格式
1572        assert!(matches!(
1573            validate_page_token("abc-def_123", "页面令牌"),
1574            ValidationResult::Valid
1575        ));
1576
1577        // 测试非法字符
1578        assert!(matches!(
1579            validate_page_token("invalid@token", "页面令牌"),
1580            ValidationResult::Invalid(_)
1581        ));
1582
1583        assert!(matches!(
1584            validate_page_token("token with spaces", "页面令牌"),
1585            ValidationResult::Invalid(_)
1586        ));
1587
1588        // 测试过长令牌
1589        let long_token = "a".repeat(pagination_limits::MAX_PAGE_TOKEN_LENGTH + 1);
1590        assert!(matches!(
1591            validate_page_token(&long_token, "页面令牌"),
1592            ValidationResult::Invalid(_)
1593        ));
1594    }
1595
1596    #[test]
1597    fn test_validation_result_error_method() {
1598        let valid = ValidationResult::Valid;
1599        assert_eq!(valid.error(), None);
1600
1601        let warning = ValidationResult::Warning("warning message".to_string());
1602        assert_eq!(warning.error(), Some("warning message"));
1603
1604        let invalid = ValidationResult::Invalid("error message".to_string());
1605        assert_eq!(invalid.error(), Some("error message"));
1606    }
1607
1608    #[test]
1609    fn test_is_chinese_char_comprehensive() {
1610        // 测试基本CJK统一汉字
1611        assert!(is_chinese_char('中')); // U+4E2D
1612        assert!(is_chinese_char('文')); // U+6587
1613        assert!(is_chinese_char('测')); // U+6D4B
1614        assert!(is_chinese_char('试')); // U+8BD5
1615
1616        // 测试CJK扩展A区
1617        assert!(is_chinese_char('\u{3400}')); // 扩展A区开始
1618        assert!(is_chinese_char('\u{4DBF}')); // 扩展A区结束
1619
1620        // 测试CJK符号和标点
1621        assert!(is_chinese_char('。')); // U+3002 中文句号
1622        assert!(is_chinese_char('、')); // U+3001 中文顿号
1623
1624        // 测试康熙部首
1625        assert!(is_chinese_char('\u{2F00}')); // 康熙部首开始
1626
1627        // 测试CJK兼容汉字
1628        assert!(is_chinese_char('\u{F900}')); // 兼容汉字开始
1629
1630        // 测试非中文字符
1631        assert!(!is_chinese_char('a'));
1632        assert!(!is_chinese_char('A'));
1633        assert!(!is_chinese_char('1'));
1634        assert!(!is_chinese_char('!'));
1635        assert!(!is_chinese_char(' '));
1636        assert!(!is_chinese_char('\n'));
1637        assert!(!is_chinese_char('α')); // 希腊字母
1638        assert!(!is_chinese_char('й')); // 西里尔字母
1639
1640        // 测试边界值
1641        assert!(!is_chinese_char('\u{4DFF}')); // CJK统一汉字前
1642        assert!(is_chinese_char('\u{4E00}')); // CJK统一汉字开始
1643        assert!(is_chinese_char('\u{9FFF}')); // CJK统一汉字结束
1644        assert!(!is_chinese_char('\u{A000}')); // CJK统一汉字后
1645    }
1646
1647    #[test]
1648    fn test_validate_content_size_edge_cases() {
1649        // 测试空内容
1650        assert!(validate_content_size("", 100, "测试内容"));
1651
1652        // 测试临界值
1653        let content = "a".repeat(100);
1654        assert!(validate_content_size(&content, 100, "测试内容"));
1655
1656        // 测试刚好超出
1657        let content = "a".repeat(101);
1658        assert!(!validate_content_size(&content, 100, "测试内容"));
1659
1660        // 测试UTF-8字符
1661        let chinese_content = "中文测试内容";
1662        // 中文字符每个占3个字节
1663        assert!(validate_content_size(chinese_content, 50, "中文内容"));
1664        assert!(!validate_content_size(chinese_content, 10, "中文内容"));
1665    }
1666
1667    #[test]
1668    fn test_validate_string_length_utf8() {
1669        // 测试UTF-8字符串截断(注意:这个函数按字节截断,可能会截断UTF-8字符)
1670        let chinese_input = "中文测试".to_string(); // 每个中文字符3字节
1671        let result = validate_string_length(chinese_input, 6, "中文字段");
1672        // 应该截断到6字节,可能会有问题
1673        assert_eq!(result.len(), 6);
1674
1675        // 测试英文字符串正常截断
1676        let english_input = "hello world".to_string();
1677        let result = validate_string_length(english_input, 5, "英文字段");
1678        assert_eq!(result, "hello");
1679    }
1680
1681    #[test]
1682    fn test_validate_builder_trait() {
1683        // 创建一个测试结构体实现ValidateBuilder trait
1684        struct TestBuilder {
1685            result: ValidationResult,
1686        }
1687
1688        impl ValidateBuilder for TestBuilder {
1689            fn validate(&self) -> ValidationResult {
1690                self.result.clone()
1691            }
1692        }
1693
1694        // 测试Valid情况
1695        let valid_builder = TestBuilder {
1696            result: ValidationResult::Valid,
1697        };
1698        assert!(valid_builder.validate_and_log());
1699
1700        // 测试Warning情况
1701        let warning_builder = TestBuilder {
1702            result: ValidationResult::Warning("test warning".to_string()),
1703        };
1704        assert!(warning_builder.validate_and_log());
1705
1706        // 测试Invalid情况
1707        let invalid_builder = TestBuilder {
1708            result: ValidationResult::Invalid("test error".to_string()),
1709        };
1710        assert!(!invalid_builder.validate_and_log());
1711    }
1712}
1713
1714// ============================================================================
1715// 员工/人才验证常量
1716// ============================================================================
1717
1718/// 员工/人才验证常量
1719pub mod employee_limits {
1720    /// 姓名最小长度
1721    pub const NAME_MIN_LENGTH: usize = 2;
1722    /// 姓名最大长度
1723    pub const NAME_MAX_LENGTH: usize = 100;
1724    /// 邮箱最大长度
1725    pub const EMAIL_MAX_LENGTH: usize = 254;
1726    /// 电话号码最大长度
1727    pub const PHONE_MAX_LENGTH: usize = 20;
1728    /// 电话号码最小长度
1729    pub const PHONE_MIN_LENGTH: usize = 7;
1730    /// 工作年限最小值
1731    pub const WORK_EXPERIENCE_MIN: u32 = 0;
1732    /// 工作年限最大值
1733    pub const WORK_EXPERIENCE_MAX: u32 = 50;
1734    /// 简历附件最大数量
1735    pub const MAX_RESUME_ATTACHMENTS: usize = 10;
1736    /// 人才标签最大数量
1737    pub const MAX_TALENT_TAGS: usize = 20;
1738    /// 标签最大长度
1739    pub const TAG_MAX_LENGTH: usize = 50;
1740    /// 自定义字段键最大长度
1741    pub const CUSTOM_FIELD_KEY_MAX_LENGTH: usize = 100;
1742    /// 自定义字段值最大长度
1743    pub const CUSTOM_FIELD_VALUE_MAX_LENGTH: usize = 1000;
1744    /// 期望薪资最大长度
1745    pub const EXPECTED_SALARY_MAX_LENGTH: usize = 100;
1746}
1747
1748// ============================================================================
1749// 员工/人才验证函数
1750// ============================================================================
1751
1752/// 验证姓名
1753pub fn validate_name(name: &str, field_name: &str) -> ValidationResult {
1754    if name.is_empty() {
1755        return ValidationResult::Invalid(format!("{} cannot be empty", field_name));
1756    }
1757
1758    // 计算字符数(不是字节数)
1759    let char_count = name.chars().count();
1760
1761    if char_count < employee_limits::NAME_MIN_LENGTH {
1762        return ValidationResult::Invalid(format!(
1763            "{} must be at least {} characters long",
1764            field_name,
1765            employee_limits::NAME_MIN_LENGTH
1766        ));
1767    }
1768
1769    if char_count > employee_limits::NAME_MAX_LENGTH {
1770        return ValidationResult::Invalid(format!(
1771            "{} must not exceed {} characters",
1772            field_name,
1773            employee_limits::NAME_MAX_LENGTH
1774        ));
1775    }
1776
1777    // 检查是否包含特殊字符(只允许字母、数字、中文、空格和常见标点)
1778    if !name
1779        .chars()
1780        .all(|c| c.is_alphanumeric() || c.is_whitespace() || is_chinese_char(c) || "-.".contains(c))
1781    {
1782        return ValidationResult::Invalid(format!(
1783            "{} contains invalid characters. Only letters, numbers, Chinese characters, spaces, hyphens and periods are allowed",
1784            field_name
1785        ));
1786    }
1787
1788    ValidationResult::Valid
1789}
1790
1791/// 验证邮箱地址
1792pub fn validate_email(email: &str, field_name: &str) -> ValidationResult {
1793    if email.is_empty() {
1794        return ValidationResult::Invalid(format!("{} cannot be empty", field_name));
1795    }
1796
1797    if email.len() > employee_limits::EMAIL_MAX_LENGTH {
1798        return ValidationResult::Invalid(format!(
1799            "{} must not exceed {} characters",
1800            field_name,
1801            employee_limits::EMAIL_MAX_LENGTH
1802        ));
1803    }
1804
1805    // 简单的邮箱格式验证
1806    let email_regex =
1807        regex::Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap();
1808    if !email_regex.is_match(email) {
1809        return ValidationResult::Invalid(format!("{} must be a valid email address", field_name));
1810    }
1811
1812    ValidationResult::Valid
1813}
1814
1815/// 验证电话号码
1816pub fn validate_phone(phone: &str, field_name: &str) -> ValidationResult {
1817    if phone.is_empty() {
1818        return ValidationResult::Valid; // 电话是可选的
1819    }
1820
1821    if phone.len() < employee_limits::PHONE_MIN_LENGTH {
1822        return ValidationResult::Invalid(format!(
1823            "{} must be at least {} characters long (got {})",
1824            field_name,
1825            employee_limits::PHONE_MIN_LENGTH,
1826            phone.len()
1827        ));
1828    }
1829
1830    if phone.len() > employee_limits::PHONE_MAX_LENGTH {
1831        return ValidationResult::Invalid(format!(
1832            "{} must not exceed {} characters",
1833            field_name,
1834            employee_limits::PHONE_MAX_LENGTH
1835        ));
1836    }
1837
1838    // 检查是否只包含数字、加号、空格和连字符
1839    if !phone
1840        .chars()
1841        .all(|c| c.is_ascii_digit() || c == '+' || c == ' ' || c == '-')
1842    {
1843        return ValidationResult::Invalid(format!(
1844            "{} contains invalid characters. Only numbers, +, spaces and hyphens are allowed",
1845            field_name
1846        ));
1847    }
1848
1849    ValidationResult::Valid
1850}
1851
1852/// 验证工作年限
1853pub fn validate_work_experience(years: u32, field_name: &str) -> ValidationResult {
1854    if years > employee_limits::WORK_EXPERIENCE_MAX {
1855        return ValidationResult::Invalid(format!(
1856            "{} must not exceed {} years",
1857            field_name,
1858            employee_limits::WORK_EXPERIENCE_MAX
1859        ));
1860    }
1861
1862    ValidationResult::Valid
1863}
1864
1865/// 验证生日(可选字段)
1866pub fn validate_birthday(birthday: &Option<String>, field_name: &str) -> ValidationResult {
1867    if let Some(bday) = birthday {
1868        if bday.is_empty() {
1869            return ValidationResult::Valid;
1870        }
1871
1872        // 简单的日期格式验证(YYYY-MM-DD)
1873        let date_regex = regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
1874        if !date_regex.is_match(bday) {
1875            return ValidationResult::Invalid(format!(
1876                "{} must be in YYYY-MM-DD format",
1877                field_name
1878            ));
1879        }
1880
1881        // 验证日期有效性
1882        if chrono::NaiveDate::parse_from_str(bday, "%Y-%m-%d").is_err() {
1883            return ValidationResult::Invalid(format!(
1884                "{} must be a valid date in YYYY-MM-DD format",
1885                field_name
1886            ));
1887        }
1888    }
1889
1890    ValidationResult::Valid
1891}
1892
1893/// 验证期望薪资
1894pub fn validate_expected_salary(salary: &Option<String>, field_name: &str) -> ValidationResult {
1895    if let Some(sal) = salary {
1896        if sal.is_empty() {
1897            return ValidationResult::Valid;
1898        }
1899
1900        if sal.len() > employee_limits::EXPECTED_SALARY_MAX_LENGTH {
1901            return ValidationResult::Invalid(format!(
1902                "{} must not exceed {} characters",
1903                field_name,
1904                employee_limits::EXPECTED_SALARY_MAX_LENGTH
1905            ));
1906        }
1907
1908        // 检查薪资格式(例如:10-20K, 50-80万, 100万+)
1909        let salary_regex = regex::Regex::new(r"^(\d+(-\d+)?[Kk万]?([+-])?|面议)$").unwrap();
1910        if !salary_regex.is_match(sal) {
1911            return ValidationResult::Invalid(format!(
1912                "{} must be a valid salary format (e.g., 10-20K, 50-80万, 100万+)",
1913                field_name
1914            ));
1915        }
1916
1917        // 检查薪资数值是否合理
1918        if let Some(captures) = salary_regex.captures(sal) {
1919            if let Some(num_str) = captures.get(1) {
1920                let num_part = num_str.as_str();
1921                // 提取数字部分
1922                let num_regex = regex::Regex::new(r"^(\d+)").unwrap();
1923                if let Some(num_captures) = num_regex.captures(num_part) {
1924                    if let Some(num_match) = num_captures.get(1) {
1925                        if let Ok(num) = num_match.as_str().parse::<u32>() {
1926                            // 检查是否是K(千)为单位
1927                            let is_k = sal.contains('K') || sal.contains('k');
1928                            // 检查是否是万为单位
1929                            let is_wan = sal.contains('万');
1930
1931                            let actual_num = if is_k {
1932                                num
1933                            } else if is_wan {
1934                                num * 10 // 转换为千单位
1935                            } else {
1936                                num / 1000 // 假设没有单位的是元,转换为千元
1937                            };
1938
1939                            // 检查薪资是否过高(月薪超过500K或年薪超过600万)
1940                            if actual_num > 500 {
1941                                return ValidationResult::Invalid(format!(
1942                                    "{}: salary range is unreasonably high",
1943                                    field_name
1944                                ));
1945                            }
1946                        }
1947                    }
1948                }
1949            }
1950        }
1951    }
1952
1953    ValidationResult::Valid
1954}
1955
1956/// 验证标签列表
1957pub fn validate_tags(tags: &[String], field_name: &str) -> ValidationResult {
1958    if tags.len() > employee_limits::MAX_TALENT_TAGS {
1959        return ValidationResult::Invalid(format!(
1960            "{} cannot have more than {} tags",
1961            field_name,
1962            employee_limits::MAX_TALENT_TAGS
1963        ));
1964    }
1965
1966    for (i, tag) in tags.iter().enumerate() {
1967        if tag.is_empty() {
1968            return ValidationResult::Invalid(format!(
1969                "{} tag at index {} cannot be empty",
1970                field_name, i
1971            ));
1972        }
1973
1974        if tag.len() > employee_limits::TAG_MAX_LENGTH {
1975            return ValidationResult::Invalid(format!(
1976                "{} tag '{}' exceeds maximum length of {} characters",
1977                field_name,
1978                tag,
1979                employee_limits::TAG_MAX_LENGTH
1980            ));
1981        }
1982
1983        // 检查标签字符
1984        if !tag
1985            .chars()
1986            .all(|c| c.is_alphanumeric() || c == '_' || c == '-' || is_chinese_char(c))
1987        {
1988            return ValidationResult::Invalid(format!(
1989                "{} tag '{}' contains invalid characters. Only letters, numbers, Chinese characters, underscores and hyphens are allowed",
1990                field_name,
1991                tag
1992            ));
1993        }
1994    }
1995
1996    ValidationResult::Valid
1997}
1998
1999/// 验证自定义字段
2000pub fn validate_custom_fields(
2001    fields: &Option<std::collections::HashMap<String, serde_json::Value>>,
2002    field_name: &str,
2003) -> ValidationResult {
2004    if let Some(custom_fields) = fields {
2005        if custom_fields.len() > 50 {
2006            // 限制自定义字段数量
2007            return ValidationResult::Invalid(format!(
2008                "{} cannot have more than 50 custom fields",
2009                field_name
2010            ));
2011        }
2012
2013        for (key, value) in custom_fields {
2014            // 验证键
2015            if key.is_empty() {
2016                return ValidationResult::Invalid(format!(
2017                    "{} custom field key cannot be empty",
2018                    field_name
2019                ));
2020            }
2021
2022            if key.len() > employee_limits::CUSTOM_FIELD_KEY_MAX_LENGTH {
2023                return ValidationResult::Invalid(format!(
2024                    "{} custom field key '{}' exceeds maximum length of {} characters",
2025                    field_name,
2026                    key,
2027                    employee_limits::CUSTOM_FIELD_KEY_MAX_LENGTH
2028                ));
2029            }
2030
2031            // 验证值
2032            match value {
2033                serde_json::Value::String(s) => {
2034                    if s.len() > employee_limits::CUSTOM_FIELD_VALUE_MAX_LENGTH {
2035                        return ValidationResult::Invalid(format!(
2036                            "{} custom field value for key '{}' exceeds maximum length of {} characters",
2037                            field_name,
2038                            key,
2039                            employee_limits::CUSTOM_FIELD_VALUE_MAX_LENGTH
2040                        ));
2041                    }
2042                }
2043                serde_json::Value::Number(n) => {
2044                    if !n.is_i64() && !n.is_u64() && !n.is_f64() {
2045                        return ValidationResult::Invalid(format!(
2046                            "{} custom field value for key '{}' is not a valid number",
2047                            field_name, key
2048                        ));
2049                    }
2050                }
2051                serde_json::Value::Bool(_) => {
2052                    // 布尔值总是有效的
2053                }
2054                serde_json::Value::Array(arr) => {
2055                    if arr.len() > 100 {
2056                        return ValidationResult::Invalid(format!(
2057                            "{} custom field array for key '{}' cannot have more than 100 items",
2058                            field_name, key
2059                        ));
2060                    }
2061                }
2062                serde_json::Value::Object(_) => {
2063                    return ValidationResult::Invalid(format!(
2064                        "{} custom field value for key '{}' cannot be an object",
2065                        field_name, key
2066                    ));
2067                }
2068                serde_json::Value::Null => {
2069                    // null 值是允许的
2070                }
2071            }
2072        }
2073    }
2074
2075    ValidationResult::Valid
2076}
2077
2078/// 验证简历附件ID列表
2079pub fn validate_resume_attachment_ids(
2080    attachment_ids: &[String],
2081    field_name: &str,
2082) -> ValidationResult {
2083    if attachment_ids.len() > employee_limits::MAX_RESUME_ATTACHMENTS {
2084        return ValidationResult::Invalid(format!(
2085            "{} cannot have more than {} resume attachments",
2086            field_name,
2087            employee_limits::MAX_RESUME_ATTACHMENTS
2088        ));
2089    }
2090
2091    for (i, id) in attachment_ids.iter().enumerate() {
2092        if id.is_empty() {
2093            return ValidationResult::Invalid(format!(
2094                "{} attachment ID at index {} cannot be empty",
2095                field_name, i
2096            ));
2097        }
2098
2099        // 检查ID格式(假设是UUID或类似格式)
2100        if id.len() < 10 || id.len() > 100 {
2101            return ValidationResult::Invalid(format!(
2102                "{} attachment ID at index {} has invalid length",
2103                field_name, i
2104            ));
2105        }
2106    }
2107
2108    ValidationResult::Valid
2109}
2110
2111/// 清理和验证姓名
2112pub fn sanitize_name(name: &str) -> String {
2113    // 去除首尾空格
2114    let trimmed = name.trim();
2115
2116    // 替换多个连续空格为单个空格
2117    let normalized = trimmed.chars().collect::<Vec<_>>();
2118    let mut result = Vec::new();
2119    let mut prev_was_space = false;
2120
2121    for c in normalized {
2122        if c.is_whitespace() {
2123            if !prev_was_space {
2124                result.push(' ');
2125                prev_was_space = true;
2126            }
2127        } else {
2128            result.push(c);
2129            prev_was_space = false;
2130        }
2131    }
2132
2133    result.into_iter().collect()
2134}
2135
2136/// 清理和验证标签
2137pub fn sanitize_tags(tags: &[String]) -> Vec<String> {
2138    let mut result = Vec::new();
2139
2140    for tag in tags {
2141        let sanitized = tag
2142            .trim()
2143            .replace(['_', '-'], "_") // 统一替换为下划线
2144            .to_lowercase();
2145
2146        if !sanitized.is_empty() && !result.contains(&sanitized) {
2147            result.push(sanitized);
2148        }
2149    }
2150
2151    result
2152}
2153
2154/// 验证单个标签
2155///
2156/// # 参数
2157/// - `tag`: 标签字符串
2158/// - `field_name`: 字段名称(用于错误消息)
2159///
2160/// # 返回
2161/// 验证结果
2162pub fn validate_talent_tag(tag: &str, field_name: &str) -> ValidationResult {
2163    if tag.is_empty() {
2164        return ValidationResult::Invalid(format!("{}: tag cannot be empty", field_name));
2165    }
2166
2167    if tag.len() > 50 {
2168        return ValidationResult::Invalid(format!(
2169            "{}: tag must not exceed 50 characters (got {})",
2170            field_name,
2171            tag.len()
2172        ));
2173    }
2174
2175    // 检查标签格式(允许字母、数字、下划线、连字符、空格和中文)
2176    for c in tag.chars() {
2177        if !(c.is_alphanumeric() || c == '_' || c == '-' || c == ' ' || is_chinese_char(c)) {
2178            return ValidationResult::Invalid(format!(
2179                "{}: tag contains invalid character '{}'. Only letters, numbers, spaces, hyphens, underscores and Chinese characters are allowed",
2180                field_name, c
2181            ));
2182        }
2183    }
2184
2185    ValidationResult::Valid
2186}
2187
2188/// 验证标签列表
2189///
2190/// # 参数
2191/// - `tags`: 标签列表
2192///
2193/// # 返回
2194/// 验证结果
2195pub fn validate_talent_tags(tags: &[String]) -> ValidationResult {
2196    if tags.len() > 20 {
2197        return ValidationResult::Invalid(format!(
2198            "Invalid tags: maximum number of tags is 20 (got {})",
2199            tags.len()
2200        ));
2201    }
2202
2203    for (index, tag) in tags.iter().enumerate() {
2204        match validate_talent_tag(tag, &format!("tags[{}]", index)) {
2205            ValidationResult::Valid => {}
2206            ValidationResult::Warning(msg) => {
2207                return ValidationResult::Warning(msg);
2208            }
2209            ValidationResult::Invalid(msg) => {
2210                return ValidationResult::Invalid(msg);
2211            }
2212        }
2213    }
2214
2215    ValidationResult::Valid
2216}
2217
2218/// 验证单个简历附件ID
2219///
2220/// # 参数
2221/// - `attachment_id`: 附件ID
2222/// - `field_name`: 字段名称(用于错误消息)
2223///
2224/// # 返回
2225/// 验证结果
2226pub fn validate_resume_attachment(attachment_id: &str, field_name: &str) -> ValidationResult {
2227    if attachment_id.is_empty() {
2228        return ValidationResult::Invalid(format!("{}: attachment ID cannot be empty", field_name));
2229    }
2230
2231    if attachment_id.len() > 100 {
2232        return ValidationResult::Invalid(format!(
2233            "{}: attachment ID must not exceed 100 characters (got {})",
2234            field_name,
2235            attachment_id.len()
2236        ));
2237    }
2238
2239    // 检查附件ID格式(通常只包含字母、数字和连字符)
2240    if !attachment_id
2241        .chars()
2242        .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
2243    {
2244        return ValidationResult::Invalid(format!(
2245            "{}: attachment ID can only contain letters, numbers, hyphens and underscores",
2246            field_name
2247        ));
2248    }
2249
2250    ValidationResult::Valid
2251}
2252
2253/// 分页验证常量
2254pub mod pagination_limits {
2255    /// 默认分页大小
2256    pub const DEFAULT_PAGE_SIZE: u32 = 20;
2257    /// 最小分页大小
2258    pub const MIN_PAGE_SIZE: u32 = 1;
2259    /// 最大分页大小
2260    pub const MAX_PAGE_SIZE: u32 = 500;
2261    /// 推荐的分页大小(性能最优)
2262    pub const RECOMMENDED_PAGE_SIZE: u32 = 50;
2263    /// 分页标记最大长度
2264    pub const MAX_PAGE_TOKEN_LENGTH: usize = 1024;
2265}
2266
2267/// 验证分页大小
2268///
2269/// # 参数
2270/// - `page_size`: 分页大小
2271/// - `field_name`: 字段名称(用于错误消息)
2272///
2273/// # 返回
2274/// 验证结果
2275pub fn validate_page_size(page_size: u32, field_name: &str) -> ValidationResult {
2276    if page_size < pagination_limits::MIN_PAGE_SIZE {
2277        return ValidationResult::Invalid(format!(
2278            "{}: page size must be at least {}",
2279            field_name,
2280            pagination_limits::MIN_PAGE_SIZE
2281        ));
2282    }
2283
2284    if page_size > pagination_limits::MAX_PAGE_SIZE {
2285        return ValidationResult::Invalid(format!(
2286            "{}: page size must not exceed {} (recommended: {})",
2287            field_name,
2288            pagination_limits::MAX_PAGE_SIZE,
2289            pagination_limits::RECOMMENDED_PAGE_SIZE
2290        ));
2291    }
2292
2293    // 检查是否为推荐的分页大小(性能考虑)
2294    if page_size > pagination_limits::RECOMMENDED_PAGE_SIZE {
2295        log::warn!(
2296            "{}: page size {} is larger than recommended value {}. This may impact performance.",
2297            field_name,
2298            page_size,
2299            pagination_limits::RECOMMENDED_PAGE_SIZE
2300        );
2301    }
2302
2303    ValidationResult::Valid
2304}
2305
2306/// 验证分页标记
2307///
2308/// # 参数
2309/// - `page_token`: 分页标记
2310/// - `field_name`: 字段名称(用于错误消息)
2311///
2312/// # 返回
2313/// 验证结果
2314pub fn validate_page_token(page_token: &str, field_name: &str) -> ValidationResult {
2315    if page_token.is_empty() {
2316        // 空的 page_token 是有效的,表示第一页
2317        return ValidationResult::Valid;
2318    }
2319
2320    if page_token.len() > pagination_limits::MAX_PAGE_TOKEN_LENGTH {
2321        return ValidationResult::Invalid(format!(
2322            "{}: page token must not exceed {} characters",
2323            field_name,
2324            pagination_limits::MAX_PAGE_TOKEN_LENGTH
2325        ));
2326    }
2327
2328    // 检查 page_token 格式(通常为 base64 编码的字符串)
2329    if !page_token.chars().all(|c| {
2330        c.is_ascii_alphanumeric() || c == '/' || c == '+' || c == '=' || c == '-' || c == '_'
2331    }) {
2332        return ValidationResult::Invalid(format!(
2333            "{}: page token contains invalid characters. Expected base64 format",
2334            field_name
2335        ));
2336    }
2337
2338    ValidationResult::Valid
2339}
2340
2341/// 验证分页参数组合
2342///
2343/// # 参数
2344/// - `page_size`: 分页大小
2345/// - `page_token`: 分页标记
2346/// - `field_prefix`: 字段前缀(用于错误消息)
2347///
2348/// # 返回
2349/// 验证结果
2350pub fn validate_pagination_params(
2351    page_size: Option<u32>,
2352    page_token: Option<&str>,
2353    field_prefix: &str,
2354) -> ValidationResult {
2355    // 验证分页大小
2356    if let Some(size) = page_size {
2357        match validate_page_size(size, &format!("{}_page_size", field_prefix)) {
2358            ValidationResult::Valid => {}
2359            ValidationResult::Warning(msg) => {
2360                log::warn!("Pagination warning: {}", msg);
2361            }
2362            ValidationResult::Invalid(msg) => {
2363                return ValidationResult::Invalid(msg);
2364            }
2365        }
2366    }
2367
2368    // 验证分页标记
2369    if let Some(token) = page_token {
2370        match validate_page_token(token, &format!("{}_page_token", field_prefix)) {
2371            ValidationResult::Valid => {}
2372            ValidationResult::Warning(msg) => {
2373                log::warn!("Pagination warning: {}", msg);
2374            }
2375            ValidationResult::Invalid(msg) => {
2376                return ValidationResult::Invalid(msg);
2377            }
2378        }
2379    }
2380
2381    // 检查参数组合的逻辑性
2382    if page_token.is_some() && page_size.is_none() {
2383        log::warn!(
2384            "{}: page_token provided without page_size. Using default page size {}",
2385            field_prefix,
2386            pagination_limits::DEFAULT_PAGE_SIZE
2387        );
2388    }
2389
2390    ValidationResult::Valid
2391}
2392
2393/// 清理单个标签
2394///
2395/// # 参数
2396/// - `tag`: 标签字符串
2397///
2398/// # 返回
2399/// 清理后的标签
2400pub fn sanitize_tag(tag: &str) -> String {
2401    tag.trim()
2402        .replace(['_', '-'], "_") // 统一替换为下划线
2403        .to_lowercase()
2404}
2405
2406/// Sheets 验证模块
2407pub mod sheets;
2408
2409/// IM(即时消息)验证模块
2410pub mod im;
2411
2412/// 招聘服务验证模块
2413pub mod hire;
2414
2415/// 日历服务验证模块
2416pub mod calendar;
2417/// Drive(云文档/文件)服务验证模块
2418pub mod drive;
2419
2420/// 分页验证模块
2421pub mod pagination;
2422
2423/// 重新导出分页相关的公共接口
2424pub use pagination::{PaginatedResponse, PaginationIterator, PaginationRequestBuilder};
2425
2426/// 重新导出 Sheets 验证的公共接口
2427pub use sheets::{
2428    validate_cell_range, validate_data_matrix_consistency, validate_date_time_render_option,
2429    validate_find_options, validate_merge_range, validate_value_render_option,
2430};
2431
2432/// 重新导出 IM 验证的公共接口
2433pub use im::{
2434    validate_file_upload, validate_message_content, validate_message_forward,
2435    validate_message_reaction, validate_message_read_status, validate_message_recall,
2436    validate_message_receivers, validate_message_template, validate_message_type,
2437    validate_receiver_id, validate_uuid, ValidateImBuilder,
2438};
2439
2440/// 重新导出招聘验证的公共接口
2441pub use hire::{
2442    validate_birthday as validate_hire_birthday, validate_candidate_basic_info,
2443    validate_candidate_tags, validate_education_background, validate_hiring_requirement,
2444    validate_hiring_status_transition, validate_interview_arrangement, validate_interview_feedback,
2445    validate_job_position, validate_offer_info, validate_salary_range,
2446    validate_work_experience as validate_hire_work_experience, ValidateHireBuilder,
2447};