1use tracing::warn;
4
5pub fn is_chinese_char(c: char) -> bool {
15 let ranges = [
17 (0x4E00, 0x9FFF), (0x3400, 0x4DBF), (0x20000, 0x2A6DF), (0x2A700, 0x2B73F), (0x2B740, 0x2B81F), (0x2B820, 0x2CEAF), (0x2CEB0, 0x2EBEF), (0x3000, 0x303F), (0x31C0, 0x31EF), (0x2F00, 0x2FD5), (0x2E80, 0x2EFF), (0xF900, 0xFAFF), (0x2F800, 0x2FA1F), ];
31
32 let code = c as u32;
33 ranges
34 .iter()
35 .any(|&(start, end)| code >= start && code <= end)
36}
37
38pub fn validate_string_length(input: String, max_len: usize, field_name: &str) -> String {
48 if input.len() > max_len {
49 warn!(
50 "字段 {} 超过最大长度 {},当前长度 {},将被截断",
51 field_name,
52 max_len,
53 input.len()
54 );
55 input.chars().take(max_len).collect()
57 } else {
58 input
59 }
60}
61
62pub fn validate_required_list_length(values: &[String], max_len: usize, field_name: &str) -> bool {
72 if values.is_empty() {
73 warn!("必填列表字段 {} 为空", field_name);
74 false
75 } else if values.len() > max_len {
76 warn!(
77 "必填列表字段 {} 长度 {} 超过最大限制 {}",
78 field_name,
79 values.len(),
80 max_len
81 );
82 false
83 } else {
84 true
85 }
86}
87
88pub fn validate_content_size(content: &str, max_size: usize, content_type: &str) -> bool {
98 let content_size = content.len();
99 if content_size > max_size {
100 warn!(
101 "{} 内容大小 {} 超过最大限制 {}",
102 content_type, content_size, max_size
103 );
104 false
105 } else {
106 true
107 }
108}
109
110#[derive(Debug, Clone, PartialEq)]
112pub enum ValidationResult {
113 Valid,
115 Invalid(String),
117 Sanitized(String),
119}
120
121impl ValidationResult {
122 pub fn is_valid(&self) -> bool {
124 matches!(self, ValidationResult::Valid)
125 }
126
127 pub fn error_message(&self) -> Option<&String> {
129 match self {
130 ValidationResult::Invalid(msg) => Some(msg),
131 _ => None,
132 }
133 }
134
135 pub fn sanitized_value(&self) -> Option<&String> {
137 match self {
138 ValidationResult::Sanitized(value) => Some(value),
139 _ => None,
140 }
141 }
142
143 pub fn into_result(self) -> Result<String, String> {
145 match self {
146 ValidationResult::Valid => Ok(String::new()),
147 ValidationResult::Invalid(msg) => Err(msg),
148 ValidationResult::Sanitized(value) => Ok(value),
149 }
150 }
151}
152
153pub trait ValidateBuilder {
155 type Output;
157
158 fn required(self, value: Option<String>, field_name: &str) -> Self;
160
161 fn length(self, value: String, min_len: usize, max_len: usize, field_name: &str) -> Self;
163
164 fn custom<F>(self, value: String, validator: F, error_msg: &str) -> Self
166 where
167 F: FnOnce(&str) -> bool;
168
169 fn validate(&self) -> ValidationResult;
171
172 fn build(self) -> Self::Output;
174}
175
176#[derive(Debug)]
178pub struct DefaultValidateBuilder {
179 value: Option<String>,
180 errors: Vec<String>,
181}
182
183impl DefaultValidateBuilder {
184 pub fn new() -> Self {
186 Self {
187 value: None,
188 errors: Vec::new(),
189 }
190 }
191
192 pub fn value(mut self, value: String) -> Self {
194 self.value = Some(value);
195 self
196 }
197}
198
199impl ValidateBuilder for DefaultValidateBuilder {
200 type Output = Result<String, Vec<String>>;
201
202 fn required(mut self, value: Option<String>, field_name: &str) -> Self {
203 match value {
204 Some(v) => {
205 if v.trim().is_empty() {
206 self.errors.push(format!("字段 {} 不能为空", field_name));
207 } else {
208 self.value = Some(v);
209 }
210 }
211 None => {
212 self.errors.push(format!("字段 {} 不能为空", field_name));
213 }
214 }
215 self
216 }
217
218 fn length(mut self, value: String, min_len: usize, max_len: usize, field_name: &str) -> Self {
219 if value.len() < min_len {
220 self.errors.push(format!(
221 "字段 {} 长度 {} 小于最小长度 {}",
222 field_name,
223 value.len(),
224 min_len
225 ));
226 } else if value.len() > max_len {
227 self.errors.push(format!(
228 "字段 {} 长度 {} 超过最大长度 {}",
229 field_name,
230 value.len(),
231 max_len
232 ));
233 } else {
234 self.value = Some(validate_string_length(value, max_len, field_name));
235 }
236 self
237 }
238
239 fn custom<F>(mut self, value: String, validator: F, error_msg: &str) -> Self
240 where
241 F: FnOnce(&str) -> bool,
242 {
243 if validator(&value) {
244 self.value = Some(value);
245 } else {
246 self.errors.push(error_msg.to_string());
247 }
248 self
249 }
250
251 fn validate(&self) -> ValidationResult {
252 if self.errors.is_empty() {
253 ValidationResult::Valid
254 } else {
255 ValidationResult::Invalid(self.errors.join("; "))
256 }
257 }
258
259 fn build(self) -> Self::Output {
260 if self.errors.is_empty() {
261 Ok(self.value.unwrap_or_default())
262 } else {
263 Err(self.errors)
264 }
265 }
266}
267
268impl Default for DefaultValidateBuilder {
269 fn default() -> Self {
270 Self::new()
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
279 fn test_is_chinese_char() {
280 assert!(is_chinese_char('你'));
281 assert!(is_chinese_char('好'));
282 assert!(is_chinese_char('世'));
283 assert!(is_chinese_char('界'));
284 assert!(!is_chinese_char('a'));
285 assert!(!is_chinese_char('1'));
286 assert!(!is_chinese_char('!'));
287 }
288
289 #[test]
290 fn test_validate_string_length() {
291 let long_string = "这是一个很长的字符串用于测试截断功能".to_string();
292 let result = validate_string_length(long_string.clone(), 10, "测试字段");
293 assert_eq!(result.chars().count(), 10); assert!(result.len() <= 30); let short_string = "短".to_string();
297 let result = validate_string_length(short_string, 10, "测试字段");
298 assert_eq!(result, "短");
299 }
300
301 #[test]
302 fn test_validate_content_size() {
303 let content = "这是一个测试内容";
304 assert!(validate_content_size(content, 30, "测试内容")); assert!(!validate_content_size(content, 20, "测试内容")); }
307
308 #[test]
309 fn test_validation_result() {
310 let valid = ValidationResult::Valid;
311 assert!(valid.is_valid());
312 assert_eq!(valid.error_message(), None);
313 assert_eq!(valid.sanitized_value(), None);
314
315 let invalid = ValidationResult::Invalid("错误".to_string());
316 assert!(!invalid.is_valid());
317 assert_eq!(invalid.error_message(), Some(&"错误".to_string()));
318 assert_eq!(invalid.sanitized_value(), None);
319
320 let sanitized = ValidationResult::Sanitized("清理后".to_string());
321 assert!(!sanitized.is_valid());
322 assert_eq!(sanitized.error_message(), None);
323 assert_eq!(sanitized.sanitized_value(), Some(&"清理后".to_string()));
324 }
325
326 #[test]
327 fn test_default_validate_builder() {
328 let builder = DefaultValidateBuilder::new();
329
330 let result = builder
332 .value("测试值".to_string())
333 .length("测试值".to_string(), 1, 10, "测试字段")
334 .build();
335 assert!(result.is_ok());
336
337 let result = DefaultValidateBuilder::new()
339 .value("这是一个很长的测试值".to_string())
340 .length("这是一个很长的测试值".to_string(), 1, 5, "测试字段")
341 .build();
342 assert!(result.is_err());
343 }
344
345 #[test]
346 fn test_is_chinese_char_unicode_boundaries() {
347 let chinese_boundaries = [
348 (0x4E00, 0x9FFF),
349 (0x3400, 0x4DBF),
350 (0x20000, 0x2A6DF),
351 (0x2A700, 0x2B73F),
352 (0x2B740, 0x2B81F),
353 (0x2B820, 0x2CEAF),
354 (0x2CEB0, 0x2EBEF),
355 (0x3000, 0x303F),
356 (0x31C0, 0x31EF),
357 (0x2F00, 0x2FD5),
358 (0x2E80, 0x2EFF),
359 (0xF900, 0xFAFF),
360 (0x2F800, 0x2FA1F),
361 ];
362
363 for (start, end) in chinese_boundaries {
364 let start_char = char::from_u32(start).expect("start must be valid char");
365 let end_char = char::from_u32(end).expect("end must be valid char");
366 assert!(is_chinese_char(start_char));
367 assert!(is_chinese_char(end_char));
368 }
369
370 assert!(!is_chinese_char(
371 char::from_u32(0x4DFF).expect("valid char")
372 ));
373 assert!(!is_chinese_char(
374 char::from_u32(0xA000).expect("valid char")
375 ));
376 assert!(!is_chinese_char('A'));
377 assert!(!is_chinese_char('😀'));
378 }
379
380 #[test]
381 fn test_validate_string_length_utf8_truncation() {
382 let input = "A你😀B".to_string();
383 let result = validate_string_length(input, 3, "utf8字段");
384 assert_eq!(result, "A你😀");
385 assert!(result.is_char_boundary(result.len()));
386
387 let empty_result = validate_string_length("任何值".to_string(), 0, "零长度字段");
388 assert_eq!(empty_result, "");
389
390 let ascii_exact = validate_string_length("abcd".to_string(), 4, "ascii字段");
391 assert_eq!(ascii_exact, "abcd");
392 }
393
394 #[test]
395 fn test_validate_required_list_length_boundaries() {
396 let empty: Vec<String> = vec![];
397 assert!(!validate_required_list_length(&empty, 3, "列表字段"));
398
399 let one = vec!["a".to_string()];
400 assert!(validate_required_list_length(&one, 1, "列表字段"));
401
402 let max = vec!["a".to_string(), "b".to_string(), "c".to_string()];
403 assert!(validate_required_list_length(&max, 3, "列表字段"));
404
405 let overflow = vec![
406 "a".to_string(),
407 "b".to_string(),
408 "c".to_string(),
409 "d".to_string(),
410 ];
411 assert!(!validate_required_list_length(&overflow, 3, "列表字段"));
412
413 let non_empty_when_max_zero = vec!["a".to_string()];
414 assert!(!validate_required_list_length(
415 &non_empty_when_max_zero,
416 0,
417 "列表字段"
418 ));
419 }
420
421 #[test]
422 fn test_validate_content_size_various_sizes() {
423 assert!(validate_content_size("", 0, "空内容"));
424 assert!(!validate_content_size("a", 0, "单字符内容"));
425
426 let ascii = "abcd";
427 assert!(validate_content_size(ascii, 4, "ascii内容"));
428 assert!(!validate_content_size(ascii, 3, "ascii内容"));
429
430 let utf8 = "你a";
431 assert!(validate_content_size(utf8, 4, "utf8内容"));
432 assert!(!validate_content_size(utf8, 3, "utf8内容"));
433
434 let kb_content = "x".repeat(1024);
435 assert!(validate_content_size(&kb_content, 1024, "1KB内容"));
436 assert!(!validate_content_size(&kb_content, 1023, "1KB内容"));
437 }
438
439 #[test]
440 fn test_default_validate_builder_all_methods() {
441 let ok_builder = DefaultValidateBuilder::default()
442 .value("seed".to_string())
443 .required(Some("abc".to_string()), "字段A")
444 .length("abc".to_string(), 1, 10, "字段A")
445 .custom("abc".to_string(), |v| v.contains('b'), "自定义校验失败");
446
447 assert!(ok_builder.validate().is_valid());
448 assert_eq!(ok_builder.build(), Ok("abc".to_string()));
449
450 let required_none = DefaultValidateBuilder::new().required(None, "字段B");
451 assert!(matches!(
452 required_none.validate(),
453 ValidationResult::Invalid(_)
454 ));
455 assert_eq!(
456 required_none.build(),
457 Err(vec!["字段 字段B 不能为空".to_string()])
458 );
459
460 let required_blank =
461 DefaultValidateBuilder::new().required(Some(" ".to_string()), "字段C");
462 assert!(matches!(
463 required_blank.validate(),
464 ValidationResult::Invalid(_)
465 ));
466
467 let length_too_short =
468 DefaultValidateBuilder::new().length("ab".to_string(), 3, 5, "字段D");
469 assert!(matches!(
470 length_too_short.validate(),
471 ValidationResult::Invalid(_)
472 ));
473
474 let length_too_long =
475 DefaultValidateBuilder::new().length("abcdef".to_string(), 1, 5, "字段E");
476 assert!(matches!(
477 length_too_long.validate(),
478 ValidationResult::Invalid(_)
479 ));
480
481 let custom_fail =
482 DefaultValidateBuilder::new().custom("abc".to_string(), |v| v.len() > 10, "必须大于10");
483 assert!(matches!(
484 custom_fail.validate(),
485 ValidationResult::Invalid(_)
486 ));
487 }
488
489 #[test]
490 fn test_validation_result_into_result_variants() {
491 assert_eq!(ValidationResult::Valid.into_result(), Ok(String::new()));
492 assert_eq!(
493 ValidationResult::Sanitized("clean".to_string()).into_result(),
494 Ok("clean".to_string())
495 );
496 assert_eq!(
497 ValidationResult::Invalid("bad".to_string()).into_result(),
498 Err("bad".to_string())
499 );
500 }
501
502 #[test]
503 fn test_validate_string_length_empty_string() {
504 let result = validate_string_length(String::new(), 8, "空字符串字段");
505 assert_eq!(result, "");
506 }
507
508 #[test]
509 fn test_validate_required_list_length_exact_match() {
510 let values = vec!["x".to_string(), "y".to_string()];
511 assert!(validate_required_list_length(&values, 2, "列表字段"));
512 }
513
514 #[test]
515 fn test_validate_content_size_multibyte_edge() {
516 let content = "你";
517 assert!(validate_content_size(content, 3, "中文内容"));
518 assert!(!validate_content_size(content, 2, "中文内容"));
519 }
520
521 #[test]
522 fn test_default_validate_builder_value_only_build() {
523 let result = DefaultValidateBuilder::new()
524 .value("预设值".to_string())
525 .build();
526 assert_eq!(result, Ok("预设值".to_string()));
527 }
528
529 #[test]
532 fn test_is_chinese_char_cjk_extension_c_d() {
533 assert!(is_chinese_char(char::from_u32(0x2A700).unwrap()));
535 assert!(is_chinese_char(char::from_u32(0x2A800).unwrap()));
536 assert!(is_chinese_char(char::from_u32(0x2B73F).unwrap()));
537
538 assert!(is_chinese_char(char::from_u32(0x2B740).unwrap()));
540 assert!(is_chinese_char(char::from_u32(0x2B800).unwrap()));
541 assert!(is_chinese_char(char::from_u32(0x2B81F).unwrap()));
542
543 assert!(!is_chinese_char(char::from_u32(0x2A6FF).unwrap()));
545 }
546
547 #[test]
548 fn test_is_chinese_char_cjk_extension_e_f() {
549 assert!(is_chinese_char(char::from_u32(0x2B820).unwrap()));
551 assert!(is_chinese_char(char::from_u32(0x2B900).unwrap()));
552 assert!(is_chinese_char(char::from_u32(0x2CEAF).unwrap()));
553
554 assert!(is_chinese_char(char::from_u32(0x2CEB0).unwrap()));
556 assert!(is_chinese_char(char::from_u32(0x2D000).unwrap()));
557 assert!(is_chinese_char(char::from_u32(0x2EBEF).unwrap()));
558
559 assert!(!is_chinese_char(char::from_u32(0x2EBF0).unwrap()));
561 }
562
563 #[test]
564 fn test_is_chinese_char_cjk_symbols_punctuation() {
565 assert!(is_chinese_char(' ')); assert!(is_chinese_char('、')); assert!(is_chinese_char('。')); assert!(is_chinese_char(char::from_u32(0x303F).unwrap())); assert!(!is_chinese_char(char::from_u32(0x3040).unwrap()));
573 }
574
575 #[test]
576 fn test_is_chinese_char_cjk_strokes_kangxi() {
577 assert!(is_chinese_char(char::from_u32(0x31C0).unwrap()));
579 assert!(is_chinese_char(char::from_u32(0x31E0).unwrap()));
580 assert!(is_chinese_char(char::from_u32(0x31EF).unwrap()));
581
582 assert!(is_chinese_char(char::from_u32(0x2F00).unwrap()));
584 assert!(is_chinese_char(char::from_u32(0x2FA0).unwrap()));
585 assert!(is_chinese_char(char::from_u32(0x2FD5).unwrap()));
586
587 assert!(is_chinese_char(char::from_u32(0x2E80).unwrap()));
589 assert!(is_chinese_char(char::from_u32(0x2EC0).unwrap()));
590 assert!(is_chinese_char(char::from_u32(0x2EFF).unwrap()));
591 }
592
593 #[test]
594 fn test_is_chinese_char_cjk_compatibility() {
595 assert!(is_chinese_char(char::from_u32(0xF900).unwrap()));
597 assert!(is_chinese_char(char::from_u32(0xFA00).unwrap()));
598 assert!(is_chinese_char(char::from_u32(0xFAFF).unwrap()));
599
600 assert!(is_chinese_char(char::from_u32(0x2F800).unwrap()));
602 assert!(is_chinese_char(char::from_u32(0x2F900).unwrap()));
603 assert!(is_chinese_char(char::from_u32(0x2FA1F).unwrap()));
604
605 assert!(!is_chinese_char(char::from_u32(0x2FA20).unwrap()));
607 }
608
609 #[test]
610 fn test_validate_string_length_multibyte_boundary() {
611 let chinese = "中文字符测试".to_string();
614
615 let result = validate_string_length(chinese.clone(), 2, "中文测试");
617 assert_eq!(result.chars().count(), 2);
618 assert_eq!(result, "中文");
619
620 let result = validate_string_length(chinese.clone(), 4, "中文测试");
622 assert_eq!(result.chars().count(), 4);
623 assert_eq!(result, "中文字符");
624
625 let result = validate_string_length(chinese.clone(), 6, "中文测试");
627 assert_eq!(result.chars().count(), 6);
628 assert_eq!(result, "中文字符测试");
629
630 let emoji = "A😀B😁C".to_string();
632 let result = validate_string_length(emoji, 5, "emoji测试");
633 assert_eq!(result, "A😀B😁C"); let emoji = "A😀B😁C".to_string();
637 let result = validate_string_length(emoji, 3, "emoji测试");
638 assert_eq!(result, "A😀B"); }
640
641 #[test]
642 fn test_validate_string_length_exact_boundary() {
643 let s = "ABC".to_string();
645
646 let result = validate_string_length(s.clone(), 3, "边界测试");
648 assert_eq!(result, "ABC");
649
650 let result = validate_string_length(s.clone(), 2, "边界测试");
652 assert_eq!(result, "AB");
653
654 let result = validate_string_length(s.clone(), 0, "边界测试");
656 assert_eq!(result, "");
657 }
658
659 #[test]
660 fn test_validate_required_list_length_zero_max() {
661 let empty: Vec<String> = vec![];
663 assert!(!validate_required_list_length(&empty, 0, "零列表"));
664
665 let with_items = vec!["a".to_string()];
667 assert!(!validate_required_list_length(&with_items, 0, "零列表"));
668 }
669
670 #[test]
671 fn test_validate_required_list_length_large_lists() {
672 let large: Vec<String> = (0..100).map(|i| format!("item{}", i)).collect();
674 assert!(validate_required_list_length(&large, 100, "大列表"));
675 assert!(validate_required_list_length(&large, 200, "更大列表"));
676 assert!(!validate_required_list_length(&large, 50, "小列表"));
677 assert!(!validate_required_list_length(&large, 99, "刚好少一个"));
678 }
679
680 #[test]
681 fn test_validate_required_list_length_single_item() {
682 let single = vec!["only".to_string()];
684 assert!(validate_required_list_length(&single, 1, "单元素"));
685 assert!(validate_required_list_length(&single, 5, "宽松限制"));
686 assert!(!validate_required_list_length(&single, 0, "零限制"));
687 }
688
689 #[test]
690 fn test_validate_content_size_mb_sizes() {
691 let content_1mb = "x".repeat(1024 * 1024);
693 assert!(validate_content_size(&content_1mb, 1024 * 1024, "1MB内容"));
694 assert!(!validate_content_size(
695 &content_1mb,
696 1024 * 1024 - 1,
697 "1MB-1内容"
698 ));
699
700 let content_10mb = "x".repeat(10 * 1024 * 1024);
701 assert!(validate_content_size(
702 &content_10mb,
703 10 * 1024 * 1024,
704 "10MB内容"
705 ));
706 assert!(!validate_content_size(
707 &content_10mb,
708 10 * 1024 * 1024 - 1,
709 "10MB-1内容"
710 ));
711 }
712
713 #[test]
714 fn test_validate_content_size_empty_and_small() {
715 assert!(validate_content_size("", 0, "空内容0限制"));
717 assert!(validate_content_size("", 1, "空内容1限制"));
718 assert!(validate_content_size("a", 1, "单字节1限制"));
719 assert!(!validate_content_size("a", 0, "单字节0限制"));
720
721 assert!(validate_content_size("你好", 6, "中文6字节")); assert!(!validate_content_size("你好", 5, "中文5字节"));
724 }
725
726 #[test]
727 fn test_default_validate_builder_chain_multiple_validations() {
728 let builder = DefaultValidateBuilder::new()
730 .required(Some("start".to_string()), "字段1")
731 .length("mid".to_string(), 2, 10, "字段2")
732 .custom("end".to_string(), |v| v.len() < 10, "自定义校验");
733
734 let result = builder.build();
737 assert!(result.is_ok());
738 }
739
740 #[test]
741 fn test_default_validate_builder_multiple_errors() {
742 let builder = DefaultValidateBuilder::new()
744 .required(None, "字段1")
745 .required(Some("".to_string()), "字段2")
746 .length("a".to_string(), 5, 10, "字段3");
747
748 let result = builder.build();
749 assert!(result.is_err());
750 let errors = result.unwrap_err();
751 assert!(errors.len() >= 2); }
753
754 #[test]
755 fn test_default_validate_builder_custom_validator() {
756 let email_regex = |v: &str| v.contains('@') && v.contains('.');
758
759 let result = DefaultValidateBuilder::new()
761 .custom("test@example.com".to_string(), email_regex, "无效的邮箱")
762 .build();
763 assert!(result.is_ok());
764
765 let result = DefaultValidateBuilder::new()
767 .custom("invalid-email".to_string(), email_regex, "无效的邮箱")
768 .build();
769 assert!(result.is_err());
770 assert_eq!(result.unwrap_err(), vec!["无效的邮箱"]);
771 }
772
773 #[test]
774 fn test_default_validate_builder_length_exact_min_max() {
775 let result = DefaultValidateBuilder::new()
778 .length("abc".to_string(), 3, 10, "长度测试")
779 .build();
780 assert!(result.is_ok());
781
782 let result = DefaultValidateBuilder::new()
784 .length("abcdefghij".to_string(), 1, 10, "长度测试")
785 .build();
786 assert!(result.is_ok());
787
788 let result = DefaultValidateBuilder::new()
790 .length("ab".to_string(), 3, 10, "长度测试")
791 .build();
792 assert!(result.is_err());
793
794 let result = DefaultValidateBuilder::new()
796 .length("abcdefghijk".to_string(), 1, 10, "长度测试")
797 .build();
798 assert!(result.is_err());
799 }
800
801 #[test]
802 fn test_validation_result_edge_cases() {
803 let valid = ValidationResult::Valid;
805 assert!(valid.is_valid());
806 assert_eq!(valid.error_message(), None);
807 assert_eq!(valid.sanitized_value(), None);
808 assert_eq!(valid.into_result(), Ok(String::new()));
809
810 let invalid_empty = ValidationResult::Invalid("".to_string());
812 assert!(!invalid_empty.is_valid());
813 assert_eq!(invalid_empty.error_message(), Some(&"".to_string()));
814
815 let sanitized_empty = ValidationResult::Sanitized("".to_string());
817 assert!(!sanitized_empty.is_valid());
818 assert_eq!(sanitized_empty.sanitized_value(), Some(&"".to_string()));
819 assert_eq!(sanitized_empty.into_result(), Ok("".to_string()));
820 }
821}