1use crate::forms::calculations::FieldValue;
11use chrono::{NaiveDate, NaiveTime};
12use regex::Regex;
13use std::collections::HashMap;
14use std::fmt;
15
16#[derive(Debug, Clone, Default)]
18pub struct FormValidationSystem {
19 validators: HashMap<String, FieldValidator>,
21 required_fields: HashMap<String, RequiredFieldInfo>,
23 validation_cache: HashMap<String, ValidationResult>,
25 #[allow(dead_code)]
27 settings: ValidationSettings,
28}
29
30#[derive(Debug, Clone)]
32pub struct FieldValidator {
33 pub field_name: String,
35 pub rules: Vec<ValidationRule>,
37 pub format_mask: Option<FormatMask>,
39 pub error_message: Option<String>,
41}
42
43#[derive(Debug, Clone)]
45pub enum ValidationRule {
46 Required,
48 Range { min: Option<f64>, max: Option<f64> },
50 Length {
52 min: Option<usize>,
53 max: Option<usize>,
54 },
55 Pattern(String),
57 Email,
59 Url,
61 PhoneNumber { country: PhoneCountry },
63 Date {
65 min: Option<NaiveDate>,
66 max: Option<NaiveDate>,
67 },
68 Time {
70 min: Option<NaiveTime>,
71 max: Option<NaiveTime>,
72 },
73 CreditCard,
75 Custom {
77 name: String,
78 validator: fn(&FieldValue) -> bool,
79 },
80}
81
82#[derive(Debug, Clone)]
84pub enum FormatMask {
85 Number {
87 decimals: usize,
88 thousands_separator: bool,
89 allow_negative: bool,
90 prefix: Option<String>,
91 suffix: Option<String>,
92 },
93 Date { format: DateFormat },
95 Time {
97 format: TimeFormat,
98 use_24_hour: bool,
99 },
100 Phone { country: PhoneCountry },
102 SSN,
104 ZipCode { plus_four: bool },
106 CreditCard,
108 Custom { pattern: String, placeholder: char },
110}
111
112#[derive(Debug, Clone, Copy)]
114pub enum DateFormat {
115 MDY,
117 DMY,
119 YMD,
121 DotDMY,
123 DashMDY,
125}
126
127#[derive(Debug, Clone, Copy)]
129pub enum TimeFormat {
130 HM,
132 HMS,
134 HMAM,
136 HMSAM,
138}
139
140#[derive(Debug, Clone, Copy)]
142pub enum PhoneCountry {
143 US, UK, EU, Japan, Custom,
148}
149
150#[derive(Debug, Clone)]
152pub struct RequiredFieldInfo {
153 pub field_name: String,
155 pub error_message: String,
157 #[allow(dead_code)]
159 pub group: Option<String>,
160 pub condition: Option<RequirementCondition>,
162}
163
164#[derive(Debug, Clone)]
166pub enum RequirementCondition {
167 Always,
169 IfFieldEquals { field: String, value: FieldValue },
171 IfFieldNotEmpty { field: String },
173 IfGroupHasValue { group: String },
175}
176
177#[derive(Debug, Clone)]
179pub struct ValidationResult {
180 pub field_name: String,
182 pub is_valid: bool,
184 pub errors: Vec<ValidationError>,
186 pub warnings: Vec<String>,
188 pub formatted_value: Option<String>,
190}
191
192#[derive(Debug, Clone)]
194pub struct ValidationError {
195 pub field_name: String,
197 pub error_type: ValidationErrorType,
199 pub message: String,
201 pub details: Option<String>,
203}
204
205#[derive(Debug, Clone, PartialEq)]
207pub enum ValidationErrorType {
208 Required,
209 Format,
210 Range,
211 Length,
212 Pattern,
213 Custom,
214}
215
216#[derive(Debug, Clone)]
218pub struct ValidationSettings {
219 pub validate_on_change: bool,
221 pub show_format_hints: bool,
223 pub auto_format: bool,
225 pub allow_partial: bool,
227 pub real_time_validation: bool,
229 pub highlight_errors: bool,
231 pub show_error_messages: bool,
233}
234
235impl Default for ValidationSettings {
236 fn default() -> Self {
237 Self {
238 validate_on_change: true,
239 show_format_hints: true,
240 auto_format: true,
241 allow_partial: false,
242 real_time_validation: true,
243 highlight_errors: true,
244 show_error_messages: true,
245 }
246 }
247}
248
249impl FormValidationSystem {
250 pub fn new() -> Self {
252 Self::default()
253 }
254
255 pub fn with_settings(settings: ValidationSettings) -> Self {
257 Self {
258 settings,
259 ..Self::default()
260 }
261 }
262
263 pub fn add_validator(&mut self, validator: FieldValidator) {
265 self.validators
266 .insert(validator.field_name.clone(), validator);
267 }
268
269 pub fn add_required_field(&mut self, info: RequiredFieldInfo) {
271 self.required_fields.insert(info.field_name.clone(), info);
272 }
273
274 pub fn validate_field(&mut self, field_name: &str, value: &FieldValue) -> ValidationResult {
276 let mut errors = Vec::new();
277 let warnings = Vec::new();
278 let mut formatted_value = None;
279
280 if let Some(required_info) = self.required_fields.get(field_name) {
282 if self.is_field_required(required_info) && self.is_empty(value) {
283 errors.push(ValidationError {
284 field_name: field_name.to_string(),
285 error_type: ValidationErrorType::Required,
286 message: required_info.error_message.clone(),
287 details: None,
288 });
289 }
290 }
291
292 if let Some(validator) = self.validators.get(field_name) {
294 if let Some(ref mask) = validator.format_mask {
296 match self.apply_format_mask(value, mask) {
297 Ok(formatted) => formatted_value = Some(formatted),
298 Err(e) => errors.push(ValidationError {
299 field_name: field_name.to_string(),
300 error_type: ValidationErrorType::Format,
301 message: e,
302 details: None,
303 }),
304 }
305 }
306
307 for rule in &validator.rules {
309 if let Err(e) = self.apply_rule(value, rule) {
310 errors.push(ValidationError {
311 field_name: field_name.to_string(),
312 error_type: self.get_error_type(rule),
313 message: validator
314 .error_message
315 .clone()
316 .unwrap_or_else(|| e.to_string()),
317 details: None,
318 });
319 }
320 }
321 }
322
323 let result = ValidationResult {
324 field_name: field_name.to_string(),
325 is_valid: errors.is_empty(),
326 errors,
327 warnings,
328 formatted_value,
329 };
330
331 self.validation_cache
333 .insert(field_name.to_string(), result.clone());
334
335 result
336 }
337
338 fn is_field_required(&self, info: &RequiredFieldInfo) -> bool {
340 match &info.condition {
341 Some(RequirementCondition::Always) | None => true,
342 Some(RequirementCondition::IfFieldEquals { field: _, value: _ }) => {
343 false
346 }
347 Some(RequirementCondition::IfFieldNotEmpty { field: _ }) => {
348 false
350 }
351 Some(RequirementCondition::IfGroupHasValue { group: _ }) => {
352 false
354 }
355 }
356 }
357
358 fn is_empty(&self, value: &FieldValue) -> bool {
360 match value {
361 FieldValue::Empty => true,
362 FieldValue::Text(s) => s.is_empty(),
363 _ => false,
364 }
365 }
366
367 fn apply_rule(&self, value: &FieldValue, rule: &ValidationRule) -> Result<(), String> {
369 match rule {
370 ValidationRule::Required => {
371 if self.is_empty(value) {
372 Err("Field is required".to_string())
373 } else {
374 Ok(())
375 }
376 }
377 ValidationRule::Range { min, max } => {
378 match value {
380 FieldValue::Number(num) => {
381 if let Some(min_val) = min {
382 if num < min_val {
383 return Err(format!("Value must be at least {}", min_val));
384 }
385 }
386 if let Some(max_val) = max {
387 if num > max_val {
388 return Err(format!("Value must be at most {}", max_val));
389 }
390 }
391 Ok(())
392 }
393 FieldValue::Text(s) => {
394 match s.parse::<f64>() {
396 Ok(num) => {
397 if let Some(min_val) = min {
398 if num < *min_val {
399 return Err(format!("Value must be at least {}", min_val));
400 }
401 }
402 if let Some(max_val) = max {
403 if num > *max_val {
404 return Err(format!("Value must be at most {}", max_val));
405 }
406 }
407 Ok(())
408 }
409 Err(_) => {
410 Err("Value must be a valid number for range validation".to_string())
411 }
412 }
413 }
414 FieldValue::Boolean(_) | FieldValue::Empty => {
415 Err("Range validation requires numeric values".to_string())
416 }
417 }
418 }
419 ValidationRule::Length { min, max } => {
420 let text = value.to_string();
421 let len = text.len();
422 if let Some(min_len) = min {
423 if len < *min_len {
424 return Err(format!("Must be at least {} characters", min_len));
425 }
426 }
427 if let Some(max_len) = max {
428 if len > *max_len {
429 return Err(format!("Must be at most {} characters", max_len));
430 }
431 }
432 Ok(())
433 }
434 ValidationRule::Pattern(pattern) => {
435 let text = value.to_string();
436 let re = Regex::new(pattern).map_err(|e| e.to_string())?;
437 if re.is_match(&text) {
438 Ok(())
439 } else {
440 Err("Does not match required pattern".to_string())
441 }
442 }
443 ValidationRule::Email => {
444 let text = value.to_string();
445 if let Ok(email_regex) =
448 Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
449 {
450 if email_regex.is_match(&text) {
451 Ok(())
452 } else {
453 Err("Invalid email address".to_string())
454 }
455 } else {
456 Err("Email validation unavailable".to_string())
458 }
459 }
460 ValidationRule::Url => {
461 let text = value.to_string();
462 if let Ok(url_regex) = Regex::new(r"^https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}") {
465 if url_regex.is_match(&text) {
466 Ok(())
467 } else {
468 Err("Invalid URL".to_string())
469 }
470 } else {
471 Err("URL validation unavailable".to_string())
473 }
474 }
475 ValidationRule::PhoneNumber { country } => {
476 self.validate_phone_number(&value.to_string(), *country)
477 }
478 ValidationRule::CreditCard => self.validate_credit_card(&value.to_string()),
479 ValidationRule::Date { min, max } => {
480 let text = value.to_string();
482 let date = NaiveDate::parse_from_str(&text, "%Y-%m-%d")
483 .map_err(|e| format!("Invalid date format: {}", e))?;
484
485 if let Some(min_date) = min {
486 if date < *min_date {
487 return Err(format!("Date must be on or after {}", min_date));
488 }
489 }
490 if let Some(max_date) = max {
491 if date > *max_date {
492 return Err(format!("Date must be on or before {}", max_date));
493 }
494 }
495 Ok(())
496 }
497 ValidationRule::Time { min, max } => {
498 let text = value.to_string();
500
501 if text.contains(':') {
503 let parts: Vec<&str> = text.split(':').collect();
504 if parts.len() >= 2 {
505 if let Ok(hour) = parts[0].parse::<u32>() {
507 if hour > 23 {
508 return Err("Invalid hour: must be 0-23".to_string());
509 }
510 }
511 if let Ok(minute) = parts[1].parse::<u32>() {
513 if minute > 59 {
514 return Err("Invalid minute: must be 0-59".to_string());
515 }
516 }
517 if parts.len() >= 3 {
519 if let Ok(second) = parts[2].parse::<u32>() {
520 if second > 59 {
521 return Err("Invalid second: must be 0-59".to_string());
522 }
523 }
524 }
525 }
526 }
527
528 let time = NaiveTime::parse_from_str(&text, "%H:%M:%S")
529 .or_else(|_| NaiveTime::parse_from_str(&text, "%H:%M"))
530 .map_err(|e| format!("Invalid time format: {}", e))?;
531
532 if let Some(min_time) = min {
533 if time < *min_time {
534 return Err(format!("Time must be at or after {}", min_time));
535 }
536 }
537 if let Some(max_time) = max {
538 if time > *max_time {
539 return Err(format!("Time must be at or before {}", max_time));
540 }
541 }
542 Ok(())
543 }
544 ValidationRule::Custom { name, validator } => {
545 if validator(value) {
546 Ok(())
547 } else {
548 Err(format!("Custom validation '{}' failed", name))
549 }
550 }
551 }
552 }
553
554 fn validate_phone_number(&self, phone: &str, country: PhoneCountry) -> Result<(), String> {
556 let pattern = match country {
557 PhoneCountry::US => r"^\(?[2-9]\d{2}\)?[-.\s]?\d{3}[-.\s]?\d{4}$",
558 PhoneCountry::UK => r"^\+?44\s?\d{2}\s?\d{4}\s?\d{4}$",
559 PhoneCountry::EU => r"^\+?[0-9]{2,3}\s?[0-9]{2,4}\s?[0-9]{2,4}\s?[0-9]{2,4}$",
560 PhoneCountry::Japan => r"^0\d{1,4}-?\d{1,4}-?\d{4}$",
561 PhoneCountry::Custom => r"^[0-9+\-\s\(\)]+$",
562 };
563
564 let re = Regex::new(pattern).map_err(|e| format!("Invalid phone regex pattern: {}", e))?;
565 if re.is_match(phone) {
566 Ok(())
567 } else {
568 Err(format!("Invalid phone number format for {:?}", country))
569 }
570 }
571
572 fn validate_credit_card(&self, card_number: &str) -> Result<(), String> {
574 let digits: Vec<u32> = card_number
575 .chars()
576 .filter(|c| c.is_ascii_digit())
577 .filter_map(|c| c.to_digit(10))
578 .collect();
579
580 if digits.len() < 13 || digits.len() > 19 {
581 return Err("Invalid credit card number length".to_string());
582 }
583
584 let mut sum = 0;
586 let mut alternate = false;
587
588 for digit in digits.iter().rev() {
589 let mut n = *digit;
590 if alternate {
591 n *= 2;
592 if n > 9 {
593 n -= 9;
594 }
595 }
596 sum += n;
597 alternate = !alternate;
598 }
599
600 if sum % 10 == 0 {
601 Ok(())
602 } else {
603 Err("Invalid credit card number".to_string())
604 }
605 }
606
607 fn apply_format_mask(&self, value: &FieldValue, mask: &FormatMask) -> Result<String, String> {
609 match mask {
610 FormatMask::Number {
611 decimals,
612 thousands_separator,
613 allow_negative,
614 prefix,
615 suffix,
616 } => {
617 let num = value.to_number();
618
619 if !allow_negative && num < 0.0 {
620 return Err("Negative numbers not allowed".to_string());
621 }
622
623 let mut formatted = format!("{:.prec$}", num, prec = decimals);
624
625 if *thousands_separator {
626 let parts: Vec<&str> = formatted.split('.').collect();
628 let integer_part = parts[0];
629 let decimal_part = parts.get(1);
630
631 let mut result = String::new();
632 for (i, c) in integer_part.chars().rev().enumerate() {
633 if i > 0 && i % 3 == 0 {
634 result.insert(0, ',');
635 }
636 result.insert(0, c);
637 }
638
639 if let Some(dec) = decimal_part {
640 result.push('.');
641 result.push_str(dec);
642 }
643
644 formatted = result;
645 }
646
647 let mut result = String::new();
648 if let Some(p) = prefix {
649 result.push_str(p);
650 }
651 result.push_str(&formatted);
652 if let Some(s) = suffix {
653 result.push_str(s);
654 }
655
656 Ok(result)
657 }
658 FormatMask::Date { format } => self.format_date(&value.to_string(), *format),
659 FormatMask::Time {
660 format,
661 use_24_hour,
662 } => self.format_time(&value.to_string(), *format, *use_24_hour),
663 FormatMask::Phone { country } => self.format_phone(&value.to_string(), *country),
664 FormatMask::SSN => self.format_ssn(&value.to_string()),
665 FormatMask::ZipCode { plus_four } => self.format_zip(&value.to_string(), *plus_four),
666 FormatMask::CreditCard => self.format_credit_card(&value.to_string()),
667 FormatMask::Custom {
668 pattern,
669 placeholder,
670 } => self.apply_custom_mask(&value.to_string(), pattern, *placeholder),
671 }
672 }
673
674 fn format_date(&self, date_str: &str, format: DateFormat) -> Result<String, String> {
676 let digits: String = date_str.chars().filter(|c| c.is_ascii_digit()).collect();
678
679 if digits.len() < 8 {
680 return Err("Invalid date format".to_string());
681 }
682
683 let is_yyyy_format = if digits.len() >= 4 {
685 digits[0..4].parse::<u32>().unwrap_or(0) > 1900
686 } else {
687 false
688 };
689
690 let (year, month, day) = if is_yyyy_format {
692 (&digits[0..4], &digits[4..6], &digits[6..8])
694 } else {
695 (&digits[4..8], &digits[0..2], &digits[2..4])
697 };
698
699 let formatted = match format {
700 DateFormat::MDY => {
701 format!("{}/{}/{}", month, day, year)
702 }
703 DateFormat::DMY => {
704 format!("{}/{}/{}", day, month, year)
705 }
706 DateFormat::YMD => {
707 format!("{}-{}-{}", year, month, day)
708 }
709 DateFormat::DotDMY => {
710 format!("{}.{}.{}", day, month, year)
711 }
712 DateFormat::DashMDY => {
713 format!("{}-{}-{}", month, day, year)
714 }
715 };
716
717 Ok(formatted)
718 }
719
720 fn format_time(
722 &self,
723 time_str: &str,
724 format: TimeFormat,
725 use_24_hour: bool,
726 ) -> Result<String, String> {
727 let digits: String = time_str.chars().filter(|c| c.is_ascii_digit()).collect();
728
729 if digits.len() < 4 {
730 return Err("Invalid time format".to_string());
731 }
732
733 let hours: u32 = digits[0..2].parse().unwrap_or(0);
734 let minutes: u32 = digits[2..4].parse().unwrap_or(0);
735 let seconds: u32 = if digits.len() >= 6 {
736 digits[4..6].parse().unwrap_or(0)
737 } else {
738 0
739 };
740
741 let formatted = match format {
742 TimeFormat::HM => {
743 if use_24_hour {
744 format!("{:02}:{:02}", hours, minutes)
745 } else {
746 let (h, am_pm) = if hours == 0 {
747 (12, "AM")
748 } else if hours < 12 {
749 (hours, "AM")
750 } else if hours == 12 {
751 (12, "PM")
752 } else {
753 (hours - 12, "PM")
754 };
755 format!("{:02}:{:02} {}", h, minutes, am_pm)
756 }
757 }
758 TimeFormat::HMAM => {
759 let (h, am_pm) = if hours == 0 {
761 (12, "AM")
762 } else if hours < 12 {
763 (hours, "AM")
764 } else if hours == 12 {
765 (12, "PM")
766 } else {
767 (hours - 12, "PM")
768 };
769 format!("{:02}:{:02} {}", h, minutes, am_pm)
770 }
771 TimeFormat::HMS | TimeFormat::HMSAM => {
772 if use_24_hour {
773 format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
774 } else {
775 let (h, am_pm) = if hours == 0 {
776 (12, "AM")
777 } else if hours < 12 {
778 (hours, "AM")
779 } else if hours == 12 {
780 (12, "PM")
781 } else {
782 (hours - 12, "PM")
783 };
784 format!("{:02}:{:02}:{:02} {}", h, minutes, seconds, am_pm)
785 }
786 }
787 };
788
789 Ok(formatted)
790 }
791
792 fn format_phone(&self, phone: &str, country: PhoneCountry) -> Result<String, String> {
794 let digits: String = phone.chars().filter(|c| c.is_ascii_digit()).collect();
795
796 let formatted = match country {
797 PhoneCountry::US => {
798 if digits.len() >= 10 {
799 format!("({}) {}-{}", &digits[0..3], &digits[3..6], &digits[6..10])
800 } else {
801 return Err("Invalid US phone number".to_string());
802 }
803 }
804 PhoneCountry::UK => {
805 if digits.len() >= 11 {
806 format!("+{} {} {}", &digits[0..2], &digits[2..6], &digits[6..])
807 } else {
808 return Err("Invalid UK phone number".to_string());
809 }
810 }
811 _ => digits,
812 };
813
814 Ok(formatted)
815 }
816
817 fn format_ssn(&self, ssn: &str) -> Result<String, String> {
819 let digits: String = ssn.chars().filter(|c| c.is_ascii_digit()).collect();
820
821 if digits.len() != 9 {
822 return Err("SSN must be 9 digits".to_string());
823 }
824
825 Ok(format!(
826 "{}-{}-{}",
827 &digits[0..3],
828 &digits[3..5],
829 &digits[5..9]
830 ))
831 }
832
833 fn format_zip(&self, zip: &str, plus_four: bool) -> Result<String, String> {
835 let digits: String = zip.chars().filter(|c| c.is_ascii_digit()).collect();
836
837 if plus_four {
838 if digits.len() != 9 {
839 return Err("ZIP+4 must be 9 digits".to_string());
840 }
841 Ok(format!("{}-{}", &digits[0..5], &digits[5..9]))
842 } else {
843 if digits.len() < 5 {
844 return Err("ZIP must be at least 5 digits".to_string());
845 }
846 Ok(digits[0..5].to_string())
847 }
848 }
849
850 fn format_credit_card(&self, card: &str) -> Result<String, String> {
852 let digits: String = card.chars().filter(|c| c.is_ascii_digit()).collect();
853
854 if digits.len() < 13 || digits.len() > 19 {
855 return Err("Invalid credit card number length".to_string());
856 }
857
858 let mut formatted = String::new();
860 for (i, c) in digits.chars().enumerate() {
861 if i > 0 && i % 4 == 0 {
862 formatted.push(' ');
863 }
864 formatted.push(c);
865 }
866
867 Ok(formatted)
868 }
869
870 fn apply_custom_mask(
872 &self,
873 value: &str,
874 pattern: &str,
875 placeholder: char,
876 ) -> Result<String, String> {
877 let mut result = String::new();
878 let mut value_chars = value.chars();
879
880 for pattern_char in pattern.chars() {
881 if pattern_char == placeholder {
882 if let Some(c) = value_chars.next() {
883 result.push(c);
884 } else {
885 break;
886 }
887 } else {
888 result.push(pattern_char);
889 }
890 }
891
892 Ok(result)
893 }
894
895 fn get_error_type(&self, rule: &ValidationRule) -> ValidationErrorType {
897 match rule {
898 ValidationRule::Required => ValidationErrorType::Required,
899 ValidationRule::Range { .. } => ValidationErrorType::Range,
900 ValidationRule::Length { .. } => ValidationErrorType::Length,
901 ValidationRule::Pattern(_) => ValidationErrorType::Pattern,
902 _ => ValidationErrorType::Custom,
903 }
904 }
905
906 pub fn validate_all(&mut self, fields: &HashMap<String, FieldValue>) -> Vec<ValidationResult> {
908 let mut results = Vec::new();
909
910 for (field_name, value) in fields {
911 results.push(self.validate_field(field_name, value));
912 }
913
914 results
915 }
916
917 pub fn clear_cache(&mut self) {
919 self.validation_cache.clear();
920 }
921
922 pub fn get_cached_result(&self, field_name: &str) -> Option<&ValidationResult> {
924 self.validation_cache.get(field_name)
925 }
926}
927
928impl fmt::Display for ValidationResult {
929 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
930 if self.is_valid {
931 write!(f, "Valid")
932 } else {
933 write!(f, "Invalid: {} errors", self.errors.len())
934 }
935 }
936}
937
938#[cfg(test)]
939mod tests {
940 use super::*;
941
942 #[test]
943 fn test_required_field_validation() {
944 let mut system = FormValidationSystem::new();
945
946 let info = RequiredFieldInfo {
947 field_name: "name".to_string(),
948 error_message: "Name is required".to_string(),
949 group: None,
950 condition: None,
951 };
952
953 system.add_required_field(info);
954
955 let result = system.validate_field("name", &FieldValue::Empty);
956 assert!(!result.is_valid);
957 assert_eq!(result.errors.len(), 1);
958 assert_eq!(result.errors[0].error_type, ValidationErrorType::Required);
959 }
960
961 #[test]
962 fn test_email_validation() {
963 let mut system = FormValidationSystem::new();
964
965 let validator = FieldValidator {
966 field_name: "email".to_string(),
967 rules: vec![ValidationRule::Email],
968 format_mask: None,
969 error_message: None,
970 };
971
972 system.add_validator(validator);
973
974 let valid_result =
975 system.validate_field("email", &FieldValue::Text("test@example.com".to_string()));
976 assert!(valid_result.is_valid);
977
978 let invalid_result =
979 system.validate_field("email", &FieldValue::Text("invalid-email".to_string()));
980 assert!(!invalid_result.is_valid);
981 }
982
983 #[test]
984 fn test_phone_format_mask() {
985 let system = FormValidationSystem::new();
986
987 let mask = FormatMask::Phone {
988 country: PhoneCountry::US,
989 };
990
991 let result = system.apply_format_mask(&FieldValue::Text("5551234567".to_string()), &mask);
992
993 assert!(result.is_ok());
994 assert_eq!(result.unwrap(), "(555) 123-4567");
995 }
996
997 #[test]
998 fn test_credit_card_validation() {
999 let system = FormValidationSystem::new();
1000
1001 let valid = system.validate_credit_card("4532015112830366");
1003 assert!(valid.is_ok());
1004
1005 let invalid = system.validate_credit_card("1234567890123456");
1007 assert!(invalid.is_err());
1008 }
1009
1010 #[test]
1011 fn test_ssn_format() {
1012 let system = FormValidationSystem::new();
1013
1014 let result = system.format_ssn("123456789");
1015 assert!(result.is_ok());
1016 assert_eq!(result.unwrap(), "123-45-6789");
1017 }
1018
1019 #[test]
1020 fn test_range_validation() {
1021 let mut system = FormValidationSystem::new();
1022
1023 let validator = FieldValidator {
1024 field_name: "age".to_string(),
1025 rules: vec![ValidationRule::Range {
1026 min: Some(18.0),
1027 max: Some(100.0),
1028 }],
1029 format_mask: None,
1030 error_message: None,
1031 };
1032
1033 system.add_validator(validator);
1034
1035 let valid = system.validate_field("age", &FieldValue::Number(25.0));
1036 assert!(valid.is_valid);
1037
1038 let too_young = system.validate_field("age", &FieldValue::Number(15.0));
1039 assert!(!too_young.is_valid);
1040
1041 let too_old = system.validate_field("age", &FieldValue::Number(150.0));
1042 assert!(!too_old.is_valid);
1043 }
1044
1045 #[test]
1046 fn test_custom_mask() {
1047 let system = FormValidationSystem::new();
1048
1049 let mask = FormatMask::Custom {
1050 pattern: "(###) ###-####".to_string(),
1051 placeholder: '#',
1052 };
1053
1054 let result = system.apply_format_mask(&FieldValue::Text("5551234567".to_string()), &mask);
1055
1056 assert!(result.is_ok());
1057 assert_eq!(result.unwrap(), "(555) 123-4567");
1058 }
1059
1060 #[test]
1061 fn test_validation_settings() {
1062 let settings = ValidationSettings::default();
1063 assert!(settings.real_time_validation);
1064 assert!(settings.highlight_errors);
1065 assert!(settings.show_error_messages);
1066 }
1067
1068 #[test]
1069 fn test_url_validation() {
1070 let mut system = FormValidationSystem::new();
1071
1072 let validator = FieldValidator {
1073 field_name: "website".to_string(),
1074 rules: vec![ValidationRule::Url],
1075 format_mask: None,
1076 error_message: None,
1077 };
1078
1079 system.add_validator(validator);
1080
1081 let valid = system.validate_field(
1083 "website",
1084 &FieldValue::Text("https://example.com".to_string()),
1085 );
1086 assert!(valid.is_valid);
1087
1088 let valid_http =
1089 system.validate_field("website", &FieldValue::Text("http://test.org".to_string()));
1090 assert!(valid_http.is_valid);
1091
1092 let invalid = system.validate_field("website", &FieldValue::Text("not-a-url".to_string()));
1094 assert!(!invalid.is_valid);
1095 }
1096
1097 #[test]
1098 fn test_length_validation() {
1099 let mut system = FormValidationSystem::new();
1100
1101 let validator = FieldValidator {
1102 field_name: "comment".to_string(),
1103 rules: vec![ValidationRule::Length {
1104 min: Some(10),
1105 max: Some(100),
1106 }],
1107 format_mask: None,
1108 error_message: None,
1109 };
1110
1111 system.add_validator(validator);
1112
1113 let valid = system.validate_field(
1115 "comment",
1116 &FieldValue::Text("This is a valid comment.".to_string()),
1117 );
1118 assert!(valid.is_valid);
1119
1120 let too_short = system.validate_field("comment", &FieldValue::Text("Short".to_string()));
1122 assert!(!too_short.is_valid);
1123
1124 let too_long = system.validate_field("comment", &FieldValue::Text("x".repeat(150)));
1126 assert!(!too_long.is_valid);
1127 }
1128
1129 #[test]
1130 fn test_pattern_validation() {
1131 let mut system = FormValidationSystem::new();
1132
1133 let validator = FieldValidator {
1134 field_name: "code".to_string(),
1135 rules: vec![ValidationRule::Pattern(r"^[A-Z]{3}-\d{3}$".to_string())],
1136 format_mask: None,
1137 error_message: Some("Code must be in format ABC-123".to_string()),
1138 };
1139
1140 system.add_validator(validator);
1141
1142 let valid = system.validate_field("code", &FieldValue::Text("ABC-123".to_string()));
1144 assert!(valid.is_valid);
1145
1146 let invalid = system.validate_field("code", &FieldValue::Text("abc-123".to_string()));
1148 assert!(!invalid.is_valid);
1149 assert!(invalid.errors[0].message.contains("ABC-123"));
1150 }
1151
1152 #[test]
1153 fn test_date_validation() {
1154 let mut system = FormValidationSystem::new();
1155
1156 let validator = FieldValidator {
1157 field_name: "birthdate".to_string(),
1158 rules: vec![ValidationRule::Date {
1159 min: Some(NaiveDate::from_ymd_opt(1900, 1, 1).unwrap()),
1160 max: Some(NaiveDate::from_ymd_opt(2020, 12, 31).unwrap()),
1161 }],
1162 format_mask: None,
1163 error_message: None,
1164 };
1165
1166 system.add_validator(validator);
1167
1168 let valid = system.validate_field("birthdate", &FieldValue::Text("1990-05-15".to_string()));
1170 assert!(valid.is_valid);
1171
1172 let too_early =
1174 system.validate_field("birthdate", &FieldValue::Text("1850-01-01".to_string()));
1175 assert!(!too_early.is_valid);
1176 }
1177
1178 #[test]
1179 fn test_time_validation() {
1180 let mut system = FormValidationSystem::new();
1181
1182 let validator = FieldValidator {
1183 field_name: "appointment".to_string(),
1184 rules: vec![ValidationRule::Time {
1185 min: Some(NaiveTime::from_hms_opt(9, 0, 0).unwrap()),
1186 max: Some(NaiveTime::from_hms_opt(17, 0, 0).unwrap()),
1187 }],
1188 format_mask: None,
1189 error_message: None,
1190 };
1191
1192 system.add_validator(validator);
1193
1194 let valid = system.validate_field("appointment", &FieldValue::Text("14:30".to_string()));
1196 assert!(valid.is_valid);
1197
1198 let too_early =
1200 system.validate_field("appointment", &FieldValue::Text("08:00".to_string()));
1201 assert!(!too_early.is_valid);
1202 }
1203
1204 #[test]
1205 fn test_phone_number_uk() {
1206 let mut system = FormValidationSystem::new();
1207
1208 let validator = FieldValidator {
1209 field_name: "phone_uk".to_string(),
1210 rules: vec![ValidationRule::PhoneNumber {
1211 country: PhoneCountry::UK,
1212 }],
1213 format_mask: None,
1214 error_message: None,
1215 };
1216
1217 system.add_validator(validator);
1218
1219 let valid =
1221 system.validate_field("phone_uk", &FieldValue::Text("441234567890".to_string()));
1222 assert!(valid.is_valid);
1223
1224 let invalid = system.validate_field("phone_uk", &FieldValue::Text("12345".to_string()));
1226 assert!(!invalid.is_valid);
1227 }
1228
1229 #[test]
1230 fn test_zip_format_with_plus_four() {
1231 let system = FormValidationSystem::new();
1232
1233 let result_plus = system.format_zip("123456789", true);
1235 assert!(result_plus.is_ok());
1236 assert_eq!(result_plus.unwrap(), "12345-6789");
1237
1238 let result_regular = system.format_zip("12345", false);
1240 assert!(result_regular.is_ok());
1241 assert_eq!(result_regular.unwrap(), "12345");
1242
1243 let invalid = system.format_zip("12345", true);
1245 assert!(invalid.is_err());
1246 }
1247
1248 #[test]
1249 fn test_number_format_mask() {
1250 let system = FormValidationSystem::new();
1251
1252 let mask = FormatMask::Number {
1253 decimals: 2,
1254 thousands_separator: true,
1255 allow_negative: true,
1256 prefix: Some("$".to_string()),
1257 suffix: Some(" USD".to_string()),
1258 };
1259
1260 let result = system.apply_format_mask(&FieldValue::Number(1234567.89), &mask);
1261 assert!(result.is_ok());
1262 assert_eq!(result.unwrap(), "$1,234,567.89 USD");
1263
1264 let negative_result = system.apply_format_mask(&FieldValue::Number(-1234.56), &mask);
1266 assert!(negative_result.is_ok());
1267 assert_eq!(negative_result.unwrap(), "$-1,234.56 USD");
1268 }
1269
1270 #[test]
1271 fn test_date_format_mask() {
1272 let system = FormValidationSystem::new();
1273
1274 let mask_mdy = FormatMask::Date {
1276 format: DateFormat::MDY,
1277 };
1278 let result_mdy =
1279 system.apply_format_mask(&FieldValue::Text("01152022".to_string()), &mask_mdy);
1280 assert!(result_mdy.is_ok());
1281 assert_eq!(result_mdy.unwrap(), "01/15/2022");
1282
1283 let mask_ymd = FormatMask::Date {
1285 format: DateFormat::YMD,
1286 };
1287 let result_ymd =
1288 system.apply_format_mask(&FieldValue::Text("20220115".to_string()), &mask_ymd);
1289 assert!(result_ymd.is_ok());
1290 assert_eq!(result_ymd.unwrap(), "2022-01-15");
1291 }
1292
1293 #[test]
1294 fn test_time_format_mask() {
1295 let system = FormValidationSystem::new();
1296
1297 let mask_24 = FormatMask::Time {
1299 format: TimeFormat::HMS,
1300 use_24_hour: true,
1301 };
1302 let result_24 = system.apply_format_mask(&FieldValue::Text("143045".to_string()), &mask_24);
1303 assert!(result_24.is_ok());
1304 assert_eq!(result_24.unwrap(), "14:30:45");
1305
1306 let mask_12 = FormatMask::Time {
1308 format: TimeFormat::HMSAM,
1309 use_24_hour: false,
1310 };
1311 let result_12 = system.apply_format_mask(&FieldValue::Text("143045".to_string()), &mask_12);
1312 assert!(result_12.is_ok());
1313 assert_eq!(result_12.unwrap(), "02:30:45 PM");
1314 }
1315
1316 #[test]
1317 fn test_validation_cache() {
1318 let mut system = FormValidationSystem::new();
1319
1320 let validator = FieldValidator {
1321 field_name: "cached_field".to_string(),
1322 rules: vec![ValidationRule::Required],
1323 format_mask: None,
1324 error_message: None,
1325 };
1326
1327 system.add_validator(validator);
1328
1329 let result1 = system.validate_field("cached_field", &FieldValue::Text("value".to_string()));
1331 assert!(result1.is_valid);
1332
1333 let cached = system.get_cached_result("cached_field");
1335 assert!(cached.is_some());
1336 assert!(cached.unwrap().is_valid);
1337
1338 system.clear_cache();
1340 let cached_after_clear = system.get_cached_result("cached_field");
1341 assert!(cached_after_clear.is_none());
1342 }
1343
1344 #[test]
1345 fn test_validation_error_types() {
1346 let error_required = ValidationError {
1347 field_name: "test".to_string(),
1348 error_type: ValidationErrorType::Required,
1349 message: "Field is required".to_string(),
1350 details: None,
1351 };
1352 assert_eq!(error_required.error_type, ValidationErrorType::Required);
1353
1354 let error_range = ValidationError {
1355 field_name: "test".to_string(),
1356 error_type: ValidationErrorType::Range,
1357 message: "Value out of range".to_string(),
1358 details: Some("Must be between 1 and 100".to_string()),
1359 };
1360 assert_eq!(error_range.error_type, ValidationErrorType::Range);
1361 assert!(error_range.details.is_some());
1362 }
1363
1364 #[test]
1365 fn test_field_validator_with_multiple_rules() {
1366 let mut system = FormValidationSystem::new();
1367
1368 let validator = FieldValidator {
1369 field_name: "username".to_string(),
1370 rules: vec![
1371 ValidationRule::Required,
1372 ValidationRule::Length {
1373 min: Some(3),
1374 max: Some(20),
1375 },
1376 ValidationRule::Pattern(r"^[a-zA-Z0-9_]+$".to_string()),
1377 ],
1378 format_mask: None,
1379 error_message: None,
1380 };
1381
1382 system.add_validator(validator);
1383
1384 let valid = system.validate_field("username", &FieldValue::Text("user_123".to_string()));
1386 assert!(valid.is_valid);
1387
1388 let too_short = system.validate_field("username", &FieldValue::Text("ab".to_string()));
1390 assert!(!too_short.is_valid);
1391
1392 let invalid_chars =
1394 system.validate_field("username", &FieldValue::Text("user@123".to_string()));
1395 assert!(!invalid_chars.is_valid);
1396 }
1397
1398 #[test]
1399 fn test_credit_card_format() {
1400 let system = FormValidationSystem::new();
1401
1402 let result = system.format_credit_card("4532015112830366");
1403 assert!(result.is_ok());
1404 assert_eq!(result.unwrap(), "4532 0151 1283 0366");
1405
1406 let too_short = system.format_credit_card("123");
1408 assert!(too_short.is_err());
1409
1410 let too_long = system.format_credit_card("12345678901234567890");
1411 assert!(too_long.is_err());
1412 }
1413
1414 #[test]
1415 fn test_required_field_with_group() {
1416 let mut system = FormValidationSystem::new();
1417
1418 let info = RequiredFieldInfo {
1419 field_name: "address".to_string(),
1420 error_message: "Address is required".to_string(),
1421 group: Some("contact_info".to_string()),
1422 condition: None,
1423 };
1424
1425 system.add_required_field(info);
1426
1427 let result = system.validate_field("address", &FieldValue::Empty);
1428 assert!(!result.is_valid);
1429 assert_eq!(result.errors[0].error_type, ValidationErrorType::Required);
1430 }
1431
1432 #[test]
1433 fn test_validation_result_display() {
1434 let valid_result = ValidationResult {
1435 field_name: "test".to_string(),
1436 is_valid: true,
1437 errors: vec![],
1438 warnings: vec![],
1439 formatted_value: None,
1440 };
1441 assert_eq!(format!("{}", valid_result), "Valid");
1442
1443 let invalid_result = ValidationResult {
1444 field_name: "test".to_string(),
1445 is_valid: false,
1446 errors: vec![
1447 ValidationError {
1448 field_name: "test".to_string(),
1449 error_type: ValidationErrorType::Required,
1450 message: "Required".to_string(),
1451 details: None,
1452 },
1453 ValidationError {
1454 field_name: "test".to_string(),
1455 error_type: ValidationErrorType::Length,
1456 message: "Too short".to_string(),
1457 details: None,
1458 },
1459 ],
1460 warnings: vec![],
1461 formatted_value: None,
1462 };
1463 assert_eq!(format!("{}", invalid_result), "Invalid: 2 errors");
1464 }
1465
1466 #[test]
1467 fn test_validate_all_fields() {
1468 let mut system = FormValidationSystem::new();
1469
1470 system.add_validator(FieldValidator {
1472 field_name: "name".to_string(),
1473 rules: vec![ValidationRule::Required],
1474 format_mask: None,
1475 error_message: None,
1476 });
1477
1478 system.add_validator(FieldValidator {
1479 field_name: "age".to_string(),
1480 rules: vec![ValidationRule::Range {
1481 min: Some(0.0),
1482 max: Some(120.0),
1483 }],
1484 format_mask: None,
1485 error_message: None,
1486 });
1487
1488 let mut fields = HashMap::new();
1489 fields.insert("name".to_string(), FieldValue::Text("John".to_string()));
1490 fields.insert("age".to_string(), FieldValue::Number(30.0));
1491
1492 let results = system.validate_all(&fields);
1493 assert_eq!(results.len(), 2);
1494 assert!(results.iter().all(|r| r.is_valid));
1495 }
1496
1497 #[test]
1498 fn test_validation_settings_advanced() {
1499 let settings = ValidationSettings {
1500 validate_on_change: true,
1501 show_format_hints: true,
1502 auto_format: false,
1503 allow_partial: false,
1504 real_time_validation: true,
1505 highlight_errors: true,
1506 show_error_messages: true,
1507 };
1508
1509 let mut system = FormValidationSystem::new();
1510 system.settings = settings;
1511
1512 assert!(system.settings.validate_on_change);
1513 assert!(system.settings.show_format_hints);
1514 assert!(!system.settings.auto_format);
1515 }
1516
1517 #[test]
1518 fn test_complex_pattern_validation() {
1519 let mut system = FormValidationSystem::new();
1520
1521 system.add_validator(FieldValidator {
1523 field_name: "product_code".to_string(),
1524 rules: vec![ValidationRule::Pattern(
1525 r"^[A-Z]{3}-\d{4}-[A-Z]\d$".to_string(),
1526 )],
1527 format_mask: None,
1528 error_message: Some("Invalid product code format".to_string()),
1529 });
1530
1531 let valid_code = FieldValue::Text("ABC-1234-A5".to_string());
1532 let result = system.validate_field("product_code", &valid_code);
1533 assert!(result.is_valid);
1534
1535 let invalid_code = FieldValue::Text("abc-1234-a5".to_string());
1536 let result = system.validate_field("product_code", &invalid_code);
1537 assert!(!result.is_valid);
1538 }
1539
1540 #[test]
1541 fn test_currency_format_mask() {
1542 let system = FormValidationSystem::new();
1543
1544 let mask = FormatMask::Number {
1545 decimals: 2,
1546 thousands_separator: true,
1547 allow_negative: false,
1548 prefix: Some("$".to_string()),
1549 suffix: None,
1550 };
1551
1552 let result = system.apply_format_mask(&FieldValue::Number(1234567.89), &mask);
1553 assert!(result.is_ok());
1554 }
1556
1557 #[test]
1558 fn test_international_phone_formats() {
1559 let mut system = FormValidationSystem::new();
1560
1561 system.add_validator(FieldValidator {
1563 field_name: "us_phone".to_string(),
1564 rules: vec![ValidationRule::PhoneNumber {
1565 country: PhoneCountry::US,
1566 }],
1567 format_mask: None,
1568 error_message: None,
1569 });
1570
1571 let valid_us = FieldValue::Text("(555) 123-4567".to_string());
1572 assert!(system.validate_field("us_phone", &valid_us).is_valid);
1573
1574 system.add_validator(FieldValidator {
1576 field_name: "uk_phone".to_string(),
1577 rules: vec![ValidationRule::PhoneNumber {
1578 country: PhoneCountry::UK,
1579 }],
1580 format_mask: None,
1581 error_message: None,
1582 });
1583
1584 let valid_uk = FieldValue::Text("+44 20 1234 5678".to_string());
1585 assert!(system.validate_field("uk_phone", &valid_uk).is_valid);
1586 }
1587
1588 #[test]
1589 fn test_phone_validation_all_countries() {
1590 let mut system = FormValidationSystem::new();
1592
1593 system.add_validator(FieldValidator {
1595 field_name: "eu_phone".to_string(),
1596 rules: vec![ValidationRule::PhoneNumber {
1597 country: PhoneCountry::EU,
1598 }],
1599 format_mask: None,
1600 error_message: None,
1601 });
1602
1603 let valid_eu = FieldValue::Text("+33 123 456 7890".to_string());
1604 assert!(system.validate_field("eu_phone", &valid_eu).is_valid);
1605
1606 let invalid_eu = FieldValue::Text("123-456".to_string());
1607 assert!(!system.validate_field("eu_phone", &invalid_eu).is_valid);
1608
1609 system.add_validator(FieldValidator {
1611 field_name: "japan_phone".to_string(),
1612 rules: vec![ValidationRule::PhoneNumber {
1613 country: PhoneCountry::Japan,
1614 }],
1615 format_mask: None,
1616 error_message: None,
1617 });
1618
1619 let valid_japan = FieldValue::Text("03-1234-5678".to_string());
1620 assert!(system.validate_field("japan_phone", &valid_japan).is_valid);
1621
1622 let invalid_japan = FieldValue::Text("123".to_string());
1623 assert!(
1624 !system
1625 .validate_field("japan_phone", &invalid_japan)
1626 .is_valid
1627 );
1628
1629 system.add_validator(FieldValidator {
1631 field_name: "custom_phone".to_string(),
1632 rules: vec![ValidationRule::PhoneNumber {
1633 country: PhoneCountry::Custom,
1634 }],
1635 format_mask: None,
1636 error_message: None,
1637 });
1638
1639 let valid_custom = FieldValue::Text("+1-234-567-8900".to_string());
1640 assert!(
1641 system
1642 .validate_field("custom_phone", &valid_custom)
1643 .is_valid
1644 );
1645
1646 let invalid_custom = FieldValue::Text("not a phone".to_string());
1647 assert!(
1648 !system
1649 .validate_field("custom_phone", &invalid_custom)
1650 .is_valid
1651 );
1652 }
1653
1654 #[test]
1655 fn test_credit_card_validation_edge_cases() {
1656 let mut system = FormValidationSystem::new();
1658
1659 system.add_validator(FieldValidator {
1660 field_name: "cc".to_string(),
1661 rules: vec![ValidationRule::CreditCard],
1662 format_mask: None,
1663 error_message: None,
1664 });
1665
1666 let too_short = FieldValue::Text("123456789012".to_string());
1668 let result = system.validate_field("cc", &too_short);
1669 assert!(!result.is_valid);
1670 assert!(result.errors[0].message.contains("length"));
1671
1672 let too_long = FieldValue::Text("12345678901234567890".to_string());
1674 let result = system.validate_field("cc", &too_long);
1675 assert!(!result.is_valid);
1676 assert!(result.errors[0].message.contains("length"));
1677
1678 let invalid_luhn = FieldValue::Text("4111111111111112".to_string()); let result = system.validate_field("cc", &invalid_luhn);
1681 assert!(!result.is_valid);
1682 assert!(result.errors[0].message.contains("Invalid credit card"));
1683
1684 let valid_cc = FieldValue::Text("4111111111111111".to_string()); let result = system.validate_field("cc", &valid_cc);
1687 assert!(result.is_valid);
1688 }
1689
1690 #[test]
1691 fn test_time_validation_with_range() {
1692 let mut system = FormValidationSystem::new();
1694
1695 system.add_validator(FieldValidator {
1696 field_name: "appointment".to_string(),
1697 rules: vec![ValidationRule::Time {
1698 min: Some(NaiveTime::from_hms_opt(9, 0, 0).unwrap()),
1699 max: Some(NaiveTime::from_hms_opt(17, 0, 0).unwrap()),
1700 }],
1701 format_mask: None,
1702 error_message: None,
1703 });
1704
1705 let valid = FieldValue::Text("10:30:00".to_string());
1707 assert!(system.validate_field("appointment", &valid).is_valid);
1708
1709 let too_early = FieldValue::Text("08:30:00".to_string());
1711 let result = system.validate_field("appointment", &too_early);
1712 assert!(!result.is_valid);
1713 assert!(result.errors[0].message.contains("at or after"));
1714
1715 let too_late = FieldValue::Text("18:00:00".to_string());
1717 let result = system.validate_field("appointment", &too_late);
1718 assert!(!result.is_valid);
1719 assert!(result.errors[0].message.contains("at or before"));
1720
1721 let invalid = FieldValue::Text("not a time".to_string());
1723 let result = system.validate_field("appointment", &invalid);
1724 assert!(!result.is_valid);
1725 }
1726
1727 #[test]
1728 fn test_custom_validator() {
1729 fn is_even(value: &FieldValue) -> bool {
1731 if let FieldValue::Text(s) = value {
1732 if let Ok(n) = s.parse::<i32>() {
1733 return n % 2 == 0;
1734 }
1735 }
1736 false
1737 }
1738
1739 let mut system = FormValidationSystem::new();
1740
1741 system.add_validator(FieldValidator {
1742 field_name: "even_number".to_string(),
1743 rules: vec![ValidationRule::Custom {
1744 name: "even_check".to_string(),
1745 validator: is_even,
1746 }],
1747 format_mask: None,
1748 error_message: None,
1749 });
1750
1751 let valid = FieldValue::Text("42".to_string());
1753 assert!(system.validate_field("even_number", &valid).is_valid);
1754
1755 let invalid = FieldValue::Text("43".to_string());
1757 let result = system.validate_field("even_number", &invalid);
1758 assert!(!result.is_valid);
1759 assert!(result.errors[0]
1760 .message
1761 .contains("Custom validation 'even_check' failed"));
1762
1763 let non_number = FieldValue::Text("abc".to_string());
1765 let result = system.validate_field("even_number", &non_number);
1766 assert!(!result.is_valid);
1767 }
1768
1769 #[test]
1770 fn test_format_mask_number_with_all_options() {
1771 let system = FormValidationSystem::new();
1773
1774 let mask = FormatMask::Number {
1775 decimals: 3,
1776 thousands_separator: true,
1777 allow_negative: true,
1778 prefix: Some("€ ".to_string()),
1779 suffix: Some(" EUR".to_string()),
1780 };
1781
1782 let value = FieldValue::Number(12345.6789);
1784 let formatted = system.apply_format_mask(&value, &mask);
1785 assert!(formatted.is_ok());
1786 assert_eq!(formatted.unwrap(), "€ 12,345.679 EUR");
1787
1788 let value = FieldValue::Number(-9876.543);
1790 let formatted = system.apply_format_mask(&value, &mask);
1791 assert!(formatted.is_ok());
1792 assert_eq!(formatted.unwrap(), "€ -9,876.543 EUR");
1793
1794 let mask_no_neg = FormatMask::Number {
1796 decimals: 2,
1797 thousands_separator: false,
1798 allow_negative: false,
1799 prefix: None,
1800 suffix: None,
1801 };
1802
1803 let value = FieldValue::Number(-123.456);
1804 let formatted = system.apply_format_mask(&value, &mask_no_neg);
1805 assert!(formatted.is_err());
1806 assert!(formatted
1807 .unwrap_err()
1808 .contains("Negative numbers not allowed"));
1809 }
1810
1811 #[test]
1812 fn test_ssn_and_zip_format_masks() {
1813 let system = FormValidationSystem::new();
1815
1816 let ssn_mask = FormatMask::SSN;
1818 let ssn_value = FieldValue::Text("123456789".to_string());
1819 let formatted = system.apply_format_mask(&ssn_value, &ssn_mask);
1820 assert!(formatted.is_ok());
1821 assert_eq!(formatted.unwrap(), "123-45-6789");
1822
1823 let invalid_ssn = FieldValue::Text("12345".to_string());
1825 let formatted = system.apply_format_mask(&invalid_ssn, &ssn_mask);
1826 assert!(formatted.is_err());
1827 assert!(formatted.unwrap_err().contains("9 digits"));
1828
1829 let zip5_mask = FormatMask::ZipCode { plus_four: false };
1831 let zip5_value = FieldValue::Text("12345".to_string());
1832 let formatted = system.apply_format_mask(&zip5_value, &zip5_mask);
1833 assert!(formatted.is_ok());
1834 assert_eq!(formatted.unwrap(), "12345");
1835
1836 let invalid_zip5 = FieldValue::Text("1234".to_string());
1838 let formatted = system.apply_format_mask(&invalid_zip5, &zip5_mask);
1839 assert!(formatted.is_err());
1840
1841 let zip9_mask = FormatMask::ZipCode { plus_four: true };
1843 let zip9_value = FieldValue::Text("123456789".to_string());
1844 let formatted = system.apply_format_mask(&zip9_value, &zip9_mask);
1845 assert!(formatted.is_ok());
1846 assert_eq!(formatted.unwrap(), "12345-6789");
1847
1848 let invalid_zip9 = FieldValue::Text("12345".to_string());
1850 let formatted = system.apply_format_mask(&invalid_zip9, &zip9_mask);
1851 assert!(formatted.is_err());
1852 }
1853
1854 #[test]
1855 fn test_date_format_masks() {
1856 let system = FormValidationSystem::new();
1858
1859 let date = NaiveDate::from_ymd_opt(2024, 3, 15).unwrap();
1860 let value = FieldValue::Text(date.to_string());
1861
1862 let mask = FormatMask::Date {
1864 format: DateFormat::MDY,
1865 };
1866 let formatted = system.apply_format_mask(&value, &mask);
1867 assert!(formatted.is_ok());
1868 assert_eq!(formatted.unwrap(), "03/15/2024");
1869
1870 let mask = FormatMask::Date {
1872 format: DateFormat::DMY,
1873 };
1874 let formatted = system.apply_format_mask(&value, &mask);
1875 assert!(formatted.is_ok());
1876 assert_eq!(formatted.unwrap(), "15/03/2024");
1877
1878 let mask = FormatMask::Date {
1880 format: DateFormat::YMD,
1881 };
1882 let formatted = system.apply_format_mask(&value, &mask);
1883 assert!(formatted.is_ok());
1884 assert_eq!(formatted.unwrap(), "2024-03-15");
1885
1886 let mask = FormatMask::Date {
1888 format: DateFormat::DotDMY,
1889 };
1890 let formatted = system.apply_format_mask(&value, &mask);
1891 assert!(formatted.is_ok());
1892 assert_eq!(formatted.unwrap(), "15.03.2024");
1893
1894 let mask = FormatMask::Date {
1896 format: DateFormat::DashMDY,
1897 };
1898 let formatted = system.apply_format_mask(&value, &mask);
1899 assert!(formatted.is_ok());
1900 assert_eq!(formatted.unwrap(), "03-15-2024");
1901 }
1902
1903 #[test]
1904 fn test_time_format_masks() {
1905 let system = FormValidationSystem::new();
1907
1908 let time_am = NaiveTime::from_hms_opt(9, 30, 45).unwrap();
1910 let value_am = FieldValue::Text(time_am.to_string());
1911
1912 let time_pm = NaiveTime::from_hms_opt(15, 45, 30).unwrap();
1914 let value_pm = FieldValue::Text(time_pm.to_string());
1915
1916 let mask = FormatMask::Time {
1918 format: TimeFormat::HM,
1919 use_24_hour: true,
1920 };
1921 let formatted = system.apply_format_mask(&value_am, &mask);
1922 assert!(formatted.is_ok());
1923 assert_eq!(formatted.unwrap(), "09:30");
1924
1925 let mask = FormatMask::Time {
1927 format: TimeFormat::HMS,
1928 use_24_hour: true,
1929 };
1930 let formatted = system.apply_format_mask(&value_am, &mask);
1931 assert!(formatted.is_ok());
1932 assert_eq!(formatted.unwrap(), "09:30:45");
1933
1934 let mask = FormatMask::Time {
1936 format: TimeFormat::HMAM,
1937 use_24_hour: false,
1938 };
1939 let formatted = system.apply_format_mask(&value_am, &mask);
1940 assert!(formatted.is_ok());
1941 assert_eq!(formatted.unwrap(), "09:30 AM");
1942
1943 let formatted = system.apply_format_mask(&value_pm, &mask);
1944 assert!(formatted.is_ok());
1945 assert_eq!(formatted.unwrap(), "03:45 PM");
1946
1947 let mask = FormatMask::Time {
1949 format: TimeFormat::HMSAM,
1950 use_24_hour: false,
1951 };
1952 let formatted = system.apply_format_mask(&value_pm, &mask);
1953 assert!(formatted.is_ok());
1954 assert_eq!(formatted.unwrap(), "03:45:30 PM");
1955 }
1956
1957 #[test]
1958 fn test_multiple_validation_rules() {
1959 let mut system = FormValidationSystem::new();
1960
1961 system.add_validator(FieldValidator {
1963 field_name: "password".to_string(),
1964 rules: vec![
1965 ValidationRule::Required,
1966 ValidationRule::Length {
1967 min: Some(8),
1968 max: Some(32),
1969 },
1970 ValidationRule::Pattern(r".*[A-Z].*[a-z].*[0-9].*|.*[A-Z].*[0-9].*[a-z].*|.*[a-z].*[A-Z].*[0-9].*|.*[a-z].*[0-9].*[A-Z].*|.*[0-9].*[A-Z].*[a-z].*|.*[0-9].*[a-z].*[A-Z].*".to_string()),
1971 ],
1972 format_mask: None,
1973 error_message: Some(
1974 "Password must be 8-32 chars with uppercase, lowercase, and number".to_string(),
1975 ),
1976 });
1977
1978 let weak_password = FieldValue::Text("abc123".to_string());
1979 let result = system.validate_field("password", &weak_password);
1980 assert!(!result.is_valid);
1981 assert!(result.errors.len() >= 2); let strong_password = FieldValue::Text("SecurePass123".to_string());
1984 assert!(system.validate_field("password", &strong_password).is_valid);
1985 }
1986
1987 #[test]
1988 fn test_conditional_required_field() {
1989 let mut system = FormValidationSystem::new();
1990
1991 let info = RequiredFieldInfo {
1992 field_name: "shipping_address".to_string(),
1993 error_message: "Shipping address required when different from billing".to_string(),
1994 group: Some("shipping".to_string()),
1995 condition: Some(RequirementCondition::IfFieldNotEmpty {
1996 field: "different_shipping".to_string(),
1997 }),
1998 };
1999
2000 system.add_required_field(info);
2001
2002 let _result = system.validate_field("shipping_address", &FieldValue::Empty);
2004 }
2006
2007 #[test]
2008 fn test_validation_cache_advanced() {
2009 let mut system = FormValidationSystem::new();
2010
2011 system.add_validator(FieldValidator {
2012 field_name: "cached_field".to_string(),
2013 rules: vec![ValidationRule::Required],
2014 format_mask: None,
2015 error_message: None,
2016 });
2017
2018 let value = FieldValue::Text("test".to_string());
2019
2020 let _result1 = system.validate_field("cached_field", &value);
2022
2023 assert!(system.validation_cache.contains_key("cached_field"));
2025
2026 system.clear_cache();
2028 assert!(system.validation_cache.is_empty());
2029 }
2030
2031 #[test]
2032 fn test_custom_validator_function() {
2033 let mut system = FormValidationSystem::new();
2034
2035 fn is_even_number(value: &FieldValue) -> bool {
2036 match value {
2037 FieldValue::Number(n) => (*n as i32) % 2 == 0,
2038 _ => false,
2039 }
2040 }
2041
2042 system.add_validator(FieldValidator {
2043 field_name: "even_number".to_string(),
2044 rules: vec![ValidationRule::Custom {
2045 name: "even_check".to_string(),
2046 validator: is_even_number,
2047 }],
2048 format_mask: None,
2049 error_message: Some("Must be an even number".to_string()),
2050 });
2051
2052 assert!(
2053 system
2054 .validate_field("even_number", &FieldValue::Number(4.0))
2055 .is_valid
2056 );
2057 assert!(
2058 !system
2059 .validate_field("even_number", &FieldValue::Number(5.0))
2060 .is_valid
2061 );
2062 }
2063
2064 #[test]
2065 fn test_percentage_format() {
2066 let system = FormValidationSystem::new();
2067
2068 let mask = FormatMask::Number {
2069 decimals: 1,
2070 thousands_separator: false,
2071 allow_negative: false,
2072 prefix: None,
2073 suffix: Some("%".to_string()),
2074 };
2075
2076 let result = system.apply_format_mask(&FieldValue::Number(0.856), &mask);
2077 assert!(result.is_ok());
2078 }
2080
2081 #[test]
2082 fn test_clear_validation_errors() {
2083 let mut system = FormValidationSystem::new();
2084
2085 system.add_validator(FieldValidator {
2086 field_name: "test".to_string(),
2087 rules: vec![ValidationRule::Required],
2088 format_mask: None,
2089 error_message: None,
2090 });
2091
2092 let _ = system.validate_field("test", &FieldValue::Empty);
2094 assert!(system.validation_cache.contains_key("test"));
2095
2096 system.clear_cache();
2098 assert!(system.validation_cache.is_empty());
2099 }
2100
2101 #[test]
2102 fn test_batch_validation() {
2103 let mut system = FormValidationSystem::new();
2104
2105 for i in 0..5 {
2107 system.add_validator(FieldValidator {
2108 field_name: format!("field_{}", i),
2109 rules: vec![ValidationRule::Required],
2110 format_mask: None,
2111 error_message: None,
2112 });
2113 }
2114
2115 let mut fields = HashMap::new();
2116 for i in 0..5 {
2117 fields.insert(
2118 format!("field_{}", i),
2119 FieldValue::Text(format!("value_{}", i)),
2120 );
2121 }
2122
2123 let results = system.validate_all(&fields);
2124 assert_eq!(results.len(), 5);
2125 assert!(results.iter().all(|r| r.is_valid));
2126 }
2127
2128 #[test]
2133 fn test_unicode_text_validation() {
2134 let mut system = FormValidationSystem::new();
2135
2136 system.add_validator(FieldValidator {
2137 field_name: "unicode_text".to_string(),
2138 rules: vec![ValidationRule::Length {
2139 min: Some(1),
2140 max: Some(100),
2141 }],
2142 format_mask: None,
2143 error_message: None,
2144 });
2145
2146 let test_cases = vec![
2148 ("Hello World", true), ("Café münü", true), ("🚀 Rocket ship", true), ("こんにちは", true), ("مرحبا", true), ("Привет", true), ("🏳️⚧️🏳️🌈", true), ("𝒯𝒽𝒾𝓈 𝒾𝓈 𝓂𝒶𝓉𝒽", true), ("ℌ𝔢𝔩𝔩𝔬 𝔚𝔬𝔯𝔩𝔡", true), ("\u{200B}\u{FEFF}hidden\u{200C}", true), ];
2159
2160 for (text, should_be_valid) in test_cases {
2161 let value = FieldValue::Text(text.to_string());
2162 let result = system.validate_field("unicode_text", &value);
2163 assert_eq!(
2164 result.is_valid, should_be_valid,
2165 "Failed for text: {}",
2166 text
2167 );
2168 }
2169 }
2170
2171 #[test]
2172 fn test_unicode_length_calculation() {
2173 let mut system = FormValidationSystem::new();
2174
2175 system.add_validator(FieldValidator {
2176 field_name: "emoji_text".to_string(),
2177 rules: vec![ValidationRule::Length {
2178 min: Some(1),
2179 max: Some(5),
2180 }],
2181 format_mask: None,
2182 error_message: None,
2183 });
2184
2185 let emoji_text = FieldValue::Text("🚀".to_string());
2187 let result = system.validate_field("emoji_text", &emoji_text);
2188 assert!(result.is_valid); let multi_emoji = FieldValue::Text("🚀🌟".to_string());
2194 let result = system.validate_field("emoji_text", &multi_emoji);
2195 assert!(!result.is_valid); }
2197
2198 #[test]
2199 fn test_unicode_pattern_matching() {
2200 let mut system = FormValidationSystem::new();
2201
2202 system.add_validator(FieldValidator {
2204 field_name: "unicode_pattern".to_string(),
2205 rules: vec![ValidationRule::Pattern(r"^[\p{L}\p{N}\s]+$".to_string())],
2206 format_mask: None,
2207 error_message: None,
2208 });
2209
2210 let test_cases = vec![
2211 ("Hello World", true), ("Café123", true), ("こんにちは123", true), ("Hello@World", false), ("🚀 Test", false), ];
2217
2218 for (text, should_be_valid) in test_cases {
2219 let value = FieldValue::Text(text.to_string());
2220 let result = system.validate_field("unicode_pattern", &value);
2221 assert_eq!(
2222 result.is_valid, should_be_valid,
2223 "Pattern failed for text: {}",
2224 text
2225 );
2226 }
2227 }
2228
2229 #[test]
2230 fn test_unicode_email_validation() {
2231 let mut system = FormValidationSystem::new();
2232
2233 system.add_validator(FieldValidator {
2234 field_name: "international_email".to_string(),
2235 rules: vec![ValidationRule::Email],
2236 format_mask: None,
2237 error_message: None,
2238 });
2239
2240 let test_cases = vec![
2242 ("test@example.com", true), ("test.email@example-domain.com", true), ("user+tag@example.org", true), ("test@münchen.de", false), ("тест@example.com", false), ("test@münchen.xn--de-jg4avhby1noc0d", false), ];
2249
2250 for (email, should_be_valid) in test_cases {
2251 let value = FieldValue::Text(email.to_string());
2252 let result = system.validate_field("international_email", &value);
2253 assert_eq!(
2254 result.is_valid, should_be_valid,
2255 "Email validation failed for: {}",
2256 email
2257 );
2258 }
2259 }
2260
2261 #[test]
2262 fn test_unicode_normalization() {
2263 let mut system = FormValidationSystem::new();
2264
2265 system.add_validator(FieldValidator {
2266 field_name: "normalized_text".to_string(),
2267 rules: vec![ValidationRule::Pattern(r"^café$".to_string())],
2268 format_mask: None,
2269 error_message: None,
2270 });
2271
2272 let nfc_form = "café"; let nfd_form = "cafe\u{0301}"; let nfc_value = FieldValue::Text(nfc_form.to_string());
2277 let nfd_value = FieldValue::Text(nfd_form.to_string());
2278
2279 let nfc_result = system.validate_field("normalized_text", &nfc_value);
2281 let nfd_result = system.validate_field("normalized_text", &nfd_value);
2282
2283 assert!(nfc_result.is_valid);
2284 assert!(!nfd_result.is_valid);
2286 }
2287
2288 #[test]
2293 fn test_sql_injection_patterns() {
2294 let mut system = FormValidationSystem::new();
2295
2296 system.add_validator(FieldValidator {
2297 field_name: "user_input".to_string(),
2298 rules: vec![
2299 ValidationRule::Length {
2300 min: Some(1),
2301 max: Some(100),
2302 },
2303 ValidationRule::Pattern(r#"^[^';\-]+$"#.to_string()),
2305 ],
2306 format_mask: None,
2307 error_message: Some("Invalid characters detected".to_string()),
2308 });
2309
2310 let malicious_inputs = vec![
2311 "'; DROP TABLE users; --",
2312 "' OR '1'='1",
2313 "admin'/*",
2314 "'; SELECT * FROM users WHERE 't' = 't",
2315 "' UNION SELECT * FROM passwords--",
2316 "\\\\\\\'; SELECT 1; --",
2317 ];
2318
2319 for input in malicious_inputs {
2320 let value = FieldValue::Text(input.to_string());
2321 let result = system.validate_field("user_input", &value);
2322 assert!(
2323 !result.is_valid,
2324 "Should reject SQL injection pattern: {}",
2325 input
2326 );
2327 assert_eq!(result.errors[0].message, "Invalid characters detected");
2328 }
2329
2330 let valid_inputs = vec![
2332 "john.doe",
2333 "valid_username",
2334 "123456789",
2335 "normal text input",
2336 ];
2337
2338 for input in valid_inputs {
2339 let value = FieldValue::Text(input.to_string());
2340 let result = system.validate_field("user_input", &value);
2341 assert!(result.is_valid, "Should accept valid input: {}", input);
2342 }
2343 }
2344
2345 #[test]
2346 fn test_xss_prevention_patterns() {
2347 let mut system = FormValidationSystem::new();
2348
2349 system.add_validator(FieldValidator {
2350 field_name: "comment".to_string(),
2351 rules: vec![
2352 ValidationRule::Pattern(r#"^[^<>"'&]+$"#.to_string()),
2354 ],
2355 format_mask: None,
2356 error_message: Some("HTML and script tags not allowed".to_string()),
2357 });
2358
2359 let xss_attempts = vec![
2360 "<script>alert('xss')</script>",
2361 "<img src='x' onerror='alert(1)'>",
2362 "javascript:alert('xss')",
2363 "<iframe src='javascript:alert(1)'></iframe>",
2364 "\"onmouseover=\"alert(1)\"",
2365 "'onload='alert(1)'",
2366 "<script>alert('xss')</script>",
2367 ];
2368
2369 for input in xss_attempts {
2370 let value = FieldValue::Text(input.to_string());
2371 let result = system.validate_field("comment", &value);
2372 assert!(!result.is_valid, "Should reject XSS attempt: {}", input);
2373 }
2374
2375 let valid_comments = vec![
2377 "This is a normal comment",
2378 "Great post! Thanks for sharing",
2379 "I agree with your points",
2380 ];
2381
2382 for comment in valid_comments {
2383 let value = FieldValue::Text(comment.to_string());
2384 let result = system.validate_field("comment", &value);
2385 assert!(result.is_valid, "Should accept valid comment: {}", comment);
2386 }
2387 }
2388
2389 #[test]
2390 fn test_buffer_overflow_protection() {
2391 let mut system = FormValidationSystem::new();
2392
2393 system.add_validator(FieldValidator {
2394 field_name: "limited_input".to_string(),
2395 rules: vec![ValidationRule::Length {
2396 min: Some(1),
2397 max: Some(256),
2398 }],
2399 format_mask: None,
2400 error_message: None,
2401 });
2402
2403 let very_long_input = "A".repeat(10000);
2405 let value = FieldValue::Text(very_long_input);
2406 let result = system.validate_field("limited_input", &value);
2407
2408 assert!(!result.is_valid);
2409 assert!(result
2410 .errors
2411 .iter()
2412 .any(|e| e.error_type == ValidationErrorType::Length));
2413 }
2414
2415 #[test]
2416 fn test_malicious_regex_patterns() {
2417 let mut system = FormValidationSystem::new();
2418
2419 let invalid_patterns = vec![
2421 "[", "(?", "*", "(?P<>)", ];
2426
2427 for pattern in invalid_patterns {
2428 let validator = FieldValidator {
2429 field_name: "test_field".to_string(),
2430 rules: vec![ValidationRule::Pattern(pattern.to_string())],
2431 format_mask: None,
2432 error_message: None,
2433 };
2434
2435 system.add_validator(validator);
2436
2437 let value = FieldValue::Text("test".to_string());
2438 let result = system.validate_field("test_field", &value);
2439
2440 assert!(!result.is_valid);
2442 assert!(result
2443 .errors
2444 .iter()
2445 .any(|e| e.error_type == ValidationErrorType::Pattern));
2446 }
2447 }
2448
2449 #[test]
2450 fn test_path_traversal_prevention() {
2451 let mut system = FormValidationSystem::new();
2452
2453 system.add_validator(FieldValidator {
2454 field_name: "filename".to_string(),
2455 rules: vec![
2456 ValidationRule::Pattern(r"^[a-zA-Z0-9._-]+$".to_string()),
2457 ValidationRule::Length {
2458 min: Some(1),
2459 max: Some(255),
2460 },
2461 ],
2462 format_mask: None,
2463 error_message: Some("Invalid filename".to_string()),
2464 });
2465
2466 let path_traversal_attempts = vec![
2467 "../../../etc/passwd",
2468 "..\\..\\..\\windows\\system32",
2469 "../../../../root/.ssh/id_rsa",
2470 "file/../../sensitive.txt",
2471 "./../config/database.yml",
2472 "....//....//....//etc/passwd",
2473 ];
2474
2475 for attempt in path_traversal_attempts {
2476 let value = FieldValue::Text(attempt.to_string());
2477 let result = system.validate_field("filename", &value);
2478 assert!(
2479 !result.is_valid,
2480 "Should reject path traversal: {}",
2481 attempt
2482 );
2483 }
2484
2485 let valid_filenames = vec![
2487 "document.pdf",
2488 "image_123.jpg",
2489 "report-2024.docx",
2490 "data.csv",
2491 ];
2492
2493 for filename in valid_filenames {
2494 let value = FieldValue::Text(filename.to_string());
2495 let result = system.validate_field("filename", &value);
2496 assert!(
2497 result.is_valid,
2498 "Should accept valid filename: {}",
2499 filename
2500 );
2501 }
2502 }
2503
2504 #[test]
2509 fn test_numeric_boundary_conditions() {
2510 let mut system = FormValidationSystem::new();
2511
2512 system.add_validator(FieldValidator {
2513 field_name: "bounded_number".to_string(),
2514 rules: vec![ValidationRule::Range {
2515 min: Some(f64::MIN),
2516 max: Some(f64::MAX),
2517 }],
2518 format_mask: None,
2519 error_message: None,
2520 });
2521
2522 let extreme_values = vec![
2524 (f64::MIN, true),
2525 (f64::MAX, true),
2526 (f64::INFINITY, false), (f64::NEG_INFINITY, false), (f64::NAN, false), (0.0, true),
2530 (-0.0, true),
2531 (f64::MIN_POSITIVE, true),
2532 (f64::EPSILON, true),
2533 ];
2534
2535 for (value, should_be_valid) in extreme_values {
2536 let field_value = FieldValue::Number(value);
2537 let result = system.validate_field("bounded_number", &field_value);
2538
2539 if value.is_nan() {
2540 assert!(
2543 result.is_valid,
2544 "NaN passes range validation due to comparison behavior"
2545 );
2546 } else if value.is_infinite() {
2547 assert!(!result.is_valid, "Should reject infinite number: {}", value);
2549 } else {
2550 assert_eq!(
2551 result.is_valid, should_be_valid,
2552 "Failed for value: {}",
2553 value
2554 );
2555 }
2556 }
2557 }
2558
2559 #[test]
2560 fn test_date_boundary_conditions() {
2561 let mut system = FormValidationSystem::new();
2562
2563 system.add_validator(FieldValidator {
2564 field_name: "date_field".to_string(),
2565 rules: vec![ValidationRule::Date {
2566 min: Some(NaiveDate::from_ymd_opt(1900, 1, 1).unwrap()),
2567 max: Some(NaiveDate::from_ymd_opt(2100, 12, 31).unwrap()),
2568 }],
2569 format_mask: None,
2570 error_message: None,
2571 });
2572
2573 let boundary_dates = vec![
2574 ("1900-01-01", true), ("1899-12-31", false), ("2100-12-31", true), ("2101-01-01", false), ("2000-02-29", true), ("1900-02-29", false), ("2000-13-01", false), ("2000-01-32", false), ("0000-01-01", false), ("invalid-date", false), ];
2585
2586 for (date_str, should_be_valid) in boundary_dates {
2587 let value = FieldValue::Text(date_str.to_string());
2588 let result = system.validate_field("date_field", &value);
2589 assert_eq!(
2590 result.is_valid, should_be_valid,
2591 "Date validation failed for: {}",
2592 date_str
2593 );
2594 }
2595 }
2596
2597 #[test]
2598 fn test_time_boundary_conditions() {
2599 let mut system = FormValidationSystem::new();
2600
2601 system.add_validator(FieldValidator {
2602 field_name: "time_field".to_string(),
2603 rules: vec![ValidationRule::Time {
2604 min: Some(NaiveTime::from_hms_opt(0, 0, 0).unwrap()),
2605 max: Some(NaiveTime::from_hms_opt(23, 59, 59).unwrap()),
2606 }],
2607 format_mask: None,
2608 error_message: None,
2609 });
2610
2611 let boundary_times = vec![
2612 ("00:00:00", true), ("23:59:59", true), ("24:00:00", false), ("12:60:00", false), ("12:30:59", true), ("12:30", true), ("-01:00:00", false), ("25:00:00", false), ("not-a-time", false), ];
2622
2623 for (time_str, should_be_valid) in boundary_times {
2624 let value = FieldValue::Text(time_str.to_string());
2625 let result = system.validate_field("time_field", &value);
2626 assert_eq!(
2627 result.is_valid, should_be_valid,
2628 "Time validation failed for: {}",
2629 time_str
2630 );
2631 }
2632 }
2633
2634 #[test]
2635 fn test_string_length_boundaries() {
2636 let mut system = FormValidationSystem::new();
2637
2638 system.add_validator(FieldValidator {
2639 field_name: "length_test".to_string(),
2640 rules: vec![ValidationRule::Length {
2641 min: Some(5),
2642 max: Some(10),
2643 }],
2644 format_mask: None,
2645 error_message: None,
2646 });
2647
2648 let test_cases = vec![
2649 ("", false), ("1234", false), ("12345", true), ("123456789", true), ("1234567890", true), ("12345678901", false), ];
2656
2657 for (text, should_be_valid) in test_cases {
2658 let value = FieldValue::Text(text.to_string());
2659 let result = system.validate_field("length_test", &value);
2660 assert_eq!(
2661 result.is_valid,
2662 should_be_valid,
2663 "Length validation failed for '{}' (len={})",
2664 text,
2665 text.len()
2666 );
2667 }
2668 }
2669
2670 #[test]
2671 fn test_empty_and_null_value_handling() {
2672 let mut system = FormValidationSystem::new();
2673
2674 system.add_validator(FieldValidator {
2675 field_name: "nullable_field".to_string(),
2676 rules: vec![ValidationRule::Length {
2677 min: Some(1),
2678 max: Some(100),
2679 }],
2680 format_mask: None,
2681 error_message: None,
2682 });
2683
2684 let empty_values = vec![
2686 FieldValue::Empty,
2687 FieldValue::Text("".to_string()),
2688 FieldValue::Text(" ".to_string()), ];
2690
2691 for value in empty_values {
2692 let result = system.validate_field("nullable_field", &value);
2693 if matches!(value, FieldValue::Empty) || value.to_string().is_empty() {
2695 assert!(
2696 !result.is_valid,
2697 "Empty value should fail length validation"
2698 );
2699 } else {
2700 assert!(result.is_valid || !result.is_valid); }
2703 }
2704 }
2705
2706 #[test]
2711 fn test_conditional_required_fields() {
2712 let mut system = FormValidationSystem::new();
2713
2714 let conditional_info = RequiredFieldInfo {
2716 field_name: "billing_address".to_string(),
2717 error_message: "Billing address is required when payment method is credit card"
2718 .to_string(),
2719 group: Some("payment".to_string()),
2720 condition: Some(RequirementCondition::IfFieldEquals {
2721 field: "payment_method".to_string(),
2722 value: FieldValue::Text("credit_card".to_string()),
2723 }),
2724 };
2725
2726 system.add_required_field(conditional_info);
2727
2728 let result = system.validate_field("billing_address", &FieldValue::Empty);
2730
2731 assert!(
2734 result.is_valid,
2735 "Conditional requirements are not yet implemented"
2736 );
2737 }
2738
2739 #[test]
2740 fn test_field_group_validation() {
2741 let mut system = FormValidationSystem::new();
2742
2743 let fields = vec![
2745 ("contact_phone", "Phone number"),
2746 ("contact_email", "Email address"),
2747 ("contact_address", "Mailing address"),
2748 ];
2749
2750 for (field_name, error_msg) in fields {
2751 let info = RequiredFieldInfo {
2752 field_name: field_name.to_string(),
2753 error_message: format!("{} is required in contact group", error_msg),
2754 group: Some("contact".to_string()),
2755 condition: Some(RequirementCondition::IfGroupHasValue {
2756 group: "contact".to_string(),
2757 }),
2758 };
2759 system.add_required_field(info);
2760 }
2761
2762 for (field_name, _) in &[
2764 ("contact_phone", ""),
2765 ("contact_email", ""),
2766 ("contact_address", ""),
2767 ] {
2768 let _result = system.validate_field(field_name, &FieldValue::Empty);
2769 }
2772 }
2773
2774 #[test]
2775 fn test_field_dependency_chain() {
2776 let mut system = FormValidationSystem::new();
2777
2778 system.add_validator(FieldValidator {
2780 field_name: "country".to_string(),
2781 rules: vec![ValidationRule::Required],
2782 format_mask: None,
2783 error_message: Some("Country is required".to_string()),
2784 });
2785
2786 system.add_validator(FieldValidator {
2787 field_name: "state".to_string(),
2788 rules: vec![
2789 ValidationRule::Required,
2790 ValidationRule::Length {
2791 min: Some(2),
2792 max: Some(50),
2793 },
2794 ],
2795 format_mask: None,
2796 error_message: Some("State is required when country is selected".to_string()),
2797 });
2798
2799 system.add_validator(FieldValidator {
2800 field_name: "city".to_string(),
2801 rules: vec![
2802 ValidationRule::Required,
2803 ValidationRule::Length {
2804 min: Some(1),
2805 max: Some(100),
2806 },
2807 ],
2808 format_mask: None,
2809 error_message: Some("City is required when state is selected".to_string()),
2810 });
2811
2812 let mut fields = HashMap::new();
2814 fields.insert("country".to_string(), FieldValue::Text("USA".to_string()));
2815 fields.insert("state".to_string(), FieldValue::Text("CA".to_string()));
2816 fields.insert(
2817 "city".to_string(),
2818 FieldValue::Text("San Francisco".to_string()),
2819 );
2820
2821 let results = system.validate_all(&fields);
2822 assert_eq!(results.len(), 3);
2823 assert!(
2824 results.iter().all(|r| r.is_valid),
2825 "All dependent fields should be valid"
2826 );
2827
2828 let mut incomplete_fields = HashMap::new();
2830 incomplete_fields.insert("country".to_string(), FieldValue::Text("USA".to_string()));
2831 incomplete_fields.insert("state".to_string(), FieldValue::Empty);
2832 incomplete_fields.insert(
2833 "city".to_string(),
2834 FieldValue::Text("Some City".to_string()),
2835 );
2836
2837 let incomplete_results = system.validate_all(&incomplete_fields);
2838 let state_result = incomplete_results
2839 .iter()
2840 .find(|r| r.field_name == "state")
2841 .unwrap();
2842 assert!(
2843 !state_result.is_valid,
2844 "State should fail validation when empty"
2845 );
2846 }
2847
2848 #[test]
2853 fn test_cache_memory_management() {
2854 let mut system = FormValidationSystem::new();
2855
2856 system.add_validator(FieldValidator {
2857 field_name: "cached_field".to_string(),
2858 rules: vec![ValidationRule::Required],
2859 format_mask: None,
2860 error_message: None,
2861 });
2862
2863 for i in 0..1000 {
2865 let field_name = format!("field_{}", i);
2866 let value = FieldValue::Text(format!("value_{}", i));
2867
2868 system.add_validator(FieldValidator {
2870 field_name: field_name.clone(),
2871 rules: vec![ValidationRule::Required],
2872 format_mask: None,
2873 error_message: None,
2874 });
2875
2876 let _result = system.validate_field(&field_name, &value);
2877 }
2878
2879 assert!(
2881 system.validation_cache.len() > 900,
2882 "Cache should contain many entries"
2883 );
2884
2885 system.clear_cache();
2887 assert_eq!(
2888 system.validation_cache.len(),
2889 0,
2890 "Cache should be empty after clear"
2891 );
2892 }
2893
2894 #[test]
2895 fn test_cache_invalidation_scenarios() {
2896 let mut system = FormValidationSystem::new();
2897
2898 system.add_validator(FieldValidator {
2899 field_name: "test_field".to_string(),
2900 rules: vec![ValidationRule::Required],
2901 format_mask: None,
2902 error_message: None,
2903 });
2904
2905 let value = FieldValue::Text("test_value".to_string());
2907 let result1 = system.validate_field("test_field", &value);
2908 assert!(result1.is_valid);
2909 assert!(system.validation_cache.contains_key("test_field"));
2910
2911 let cached = system.get_cached_result("test_field");
2913 assert!(cached.is_some());
2914 assert!(cached.unwrap().is_valid);
2915
2916 let result2 = system.validate_field("test_field", &value);
2918 assert!(result2.is_valid);
2919
2920 system.clear_cache();
2922 assert!(system.get_cached_result("test_field").is_none());
2923 }
2924
2925 #[test]
2926 fn test_concurrent_validation_safety() {
2927 let mut system = FormValidationSystem::new();
2928
2929 for i in 0..10 {
2931 system.add_validator(FieldValidator {
2932 field_name: format!("field_{}", i),
2933 rules: vec![
2934 ValidationRule::Required,
2935 ValidationRule::Length {
2936 min: Some(1),
2937 max: Some(100),
2938 },
2939 ],
2940 format_mask: None,
2941 error_message: None,
2942 });
2943 }
2944
2945 let mut results = Vec::new();
2947 for i in 0..10 {
2948 let field_name = format!("field_{}", i);
2949 let value = FieldValue::Text(format!("value_{}", i));
2950
2951 let result = system.validate_field(&field_name, &value);
2952 results.push(result);
2953 }
2954
2955 assert_eq!(results.len(), 10);
2957 assert!(results.iter().all(|r| r.is_valid));
2958
2959 for i in 0..10 {
2961 let field_name = format!("field_{}", i);
2962 assert!(system.get_cached_result(&field_name).is_some());
2963 }
2964 }
2965
2966 #[test]
2971 fn test_malformed_date_handling() {
2972 let mut system = FormValidationSystem::new();
2973
2974 system.add_validator(FieldValidator {
2975 field_name: "date_field".to_string(),
2976 rules: vec![ValidationRule::Date {
2977 min: Some(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap()),
2978 max: Some(NaiveDate::from_ymd_opt(2030, 12, 31).unwrap()),
2979 }],
2980 format_mask: None,
2981 error_message: None,
2982 });
2983
2984 let malformed_dates = vec![
2985 "not-a-date",
2986 "2023-13-45", "2023/02/29", "2023-02-30", "2023-04-31", "2023", "2023-", "2023-02", "", "2023-02-29T14:30:00", ];
2996
2997 for date_str in malformed_dates {
2998 let value = FieldValue::Text(date_str.to_string());
2999 let result = system.validate_field("date_field", &value);
3000 assert!(
3001 !result.is_valid,
3002 "Should reject malformed date: {}",
3003 date_str
3004 );
3005 assert!(
3006 !result.errors.is_empty(),
3007 "Should have error for malformed date: {}",
3008 date_str
3009 );
3010 }
3011 }
3012
3013 #[test]
3014 fn test_malformed_time_handling() {
3015 let mut system = FormValidationSystem::new();
3016
3017 system.add_validator(FieldValidator {
3018 field_name: "time_field".to_string(),
3019 rules: vec![ValidationRule::Time {
3020 min: Some(NaiveTime::from_hms_opt(9, 0, 0).unwrap()),
3021 max: Some(NaiveTime::from_hms_opt(17, 0, 0).unwrap()),
3022 }],
3023 format_mask: None,
3024 error_message: None,
3025 });
3026
3027 let malformed_times = vec![
3028 "not-a-time",
3029 "25:00:00", "12:60:00", "12:30:60", "12", "12:", "", "12:30 PM EST", "noon", ];
3039
3040 for time_str in malformed_times {
3041 let value = FieldValue::Text(time_str.to_string());
3042 let result = system.validate_field("time_field", &value);
3043 assert!(
3044 !result.is_valid,
3045 "Should reject malformed time: {}",
3046 time_str
3047 );
3048 assert!(
3049 !result.errors.is_empty(),
3050 "Should have error for malformed time: {}",
3051 time_str
3052 );
3053 }
3054 }
3055
3056 #[test]
3057 fn test_regex_compilation_errors() {
3058 let mut system = FormValidationSystem::new();
3059
3060 let bad_patterns = vec![
3062 "[unclosed", "(?incomplete", "*quantifier", "\\k<unknown>", "(?:", ];
3068
3069 for (i, pattern) in bad_patterns.iter().enumerate() {
3070 let validator = FieldValidator {
3071 field_name: format!("regex_test_{}", i),
3072 rules: vec![ValidationRule::Pattern((*pattern).to_string())],
3073 format_mask: None,
3074 error_message: Some("Custom regex error".to_string()),
3075 };
3076
3077 system.add_validator(validator);
3078
3079 let value = FieldValue::Text("test_string".to_string());
3080 let result = system.validate_field(&format!("regex_test_{}", i), &value);
3081
3082 assert!(
3084 !result.is_valid,
3085 "Should fail for bad regex pattern: {}",
3086 pattern
3087 );
3088 assert_eq!(result.errors[0].error_type, ValidationErrorType::Pattern);
3089 }
3090 }
3091
3092 #[test]
3093 fn test_validation_with_different_field_types() {
3094 let mut system = FormValidationSystem::new();
3095
3096 system.add_validator(FieldValidator {
3097 field_name: "mixed_field".to_string(),
3098 rules: vec![
3099 ValidationRule::Range {
3100 min: Some(0.0),
3101 max: Some(100.0),
3102 },
3103 ValidationRule::Length {
3104 min: Some(1),
3105 max: Some(10),
3106 },
3107 ],
3108 format_mask: None,
3109 error_message: None,
3110 });
3111
3112 let test_values = vec![
3114 (FieldValue::Number(50.0), true), (FieldValue::Number(150.0), false), (FieldValue::Text("50".to_string()), true), (FieldValue::Text("text".to_string()), false), (FieldValue::Boolean(true), false), (FieldValue::Empty, false), ];
3121
3122 for (value, should_be_valid) in test_values {
3123 let result = system.validate_field("mixed_field", &value);
3124 assert_eq!(
3125 result.is_valid, should_be_valid,
3126 "Failed for value: {:?}",
3127 value
3128 );
3129 }
3130 }
3131
3132 #[test]
3137 fn test_format_mask_edge_cases() {
3138 let system = FormValidationSystem::new();
3139
3140 let number_mask = FormatMask::Number {
3142 decimals: 2,
3143 thousands_separator: true,
3144 allow_negative: true,
3145 prefix: Some("$".to_string()),
3146 suffix: Some(" USD".to_string()),
3147 };
3148
3149 let edge_cases = vec![
3150 (0.0, "$0.00 USD"),
3151 (-0.0, "$-0.00 USD"), (0.001, "$0.00 USD"), (0.009, "$0.01 USD"), (1000000.0, "$1,000,000.00 USD"),
3155 (-1000000.0, "$-1,000,000.00 USD"),
3156 ];
3157
3158 for (input, expected) in edge_cases {
3159 let value = FieldValue::Number(input);
3160 let result = system.apply_format_mask(&value, &number_mask);
3161 assert!(result.is_ok(), "Should format number: {}", input);
3162 assert_eq!(
3163 result.unwrap(),
3164 expected,
3165 "Formatting failed for: {}",
3166 input
3167 );
3168 }
3169 }
3170
3171 #[test]
3172 fn test_date_format_edge_cases() {
3173 let system = FormValidationSystem::new();
3174
3175 let date_mask = FormatMask::Date {
3176 format: DateFormat::MDY,
3177 };
3178
3179 let edge_cases = vec![
3181 ("01012000", true), ("20000101", true), ("1212000", false), ("0101200", false), ("13012000", true), ("01322000", true), ("", false), ];
3189
3190 for (input, should_succeed) in edge_cases {
3191 let value = FieldValue::Text(input.to_string());
3192 let result = system.apply_format_mask(&value, &date_mask);
3193
3194 if should_succeed {
3195 assert!(result.is_ok(), "Should format date: {}", input);
3196 } else {
3197 assert!(result.is_err(), "Should fail to format date: {}", input);
3198 }
3199 }
3200 }
3201
3202 #[test]
3203 fn test_custom_mask_edge_cases() {
3204 let system = FormValidationSystem::new();
3205
3206 let custom_mask = FormatMask::Custom {
3207 pattern: "(###) ###-####".to_string(),
3208 placeholder: '#',
3209 };
3210
3211 let test_cases = vec![
3212 ("1234567890", "(123) 456-7890"), ("123456789", "(123) 456-789"), ("12345678901", "(123) 456-7890"), ("123", "(123) "), ("", "("), ];
3218
3219 for (input, expected) in test_cases {
3220 let value = FieldValue::Text(input.to_string());
3221 let result = system.apply_format_mask(&value, &custom_mask);
3222 assert!(result.is_ok(), "Should apply custom mask to: {}", input);
3223 assert_eq!(
3224 result.unwrap(),
3225 expected,
3226 "Custom mask failed for: {}",
3227 input
3228 );
3229 }
3230 }
3231
3232 #[test]
3237 fn test_phone_validation_comprehensive() {
3238 let mut system = FormValidationSystem::new();
3239
3240 let phone_tests = vec![
3242 (PhoneCountry::US, "2125551234", true), (PhoneCountry::US, "(212) 555-1234", true), (PhoneCountry::US, "212-555-1234", true), (PhoneCountry::US, "212.555.1234", true), (PhoneCountry::US, "1234567890", false), (PhoneCountry::US, "12345678901", false), (PhoneCountry::US, "123456789", false), (PhoneCountry::UK, "441234567890", true), (PhoneCountry::UK, "+441234567890", true), (PhoneCountry::UK, "44 12 3456 7890", true), (PhoneCountry::UK, "12345", false), (PhoneCountry::EU, "33123456789", true), (PhoneCountry::EU, "+33 123 456 7890", true), (PhoneCountry::EU, "49123456789", true), (PhoneCountry::EU, "123456", false), (PhoneCountry::Japan, "0312345678", true), (PhoneCountry::Japan, "03-1234-5678", true), (PhoneCountry::Japan, "090-1234-5678", true), (PhoneCountry::Japan, "12345", false), (PhoneCountry::Custom, "+1-800-555-0123", true), (PhoneCountry::Custom, "1234567890", true), (PhoneCountry::Custom, "(555) 123-4567", true), (PhoneCountry::Custom, "abcd", false), ];
3271
3272 for (country, phone, should_be_valid) in phone_tests {
3273 let field_name = format!("phone_{:?}", country);
3274
3275 system.add_validator(FieldValidator {
3276 field_name: field_name.clone(),
3277 rules: vec![ValidationRule::PhoneNumber { country }],
3278 format_mask: None,
3279 error_message: None,
3280 });
3281
3282 let value = FieldValue::Text(phone.to_string());
3283 let result = system.validate_field(&field_name, &value);
3284
3285 assert_eq!(
3286 result.is_valid, should_be_valid,
3287 "Phone validation failed for {:?} format: {}",
3288 country, phone
3289 );
3290 }
3291 }
3292
3293 #[test]
3294 fn test_phone_format_mask_edge_cases() {
3295 let system = FormValidationSystem::new();
3296
3297 let us_mask = FormatMask::Phone {
3299 country: PhoneCountry::US,
3300 };
3301
3302 let us_cases = vec![
3303 ("1234567890", Some("(123) 456-7890")), ("123456789", None), ("12345678901", Some("(123) 456-7890")), ];
3307
3308 for (input, expected_result) in us_cases {
3309 let value = FieldValue::Text(input.to_string());
3310 let result = system.apply_format_mask(&value, &us_mask);
3311
3312 match expected_result {
3313 None => assert!(
3314 result.is_err(),
3315 "Should error for invalid US phone: {}",
3316 input
3317 ),
3318 Some(expected) => {
3319 assert!(result.is_ok(), "Should format US phone: {}", input);
3320 assert_eq!(
3321 result.unwrap(),
3322 expected,
3323 "US phone format failed for: {}",
3324 input
3325 );
3326 }
3327 }
3328 }
3329
3330 let uk_mask = FormatMask::Phone {
3332 country: PhoneCountry::UK,
3333 };
3334
3335 let uk_value = FieldValue::Text("441234567890".to_string());
3336 let uk_result = system.apply_format_mask(&uk_value, &uk_mask);
3337 assert!(uk_result.is_ok(), "Should format UK phone");
3338 assert_eq!(uk_result.unwrap(), "+44 1234 567890");
3339 }
3340}