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.to_string(),
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 let email_regex = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
446 .expect("Email regex pattern should be valid");
447 if email_regex.is_match(&text) {
448 Ok(())
449 } else {
450 Err("Invalid email address".to_string())
451 }
452 }
453 ValidationRule::Url => {
454 let text = value.to_string();
455 let url_regex = Regex::new(r"^https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}")
456 .expect("URL regex pattern should be valid");
457 if url_regex.is_match(&text) {
458 Ok(())
459 } else {
460 Err("Invalid URL".to_string())
461 }
462 }
463 ValidationRule::PhoneNumber { country } => {
464 self.validate_phone_number(&value.to_string(), *country)
465 }
466 ValidationRule::CreditCard => self.validate_credit_card(&value.to_string()),
467 ValidationRule::Date { min, max } => {
468 let text = value.to_string();
470 let date = NaiveDate::parse_from_str(&text, "%Y-%m-%d")
471 .map_err(|e| format!("Invalid date format: {}", e))?;
472
473 if let Some(min_date) = min {
474 if date < *min_date {
475 return Err(format!("Date must be on or after {}", min_date));
476 }
477 }
478 if let Some(max_date) = max {
479 if date > *max_date {
480 return Err(format!("Date must be on or before {}", max_date));
481 }
482 }
483 Ok(())
484 }
485 ValidationRule::Time { min, max } => {
486 let text = value.to_string();
488
489 if text.contains(':') {
491 let parts: Vec<&str> = text.split(':').collect();
492 if parts.len() >= 2 {
493 if let Ok(hour) = parts[0].parse::<u32>() {
495 if hour > 23 {
496 return Err("Invalid hour: must be 0-23".to_string());
497 }
498 }
499 if let Ok(minute) = parts[1].parse::<u32>() {
501 if minute > 59 {
502 return Err("Invalid minute: must be 0-59".to_string());
503 }
504 }
505 if parts.len() >= 3 {
507 if let Ok(second) = parts[2].parse::<u32>() {
508 if second > 59 {
509 return Err("Invalid second: must be 0-59".to_string());
510 }
511 }
512 }
513 }
514 }
515
516 let time = NaiveTime::parse_from_str(&text, "%H:%M:%S")
517 .or_else(|_| NaiveTime::parse_from_str(&text, "%H:%M"))
518 .map_err(|e| format!("Invalid time format: {}", e))?;
519
520 if let Some(min_time) = min {
521 if time < *min_time {
522 return Err(format!("Time must be at or after {}", min_time));
523 }
524 }
525 if let Some(max_time) = max {
526 if time > *max_time {
527 return Err(format!("Time must be at or before {}", max_time));
528 }
529 }
530 Ok(())
531 }
532 ValidationRule::Custom { name, validator } => {
533 if validator(value) {
534 Ok(())
535 } else {
536 Err(format!("Custom validation '{}' failed", name))
537 }
538 }
539 }
540 }
541
542 fn validate_phone_number(&self, phone: &str, country: PhoneCountry) -> Result<(), String> {
544 let pattern = match country {
545 PhoneCountry::US => r"^\(?[2-9]\d{2}\)?[-.\s]?\d{3}[-.\s]?\d{4}$",
546 PhoneCountry::UK => r"^\+?44\s?\d{2}\s?\d{4}\s?\d{4}$",
547 PhoneCountry::EU => r"^\+?[0-9]{2,3}\s?[0-9]{2,4}\s?[0-9]{2,4}\s?[0-9]{2,4}$",
548 PhoneCountry::Japan => r"^0\d{1,4}-?\d{1,4}-?\d{4}$",
549 PhoneCountry::Custom => r"^[0-9+\-\s\(\)]+$",
550 };
551
552 let re = Regex::new(pattern).map_err(|e| format!("Invalid phone regex pattern: {}", e))?;
553 if re.is_match(phone) {
554 Ok(())
555 } else {
556 Err(format!("Invalid phone number format for {:?}", country))
557 }
558 }
559
560 fn validate_credit_card(&self, card_number: &str) -> Result<(), String> {
562 let digits: Vec<u32> = card_number
563 .chars()
564 .filter(|c| c.is_ascii_digit())
565 .filter_map(|c| c.to_digit(10))
566 .collect();
567
568 if digits.len() < 13 || digits.len() > 19 {
569 return Err("Invalid credit card number length".to_string());
570 }
571
572 let mut sum = 0;
574 let mut alternate = false;
575
576 for digit in digits.iter().rev() {
577 let mut n = *digit;
578 if alternate {
579 n *= 2;
580 if n > 9 {
581 n -= 9;
582 }
583 }
584 sum += n;
585 alternate = !alternate;
586 }
587
588 if sum % 10 == 0 {
589 Ok(())
590 } else {
591 Err("Invalid credit card number".to_string())
592 }
593 }
594
595 fn apply_format_mask(&self, value: &FieldValue, mask: &FormatMask) -> Result<String, String> {
597 match mask {
598 FormatMask::Number {
599 decimals,
600 thousands_separator,
601 allow_negative,
602 prefix,
603 suffix,
604 } => {
605 let num = value.to_number();
606
607 if !allow_negative && num < 0.0 {
608 return Err("Negative numbers not allowed".to_string());
609 }
610
611 let mut formatted = format!("{:.prec$}", num, prec = decimals);
612
613 if *thousands_separator {
614 let parts: Vec<&str> = formatted.split('.').collect();
616 let integer_part = parts[0];
617 let decimal_part = parts.get(1);
618
619 let mut result = String::new();
620 for (i, c) in integer_part.chars().rev().enumerate() {
621 if i > 0 && i % 3 == 0 {
622 result.insert(0, ',');
623 }
624 result.insert(0, c);
625 }
626
627 if let Some(dec) = decimal_part {
628 result.push('.');
629 result.push_str(dec);
630 }
631
632 formatted = result;
633 }
634
635 let mut result = String::new();
636 if let Some(p) = prefix {
637 result.push_str(p);
638 }
639 result.push_str(&formatted);
640 if let Some(s) = suffix {
641 result.push_str(s);
642 }
643
644 Ok(result)
645 }
646 FormatMask::Date { format } => self.format_date(&value.to_string(), *format),
647 FormatMask::Time {
648 format,
649 use_24_hour,
650 } => self.format_time(&value.to_string(), *format, *use_24_hour),
651 FormatMask::Phone { country } => self.format_phone(&value.to_string(), *country),
652 FormatMask::SSN => self.format_ssn(&value.to_string()),
653 FormatMask::ZipCode { plus_four } => self.format_zip(&value.to_string(), *plus_four),
654 FormatMask::CreditCard => self.format_credit_card(&value.to_string()),
655 FormatMask::Custom {
656 pattern,
657 placeholder,
658 } => self.apply_custom_mask(&value.to_string(), pattern, *placeholder),
659 }
660 }
661
662 fn format_date(&self, date_str: &str, format: DateFormat) -> Result<String, String> {
664 let digits: String = date_str.chars().filter(|c| c.is_ascii_digit()).collect();
666
667 if digits.len() < 8 {
668 return Err("Invalid date format".to_string());
669 }
670
671 let is_yyyy_format = if digits.len() >= 4 {
673 digits[0..4].parse::<u32>().unwrap_or(0) > 1900
674 } else {
675 false
676 };
677
678 let (year, month, day) = if is_yyyy_format {
680 (&digits[0..4], &digits[4..6], &digits[6..8])
682 } else {
683 (&digits[4..8], &digits[0..2], &digits[2..4])
685 };
686
687 let formatted = match format {
688 DateFormat::MDY => {
689 format!("{}/{}/{}", month, day, year)
690 }
691 DateFormat::DMY => {
692 format!("{}/{}/{}", day, month, year)
693 }
694 DateFormat::YMD => {
695 format!("{}-{}-{}", year, month, day)
696 }
697 DateFormat::DotDMY => {
698 format!("{}.{}.{}", day, month, year)
699 }
700 DateFormat::DashMDY => {
701 format!("{}-{}-{}", month, day, year)
702 }
703 };
704
705 Ok(formatted)
706 }
707
708 fn format_time(
710 &self,
711 time_str: &str,
712 format: TimeFormat,
713 use_24_hour: bool,
714 ) -> Result<String, String> {
715 let digits: String = time_str.chars().filter(|c| c.is_ascii_digit()).collect();
716
717 if digits.len() < 4 {
718 return Err("Invalid time format".to_string());
719 }
720
721 let hours: u32 = digits[0..2].parse().unwrap_or(0);
722 let minutes: u32 = digits[2..4].parse().unwrap_or(0);
723 let seconds: u32 = if digits.len() >= 6 {
724 digits[4..6].parse().unwrap_or(0)
725 } else {
726 0
727 };
728
729 let formatted = match format {
730 TimeFormat::HM => {
731 if use_24_hour {
732 format!("{:02}:{:02}", hours, minutes)
733 } else {
734 let (h, am_pm) = if hours == 0 {
735 (12, "AM")
736 } else if hours < 12 {
737 (hours, "AM")
738 } else if hours == 12 {
739 (12, "PM")
740 } else {
741 (hours - 12, "PM")
742 };
743 format!("{:02}:{:02} {}", h, minutes, am_pm)
744 }
745 }
746 TimeFormat::HMAM => {
747 let (h, am_pm) = if hours == 0 {
749 (12, "AM")
750 } else if hours < 12 {
751 (hours, "AM")
752 } else if hours == 12 {
753 (12, "PM")
754 } else {
755 (hours - 12, "PM")
756 };
757 format!("{:02}:{:02} {}", h, minutes, am_pm)
758 }
759 TimeFormat::HMS | TimeFormat::HMSAM => {
760 if use_24_hour {
761 format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
762 } else {
763 let (h, am_pm) = if hours == 0 {
764 (12, "AM")
765 } else if hours < 12 {
766 (hours, "AM")
767 } else if hours == 12 {
768 (12, "PM")
769 } else {
770 (hours - 12, "PM")
771 };
772 format!("{:02}:{:02}:{:02} {}", h, minutes, seconds, am_pm)
773 }
774 }
775 };
776
777 Ok(formatted)
778 }
779
780 fn format_phone(&self, phone: &str, country: PhoneCountry) -> Result<String, String> {
782 let digits: String = phone.chars().filter(|c| c.is_ascii_digit()).collect();
783
784 let formatted = match country {
785 PhoneCountry::US => {
786 if digits.len() >= 10 {
787 format!("({}) {}-{}", &digits[0..3], &digits[3..6], &digits[6..10])
788 } else {
789 return Err("Invalid US phone number".to_string());
790 }
791 }
792 PhoneCountry::UK => {
793 if digits.len() >= 11 {
794 format!("+{} {} {}", &digits[0..2], &digits[2..6], &digits[6..])
795 } else {
796 return Err("Invalid UK phone number".to_string());
797 }
798 }
799 _ => digits,
800 };
801
802 Ok(formatted)
803 }
804
805 fn format_ssn(&self, ssn: &str) -> Result<String, String> {
807 let digits: String = ssn.chars().filter(|c| c.is_ascii_digit()).collect();
808
809 if digits.len() != 9 {
810 return Err("SSN must be 9 digits".to_string());
811 }
812
813 Ok(format!(
814 "{}-{}-{}",
815 &digits[0..3],
816 &digits[3..5],
817 &digits[5..9]
818 ))
819 }
820
821 fn format_zip(&self, zip: &str, plus_four: bool) -> Result<String, String> {
823 let digits: String = zip.chars().filter(|c| c.is_ascii_digit()).collect();
824
825 if plus_four {
826 if digits.len() != 9 {
827 return Err("ZIP+4 must be 9 digits".to_string());
828 }
829 Ok(format!("{}-{}", &digits[0..5], &digits[5..9]))
830 } else {
831 if digits.len() < 5 {
832 return Err("ZIP must be at least 5 digits".to_string());
833 }
834 Ok(digits[0..5].to_string())
835 }
836 }
837
838 fn format_credit_card(&self, card: &str) -> Result<String, String> {
840 let digits: String = card.chars().filter(|c| c.is_ascii_digit()).collect();
841
842 if digits.len() < 13 || digits.len() > 19 {
843 return Err("Invalid credit card number length".to_string());
844 }
845
846 let mut formatted = String::new();
848 for (i, c) in digits.chars().enumerate() {
849 if i > 0 && i % 4 == 0 {
850 formatted.push(' ');
851 }
852 formatted.push(c);
853 }
854
855 Ok(formatted)
856 }
857
858 fn apply_custom_mask(
860 &self,
861 value: &str,
862 pattern: &str,
863 placeholder: char,
864 ) -> Result<String, String> {
865 let mut result = String::new();
866 let mut value_chars = value.chars();
867
868 for pattern_char in pattern.chars() {
869 if pattern_char == placeholder {
870 if let Some(c) = value_chars.next() {
871 result.push(c);
872 } else {
873 break;
874 }
875 } else {
876 result.push(pattern_char);
877 }
878 }
879
880 Ok(result)
881 }
882
883 fn get_error_type(&self, rule: &ValidationRule) -> ValidationErrorType {
885 match rule {
886 ValidationRule::Required => ValidationErrorType::Required,
887 ValidationRule::Range { .. } => ValidationErrorType::Range,
888 ValidationRule::Length { .. } => ValidationErrorType::Length,
889 ValidationRule::Pattern(_) => ValidationErrorType::Pattern,
890 _ => ValidationErrorType::Custom,
891 }
892 }
893
894 pub fn validate_all(&mut self, fields: &HashMap<String, FieldValue>) -> Vec<ValidationResult> {
896 let mut results = Vec::new();
897
898 for (field_name, value) in fields {
899 results.push(self.validate_field(field_name, value));
900 }
901
902 results
903 }
904
905 pub fn clear_cache(&mut self) {
907 self.validation_cache.clear();
908 }
909
910 pub fn get_cached_result(&self, field_name: &str) -> Option<&ValidationResult> {
912 self.validation_cache.get(field_name)
913 }
914}
915
916impl fmt::Display for ValidationResult {
917 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
918 if self.is_valid {
919 write!(f, "Valid")
920 } else {
921 write!(f, "Invalid: {} errors", self.errors.len())
922 }
923 }
924}
925
926#[cfg(test)]
927mod tests {
928 use super::*;
929
930 #[test]
931 fn test_required_field_validation() {
932 let mut system = FormValidationSystem::new();
933
934 let info = RequiredFieldInfo {
935 field_name: "name".to_string(),
936 error_message: "Name is required".to_string(),
937 group: None,
938 condition: None,
939 };
940
941 system.add_required_field(info);
942
943 let result = system.validate_field("name", &FieldValue::Empty);
944 assert!(!result.is_valid);
945 assert_eq!(result.errors.len(), 1);
946 assert_eq!(result.errors[0].error_type, ValidationErrorType::Required);
947 }
948
949 #[test]
950 fn test_email_validation() {
951 let mut system = FormValidationSystem::new();
952
953 let validator = FieldValidator {
954 field_name: "email".to_string(),
955 rules: vec![ValidationRule::Email],
956 format_mask: None,
957 error_message: None,
958 };
959
960 system.add_validator(validator);
961
962 let valid_result =
963 system.validate_field("email", &FieldValue::Text("test@example.com".to_string()));
964 assert!(valid_result.is_valid);
965
966 let invalid_result =
967 system.validate_field("email", &FieldValue::Text("invalid-email".to_string()));
968 assert!(!invalid_result.is_valid);
969 }
970
971 #[test]
972 fn test_phone_format_mask() {
973 let system = FormValidationSystem::new();
974
975 let mask = FormatMask::Phone {
976 country: PhoneCountry::US,
977 };
978
979 let result = system.apply_format_mask(&FieldValue::Text("5551234567".to_string()), &mask);
980
981 assert!(result.is_ok());
982 assert_eq!(result.unwrap(), "(555) 123-4567");
983 }
984
985 #[test]
986 fn test_credit_card_validation() {
987 let system = FormValidationSystem::new();
988
989 let valid = system.validate_credit_card("4532015112830366");
991 assert!(valid.is_ok());
992
993 let invalid = system.validate_credit_card("1234567890123456");
995 assert!(invalid.is_err());
996 }
997
998 #[test]
999 fn test_ssn_format() {
1000 let system = FormValidationSystem::new();
1001
1002 let result = system.format_ssn("123456789");
1003 assert!(result.is_ok());
1004 assert_eq!(result.unwrap(), "123-45-6789");
1005 }
1006
1007 #[test]
1008 fn test_range_validation() {
1009 let mut system = FormValidationSystem::new();
1010
1011 let validator = FieldValidator {
1012 field_name: "age".to_string(),
1013 rules: vec![ValidationRule::Range {
1014 min: Some(18.0),
1015 max: Some(100.0),
1016 }],
1017 format_mask: None,
1018 error_message: None,
1019 };
1020
1021 system.add_validator(validator);
1022
1023 let valid = system.validate_field("age", &FieldValue::Number(25.0));
1024 assert!(valid.is_valid);
1025
1026 let too_young = system.validate_field("age", &FieldValue::Number(15.0));
1027 assert!(!too_young.is_valid);
1028
1029 let too_old = system.validate_field("age", &FieldValue::Number(150.0));
1030 assert!(!too_old.is_valid);
1031 }
1032
1033 #[test]
1034 fn test_custom_mask() {
1035 let system = FormValidationSystem::new();
1036
1037 let mask = FormatMask::Custom {
1038 pattern: "(###) ###-####".to_string(),
1039 placeholder: '#',
1040 };
1041
1042 let result = system.apply_format_mask(&FieldValue::Text("5551234567".to_string()), &mask);
1043
1044 assert!(result.is_ok());
1045 assert_eq!(result.unwrap(), "(555) 123-4567");
1046 }
1047
1048 #[test]
1049 fn test_validation_settings() {
1050 let settings = ValidationSettings::default();
1051 assert!(settings.real_time_validation);
1052 assert!(settings.highlight_errors);
1053 assert!(settings.show_error_messages);
1054 }
1055
1056 #[test]
1057 fn test_url_validation() {
1058 let mut system = FormValidationSystem::new();
1059
1060 let validator = FieldValidator {
1061 field_name: "website".to_string(),
1062 rules: vec![ValidationRule::Url],
1063 format_mask: None,
1064 error_message: None,
1065 };
1066
1067 system.add_validator(validator);
1068
1069 let valid = system.validate_field(
1071 "website",
1072 &FieldValue::Text("https://example.com".to_string()),
1073 );
1074 assert!(valid.is_valid);
1075
1076 let valid_http =
1077 system.validate_field("website", &FieldValue::Text("http://test.org".to_string()));
1078 assert!(valid_http.is_valid);
1079
1080 let invalid = system.validate_field("website", &FieldValue::Text("not-a-url".to_string()));
1082 assert!(!invalid.is_valid);
1083 }
1084
1085 #[test]
1086 fn test_length_validation() {
1087 let mut system = FormValidationSystem::new();
1088
1089 let validator = FieldValidator {
1090 field_name: "comment".to_string(),
1091 rules: vec![ValidationRule::Length {
1092 min: Some(10),
1093 max: Some(100),
1094 }],
1095 format_mask: None,
1096 error_message: None,
1097 };
1098
1099 system.add_validator(validator);
1100
1101 let valid = system.validate_field(
1103 "comment",
1104 &FieldValue::Text("This is a valid comment.".to_string()),
1105 );
1106 assert!(valid.is_valid);
1107
1108 let too_short = system.validate_field("comment", &FieldValue::Text("Short".to_string()));
1110 assert!(!too_short.is_valid);
1111
1112 let too_long = system.validate_field("comment", &FieldValue::Text("x".repeat(150)));
1114 assert!(!too_long.is_valid);
1115 }
1116
1117 #[test]
1118 fn test_pattern_validation() {
1119 let mut system = FormValidationSystem::new();
1120
1121 let validator = FieldValidator {
1122 field_name: "code".to_string(),
1123 rules: vec![ValidationRule::Pattern(r"^[A-Z]{3}-\d{3}$".to_string())],
1124 format_mask: None,
1125 error_message: Some("Code must be in format ABC-123".to_string()),
1126 };
1127
1128 system.add_validator(validator);
1129
1130 let valid = system.validate_field("code", &FieldValue::Text("ABC-123".to_string()));
1132 assert!(valid.is_valid);
1133
1134 let invalid = system.validate_field("code", &FieldValue::Text("abc-123".to_string()));
1136 assert!(!invalid.is_valid);
1137 assert!(invalid.errors[0].message.contains("ABC-123"));
1138 }
1139
1140 #[test]
1141 fn test_date_validation() {
1142 let mut system = FormValidationSystem::new();
1143
1144 let validator = FieldValidator {
1145 field_name: "birthdate".to_string(),
1146 rules: vec![ValidationRule::Date {
1147 min: Some(NaiveDate::from_ymd_opt(1900, 1, 1).unwrap()),
1148 max: Some(NaiveDate::from_ymd_opt(2020, 12, 31).unwrap()),
1149 }],
1150 format_mask: None,
1151 error_message: None,
1152 };
1153
1154 system.add_validator(validator);
1155
1156 let valid = system.validate_field("birthdate", &FieldValue::Text("1990-05-15".to_string()));
1158 assert!(valid.is_valid);
1159
1160 let too_early =
1162 system.validate_field("birthdate", &FieldValue::Text("1850-01-01".to_string()));
1163 assert!(!too_early.is_valid);
1164 }
1165
1166 #[test]
1167 fn test_time_validation() {
1168 let mut system = FormValidationSystem::new();
1169
1170 let validator = FieldValidator {
1171 field_name: "appointment".to_string(),
1172 rules: vec![ValidationRule::Time {
1173 min: Some(NaiveTime::from_hms_opt(9, 0, 0).unwrap()),
1174 max: Some(NaiveTime::from_hms_opt(17, 0, 0).unwrap()),
1175 }],
1176 format_mask: None,
1177 error_message: None,
1178 };
1179
1180 system.add_validator(validator);
1181
1182 let valid = system.validate_field("appointment", &FieldValue::Text("14:30".to_string()));
1184 assert!(valid.is_valid);
1185
1186 let too_early =
1188 system.validate_field("appointment", &FieldValue::Text("08:00".to_string()));
1189 assert!(!too_early.is_valid);
1190 }
1191
1192 #[test]
1193 fn test_phone_number_uk() {
1194 let mut system = FormValidationSystem::new();
1195
1196 let validator = FieldValidator {
1197 field_name: "phone_uk".to_string(),
1198 rules: vec![ValidationRule::PhoneNumber {
1199 country: PhoneCountry::UK,
1200 }],
1201 format_mask: None,
1202 error_message: None,
1203 };
1204
1205 system.add_validator(validator);
1206
1207 let valid =
1209 system.validate_field("phone_uk", &FieldValue::Text("441234567890".to_string()));
1210 assert!(valid.is_valid);
1211
1212 let invalid = system.validate_field("phone_uk", &FieldValue::Text("12345".to_string()));
1214 assert!(!invalid.is_valid);
1215 }
1216
1217 #[test]
1218 fn test_zip_format_with_plus_four() {
1219 let system = FormValidationSystem::new();
1220
1221 let result_plus = system.format_zip("123456789", true);
1223 assert!(result_plus.is_ok());
1224 assert_eq!(result_plus.unwrap(), "12345-6789");
1225
1226 let result_regular = system.format_zip("12345", false);
1228 assert!(result_regular.is_ok());
1229 assert_eq!(result_regular.unwrap(), "12345");
1230
1231 let invalid = system.format_zip("12345", true);
1233 assert!(invalid.is_err());
1234 }
1235
1236 #[test]
1237 fn test_number_format_mask() {
1238 let system = FormValidationSystem::new();
1239
1240 let mask = FormatMask::Number {
1241 decimals: 2,
1242 thousands_separator: true,
1243 allow_negative: true,
1244 prefix: Some("$".to_string()),
1245 suffix: Some(" USD".to_string()),
1246 };
1247
1248 let result = system.apply_format_mask(&FieldValue::Number(1234567.89), &mask);
1249 assert!(result.is_ok());
1250 assert_eq!(result.unwrap(), "$1,234,567.89 USD");
1251
1252 let negative_result = system.apply_format_mask(&FieldValue::Number(-1234.56), &mask);
1254 assert!(negative_result.is_ok());
1255 assert_eq!(negative_result.unwrap(), "$-1,234.56 USD");
1256 }
1257
1258 #[test]
1259 fn test_date_format_mask() {
1260 let system = FormValidationSystem::new();
1261
1262 let mask_mdy = FormatMask::Date {
1264 format: DateFormat::MDY,
1265 };
1266 let result_mdy =
1267 system.apply_format_mask(&FieldValue::Text("01152022".to_string()), &mask_mdy);
1268 assert!(result_mdy.is_ok());
1269 assert_eq!(result_mdy.unwrap(), "01/15/2022");
1270
1271 let mask_ymd = FormatMask::Date {
1273 format: DateFormat::YMD,
1274 };
1275 let result_ymd =
1276 system.apply_format_mask(&FieldValue::Text("20220115".to_string()), &mask_ymd);
1277 assert!(result_ymd.is_ok());
1278 assert_eq!(result_ymd.unwrap(), "2022-01-15");
1279 }
1280
1281 #[test]
1282 fn test_time_format_mask() {
1283 let system = FormValidationSystem::new();
1284
1285 let mask_24 = FormatMask::Time {
1287 format: TimeFormat::HMS,
1288 use_24_hour: true,
1289 };
1290 let result_24 = system.apply_format_mask(&FieldValue::Text("143045".to_string()), &mask_24);
1291 assert!(result_24.is_ok());
1292 assert_eq!(result_24.unwrap(), "14:30:45");
1293
1294 let mask_12 = FormatMask::Time {
1296 format: TimeFormat::HMSAM,
1297 use_24_hour: false,
1298 };
1299 let result_12 = system.apply_format_mask(&FieldValue::Text("143045".to_string()), &mask_12);
1300 assert!(result_12.is_ok());
1301 assert_eq!(result_12.unwrap(), "02:30:45 PM");
1302 }
1303
1304 #[test]
1305 fn test_validation_cache() {
1306 let mut system = FormValidationSystem::new();
1307
1308 let validator = FieldValidator {
1309 field_name: "cached_field".to_string(),
1310 rules: vec![ValidationRule::Required],
1311 format_mask: None,
1312 error_message: None,
1313 };
1314
1315 system.add_validator(validator);
1316
1317 let result1 = system.validate_field("cached_field", &FieldValue::Text("value".to_string()));
1319 assert!(result1.is_valid);
1320
1321 let cached = system.get_cached_result("cached_field");
1323 assert!(cached.is_some());
1324 assert!(cached.unwrap().is_valid);
1325
1326 system.clear_cache();
1328 let cached_after_clear = system.get_cached_result("cached_field");
1329 assert!(cached_after_clear.is_none());
1330 }
1331
1332 #[test]
1333 fn test_validation_error_types() {
1334 let error_required = ValidationError {
1335 field_name: "test".to_string(),
1336 error_type: ValidationErrorType::Required,
1337 message: "Field is required".to_string(),
1338 details: None,
1339 };
1340 assert_eq!(error_required.error_type, ValidationErrorType::Required);
1341
1342 let error_range = ValidationError {
1343 field_name: "test".to_string(),
1344 error_type: ValidationErrorType::Range,
1345 message: "Value out of range".to_string(),
1346 details: Some("Must be between 1 and 100".to_string()),
1347 };
1348 assert_eq!(error_range.error_type, ValidationErrorType::Range);
1349 assert!(error_range.details.is_some());
1350 }
1351
1352 #[test]
1353 fn test_field_validator_with_multiple_rules() {
1354 let mut system = FormValidationSystem::new();
1355
1356 let validator = FieldValidator {
1357 field_name: "username".to_string(),
1358 rules: vec![
1359 ValidationRule::Required,
1360 ValidationRule::Length {
1361 min: Some(3),
1362 max: Some(20),
1363 },
1364 ValidationRule::Pattern(r"^[a-zA-Z0-9_]+$".to_string()),
1365 ],
1366 format_mask: None,
1367 error_message: None,
1368 };
1369
1370 system.add_validator(validator);
1371
1372 let valid = system.validate_field("username", &FieldValue::Text("user_123".to_string()));
1374 assert!(valid.is_valid);
1375
1376 let too_short = system.validate_field("username", &FieldValue::Text("ab".to_string()));
1378 assert!(!too_short.is_valid);
1379
1380 let invalid_chars =
1382 system.validate_field("username", &FieldValue::Text("user@123".to_string()));
1383 assert!(!invalid_chars.is_valid);
1384 }
1385
1386 #[test]
1387 fn test_credit_card_format() {
1388 let system = FormValidationSystem::new();
1389
1390 let result = system.format_credit_card("4532015112830366");
1391 assert!(result.is_ok());
1392 assert_eq!(result.unwrap(), "4532 0151 1283 0366");
1393
1394 let too_short = system.format_credit_card("123");
1396 assert!(too_short.is_err());
1397
1398 let too_long = system.format_credit_card("12345678901234567890");
1399 assert!(too_long.is_err());
1400 }
1401
1402 #[test]
1403 fn test_required_field_with_group() {
1404 let mut system = FormValidationSystem::new();
1405
1406 let info = RequiredFieldInfo {
1407 field_name: "address".to_string(),
1408 error_message: "Address is required".to_string(),
1409 group: Some("contact_info".to_string()),
1410 condition: None,
1411 };
1412
1413 system.add_required_field(info);
1414
1415 let result = system.validate_field("address", &FieldValue::Empty);
1416 assert!(!result.is_valid);
1417 assert_eq!(result.errors[0].error_type, ValidationErrorType::Required);
1418 }
1419
1420 #[test]
1421 fn test_validation_result_display() {
1422 let valid_result = ValidationResult {
1423 field_name: "test".to_string(),
1424 is_valid: true,
1425 errors: vec![],
1426 warnings: vec![],
1427 formatted_value: None,
1428 };
1429 assert_eq!(format!("{}", valid_result), "Valid");
1430
1431 let invalid_result = ValidationResult {
1432 field_name: "test".to_string(),
1433 is_valid: false,
1434 errors: vec![
1435 ValidationError {
1436 field_name: "test".to_string(),
1437 error_type: ValidationErrorType::Required,
1438 message: "Required".to_string(),
1439 details: None,
1440 },
1441 ValidationError {
1442 field_name: "test".to_string(),
1443 error_type: ValidationErrorType::Length,
1444 message: "Too short".to_string(),
1445 details: None,
1446 },
1447 ],
1448 warnings: vec![],
1449 formatted_value: None,
1450 };
1451 assert_eq!(format!("{}", invalid_result), "Invalid: 2 errors");
1452 }
1453
1454 #[test]
1455 fn test_validate_all_fields() {
1456 let mut system = FormValidationSystem::new();
1457
1458 system.add_validator(FieldValidator {
1460 field_name: "name".to_string(),
1461 rules: vec![ValidationRule::Required],
1462 format_mask: None,
1463 error_message: None,
1464 });
1465
1466 system.add_validator(FieldValidator {
1467 field_name: "age".to_string(),
1468 rules: vec![ValidationRule::Range {
1469 min: Some(0.0),
1470 max: Some(120.0),
1471 }],
1472 format_mask: None,
1473 error_message: None,
1474 });
1475
1476 let mut fields = HashMap::new();
1477 fields.insert("name".to_string(), FieldValue::Text("John".to_string()));
1478 fields.insert("age".to_string(), FieldValue::Number(30.0));
1479
1480 let results = system.validate_all(&fields);
1481 assert_eq!(results.len(), 2);
1482 assert!(results.iter().all(|r| r.is_valid));
1483 }
1484
1485 #[test]
1486 fn test_validation_settings_advanced() {
1487 let settings = ValidationSettings {
1488 validate_on_change: true,
1489 show_format_hints: true,
1490 auto_format: false,
1491 allow_partial: false,
1492 real_time_validation: true,
1493 highlight_errors: true,
1494 show_error_messages: true,
1495 };
1496
1497 let mut system = FormValidationSystem::new();
1498 system.settings = settings.clone();
1499
1500 assert!(system.settings.validate_on_change);
1501 assert!(system.settings.show_format_hints);
1502 assert!(!system.settings.auto_format);
1503 }
1504
1505 #[test]
1506 fn test_complex_pattern_validation() {
1507 let mut system = FormValidationSystem::new();
1508
1509 system.add_validator(FieldValidator {
1511 field_name: "product_code".to_string(),
1512 rules: vec![ValidationRule::Pattern(
1513 r"^[A-Z]{3}-\d{4}-[A-Z]\d$".to_string(),
1514 )],
1515 format_mask: None,
1516 error_message: Some("Invalid product code format".to_string()),
1517 });
1518
1519 let valid_code = FieldValue::Text("ABC-1234-A5".to_string());
1520 let result = system.validate_field("product_code", &valid_code);
1521 assert!(result.is_valid);
1522
1523 let invalid_code = FieldValue::Text("abc-1234-a5".to_string());
1524 let result = system.validate_field("product_code", &invalid_code);
1525 assert!(!result.is_valid);
1526 }
1527
1528 #[test]
1529 fn test_currency_format_mask() {
1530 let system = FormValidationSystem::new();
1531
1532 let mask = FormatMask::Number {
1533 decimals: 2,
1534 thousands_separator: true,
1535 allow_negative: false,
1536 prefix: Some("$".to_string()),
1537 suffix: None,
1538 };
1539
1540 let result = system.apply_format_mask(&FieldValue::Number(1234567.89), &mask);
1541 assert!(result.is_ok());
1542 }
1544
1545 #[test]
1546 fn test_international_phone_formats() {
1547 let mut system = FormValidationSystem::new();
1548
1549 system.add_validator(FieldValidator {
1551 field_name: "us_phone".to_string(),
1552 rules: vec![ValidationRule::PhoneNumber {
1553 country: PhoneCountry::US,
1554 }],
1555 format_mask: None,
1556 error_message: None,
1557 });
1558
1559 let valid_us = FieldValue::Text("(555) 123-4567".to_string());
1560 assert!(system.validate_field("us_phone", &valid_us).is_valid);
1561
1562 system.add_validator(FieldValidator {
1564 field_name: "uk_phone".to_string(),
1565 rules: vec![ValidationRule::PhoneNumber {
1566 country: PhoneCountry::UK,
1567 }],
1568 format_mask: None,
1569 error_message: None,
1570 });
1571
1572 let valid_uk = FieldValue::Text("+44 20 1234 5678".to_string());
1573 assert!(system.validate_field("uk_phone", &valid_uk).is_valid);
1574 }
1575
1576 #[test]
1577 fn test_phone_validation_all_countries() {
1578 let mut system = FormValidationSystem::new();
1580
1581 system.add_validator(FieldValidator {
1583 field_name: "eu_phone".to_string(),
1584 rules: vec![ValidationRule::PhoneNumber {
1585 country: PhoneCountry::EU,
1586 }],
1587 format_mask: None,
1588 error_message: None,
1589 });
1590
1591 let valid_eu = FieldValue::Text("+33 123 456 7890".to_string());
1592 assert!(system.validate_field("eu_phone", &valid_eu).is_valid);
1593
1594 let invalid_eu = FieldValue::Text("123-456".to_string());
1595 assert!(!system.validate_field("eu_phone", &invalid_eu).is_valid);
1596
1597 system.add_validator(FieldValidator {
1599 field_name: "japan_phone".to_string(),
1600 rules: vec![ValidationRule::PhoneNumber {
1601 country: PhoneCountry::Japan,
1602 }],
1603 format_mask: None,
1604 error_message: None,
1605 });
1606
1607 let valid_japan = FieldValue::Text("03-1234-5678".to_string());
1608 assert!(system.validate_field("japan_phone", &valid_japan).is_valid);
1609
1610 let invalid_japan = FieldValue::Text("123".to_string());
1611 assert!(
1612 !system
1613 .validate_field("japan_phone", &invalid_japan)
1614 .is_valid
1615 );
1616
1617 system.add_validator(FieldValidator {
1619 field_name: "custom_phone".to_string(),
1620 rules: vec![ValidationRule::PhoneNumber {
1621 country: PhoneCountry::Custom,
1622 }],
1623 format_mask: None,
1624 error_message: None,
1625 });
1626
1627 let valid_custom = FieldValue::Text("+1-234-567-8900".to_string());
1628 assert!(
1629 system
1630 .validate_field("custom_phone", &valid_custom)
1631 .is_valid
1632 );
1633
1634 let invalid_custom = FieldValue::Text("not a phone".to_string());
1635 assert!(
1636 !system
1637 .validate_field("custom_phone", &invalid_custom)
1638 .is_valid
1639 );
1640 }
1641
1642 #[test]
1643 fn test_credit_card_validation_edge_cases() {
1644 let mut system = FormValidationSystem::new();
1646
1647 system.add_validator(FieldValidator {
1648 field_name: "cc".to_string(),
1649 rules: vec![ValidationRule::CreditCard],
1650 format_mask: None,
1651 error_message: None,
1652 });
1653
1654 let too_short = FieldValue::Text("123456789012".to_string());
1656 let result = system.validate_field("cc", &too_short);
1657 assert!(!result.is_valid);
1658 assert!(result.errors[0].message.contains("length"));
1659
1660 let too_long = FieldValue::Text("12345678901234567890".to_string());
1662 let result = system.validate_field("cc", &too_long);
1663 assert!(!result.is_valid);
1664 assert!(result.errors[0].message.contains("length"));
1665
1666 let invalid_luhn = FieldValue::Text("4111111111111112".to_string()); let result = system.validate_field("cc", &invalid_luhn);
1669 assert!(!result.is_valid);
1670 assert!(result.errors[0].message.contains("Invalid credit card"));
1671
1672 let valid_cc = FieldValue::Text("4111111111111111".to_string()); let result = system.validate_field("cc", &valid_cc);
1675 assert!(result.is_valid);
1676 }
1677
1678 #[test]
1679 fn test_time_validation_with_range() {
1680 let mut system = FormValidationSystem::new();
1682
1683 system.add_validator(FieldValidator {
1684 field_name: "appointment".to_string(),
1685 rules: vec![ValidationRule::Time {
1686 min: Some(NaiveTime::from_hms_opt(9, 0, 0).unwrap()),
1687 max: Some(NaiveTime::from_hms_opt(17, 0, 0).unwrap()),
1688 }],
1689 format_mask: None,
1690 error_message: None,
1691 });
1692
1693 let valid = FieldValue::Text("10:30:00".to_string());
1695 assert!(system.validate_field("appointment", &valid).is_valid);
1696
1697 let too_early = FieldValue::Text("08:30:00".to_string());
1699 let result = system.validate_field("appointment", &too_early);
1700 assert!(!result.is_valid);
1701 assert!(result.errors[0].message.contains("at or after"));
1702
1703 let too_late = FieldValue::Text("18:00:00".to_string());
1705 let result = system.validate_field("appointment", &too_late);
1706 assert!(!result.is_valid);
1707 assert!(result.errors[0].message.contains("at or before"));
1708
1709 let invalid = FieldValue::Text("not a time".to_string());
1711 let result = system.validate_field("appointment", &invalid);
1712 assert!(!result.is_valid);
1713 }
1714
1715 #[test]
1716 fn test_custom_validator() {
1717 fn is_even(value: &FieldValue) -> bool {
1719 if let FieldValue::Text(s) = value {
1720 if let Ok(n) = s.parse::<i32>() {
1721 return n % 2 == 0;
1722 }
1723 }
1724 false
1725 }
1726
1727 let mut system = FormValidationSystem::new();
1728
1729 system.add_validator(FieldValidator {
1730 field_name: "even_number".to_string(),
1731 rules: vec![ValidationRule::Custom {
1732 name: "even_check".to_string(),
1733 validator: is_even,
1734 }],
1735 format_mask: None,
1736 error_message: None,
1737 });
1738
1739 let valid = FieldValue::Text("42".to_string());
1741 assert!(system.validate_field("even_number", &valid).is_valid);
1742
1743 let invalid = FieldValue::Text("43".to_string());
1745 let result = system.validate_field("even_number", &invalid);
1746 assert!(!result.is_valid);
1747 assert!(result.errors[0]
1748 .message
1749 .contains("Custom validation 'even_check' failed"));
1750
1751 let non_number = FieldValue::Text("abc".to_string());
1753 let result = system.validate_field("even_number", &non_number);
1754 assert!(!result.is_valid);
1755 }
1756
1757 #[test]
1758 fn test_format_mask_number_with_all_options() {
1759 let system = FormValidationSystem::new();
1761
1762 let mask = FormatMask::Number {
1763 decimals: 3,
1764 thousands_separator: true,
1765 allow_negative: true,
1766 prefix: Some("€ ".to_string()),
1767 suffix: Some(" EUR".to_string()),
1768 };
1769
1770 let value = FieldValue::Number(12345.6789);
1772 let formatted = system.apply_format_mask(&value, &mask);
1773 assert!(formatted.is_ok());
1774 assert_eq!(formatted.unwrap(), "€ 12,345.679 EUR");
1775
1776 let value = FieldValue::Number(-9876.543);
1778 let formatted = system.apply_format_mask(&value, &mask);
1779 assert!(formatted.is_ok());
1780 assert_eq!(formatted.unwrap(), "€ -9,876.543 EUR");
1781
1782 let mask_no_neg = FormatMask::Number {
1784 decimals: 2,
1785 thousands_separator: false,
1786 allow_negative: false,
1787 prefix: None,
1788 suffix: None,
1789 };
1790
1791 let value = FieldValue::Number(-123.456);
1792 let formatted = system.apply_format_mask(&value, &mask_no_neg);
1793 assert!(formatted.is_err());
1794 assert!(formatted
1795 .unwrap_err()
1796 .contains("Negative numbers not allowed"));
1797 }
1798
1799 #[test]
1800 fn test_ssn_and_zip_format_masks() {
1801 let system = FormValidationSystem::new();
1803
1804 let ssn_mask = FormatMask::SSN;
1806 let ssn_value = FieldValue::Text("123456789".to_string());
1807 let formatted = system.apply_format_mask(&ssn_value, &ssn_mask);
1808 assert!(formatted.is_ok());
1809 assert_eq!(formatted.unwrap(), "123-45-6789");
1810
1811 let invalid_ssn = FieldValue::Text("12345".to_string());
1813 let formatted = system.apply_format_mask(&invalid_ssn, &ssn_mask);
1814 assert!(formatted.is_err());
1815 assert!(formatted.unwrap_err().contains("9 digits"));
1816
1817 let zip5_mask = FormatMask::ZipCode { plus_four: false };
1819 let zip5_value = FieldValue::Text("12345".to_string());
1820 let formatted = system.apply_format_mask(&zip5_value, &zip5_mask);
1821 assert!(formatted.is_ok());
1822 assert_eq!(formatted.unwrap(), "12345");
1823
1824 let invalid_zip5 = FieldValue::Text("1234".to_string());
1826 let formatted = system.apply_format_mask(&invalid_zip5, &zip5_mask);
1827 assert!(formatted.is_err());
1828
1829 let zip9_mask = FormatMask::ZipCode { plus_four: true };
1831 let zip9_value = FieldValue::Text("123456789".to_string());
1832 let formatted = system.apply_format_mask(&zip9_value, &zip9_mask);
1833 assert!(formatted.is_ok());
1834 assert_eq!(formatted.unwrap(), "12345-6789");
1835
1836 let invalid_zip9 = FieldValue::Text("12345".to_string());
1838 let formatted = system.apply_format_mask(&invalid_zip9, &zip9_mask);
1839 assert!(formatted.is_err());
1840 }
1841
1842 #[test]
1843 fn test_date_format_masks() {
1844 let system = FormValidationSystem::new();
1846
1847 let date = NaiveDate::from_ymd_opt(2024, 3, 15).unwrap();
1848 let value = FieldValue::Text(date.to_string());
1849
1850 let mask = FormatMask::Date {
1852 format: DateFormat::MDY,
1853 };
1854 let formatted = system.apply_format_mask(&value, &mask);
1855 assert!(formatted.is_ok());
1856 assert_eq!(formatted.unwrap(), "03/15/2024");
1857
1858 let mask = FormatMask::Date {
1860 format: DateFormat::DMY,
1861 };
1862 let formatted = system.apply_format_mask(&value, &mask);
1863 assert!(formatted.is_ok());
1864 assert_eq!(formatted.unwrap(), "15/03/2024");
1865
1866 let mask = FormatMask::Date {
1868 format: DateFormat::YMD,
1869 };
1870 let formatted = system.apply_format_mask(&value, &mask);
1871 assert!(formatted.is_ok());
1872 assert_eq!(formatted.unwrap(), "2024-03-15");
1873
1874 let mask = FormatMask::Date {
1876 format: DateFormat::DotDMY,
1877 };
1878 let formatted = system.apply_format_mask(&value, &mask);
1879 assert!(formatted.is_ok());
1880 assert_eq!(formatted.unwrap(), "15.03.2024");
1881
1882 let mask = FormatMask::Date {
1884 format: DateFormat::DashMDY,
1885 };
1886 let formatted = system.apply_format_mask(&value, &mask);
1887 assert!(formatted.is_ok());
1888 assert_eq!(formatted.unwrap(), "03-15-2024");
1889 }
1890
1891 #[test]
1892 fn test_time_format_masks() {
1893 let system = FormValidationSystem::new();
1895
1896 let time_am = NaiveTime::from_hms_opt(9, 30, 45).unwrap();
1898 let value_am = FieldValue::Text(time_am.to_string());
1899
1900 let time_pm = NaiveTime::from_hms_opt(15, 45, 30).unwrap();
1902 let value_pm = FieldValue::Text(time_pm.to_string());
1903
1904 let mask = FormatMask::Time {
1906 format: TimeFormat::HM,
1907 use_24_hour: true,
1908 };
1909 let formatted = system.apply_format_mask(&value_am, &mask);
1910 assert!(formatted.is_ok());
1911 assert_eq!(formatted.unwrap(), "09:30");
1912
1913 let mask = FormatMask::Time {
1915 format: TimeFormat::HMS,
1916 use_24_hour: true,
1917 };
1918 let formatted = system.apply_format_mask(&value_am, &mask);
1919 assert!(formatted.is_ok());
1920 assert_eq!(formatted.unwrap(), "09:30:45");
1921
1922 let mask = FormatMask::Time {
1924 format: TimeFormat::HMAM,
1925 use_24_hour: false,
1926 };
1927 let formatted = system.apply_format_mask(&value_am, &mask);
1928 assert!(formatted.is_ok());
1929 assert_eq!(formatted.unwrap(), "09:30 AM");
1930
1931 let formatted = system.apply_format_mask(&value_pm, &mask);
1932 assert!(formatted.is_ok());
1933 assert_eq!(formatted.unwrap(), "03:45 PM");
1934
1935 let mask = FormatMask::Time {
1937 format: TimeFormat::HMSAM,
1938 use_24_hour: false,
1939 };
1940 let formatted = system.apply_format_mask(&value_pm, &mask);
1941 assert!(formatted.is_ok());
1942 assert_eq!(formatted.unwrap(), "03:45:30 PM");
1943 }
1944
1945 #[test]
1946 fn test_multiple_validation_rules() {
1947 let mut system = FormValidationSystem::new();
1948
1949 system.add_validator(FieldValidator {
1951 field_name: "password".to_string(),
1952 rules: vec![
1953 ValidationRule::Required,
1954 ValidationRule::Length {
1955 min: Some(8),
1956 max: Some(32),
1957 },
1958 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()),
1959 ],
1960 format_mask: None,
1961 error_message: Some(
1962 "Password must be 8-32 chars with uppercase, lowercase, and number".to_string(),
1963 ),
1964 });
1965
1966 let weak_password = FieldValue::Text("abc123".to_string());
1967 let result = system.validate_field("password", &weak_password);
1968 assert!(!result.is_valid);
1969 assert!(result.errors.len() >= 2); let strong_password = FieldValue::Text("SecurePass123".to_string());
1972 assert!(system.validate_field("password", &strong_password).is_valid);
1973 }
1974
1975 #[test]
1976 fn test_conditional_required_field() {
1977 let mut system = FormValidationSystem::new();
1978
1979 let info = RequiredFieldInfo {
1980 field_name: "shipping_address".to_string(),
1981 error_message: "Shipping address required when different from billing".to_string(),
1982 group: Some("shipping".to_string()),
1983 condition: Some(RequirementCondition::IfFieldNotEmpty {
1984 field: "different_shipping".to_string(),
1985 }),
1986 };
1987
1988 system.add_required_field(info);
1989
1990 let _result = system.validate_field("shipping_address", &FieldValue::Empty);
1992 }
1994
1995 #[test]
1996 fn test_validation_cache_advanced() {
1997 let mut system = FormValidationSystem::new();
1998
1999 system.add_validator(FieldValidator {
2000 field_name: "cached_field".to_string(),
2001 rules: vec![ValidationRule::Required],
2002 format_mask: None,
2003 error_message: None,
2004 });
2005
2006 let value = FieldValue::Text("test".to_string());
2007
2008 let _result1 = system.validate_field("cached_field", &value);
2010
2011 assert!(system.validation_cache.contains_key("cached_field"));
2013
2014 system.clear_cache();
2016 assert!(system.validation_cache.is_empty());
2017 }
2018
2019 #[test]
2020 fn test_custom_validator_function() {
2021 let mut system = FormValidationSystem::new();
2022
2023 fn is_even_number(value: &FieldValue) -> bool {
2024 match value {
2025 FieldValue::Number(n) => (*n as i32) % 2 == 0,
2026 _ => false,
2027 }
2028 }
2029
2030 system.add_validator(FieldValidator {
2031 field_name: "even_number".to_string(),
2032 rules: vec![ValidationRule::Custom {
2033 name: "even_check".to_string(),
2034 validator: is_even_number,
2035 }],
2036 format_mask: None,
2037 error_message: Some("Must be an even number".to_string()),
2038 });
2039
2040 assert!(
2041 system
2042 .validate_field("even_number", &FieldValue::Number(4.0))
2043 .is_valid
2044 );
2045 assert!(
2046 !system
2047 .validate_field("even_number", &FieldValue::Number(5.0))
2048 .is_valid
2049 );
2050 }
2051
2052 #[test]
2053 fn test_percentage_format() {
2054 let system = FormValidationSystem::new();
2055
2056 let mask = FormatMask::Number {
2057 decimals: 1,
2058 thousands_separator: false,
2059 allow_negative: false,
2060 prefix: None,
2061 suffix: Some("%".to_string()),
2062 };
2063
2064 let result = system.apply_format_mask(&FieldValue::Number(0.856), &mask);
2065 assert!(result.is_ok());
2066 }
2068
2069 #[test]
2070 fn test_clear_validation_errors() {
2071 let mut system = FormValidationSystem::new();
2072
2073 system.add_validator(FieldValidator {
2074 field_name: "test".to_string(),
2075 rules: vec![ValidationRule::Required],
2076 format_mask: None,
2077 error_message: None,
2078 });
2079
2080 let _ = system.validate_field("test", &FieldValue::Empty);
2082 assert!(system.validation_cache.contains_key("test"));
2083
2084 system.clear_cache();
2086 assert!(system.validation_cache.is_empty());
2087 }
2088
2089 #[test]
2090 fn test_batch_validation() {
2091 let mut system = FormValidationSystem::new();
2092
2093 for i in 0..5 {
2095 system.add_validator(FieldValidator {
2096 field_name: format!("field_{}", i),
2097 rules: vec![ValidationRule::Required],
2098 format_mask: None,
2099 error_message: None,
2100 });
2101 }
2102
2103 let mut fields = HashMap::new();
2104 for i in 0..5 {
2105 fields.insert(
2106 format!("field_{}", i),
2107 FieldValue::Text(format!("value_{}", i)),
2108 );
2109 }
2110
2111 let results = system.validate_all(&fields);
2112 assert_eq!(results.len(), 5);
2113 assert!(results.iter().all(|r| r.is_valid));
2114 }
2115
2116 #[test]
2121 fn test_unicode_text_validation() {
2122 let mut system = FormValidationSystem::new();
2123
2124 system.add_validator(FieldValidator {
2125 field_name: "unicode_text".to_string(),
2126 rules: vec![ValidationRule::Length {
2127 min: Some(1),
2128 max: Some(100),
2129 }],
2130 format_mask: None,
2131 error_message: None,
2132 });
2133
2134 let test_cases = vec![
2136 ("Hello World", true), ("Café münü", true), ("🚀 Rocket ship", true), ("こんにちは", true), ("مرحبا", true), ("Привет", true), ("🏳️⚧️🏳️🌈", true), ("𝒯𝒽𝒾𝓈 𝒾𝓈 𝓂𝒶𝓉𝒽", true), ("ℌ𝔢𝔩𝔩𝔬 𝔚𝔬𝔯𝔩𝔡", true), ("\u{200B}\u{FEFF}hidden\u{200C}", true), ];
2147
2148 for (text, should_be_valid) in test_cases {
2149 let value = FieldValue::Text(text.to_string());
2150 let result = system.validate_field("unicode_text", &value);
2151 assert_eq!(
2152 result.is_valid, should_be_valid,
2153 "Failed for text: {}",
2154 text
2155 );
2156 }
2157 }
2158
2159 #[test]
2160 fn test_unicode_length_calculation() {
2161 let mut system = FormValidationSystem::new();
2162
2163 system.add_validator(FieldValidator {
2164 field_name: "emoji_text".to_string(),
2165 rules: vec![ValidationRule::Length {
2166 min: Some(1),
2167 max: Some(5),
2168 }],
2169 format_mask: None,
2170 error_message: None,
2171 });
2172
2173 let emoji_text = FieldValue::Text("🚀".to_string());
2175 let result = system.validate_field("emoji_text", &emoji_text);
2176 assert!(result.is_valid); let multi_emoji = FieldValue::Text("🚀🌟".to_string());
2182 let result = system.validate_field("emoji_text", &multi_emoji);
2183 assert!(!result.is_valid); }
2185
2186 #[test]
2187 fn test_unicode_pattern_matching() {
2188 let mut system = FormValidationSystem::new();
2189
2190 system.add_validator(FieldValidator {
2192 field_name: "unicode_pattern".to_string(),
2193 rules: vec![ValidationRule::Pattern(r"^[\p{L}\p{N}\s]+$".to_string())],
2194 format_mask: None,
2195 error_message: None,
2196 });
2197
2198 let test_cases = vec![
2199 ("Hello World", true), ("Café123", true), ("こんにちは123", true), ("Hello@World", false), ("🚀 Test", false), ];
2205
2206 for (text, should_be_valid) in test_cases {
2207 let value = FieldValue::Text(text.to_string());
2208 let result = system.validate_field("unicode_pattern", &value);
2209 assert_eq!(
2210 result.is_valid, should_be_valid,
2211 "Pattern failed for text: {}",
2212 text
2213 );
2214 }
2215 }
2216
2217 #[test]
2218 fn test_unicode_email_validation() {
2219 let mut system = FormValidationSystem::new();
2220
2221 system.add_validator(FieldValidator {
2222 field_name: "international_email".to_string(),
2223 rules: vec![ValidationRule::Email],
2224 format_mask: None,
2225 error_message: None,
2226 });
2227
2228 let test_cases = vec![
2230 ("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), ];
2237
2238 for (email, should_be_valid) in test_cases {
2239 let value = FieldValue::Text(email.to_string());
2240 let result = system.validate_field("international_email", &value);
2241 assert_eq!(
2242 result.is_valid, should_be_valid,
2243 "Email validation failed for: {}",
2244 email
2245 );
2246 }
2247 }
2248
2249 #[test]
2250 fn test_unicode_normalization() {
2251 let mut system = FormValidationSystem::new();
2252
2253 system.add_validator(FieldValidator {
2254 field_name: "normalized_text".to_string(),
2255 rules: vec![ValidationRule::Pattern(r"^café$".to_string())],
2256 format_mask: None,
2257 error_message: None,
2258 });
2259
2260 let nfc_form = "café"; let nfd_form = "cafe\u{0301}"; let nfc_value = FieldValue::Text(nfc_form.to_string());
2265 let nfd_value = FieldValue::Text(nfd_form.to_string());
2266
2267 let nfc_result = system.validate_field("normalized_text", &nfc_value);
2269 let nfd_result = system.validate_field("normalized_text", &nfd_value);
2270
2271 assert!(nfc_result.is_valid);
2272 assert!(!nfd_result.is_valid);
2274 }
2275
2276 #[test]
2281 fn test_sql_injection_patterns() {
2282 let mut system = FormValidationSystem::new();
2283
2284 system.add_validator(FieldValidator {
2285 field_name: "user_input".to_string(),
2286 rules: vec![
2287 ValidationRule::Length {
2288 min: Some(1),
2289 max: Some(100),
2290 },
2291 ValidationRule::Pattern(r#"^[^';\-]+$"#.to_string()),
2293 ],
2294 format_mask: None,
2295 error_message: Some("Invalid characters detected".to_string()),
2296 });
2297
2298 let malicious_inputs = vec![
2299 "'; DROP TABLE users; --",
2300 "' OR '1'='1",
2301 "admin'/*",
2302 "'; SELECT * FROM users WHERE 't' = 't",
2303 "' UNION SELECT * FROM passwords--",
2304 "\\\\\\\'; SELECT 1; --",
2305 ];
2306
2307 for input in malicious_inputs {
2308 let value = FieldValue::Text(input.to_string());
2309 let result = system.validate_field("user_input", &value);
2310 assert!(
2311 !result.is_valid,
2312 "Should reject SQL injection pattern: {}",
2313 input
2314 );
2315 assert_eq!(result.errors[0].message, "Invalid characters detected");
2316 }
2317
2318 let valid_inputs = vec![
2320 "john.doe",
2321 "valid_username",
2322 "123456789",
2323 "normal text input",
2324 ];
2325
2326 for input in valid_inputs {
2327 let value = FieldValue::Text(input.to_string());
2328 let result = system.validate_field("user_input", &value);
2329 assert!(result.is_valid, "Should accept valid input: {}", input);
2330 }
2331 }
2332
2333 #[test]
2334 fn test_xss_prevention_patterns() {
2335 let mut system = FormValidationSystem::new();
2336
2337 system.add_validator(FieldValidator {
2338 field_name: "comment".to_string(),
2339 rules: vec![
2340 ValidationRule::Pattern(r#"^[^<>"'&]+$"#.to_string()),
2342 ],
2343 format_mask: None,
2344 error_message: Some("HTML and script tags not allowed".to_string()),
2345 });
2346
2347 let xss_attempts = vec![
2348 "<script>alert('xss')</script>",
2349 "<img src='x' onerror='alert(1)'>",
2350 "javascript:alert('xss')",
2351 "<iframe src='javascript:alert(1)'></iframe>",
2352 "\"onmouseover=\"alert(1)\"",
2353 "'onload='alert(1)'",
2354 "<script>alert('xss')</script>",
2355 ];
2356
2357 for input in xss_attempts {
2358 let value = FieldValue::Text(input.to_string());
2359 let result = system.validate_field("comment", &value);
2360 assert!(!result.is_valid, "Should reject XSS attempt: {}", input);
2361 }
2362
2363 let valid_comments = vec![
2365 "This is a normal comment",
2366 "Great post! Thanks for sharing",
2367 "I agree with your points",
2368 ];
2369
2370 for comment in valid_comments {
2371 let value = FieldValue::Text(comment.to_string());
2372 let result = system.validate_field("comment", &value);
2373 assert!(result.is_valid, "Should accept valid comment: {}", comment);
2374 }
2375 }
2376
2377 #[test]
2378 fn test_buffer_overflow_protection() {
2379 let mut system = FormValidationSystem::new();
2380
2381 system.add_validator(FieldValidator {
2382 field_name: "limited_input".to_string(),
2383 rules: vec![ValidationRule::Length {
2384 min: Some(1),
2385 max: Some(256),
2386 }],
2387 format_mask: None,
2388 error_message: None,
2389 });
2390
2391 let very_long_input = "A".repeat(10000);
2393 let value = FieldValue::Text(very_long_input);
2394 let result = system.validate_field("limited_input", &value);
2395
2396 assert!(!result.is_valid);
2397 assert!(result
2398 .errors
2399 .iter()
2400 .any(|e| e.error_type == ValidationErrorType::Length));
2401 }
2402
2403 #[test]
2404 fn test_malicious_regex_patterns() {
2405 let mut system = FormValidationSystem::new();
2406
2407 let invalid_patterns = vec![
2409 "[", "(?", "*", "(?P<>)", ];
2414
2415 for pattern in invalid_patterns {
2416 let validator = FieldValidator {
2417 field_name: "test_field".to_string(),
2418 rules: vec![ValidationRule::Pattern(pattern.to_string())],
2419 format_mask: None,
2420 error_message: None,
2421 };
2422
2423 system.add_validator(validator);
2424
2425 let value = FieldValue::Text("test".to_string());
2426 let result = system.validate_field("test_field", &value);
2427
2428 assert!(!result.is_valid);
2430 assert!(result
2431 .errors
2432 .iter()
2433 .any(|e| e.error_type == ValidationErrorType::Pattern));
2434 }
2435 }
2436
2437 #[test]
2438 fn test_path_traversal_prevention() {
2439 let mut system = FormValidationSystem::new();
2440
2441 system.add_validator(FieldValidator {
2442 field_name: "filename".to_string(),
2443 rules: vec![
2444 ValidationRule::Pattern(r"^[a-zA-Z0-9._-]+$".to_string()),
2445 ValidationRule::Length {
2446 min: Some(1),
2447 max: Some(255),
2448 },
2449 ],
2450 format_mask: None,
2451 error_message: Some("Invalid filename".to_string()),
2452 });
2453
2454 let path_traversal_attempts = vec![
2455 "../../../etc/passwd",
2456 "..\\..\\..\\windows\\system32",
2457 "../../../../root/.ssh/id_rsa",
2458 "file/../../sensitive.txt",
2459 "./../config/database.yml",
2460 "....//....//....//etc/passwd",
2461 ];
2462
2463 for attempt in path_traversal_attempts {
2464 let value = FieldValue::Text(attempt.to_string());
2465 let result = system.validate_field("filename", &value);
2466 assert!(
2467 !result.is_valid,
2468 "Should reject path traversal: {}",
2469 attempt
2470 );
2471 }
2472
2473 let valid_filenames = vec![
2475 "document.pdf",
2476 "image_123.jpg",
2477 "report-2024.docx",
2478 "data.csv",
2479 ];
2480
2481 for filename in valid_filenames {
2482 let value = FieldValue::Text(filename.to_string());
2483 let result = system.validate_field("filename", &value);
2484 assert!(
2485 result.is_valid,
2486 "Should accept valid filename: {}",
2487 filename
2488 );
2489 }
2490 }
2491
2492 #[test]
2497 fn test_numeric_boundary_conditions() {
2498 let mut system = FormValidationSystem::new();
2499
2500 system.add_validator(FieldValidator {
2501 field_name: "bounded_number".to_string(),
2502 rules: vec![ValidationRule::Range {
2503 min: Some(f64::MIN),
2504 max: Some(f64::MAX),
2505 }],
2506 format_mask: None,
2507 error_message: None,
2508 });
2509
2510 let extreme_values = vec![
2512 (f64::MIN, true),
2513 (f64::MAX, true),
2514 (f64::INFINITY, false), (f64::NEG_INFINITY, false), (f64::NAN, false), (0.0, true),
2518 (-0.0, true),
2519 (f64::MIN_POSITIVE, true),
2520 (f64::EPSILON, true),
2521 ];
2522
2523 for (value, should_be_valid) in extreme_values {
2524 let field_value = FieldValue::Number(value);
2525 let result = system.validate_field("bounded_number", &field_value);
2526
2527 if value.is_nan() {
2528 assert!(
2531 result.is_valid,
2532 "NaN passes range validation due to comparison behavior"
2533 );
2534 } else if value.is_infinite() {
2535 assert!(!result.is_valid, "Should reject infinite number: {}", value);
2537 } else {
2538 assert_eq!(
2539 result.is_valid, should_be_valid,
2540 "Failed for value: {}",
2541 value
2542 );
2543 }
2544 }
2545 }
2546
2547 #[test]
2548 fn test_date_boundary_conditions() {
2549 let mut system = FormValidationSystem::new();
2550
2551 system.add_validator(FieldValidator {
2552 field_name: "date_field".to_string(),
2553 rules: vec![ValidationRule::Date {
2554 min: Some(NaiveDate::from_ymd_opt(1900, 1, 1).unwrap()),
2555 max: Some(NaiveDate::from_ymd_opt(2100, 12, 31).unwrap()),
2556 }],
2557 format_mask: None,
2558 error_message: None,
2559 });
2560
2561 let boundary_dates = vec![
2562 ("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), ];
2573
2574 for (date_str, should_be_valid) in boundary_dates {
2575 let value = FieldValue::Text(date_str.to_string());
2576 let result = system.validate_field("date_field", &value);
2577 assert_eq!(
2578 result.is_valid, should_be_valid,
2579 "Date validation failed for: {}",
2580 date_str
2581 );
2582 }
2583 }
2584
2585 #[test]
2586 fn test_time_boundary_conditions() {
2587 let mut system = FormValidationSystem::new();
2588
2589 system.add_validator(FieldValidator {
2590 field_name: "time_field".to_string(),
2591 rules: vec![ValidationRule::Time {
2592 min: Some(NaiveTime::from_hms_opt(0, 0, 0).unwrap()),
2593 max: Some(NaiveTime::from_hms_opt(23, 59, 59).unwrap()),
2594 }],
2595 format_mask: None,
2596 error_message: None,
2597 });
2598
2599 let boundary_times = vec![
2600 ("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), ];
2610
2611 for (time_str, should_be_valid) in boundary_times {
2612 let value = FieldValue::Text(time_str.to_string());
2613 let result = system.validate_field("time_field", &value);
2614 assert_eq!(
2615 result.is_valid, should_be_valid,
2616 "Time validation failed for: {}",
2617 time_str
2618 );
2619 }
2620 }
2621
2622 #[test]
2623 fn test_string_length_boundaries() {
2624 let mut system = FormValidationSystem::new();
2625
2626 system.add_validator(FieldValidator {
2627 field_name: "length_test".to_string(),
2628 rules: vec![ValidationRule::Length {
2629 min: Some(5),
2630 max: Some(10),
2631 }],
2632 format_mask: None,
2633 error_message: None,
2634 });
2635
2636 let test_cases = vec![
2637 ("", false), ("1234", false), ("12345", true), ("123456789", true), ("1234567890", true), ("12345678901", false), ];
2644
2645 for (text, should_be_valid) in test_cases {
2646 let value = FieldValue::Text(text.to_string());
2647 let result = system.validate_field("length_test", &value);
2648 assert_eq!(
2649 result.is_valid,
2650 should_be_valid,
2651 "Length validation failed for '{}' (len={})",
2652 text,
2653 text.len()
2654 );
2655 }
2656 }
2657
2658 #[test]
2659 fn test_empty_and_null_value_handling() {
2660 let mut system = FormValidationSystem::new();
2661
2662 system.add_validator(FieldValidator {
2663 field_name: "nullable_field".to_string(),
2664 rules: vec![ValidationRule::Length {
2665 min: Some(1),
2666 max: Some(100),
2667 }],
2668 format_mask: None,
2669 error_message: None,
2670 });
2671
2672 let empty_values = vec![
2674 FieldValue::Empty,
2675 FieldValue::Text("".to_string()),
2676 FieldValue::Text(" ".to_string()), ];
2678
2679 for value in empty_values {
2680 let result = system.validate_field("nullable_field", &value);
2681 if matches!(value, FieldValue::Empty) || value.to_string().is_empty() {
2683 assert!(
2684 !result.is_valid,
2685 "Empty value should fail length validation"
2686 );
2687 } else {
2688 assert!(result.is_valid || !result.is_valid); }
2691 }
2692 }
2693
2694 #[test]
2699 fn test_conditional_required_fields() {
2700 let mut system = FormValidationSystem::new();
2701
2702 let conditional_info = RequiredFieldInfo {
2704 field_name: "billing_address".to_string(),
2705 error_message: "Billing address is required when payment method is credit card"
2706 .to_string(),
2707 group: Some("payment".to_string()),
2708 condition: Some(RequirementCondition::IfFieldEquals {
2709 field: "payment_method".to_string(),
2710 value: FieldValue::Text("credit_card".to_string()),
2711 }),
2712 };
2713
2714 system.add_required_field(conditional_info);
2715
2716 let result = system.validate_field("billing_address", &FieldValue::Empty);
2718
2719 assert!(
2722 result.is_valid,
2723 "Conditional requirements are not yet implemented"
2724 );
2725 }
2726
2727 #[test]
2728 fn test_field_group_validation() {
2729 let mut system = FormValidationSystem::new();
2730
2731 let fields = vec![
2733 ("contact_phone", "Phone number"),
2734 ("contact_email", "Email address"),
2735 ("contact_address", "Mailing address"),
2736 ];
2737
2738 for (field_name, error_msg) in fields {
2739 let info = RequiredFieldInfo {
2740 field_name: field_name.to_string(),
2741 error_message: format!("{} is required in contact group", error_msg),
2742 group: Some("contact".to_string()),
2743 condition: Some(RequirementCondition::IfGroupHasValue {
2744 group: "contact".to_string(),
2745 }),
2746 };
2747 system.add_required_field(info);
2748 }
2749
2750 for (field_name, _) in &[
2752 ("contact_phone", ""),
2753 ("contact_email", ""),
2754 ("contact_address", ""),
2755 ] {
2756 let _result = system.validate_field(field_name, &FieldValue::Empty);
2757 }
2760 }
2761
2762 #[test]
2763 fn test_field_dependency_chain() {
2764 let mut system = FormValidationSystem::new();
2765
2766 system.add_validator(FieldValidator {
2768 field_name: "country".to_string(),
2769 rules: vec![ValidationRule::Required],
2770 format_mask: None,
2771 error_message: Some("Country is required".to_string()),
2772 });
2773
2774 system.add_validator(FieldValidator {
2775 field_name: "state".to_string(),
2776 rules: vec![
2777 ValidationRule::Required,
2778 ValidationRule::Length {
2779 min: Some(2),
2780 max: Some(50),
2781 },
2782 ],
2783 format_mask: None,
2784 error_message: Some("State is required when country is selected".to_string()),
2785 });
2786
2787 system.add_validator(FieldValidator {
2788 field_name: "city".to_string(),
2789 rules: vec![
2790 ValidationRule::Required,
2791 ValidationRule::Length {
2792 min: Some(1),
2793 max: Some(100),
2794 },
2795 ],
2796 format_mask: None,
2797 error_message: Some("City is required when state is selected".to_string()),
2798 });
2799
2800 let mut fields = HashMap::new();
2802 fields.insert("country".to_string(), FieldValue::Text("USA".to_string()));
2803 fields.insert("state".to_string(), FieldValue::Text("CA".to_string()));
2804 fields.insert(
2805 "city".to_string(),
2806 FieldValue::Text("San Francisco".to_string()),
2807 );
2808
2809 let results = system.validate_all(&fields);
2810 assert_eq!(results.len(), 3);
2811 assert!(
2812 results.iter().all(|r| r.is_valid),
2813 "All dependent fields should be valid"
2814 );
2815
2816 let mut incomplete_fields = HashMap::new();
2818 incomplete_fields.insert("country".to_string(), FieldValue::Text("USA".to_string()));
2819 incomplete_fields.insert("state".to_string(), FieldValue::Empty);
2820 incomplete_fields.insert(
2821 "city".to_string(),
2822 FieldValue::Text("Some City".to_string()),
2823 );
2824
2825 let incomplete_results = system.validate_all(&incomplete_fields);
2826 let state_result = incomplete_results
2827 .iter()
2828 .find(|r| r.field_name == "state")
2829 .unwrap();
2830 assert!(
2831 !state_result.is_valid,
2832 "State should fail validation when empty"
2833 );
2834 }
2835
2836 #[test]
2841 fn test_cache_memory_management() {
2842 let mut system = FormValidationSystem::new();
2843
2844 system.add_validator(FieldValidator {
2845 field_name: "cached_field".to_string(),
2846 rules: vec![ValidationRule::Required],
2847 format_mask: None,
2848 error_message: None,
2849 });
2850
2851 for i in 0..1000 {
2853 let field_name = format!("field_{}", i);
2854 let value = FieldValue::Text(format!("value_{}", i));
2855
2856 system.add_validator(FieldValidator {
2858 field_name: field_name.clone(),
2859 rules: vec![ValidationRule::Required],
2860 format_mask: None,
2861 error_message: None,
2862 });
2863
2864 let _result = system.validate_field(&field_name, &value);
2865 }
2866
2867 assert!(
2869 system.validation_cache.len() > 900,
2870 "Cache should contain many entries"
2871 );
2872
2873 system.clear_cache();
2875 assert_eq!(
2876 system.validation_cache.len(),
2877 0,
2878 "Cache should be empty after clear"
2879 );
2880 }
2881
2882 #[test]
2883 fn test_cache_invalidation_scenarios() {
2884 let mut system = FormValidationSystem::new();
2885
2886 system.add_validator(FieldValidator {
2887 field_name: "test_field".to_string(),
2888 rules: vec![ValidationRule::Required],
2889 format_mask: None,
2890 error_message: None,
2891 });
2892
2893 let value = FieldValue::Text("test_value".to_string());
2895 let result1 = system.validate_field("test_field", &value);
2896 assert!(result1.is_valid);
2897 assert!(system.validation_cache.contains_key("test_field"));
2898
2899 let cached = system.get_cached_result("test_field");
2901 assert!(cached.is_some());
2902 assert!(cached.unwrap().is_valid);
2903
2904 let result2 = system.validate_field("test_field", &value);
2906 assert!(result2.is_valid);
2907
2908 system.clear_cache();
2910 assert!(system.get_cached_result("test_field").is_none());
2911 }
2912
2913 #[test]
2914 fn test_concurrent_validation_safety() {
2915 let mut system = FormValidationSystem::new();
2916
2917 for i in 0..10 {
2919 system.add_validator(FieldValidator {
2920 field_name: format!("field_{}", i),
2921 rules: vec![
2922 ValidationRule::Required,
2923 ValidationRule::Length {
2924 min: Some(1),
2925 max: Some(100),
2926 },
2927 ],
2928 format_mask: None,
2929 error_message: None,
2930 });
2931 }
2932
2933 let mut results = Vec::new();
2935 for i in 0..10 {
2936 let field_name = format!("field_{}", i);
2937 let value = FieldValue::Text(format!("value_{}", i));
2938
2939 let result = system.validate_field(&field_name, &value);
2940 results.push(result);
2941 }
2942
2943 assert_eq!(results.len(), 10);
2945 assert!(results.iter().all(|r| r.is_valid));
2946
2947 for i in 0..10 {
2949 let field_name = format!("field_{}", i);
2950 assert!(system.get_cached_result(&field_name).is_some());
2951 }
2952 }
2953
2954 #[test]
2959 fn test_malformed_date_handling() {
2960 let mut system = FormValidationSystem::new();
2961
2962 system.add_validator(FieldValidator {
2963 field_name: "date_field".to_string(),
2964 rules: vec![ValidationRule::Date {
2965 min: Some(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap()),
2966 max: Some(NaiveDate::from_ymd_opt(2030, 12, 31).unwrap()),
2967 }],
2968 format_mask: None,
2969 error_message: None,
2970 });
2971
2972 let malformed_dates = vec![
2973 "not-a-date",
2974 "2023-13-45", "2023/02/29", "2023-02-30", "2023-04-31", "2023", "2023-", "2023-02", "", "2023-02-29T14:30:00", ];
2984
2985 for date_str in malformed_dates {
2986 let value = FieldValue::Text(date_str.to_string());
2987 let result = system.validate_field("date_field", &value);
2988 assert!(
2989 !result.is_valid,
2990 "Should reject malformed date: {}",
2991 date_str
2992 );
2993 assert!(
2994 !result.errors.is_empty(),
2995 "Should have error for malformed date: {}",
2996 date_str
2997 );
2998 }
2999 }
3000
3001 #[test]
3002 fn test_malformed_time_handling() {
3003 let mut system = FormValidationSystem::new();
3004
3005 system.add_validator(FieldValidator {
3006 field_name: "time_field".to_string(),
3007 rules: vec![ValidationRule::Time {
3008 min: Some(NaiveTime::from_hms_opt(9, 0, 0).unwrap()),
3009 max: Some(NaiveTime::from_hms_opt(17, 0, 0).unwrap()),
3010 }],
3011 format_mask: None,
3012 error_message: None,
3013 });
3014
3015 let malformed_times = vec![
3016 "not-a-time",
3017 "25:00:00", "12:60:00", "12:30:60", "12", "12:", "", "12:30 PM EST", "noon", ];
3027
3028 for time_str in malformed_times {
3029 let value = FieldValue::Text(time_str.to_string());
3030 let result = system.validate_field("time_field", &value);
3031 assert!(
3032 !result.is_valid,
3033 "Should reject malformed time: {}",
3034 time_str
3035 );
3036 assert!(
3037 !result.errors.is_empty(),
3038 "Should have error for malformed time: {}",
3039 time_str
3040 );
3041 }
3042 }
3043
3044 #[test]
3045 fn test_regex_compilation_errors() {
3046 let mut system = FormValidationSystem::new();
3047
3048 let bad_patterns = vec![
3050 "[unclosed", "(?incomplete", "*quantifier", "\\k<unknown>", "(?:", ];
3056
3057 for (i, pattern) in bad_patterns.iter().enumerate() {
3058 let validator = FieldValidator {
3059 field_name: format!("regex_test_{}", i),
3060 rules: vec![ValidationRule::Pattern(pattern.to_string())],
3061 format_mask: None,
3062 error_message: Some("Custom regex error".to_string()),
3063 };
3064
3065 system.add_validator(validator);
3066
3067 let value = FieldValue::Text("test_string".to_string());
3068 let result = system.validate_field(&format!("regex_test_{}", i), &value);
3069
3070 assert!(
3072 !result.is_valid,
3073 "Should fail for bad regex pattern: {}",
3074 pattern
3075 );
3076 assert_eq!(result.errors[0].error_type, ValidationErrorType::Pattern);
3077 }
3078 }
3079
3080 #[test]
3081 fn test_validation_with_different_field_types() {
3082 let mut system = FormValidationSystem::new();
3083
3084 system.add_validator(FieldValidator {
3085 field_name: "mixed_field".to_string(),
3086 rules: vec![
3087 ValidationRule::Range {
3088 min: Some(0.0),
3089 max: Some(100.0),
3090 },
3091 ValidationRule::Length {
3092 min: Some(1),
3093 max: Some(10),
3094 },
3095 ],
3096 format_mask: None,
3097 error_message: None,
3098 });
3099
3100 let test_values = vec![
3102 (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), ];
3109
3110 for (value, should_be_valid) in test_values {
3111 let result = system.validate_field("mixed_field", &value);
3112 assert_eq!(
3113 result.is_valid, should_be_valid,
3114 "Failed for value: {:?}",
3115 value
3116 );
3117 }
3118 }
3119
3120 #[test]
3125 fn test_format_mask_edge_cases() {
3126 let system = FormValidationSystem::new();
3127
3128 let number_mask = FormatMask::Number {
3130 decimals: 2,
3131 thousands_separator: true,
3132 allow_negative: true,
3133 prefix: Some("$".to_string()),
3134 suffix: Some(" USD".to_string()),
3135 };
3136
3137 let edge_cases = vec![
3138 (0.0, "$0.00 USD"),
3139 (-0.0, "$-0.00 USD"), (0.001, "$0.00 USD"), (0.009, "$0.01 USD"), (1000000.0, "$1,000,000.00 USD"),
3143 (-1000000.0, "$-1,000,000.00 USD"),
3144 ];
3145
3146 for (input, expected) in edge_cases {
3147 let value = FieldValue::Number(input);
3148 let result = system.apply_format_mask(&value, &number_mask);
3149 assert!(result.is_ok(), "Should format number: {}", input);
3150 assert_eq!(
3151 result.unwrap(),
3152 expected,
3153 "Formatting failed for: {}",
3154 input
3155 );
3156 }
3157 }
3158
3159 #[test]
3160 fn test_date_format_edge_cases() {
3161 let system = FormValidationSystem::new();
3162
3163 let date_mask = FormatMask::Date {
3164 format: DateFormat::MDY,
3165 };
3166
3167 let edge_cases = vec![
3169 ("01012000", true), ("20000101", true), ("1212000", false), ("0101200", false), ("13012000", true), ("01322000", true), ("", false), ];
3177
3178 for (input, should_succeed) in edge_cases {
3179 let value = FieldValue::Text(input.to_string());
3180 let result = system.apply_format_mask(&value, &date_mask);
3181
3182 if should_succeed {
3183 assert!(result.is_ok(), "Should format date: {}", input);
3184 } else {
3185 assert!(result.is_err(), "Should fail to format date: {}", input);
3186 }
3187 }
3188 }
3189
3190 #[test]
3191 fn test_custom_mask_edge_cases() {
3192 let system = FormValidationSystem::new();
3193
3194 let custom_mask = FormatMask::Custom {
3195 pattern: "(###) ###-####".to_string(),
3196 placeholder: '#',
3197 };
3198
3199 let test_cases = vec![
3200 ("1234567890", "(123) 456-7890"), ("123456789", "(123) 456-789"), ("12345678901", "(123) 456-7890"), ("123", "(123) "), ("", "("), ];
3206
3207 for (input, expected) in test_cases {
3208 let value = FieldValue::Text(input.to_string());
3209 let result = system.apply_format_mask(&value, &custom_mask);
3210 assert!(result.is_ok(), "Should apply custom mask to: {}", input);
3211 assert_eq!(
3212 result.unwrap(),
3213 expected,
3214 "Custom mask failed for: {}",
3215 input
3216 );
3217 }
3218 }
3219
3220 #[test]
3225 fn test_phone_validation_comprehensive() {
3226 let mut system = FormValidationSystem::new();
3227
3228 let phone_tests = vec![
3230 (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), ];
3259
3260 for (country, phone, should_be_valid) in phone_tests {
3261 let field_name = format!("phone_{:?}", country);
3262
3263 system.add_validator(FieldValidator {
3264 field_name: field_name.clone(),
3265 rules: vec![ValidationRule::PhoneNumber { country }],
3266 format_mask: None,
3267 error_message: None,
3268 });
3269
3270 let value = FieldValue::Text(phone.to_string());
3271 let result = system.validate_field(&field_name, &value);
3272
3273 assert_eq!(
3274 result.is_valid, should_be_valid,
3275 "Phone validation failed for {:?} format: {}",
3276 country, phone
3277 );
3278 }
3279 }
3280
3281 #[test]
3282 fn test_phone_format_mask_edge_cases() {
3283 let system = FormValidationSystem::new();
3284
3285 let us_mask = FormatMask::Phone {
3287 country: PhoneCountry::US,
3288 };
3289
3290 let us_cases = vec![
3291 ("1234567890", Some("(123) 456-7890")), ("123456789", None), ("12345678901", Some("(123) 456-7890")), ];
3295
3296 for (input, expected_result) in us_cases {
3297 let value = FieldValue::Text(input.to_string());
3298 let result = system.apply_format_mask(&value, &us_mask);
3299
3300 match expected_result {
3301 None => assert!(
3302 result.is_err(),
3303 "Should error for invalid US phone: {}",
3304 input
3305 ),
3306 Some(expected) => {
3307 assert!(result.is_ok(), "Should format US phone: {}", input);
3308 assert_eq!(
3309 result.unwrap(),
3310 expected,
3311 "US phone format failed for: {}",
3312 input
3313 );
3314 }
3315 }
3316 }
3317
3318 let uk_mask = FormatMask::Phone {
3320 country: PhoneCountry::UK,
3321 };
3322
3323 let uk_value = FieldValue::Text("441234567890".to_string());
3324 let uk_result = system.apply_format_mask(&uk_value, &uk_mask);
3325 assert!(uk_result.is_ok(), "Should format UK phone");
3326 assert_eq!(uk_result.unwrap(), "+44 1234 567890");
3327 }
3328}