use crate::forms::calculations::FieldValue;
use chrono::{NaiveDate, NaiveTime};
use regex::Regex;
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, Default)]
pub struct FormValidationSystem {
validators: HashMap<String, FieldValidator>,
required_fields: HashMap<String, RequiredFieldInfo>,
validation_cache: HashMap<String, ValidationResult>,
#[allow(dead_code)]
settings: ValidationSettings,
}
#[derive(Debug, Clone)]
pub struct FieldValidator {
pub field_name: String,
pub rules: Vec<ValidationRule>,
pub format_mask: Option<FormatMask>,
pub error_message: Option<String>,
}
#[derive(Debug, Clone)]
pub enum ValidationRule {
Required,
Range { min: Option<f64>, max: Option<f64> },
Length {
min: Option<usize>,
max: Option<usize>,
},
Pattern(String),
Email,
Url,
PhoneNumber { country: PhoneCountry },
Date {
min: Option<NaiveDate>,
max: Option<NaiveDate>,
},
Time {
min: Option<NaiveTime>,
max: Option<NaiveTime>,
},
CreditCard,
Custom {
name: String,
validator: fn(&FieldValue) -> bool,
},
}
#[derive(Debug, Clone)]
pub enum FormatMask {
Number {
decimals: usize,
thousands_separator: bool,
allow_negative: bool,
prefix: Option<String>,
suffix: Option<String>,
},
Date { format: DateFormat },
Time {
format: TimeFormat,
use_24_hour: bool,
},
Phone { country: PhoneCountry },
SSN,
ZipCode { plus_four: bool },
CreditCard,
Custom { pattern: String, placeholder: char },
}
#[derive(Debug, Clone, Copy)]
pub enum DateFormat {
MDY,
DMY,
YMD,
DotDMY,
DashMDY,
}
#[derive(Debug, Clone, Copy)]
pub enum TimeFormat {
HM,
HMS,
HMAM,
HMSAM,
}
#[derive(Debug, Clone, Copy)]
pub enum PhoneCountry {
US, UK, EU, Japan, Custom,
}
#[derive(Debug, Clone)]
pub struct RequiredFieldInfo {
pub field_name: String,
pub error_message: String,
#[allow(dead_code)]
pub group: Option<String>,
pub condition: Option<RequirementCondition>,
}
#[derive(Debug, Clone)]
pub enum RequirementCondition {
Always,
IfFieldEquals { field: String, value: FieldValue },
IfFieldNotEmpty { field: String },
IfGroupHasValue { group: String },
}
#[derive(Debug, Clone)]
pub struct ValidationResult {
pub field_name: String,
pub is_valid: bool,
pub errors: Vec<ValidationError>,
pub warnings: Vec<String>,
pub formatted_value: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ValidationError {
pub field_name: String,
pub error_type: ValidationErrorType,
pub message: String,
pub details: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ValidationErrorType {
Required,
Format,
Range,
Length,
Pattern,
Custom,
}
#[derive(Debug, Clone)]
pub struct ValidationSettings {
pub validate_on_change: bool,
pub show_format_hints: bool,
pub auto_format: bool,
pub allow_partial: bool,
pub real_time_validation: bool,
pub highlight_errors: bool,
pub show_error_messages: bool,
}
impl Default for ValidationSettings {
fn default() -> Self {
Self {
validate_on_change: true,
show_format_hints: true,
auto_format: true,
allow_partial: false,
real_time_validation: true,
highlight_errors: true,
show_error_messages: true,
}
}
}
impl FormValidationSystem {
pub fn new() -> Self {
Self::default()
}
pub fn with_settings(settings: ValidationSettings) -> Self {
Self {
settings,
..Self::default()
}
}
pub fn add_validator(&mut self, validator: FieldValidator) {
self.validators
.insert(validator.field_name.clone(), validator);
}
pub fn add_required_field(&mut self, info: RequiredFieldInfo) {
self.required_fields.insert(info.field_name.clone(), info);
}
pub fn validate_field(&mut self, field_name: &str, value: &FieldValue) -> ValidationResult {
let mut errors = Vec::new();
let warnings = Vec::new();
let mut formatted_value = None;
if let Some(required_info) = self.required_fields.get(field_name) {
if self.is_field_required(required_info) && self.is_empty(value) {
errors.push(ValidationError {
field_name: field_name.to_string(),
error_type: ValidationErrorType::Required,
message: required_info.error_message.clone(),
details: None,
});
}
}
if let Some(validator) = self.validators.get(field_name) {
if let Some(ref mask) = validator.format_mask {
match self.apply_format_mask(value, mask) {
Ok(formatted) => formatted_value = Some(formatted),
Err(e) => errors.push(ValidationError {
field_name: field_name.to_string(),
error_type: ValidationErrorType::Format,
message: e,
details: None,
}),
}
}
for rule in &validator.rules {
if let Err(e) = self.apply_rule(value, rule) {
errors.push(ValidationError {
field_name: field_name.to_string(),
error_type: self.get_error_type(rule),
message: validator
.error_message
.clone()
.unwrap_or_else(|| e.to_string()),
details: None,
});
}
}
}
let result = ValidationResult {
field_name: field_name.to_string(),
is_valid: errors.is_empty(),
errors,
warnings,
formatted_value,
};
self.validation_cache
.insert(field_name.to_string(), result.clone());
result
}
fn is_field_required(&self, info: &RequiredFieldInfo) -> bool {
match &info.condition {
Some(RequirementCondition::Always) | None => true,
Some(RequirementCondition::IfFieldEquals { field: _, value: _ }) => {
false
}
Some(RequirementCondition::IfFieldNotEmpty { field: _ }) => {
false
}
Some(RequirementCondition::IfGroupHasValue { group: _ }) => {
false
}
}
}
fn is_empty(&self, value: &FieldValue) -> bool {
match value {
FieldValue::Empty => true,
FieldValue::Text(s) => s.is_empty(),
_ => false,
}
}
fn apply_rule(&self, value: &FieldValue, rule: &ValidationRule) -> Result<(), String> {
match rule {
ValidationRule::Required => {
if self.is_empty(value) {
Err("Field is required".to_string())
} else {
Ok(())
}
}
ValidationRule::Range { min, max } => {
match value {
FieldValue::Number(num) => {
if let Some(min_val) = min {
if num < min_val {
return Err(format!("Value must be at least {}", min_val));
}
}
if let Some(max_val) = max {
if num > max_val {
return Err(format!("Value must be at most {}", max_val));
}
}
Ok(())
}
FieldValue::Text(s) => {
match s.parse::<f64>() {
Ok(num) => {
if let Some(min_val) = min {
if num < *min_val {
return Err(format!("Value must be at least {}", min_val));
}
}
if let Some(max_val) = max {
if num > *max_val {
return Err(format!("Value must be at most {}", max_val));
}
}
Ok(())
}
Err(_) => {
Err("Value must be a valid number for range validation".to_string())
}
}
}
FieldValue::Boolean(_) | FieldValue::Empty => {
Err("Range validation requires numeric values".to_string())
}
}
}
ValidationRule::Length { min, max } => {
let text = value.to_string();
let len = text.len();
if let Some(min_len) = min {
if len < *min_len {
return Err(format!("Must be at least {} characters", min_len));
}
}
if let Some(max_len) = max {
if len > *max_len {
return Err(format!("Must be at most {} characters", max_len));
}
}
Ok(())
}
ValidationRule::Pattern(pattern) => {
let text = value.to_string();
let re = Regex::new(pattern).map_err(|e| e.to_string())?;
if re.is_match(&text) {
Ok(())
} else {
Err("Does not match required pattern".to_string())
}
}
ValidationRule::Email => {
let text = value.to_string();
if let Ok(email_regex) =
Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
{
if email_regex.is_match(&text) {
Ok(())
} else {
Err("Invalid email address".to_string())
}
} else {
Err("Email validation unavailable".to_string())
}
}
ValidationRule::Url => {
let text = value.to_string();
if let Ok(url_regex) = Regex::new(r"^https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}") {
if url_regex.is_match(&text) {
Ok(())
} else {
Err("Invalid URL".to_string())
}
} else {
Err("URL validation unavailable".to_string())
}
}
ValidationRule::PhoneNumber { country } => {
self.validate_phone_number(&value.to_string(), *country)
}
ValidationRule::CreditCard => self.validate_credit_card(&value.to_string()),
ValidationRule::Date { min, max } => {
let text = value.to_string();
let date = NaiveDate::parse_from_str(&text, "%Y-%m-%d")
.map_err(|e| format!("Invalid date format: {}", e))?;
if let Some(min_date) = min {
if date < *min_date {
return Err(format!("Date must be on or after {}", min_date));
}
}
if let Some(max_date) = max {
if date > *max_date {
return Err(format!("Date must be on or before {}", max_date));
}
}
Ok(())
}
ValidationRule::Time { min, max } => {
let text = value.to_string();
if text.contains(':') {
let parts: Vec<&str> = text.split(':').collect();
if parts.len() >= 2 {
if let Ok(hour) = parts[0].parse::<u32>() {
if hour > 23 {
return Err("Invalid hour: must be 0-23".to_string());
}
}
if let Ok(minute) = parts[1].parse::<u32>() {
if minute > 59 {
return Err("Invalid minute: must be 0-59".to_string());
}
}
if parts.len() >= 3 {
if let Ok(second) = parts[2].parse::<u32>() {
if second > 59 {
return Err("Invalid second: must be 0-59".to_string());
}
}
}
}
}
let time = NaiveTime::parse_from_str(&text, "%H:%M:%S")
.or_else(|_| NaiveTime::parse_from_str(&text, "%H:%M"))
.map_err(|e| format!("Invalid time format: {}", e))?;
if let Some(min_time) = min {
if time < *min_time {
return Err(format!("Time must be at or after {}", min_time));
}
}
if let Some(max_time) = max {
if time > *max_time {
return Err(format!("Time must be at or before {}", max_time));
}
}
Ok(())
}
ValidationRule::Custom { name, validator } => {
if validator(value) {
Ok(())
} else {
Err(format!("Custom validation '{}' failed", name))
}
}
}
}
fn validate_phone_number(&self, phone: &str, country: PhoneCountry) -> Result<(), String> {
let pattern = match country {
PhoneCountry::US => r"^\(?[2-9]\d{2}\)?[-.\s]?\d{3}[-.\s]?\d{4}$",
PhoneCountry::UK => r"^\+?44\s?\d{2}\s?\d{4}\s?\d{4}$",
PhoneCountry::EU => r"^\+?[0-9]{2,3}\s?[0-9]{2,4}\s?[0-9]{2,4}\s?[0-9]{2,4}$",
PhoneCountry::Japan => r"^0\d{1,4}-?\d{1,4}-?\d{4}$",
PhoneCountry::Custom => r"^[0-9+\-\s\(\)]+$",
};
let re = Regex::new(pattern).map_err(|e| format!("Invalid phone regex pattern: {}", e))?;
if re.is_match(phone) {
Ok(())
} else {
Err(format!("Invalid phone number format for {:?}", country))
}
}
fn validate_credit_card(&self, card_number: &str) -> Result<(), String> {
let digits: Vec<u32> = card_number
.chars()
.filter(|c| c.is_ascii_digit())
.filter_map(|c| c.to_digit(10))
.collect();
if digits.len() < 13 || digits.len() > 19 {
return Err("Invalid credit card number length".to_string());
}
let mut sum = 0;
let mut alternate = false;
for digit in digits.iter().rev() {
let mut n = *digit;
if alternate {
n *= 2;
if n > 9 {
n -= 9;
}
}
sum += n;
alternate = !alternate;
}
if sum % 10 == 0 {
Ok(())
} else {
Err("Invalid credit card number".to_string())
}
}
fn apply_format_mask(&self, value: &FieldValue, mask: &FormatMask) -> Result<String, String> {
match mask {
FormatMask::Number {
decimals,
thousands_separator,
allow_negative,
prefix,
suffix,
} => {
let num = value.to_number();
if !allow_negative && num < 0.0 {
return Err("Negative numbers not allowed".to_string());
}
let mut formatted = format!("{:.prec$}", num, prec = decimals);
if *thousands_separator {
let parts: Vec<&str> = formatted.split('.').collect();
let integer_part = parts[0];
let decimal_part = parts.get(1);
let mut result = String::new();
for (i, c) in integer_part.chars().rev().enumerate() {
if i > 0 && i % 3 == 0 {
result.insert(0, ',');
}
result.insert(0, c);
}
if let Some(dec) = decimal_part {
result.push('.');
result.push_str(dec);
}
formatted = result;
}
let mut result = String::new();
if let Some(p) = prefix {
result.push_str(p);
}
result.push_str(&formatted);
if let Some(s) = suffix {
result.push_str(s);
}
Ok(result)
}
FormatMask::Date { format } => self.format_date(&value.to_string(), *format),
FormatMask::Time {
format,
use_24_hour,
} => self.format_time(&value.to_string(), *format, *use_24_hour),
FormatMask::Phone { country } => self.format_phone(&value.to_string(), *country),
FormatMask::SSN => self.format_ssn(&value.to_string()),
FormatMask::ZipCode { plus_four } => self.format_zip(&value.to_string(), *plus_four),
FormatMask::CreditCard => self.format_credit_card(&value.to_string()),
FormatMask::Custom {
pattern,
placeholder,
} => self.apply_custom_mask(&value.to_string(), pattern, *placeholder),
}
}
fn format_date(&self, date_str: &str, format: DateFormat) -> Result<String, String> {
let digits: String = date_str.chars().filter(|c| c.is_ascii_digit()).collect();
if digits.len() < 8 {
return Err("Invalid date format".to_string());
}
let is_yyyy_format = if digits.len() >= 4 {
digits[0..4].parse::<u32>().unwrap_or(0) > 1900
} else {
false
};
let (year, month, day) = if is_yyyy_format {
(&digits[0..4], &digits[4..6], &digits[6..8])
} else {
(&digits[4..8], &digits[0..2], &digits[2..4])
};
let formatted = match format {
DateFormat::MDY => {
format!("{}/{}/{}", month, day, year)
}
DateFormat::DMY => {
format!("{}/{}/{}", day, month, year)
}
DateFormat::YMD => {
format!("{}-{}-{}", year, month, day)
}
DateFormat::DotDMY => {
format!("{}.{}.{}", day, month, year)
}
DateFormat::DashMDY => {
format!("{}-{}-{}", month, day, year)
}
};
Ok(formatted)
}
fn format_time(
&self,
time_str: &str,
format: TimeFormat,
use_24_hour: bool,
) -> Result<String, String> {
let digits: String = time_str.chars().filter(|c| c.is_ascii_digit()).collect();
if digits.len() < 4 {
return Err("Invalid time format".to_string());
}
let hours: u32 = digits[0..2].parse().unwrap_or(0);
let minutes: u32 = digits[2..4].parse().unwrap_or(0);
let seconds: u32 = if digits.len() >= 6 {
digits[4..6].parse().unwrap_or(0)
} else {
0
};
let formatted = match format {
TimeFormat::HM => {
if use_24_hour {
format!("{:02}:{:02}", hours, minutes)
} else {
let (h, am_pm) = if hours == 0 {
(12, "AM")
} else if hours < 12 {
(hours, "AM")
} else if hours == 12 {
(12, "PM")
} else {
(hours - 12, "PM")
};
format!("{:02}:{:02} {}", h, minutes, am_pm)
}
}
TimeFormat::HMAM => {
let (h, am_pm) = if hours == 0 {
(12, "AM")
} else if hours < 12 {
(hours, "AM")
} else if hours == 12 {
(12, "PM")
} else {
(hours - 12, "PM")
};
format!("{:02}:{:02} {}", h, minutes, am_pm)
}
TimeFormat::HMS | TimeFormat::HMSAM => {
if use_24_hour {
format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
} else {
let (h, am_pm) = if hours == 0 {
(12, "AM")
} else if hours < 12 {
(hours, "AM")
} else if hours == 12 {
(12, "PM")
} else {
(hours - 12, "PM")
};
format!("{:02}:{:02}:{:02} {}", h, minutes, seconds, am_pm)
}
}
};
Ok(formatted)
}
fn format_phone(&self, phone: &str, country: PhoneCountry) -> Result<String, String> {
let digits: String = phone.chars().filter(|c| c.is_ascii_digit()).collect();
let formatted = match country {
PhoneCountry::US => {
if digits.len() >= 10 {
format!("({}) {}-{}", &digits[0..3], &digits[3..6], &digits[6..10])
} else {
return Err("Invalid US phone number".to_string());
}
}
PhoneCountry::UK => {
if digits.len() >= 11 {
format!("+{} {} {}", &digits[0..2], &digits[2..6], &digits[6..])
} else {
return Err("Invalid UK phone number".to_string());
}
}
_ => digits,
};
Ok(formatted)
}
fn format_ssn(&self, ssn: &str) -> Result<String, String> {
let digits: String = ssn.chars().filter(|c| c.is_ascii_digit()).collect();
if digits.len() != 9 {
return Err("SSN must be 9 digits".to_string());
}
Ok(format!(
"{}-{}-{}",
&digits[0..3],
&digits[3..5],
&digits[5..9]
))
}
fn format_zip(&self, zip: &str, plus_four: bool) -> Result<String, String> {
let digits: String = zip.chars().filter(|c| c.is_ascii_digit()).collect();
if plus_four {
if digits.len() != 9 {
return Err("ZIP+4 must be 9 digits".to_string());
}
Ok(format!("{}-{}", &digits[0..5], &digits[5..9]))
} else {
if digits.len() < 5 {
return Err("ZIP must be at least 5 digits".to_string());
}
Ok(digits[0..5].to_string())
}
}
fn format_credit_card(&self, card: &str) -> Result<String, String> {
let digits: String = card.chars().filter(|c| c.is_ascii_digit()).collect();
if digits.len() < 13 || digits.len() > 19 {
return Err("Invalid credit card number length".to_string());
}
let mut formatted = String::new();
for (i, c) in digits.chars().enumerate() {
if i > 0 && i % 4 == 0 {
formatted.push(' ');
}
formatted.push(c);
}
Ok(formatted)
}
fn apply_custom_mask(
&self,
value: &str,
pattern: &str,
placeholder: char,
) -> Result<String, String> {
let mut result = String::new();
let mut value_chars = value.chars();
for pattern_char in pattern.chars() {
if pattern_char == placeholder {
if let Some(c) = value_chars.next() {
result.push(c);
} else {
break;
}
} else {
result.push(pattern_char);
}
}
Ok(result)
}
fn get_error_type(&self, rule: &ValidationRule) -> ValidationErrorType {
match rule {
ValidationRule::Required => ValidationErrorType::Required,
ValidationRule::Range { .. } => ValidationErrorType::Range,
ValidationRule::Length { .. } => ValidationErrorType::Length,
ValidationRule::Pattern(_) => ValidationErrorType::Pattern,
_ => ValidationErrorType::Custom,
}
}
pub fn validate_all(&mut self, fields: &HashMap<String, FieldValue>) -> Vec<ValidationResult> {
let mut results = Vec::new();
for (field_name, value) in fields {
results.push(self.validate_field(field_name, value));
}
results
}
pub fn clear_cache(&mut self) {
self.validation_cache.clear();
}
pub fn get_cached_result(&self, field_name: &str) -> Option<&ValidationResult> {
self.validation_cache.get(field_name)
}
}
impl fmt::Display for ValidationResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_valid {
write!(f, "Valid")
} else {
write!(f, "Invalid: {} errors", self.errors.len())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_required_field_validation() {
let mut system = FormValidationSystem::new();
let info = RequiredFieldInfo {
field_name: "name".to_string(),
error_message: "Name is required".to_string(),
group: None,
condition: None,
};
system.add_required_field(info);
let result = system.validate_field("name", &FieldValue::Empty);
assert!(!result.is_valid);
assert_eq!(result.errors.len(), 1);
assert_eq!(result.errors[0].error_type, ValidationErrorType::Required);
}
#[test]
fn test_email_validation() {
let mut system = FormValidationSystem::new();
let validator = FieldValidator {
field_name: "email".to_string(),
rules: vec![ValidationRule::Email],
format_mask: None,
error_message: None,
};
system.add_validator(validator);
let valid_result =
system.validate_field("email", &FieldValue::Text("test@example.com".to_string()));
assert!(valid_result.is_valid);
let invalid_result =
system.validate_field("email", &FieldValue::Text("invalid-email".to_string()));
assert!(!invalid_result.is_valid);
}
#[test]
fn test_phone_format_mask() {
let system = FormValidationSystem::new();
let mask = FormatMask::Phone {
country: PhoneCountry::US,
};
let result = system.apply_format_mask(&FieldValue::Text("5551234567".to_string()), &mask);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "(555) 123-4567");
}
#[test]
fn test_credit_card_validation() {
let system = FormValidationSystem::new();
let valid = system.validate_credit_card("4532015112830366");
assert!(valid.is_ok());
let invalid = system.validate_credit_card("1234567890123456");
assert!(invalid.is_err());
}
#[test]
fn test_ssn_format() {
let system = FormValidationSystem::new();
let result = system.format_ssn("123456789");
assert!(result.is_ok());
assert_eq!(result.unwrap(), "123-45-6789");
}
#[test]
fn test_range_validation() {
let mut system = FormValidationSystem::new();
let validator = FieldValidator {
field_name: "age".to_string(),
rules: vec![ValidationRule::Range {
min: Some(18.0),
max: Some(100.0),
}],
format_mask: None,
error_message: None,
};
system.add_validator(validator);
let valid = system.validate_field("age", &FieldValue::Number(25.0));
assert!(valid.is_valid);
let too_young = system.validate_field("age", &FieldValue::Number(15.0));
assert!(!too_young.is_valid);
let too_old = system.validate_field("age", &FieldValue::Number(150.0));
assert!(!too_old.is_valid);
}
#[test]
fn test_custom_mask() {
let system = FormValidationSystem::new();
let mask = FormatMask::Custom {
pattern: "(###) ###-####".to_string(),
placeholder: '#',
};
let result = system.apply_format_mask(&FieldValue::Text("5551234567".to_string()), &mask);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "(555) 123-4567");
}
#[test]
fn test_validation_settings() {
let settings = ValidationSettings::default();
assert!(settings.real_time_validation);
assert!(settings.highlight_errors);
assert!(settings.show_error_messages);
}
#[test]
fn test_url_validation() {
let mut system = FormValidationSystem::new();
let validator = FieldValidator {
field_name: "website".to_string(),
rules: vec![ValidationRule::Url],
format_mask: None,
error_message: None,
};
system.add_validator(validator);
let valid = system.validate_field(
"website",
&FieldValue::Text("https://example.com".to_string()),
);
assert!(valid.is_valid);
let valid_http =
system.validate_field("website", &FieldValue::Text("http://test.org".to_string()));
assert!(valid_http.is_valid);
let invalid = system.validate_field("website", &FieldValue::Text("not-a-url".to_string()));
assert!(!invalid.is_valid);
}
#[test]
fn test_length_validation() {
let mut system = FormValidationSystem::new();
let validator = FieldValidator {
field_name: "comment".to_string(),
rules: vec![ValidationRule::Length {
min: Some(10),
max: Some(100),
}],
format_mask: None,
error_message: None,
};
system.add_validator(validator);
let valid = system.validate_field(
"comment",
&FieldValue::Text("This is a valid comment.".to_string()),
);
assert!(valid.is_valid);
let too_short = system.validate_field("comment", &FieldValue::Text("Short".to_string()));
assert!(!too_short.is_valid);
let too_long = system.validate_field("comment", &FieldValue::Text("x".repeat(150)));
assert!(!too_long.is_valid);
}
#[test]
fn test_pattern_validation() {
let mut system = FormValidationSystem::new();
let validator = FieldValidator {
field_name: "code".to_string(),
rules: vec![ValidationRule::Pattern(r"^[A-Z]{3}-\d{3}$".to_string())],
format_mask: None,
error_message: Some("Code must be in format ABC-123".to_string()),
};
system.add_validator(validator);
let valid = system.validate_field("code", &FieldValue::Text("ABC-123".to_string()));
assert!(valid.is_valid);
let invalid = system.validate_field("code", &FieldValue::Text("abc-123".to_string()));
assert!(!invalid.is_valid);
assert!(invalid.errors[0].message.contains("ABC-123"));
}
#[test]
fn test_date_validation() {
let mut system = FormValidationSystem::new();
let validator = FieldValidator {
field_name: "birthdate".to_string(),
rules: vec![ValidationRule::Date {
min: Some(NaiveDate::from_ymd_opt(1900, 1, 1).unwrap()),
max: Some(NaiveDate::from_ymd_opt(2020, 12, 31).unwrap()),
}],
format_mask: None,
error_message: None,
};
system.add_validator(validator);
let valid = system.validate_field("birthdate", &FieldValue::Text("1990-05-15".to_string()));
assert!(valid.is_valid);
let too_early =
system.validate_field("birthdate", &FieldValue::Text("1850-01-01".to_string()));
assert!(!too_early.is_valid);
}
#[test]
fn test_time_validation() {
let mut system = FormValidationSystem::new();
let validator = FieldValidator {
field_name: "appointment".to_string(),
rules: vec![ValidationRule::Time {
min: Some(NaiveTime::from_hms_opt(9, 0, 0).unwrap()),
max: Some(NaiveTime::from_hms_opt(17, 0, 0).unwrap()),
}],
format_mask: None,
error_message: None,
};
system.add_validator(validator);
let valid = system.validate_field("appointment", &FieldValue::Text("14:30".to_string()));
assert!(valid.is_valid);
let too_early =
system.validate_field("appointment", &FieldValue::Text("08:00".to_string()));
assert!(!too_early.is_valid);
}
#[test]
fn test_phone_number_uk() {
let mut system = FormValidationSystem::new();
let validator = FieldValidator {
field_name: "phone_uk".to_string(),
rules: vec![ValidationRule::PhoneNumber {
country: PhoneCountry::UK,
}],
format_mask: None,
error_message: None,
};
system.add_validator(validator);
let valid =
system.validate_field("phone_uk", &FieldValue::Text("441234567890".to_string()));
assert!(valid.is_valid);
let invalid = system.validate_field("phone_uk", &FieldValue::Text("12345".to_string()));
assert!(!invalid.is_valid);
}
#[test]
fn test_zip_format_with_plus_four() {
let system = FormValidationSystem::new();
let result_plus = system.format_zip("123456789", true);
assert!(result_plus.is_ok());
assert_eq!(result_plus.unwrap(), "12345-6789");
let result_regular = system.format_zip("12345", false);
assert!(result_regular.is_ok());
assert_eq!(result_regular.unwrap(), "12345");
let invalid = system.format_zip("12345", true);
assert!(invalid.is_err());
}
#[test]
fn test_number_format_mask() {
let system = FormValidationSystem::new();
let mask = FormatMask::Number {
decimals: 2,
thousands_separator: true,
allow_negative: true,
prefix: Some("$".to_string()),
suffix: Some(" USD".to_string()),
};
let result = system.apply_format_mask(&FieldValue::Number(1234567.89), &mask);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "$1,234,567.89 USD");
let negative_result = system.apply_format_mask(&FieldValue::Number(-1234.56), &mask);
assert!(negative_result.is_ok());
assert_eq!(negative_result.unwrap(), "$-1,234.56 USD");
}
#[test]
fn test_date_format_mask() {
let system = FormValidationSystem::new();
let mask_mdy = FormatMask::Date {
format: DateFormat::MDY,
};
let result_mdy =
system.apply_format_mask(&FieldValue::Text("01152022".to_string()), &mask_mdy);
assert!(result_mdy.is_ok());
assert_eq!(result_mdy.unwrap(), "01/15/2022");
let mask_ymd = FormatMask::Date {
format: DateFormat::YMD,
};
let result_ymd =
system.apply_format_mask(&FieldValue::Text("20220115".to_string()), &mask_ymd);
assert!(result_ymd.is_ok());
assert_eq!(result_ymd.unwrap(), "2022-01-15");
}
#[test]
fn test_time_format_mask() {
let system = FormValidationSystem::new();
let mask_24 = FormatMask::Time {
format: TimeFormat::HMS,
use_24_hour: true,
};
let result_24 = system.apply_format_mask(&FieldValue::Text("143045".to_string()), &mask_24);
assert!(result_24.is_ok());
assert_eq!(result_24.unwrap(), "14:30:45");
let mask_12 = FormatMask::Time {
format: TimeFormat::HMSAM,
use_24_hour: false,
};
let result_12 = system.apply_format_mask(&FieldValue::Text("143045".to_string()), &mask_12);
assert!(result_12.is_ok());
assert_eq!(result_12.unwrap(), "02:30:45 PM");
}
#[test]
fn test_validation_cache() {
let mut system = FormValidationSystem::new();
let validator = FieldValidator {
field_name: "cached_field".to_string(),
rules: vec![ValidationRule::Required],
format_mask: None,
error_message: None,
};
system.add_validator(validator);
let result1 = system.validate_field("cached_field", &FieldValue::Text("value".to_string()));
assert!(result1.is_valid);
let cached = system.get_cached_result("cached_field");
assert!(cached.is_some());
assert!(cached.unwrap().is_valid);
system.clear_cache();
let cached_after_clear = system.get_cached_result("cached_field");
assert!(cached_after_clear.is_none());
}
#[test]
fn test_validation_error_types() {
let error_required = ValidationError {
field_name: "test".to_string(),
error_type: ValidationErrorType::Required,
message: "Field is required".to_string(),
details: None,
};
assert_eq!(error_required.error_type, ValidationErrorType::Required);
let error_range = ValidationError {
field_name: "test".to_string(),
error_type: ValidationErrorType::Range,
message: "Value out of range".to_string(),
details: Some("Must be between 1 and 100".to_string()),
};
assert_eq!(error_range.error_type, ValidationErrorType::Range);
assert!(error_range.details.is_some());
}
#[test]
fn test_field_validator_with_multiple_rules() {
let mut system = FormValidationSystem::new();
let validator = FieldValidator {
field_name: "username".to_string(),
rules: vec![
ValidationRule::Required,
ValidationRule::Length {
min: Some(3),
max: Some(20),
},
ValidationRule::Pattern(r"^[a-zA-Z0-9_]+$".to_string()),
],
format_mask: None,
error_message: None,
};
system.add_validator(validator);
let valid = system.validate_field("username", &FieldValue::Text("user_123".to_string()));
assert!(valid.is_valid);
let too_short = system.validate_field("username", &FieldValue::Text("ab".to_string()));
assert!(!too_short.is_valid);
let invalid_chars =
system.validate_field("username", &FieldValue::Text("user@123".to_string()));
assert!(!invalid_chars.is_valid);
}
#[test]
fn test_credit_card_format() {
let system = FormValidationSystem::new();
let result = system.format_credit_card("4532015112830366");
assert!(result.is_ok());
assert_eq!(result.unwrap(), "4532 0151 1283 0366");
let too_short = system.format_credit_card("123");
assert!(too_short.is_err());
let too_long = system.format_credit_card("12345678901234567890");
assert!(too_long.is_err());
}
#[test]
fn test_required_field_with_group() {
let mut system = FormValidationSystem::new();
let info = RequiredFieldInfo {
field_name: "address".to_string(),
error_message: "Address is required".to_string(),
group: Some("contact_info".to_string()),
condition: None,
};
system.add_required_field(info);
let result = system.validate_field("address", &FieldValue::Empty);
assert!(!result.is_valid);
assert_eq!(result.errors[0].error_type, ValidationErrorType::Required);
}
#[test]
fn test_validation_result_display() {
let valid_result = ValidationResult {
field_name: "test".to_string(),
is_valid: true,
errors: vec![],
warnings: vec![],
formatted_value: None,
};
assert_eq!(format!("{}", valid_result), "Valid");
let invalid_result = ValidationResult {
field_name: "test".to_string(),
is_valid: false,
errors: vec![
ValidationError {
field_name: "test".to_string(),
error_type: ValidationErrorType::Required,
message: "Required".to_string(),
details: None,
},
ValidationError {
field_name: "test".to_string(),
error_type: ValidationErrorType::Length,
message: "Too short".to_string(),
details: None,
},
],
warnings: vec![],
formatted_value: None,
};
assert_eq!(format!("{}", invalid_result), "Invalid: 2 errors");
}
#[test]
fn test_validate_all_fields() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "name".to_string(),
rules: vec![ValidationRule::Required],
format_mask: None,
error_message: None,
});
system.add_validator(FieldValidator {
field_name: "age".to_string(),
rules: vec![ValidationRule::Range {
min: Some(0.0),
max: Some(120.0),
}],
format_mask: None,
error_message: None,
});
let mut fields = HashMap::new();
fields.insert("name".to_string(), FieldValue::Text("John".to_string()));
fields.insert("age".to_string(), FieldValue::Number(30.0));
let results = system.validate_all(&fields);
assert_eq!(results.len(), 2);
assert!(results.iter().all(|r| r.is_valid));
}
#[test]
fn test_validation_settings_advanced() {
let settings = ValidationSettings {
validate_on_change: true,
show_format_hints: true,
auto_format: false,
allow_partial: false,
real_time_validation: true,
highlight_errors: true,
show_error_messages: true,
};
let mut system = FormValidationSystem::new();
system.settings = settings;
assert!(system.settings.validate_on_change);
assert!(system.settings.show_format_hints);
assert!(!system.settings.auto_format);
}
#[test]
fn test_complex_pattern_validation() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "product_code".to_string(),
rules: vec![ValidationRule::Pattern(
r"^[A-Z]{3}-\d{4}-[A-Z]\d$".to_string(),
)],
format_mask: None,
error_message: Some("Invalid product code format".to_string()),
});
let valid_code = FieldValue::Text("ABC-1234-A5".to_string());
let result = system.validate_field("product_code", &valid_code);
assert!(result.is_valid);
let invalid_code = FieldValue::Text("abc-1234-a5".to_string());
let result = system.validate_field("product_code", &invalid_code);
assert!(!result.is_valid);
}
#[test]
fn test_currency_format_mask() {
let system = FormValidationSystem::new();
let mask = FormatMask::Number {
decimals: 2,
thousands_separator: true,
allow_negative: false,
prefix: Some("$".to_string()),
suffix: None,
};
let result = system.apply_format_mask(&FieldValue::Number(1234567.89), &mask);
assert!(result.is_ok());
}
#[test]
fn test_international_phone_formats() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "us_phone".to_string(),
rules: vec![ValidationRule::PhoneNumber {
country: PhoneCountry::US,
}],
format_mask: None,
error_message: None,
});
let valid_us = FieldValue::Text("(555) 123-4567".to_string());
assert!(system.validate_field("us_phone", &valid_us).is_valid);
system.add_validator(FieldValidator {
field_name: "uk_phone".to_string(),
rules: vec![ValidationRule::PhoneNumber {
country: PhoneCountry::UK,
}],
format_mask: None,
error_message: None,
});
let valid_uk = FieldValue::Text("+44 20 1234 5678".to_string());
assert!(system.validate_field("uk_phone", &valid_uk).is_valid);
}
#[test]
fn test_phone_validation_all_countries() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "eu_phone".to_string(),
rules: vec![ValidationRule::PhoneNumber {
country: PhoneCountry::EU,
}],
format_mask: None,
error_message: None,
});
let valid_eu = FieldValue::Text("+33 123 456 7890".to_string());
assert!(system.validate_field("eu_phone", &valid_eu).is_valid);
let invalid_eu = FieldValue::Text("123-456".to_string());
assert!(!system.validate_field("eu_phone", &invalid_eu).is_valid);
system.add_validator(FieldValidator {
field_name: "japan_phone".to_string(),
rules: vec![ValidationRule::PhoneNumber {
country: PhoneCountry::Japan,
}],
format_mask: None,
error_message: None,
});
let valid_japan = FieldValue::Text("03-1234-5678".to_string());
assert!(system.validate_field("japan_phone", &valid_japan).is_valid);
let invalid_japan = FieldValue::Text("123".to_string());
assert!(
!system
.validate_field("japan_phone", &invalid_japan)
.is_valid
);
system.add_validator(FieldValidator {
field_name: "custom_phone".to_string(),
rules: vec![ValidationRule::PhoneNumber {
country: PhoneCountry::Custom,
}],
format_mask: None,
error_message: None,
});
let valid_custom = FieldValue::Text("+1-234-567-8900".to_string());
assert!(
system
.validate_field("custom_phone", &valid_custom)
.is_valid
);
let invalid_custom = FieldValue::Text("not a phone".to_string());
assert!(
!system
.validate_field("custom_phone", &invalid_custom)
.is_valid
);
}
#[test]
fn test_credit_card_validation_edge_cases() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "cc".to_string(),
rules: vec![ValidationRule::CreditCard],
format_mask: None,
error_message: None,
});
let too_short = FieldValue::Text("123456789012".to_string());
let result = system.validate_field("cc", &too_short);
assert!(!result.is_valid);
assert!(result.errors[0].message.contains("length"));
let too_long = FieldValue::Text("12345678901234567890".to_string());
let result = system.validate_field("cc", &too_long);
assert!(!result.is_valid);
assert!(result.errors[0].message.contains("length"));
let invalid_luhn = FieldValue::Text("4111111111111112".to_string()); let result = system.validate_field("cc", &invalid_luhn);
assert!(!result.is_valid);
assert!(result.errors[0].message.contains("Invalid credit card"));
let valid_cc = FieldValue::Text("4111111111111111".to_string()); let result = system.validate_field("cc", &valid_cc);
assert!(result.is_valid);
}
#[test]
fn test_time_validation_with_range() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "appointment".to_string(),
rules: vec![ValidationRule::Time {
min: Some(NaiveTime::from_hms_opt(9, 0, 0).unwrap()),
max: Some(NaiveTime::from_hms_opt(17, 0, 0).unwrap()),
}],
format_mask: None,
error_message: None,
});
let valid = FieldValue::Text("10:30:00".to_string());
assert!(system.validate_field("appointment", &valid).is_valid);
let too_early = FieldValue::Text("08:30:00".to_string());
let result = system.validate_field("appointment", &too_early);
assert!(!result.is_valid);
assert!(result.errors[0].message.contains("at or after"));
let too_late = FieldValue::Text("18:00:00".to_string());
let result = system.validate_field("appointment", &too_late);
assert!(!result.is_valid);
assert!(result.errors[0].message.contains("at or before"));
let invalid = FieldValue::Text("not a time".to_string());
let result = system.validate_field("appointment", &invalid);
assert!(!result.is_valid);
}
#[test]
fn test_custom_validator() {
fn is_even(value: &FieldValue) -> bool {
if let FieldValue::Text(s) = value {
if let Ok(n) = s.parse::<i32>() {
return n % 2 == 0;
}
}
false
}
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "even_number".to_string(),
rules: vec![ValidationRule::Custom {
name: "even_check".to_string(),
validator: is_even,
}],
format_mask: None,
error_message: None,
});
let valid = FieldValue::Text("42".to_string());
assert!(system.validate_field("even_number", &valid).is_valid);
let invalid = FieldValue::Text("43".to_string());
let result = system.validate_field("even_number", &invalid);
assert!(!result.is_valid);
assert!(result.errors[0]
.message
.contains("Custom validation 'even_check' failed"));
let non_number = FieldValue::Text("abc".to_string());
let result = system.validate_field("even_number", &non_number);
assert!(!result.is_valid);
}
#[test]
fn test_format_mask_number_with_all_options() {
let system = FormValidationSystem::new();
let mask = FormatMask::Number {
decimals: 3,
thousands_separator: true,
allow_negative: true,
prefix: Some("€ ".to_string()),
suffix: Some(" EUR".to_string()),
};
let value = FieldValue::Number(12345.6789);
let formatted = system.apply_format_mask(&value, &mask);
assert!(formatted.is_ok());
assert_eq!(formatted.unwrap(), "€ 12,345.679 EUR");
let value = FieldValue::Number(-9876.543);
let formatted = system.apply_format_mask(&value, &mask);
assert!(formatted.is_ok());
assert_eq!(formatted.unwrap(), "€ -9,876.543 EUR");
let mask_no_neg = FormatMask::Number {
decimals: 2,
thousands_separator: false,
allow_negative: false,
prefix: None,
suffix: None,
};
let value = FieldValue::Number(-123.456);
let formatted = system.apply_format_mask(&value, &mask_no_neg);
assert!(formatted.is_err());
assert!(formatted
.unwrap_err()
.contains("Negative numbers not allowed"));
}
#[test]
fn test_ssn_and_zip_format_masks() {
let system = FormValidationSystem::new();
let ssn_mask = FormatMask::SSN;
let ssn_value = FieldValue::Text("123456789".to_string());
let formatted = system.apply_format_mask(&ssn_value, &ssn_mask);
assert!(formatted.is_ok());
assert_eq!(formatted.unwrap(), "123-45-6789");
let invalid_ssn = FieldValue::Text("12345".to_string());
let formatted = system.apply_format_mask(&invalid_ssn, &ssn_mask);
assert!(formatted.is_err());
assert!(formatted.unwrap_err().contains("9 digits"));
let zip5_mask = FormatMask::ZipCode { plus_four: false };
let zip5_value = FieldValue::Text("12345".to_string());
let formatted = system.apply_format_mask(&zip5_value, &zip5_mask);
assert!(formatted.is_ok());
assert_eq!(formatted.unwrap(), "12345");
let invalid_zip5 = FieldValue::Text("1234".to_string());
let formatted = system.apply_format_mask(&invalid_zip5, &zip5_mask);
assert!(formatted.is_err());
let zip9_mask = FormatMask::ZipCode { plus_four: true };
let zip9_value = FieldValue::Text("123456789".to_string());
let formatted = system.apply_format_mask(&zip9_value, &zip9_mask);
assert!(formatted.is_ok());
assert_eq!(formatted.unwrap(), "12345-6789");
let invalid_zip9 = FieldValue::Text("12345".to_string());
let formatted = system.apply_format_mask(&invalid_zip9, &zip9_mask);
assert!(formatted.is_err());
}
#[test]
fn test_date_format_masks() {
let system = FormValidationSystem::new();
let date = NaiveDate::from_ymd_opt(2024, 3, 15).unwrap();
let value = FieldValue::Text(date.to_string());
let mask = FormatMask::Date {
format: DateFormat::MDY,
};
let formatted = system.apply_format_mask(&value, &mask);
assert!(formatted.is_ok());
assert_eq!(formatted.unwrap(), "03/15/2024");
let mask = FormatMask::Date {
format: DateFormat::DMY,
};
let formatted = system.apply_format_mask(&value, &mask);
assert!(formatted.is_ok());
assert_eq!(formatted.unwrap(), "15/03/2024");
let mask = FormatMask::Date {
format: DateFormat::YMD,
};
let formatted = system.apply_format_mask(&value, &mask);
assert!(formatted.is_ok());
assert_eq!(formatted.unwrap(), "2024-03-15");
let mask = FormatMask::Date {
format: DateFormat::DotDMY,
};
let formatted = system.apply_format_mask(&value, &mask);
assert!(formatted.is_ok());
assert_eq!(formatted.unwrap(), "15.03.2024");
let mask = FormatMask::Date {
format: DateFormat::DashMDY,
};
let formatted = system.apply_format_mask(&value, &mask);
assert!(formatted.is_ok());
assert_eq!(formatted.unwrap(), "03-15-2024");
}
#[test]
fn test_time_format_masks() {
let system = FormValidationSystem::new();
let time_am = NaiveTime::from_hms_opt(9, 30, 45).unwrap();
let value_am = FieldValue::Text(time_am.to_string());
let time_pm = NaiveTime::from_hms_opt(15, 45, 30).unwrap();
let value_pm = FieldValue::Text(time_pm.to_string());
let mask = FormatMask::Time {
format: TimeFormat::HM,
use_24_hour: true,
};
let formatted = system.apply_format_mask(&value_am, &mask);
assert!(formatted.is_ok());
assert_eq!(formatted.unwrap(), "09:30");
let mask = FormatMask::Time {
format: TimeFormat::HMS,
use_24_hour: true,
};
let formatted = system.apply_format_mask(&value_am, &mask);
assert!(formatted.is_ok());
assert_eq!(formatted.unwrap(), "09:30:45");
let mask = FormatMask::Time {
format: TimeFormat::HMAM,
use_24_hour: false,
};
let formatted = system.apply_format_mask(&value_am, &mask);
assert!(formatted.is_ok());
assert_eq!(formatted.unwrap(), "09:30 AM");
let formatted = system.apply_format_mask(&value_pm, &mask);
assert!(formatted.is_ok());
assert_eq!(formatted.unwrap(), "03:45 PM");
let mask = FormatMask::Time {
format: TimeFormat::HMSAM,
use_24_hour: false,
};
let formatted = system.apply_format_mask(&value_pm, &mask);
assert!(formatted.is_ok());
assert_eq!(formatted.unwrap(), "03:45:30 PM");
}
#[test]
fn test_multiple_validation_rules() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "password".to_string(),
rules: vec![
ValidationRule::Required,
ValidationRule::Length {
min: Some(8),
max: Some(32),
},
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()),
],
format_mask: None,
error_message: Some(
"Password must be 8-32 chars with uppercase, lowercase, and number".to_string(),
),
});
let weak_password = FieldValue::Text("abc123".to_string());
let result = system.validate_field("password", &weak_password);
assert!(!result.is_valid);
assert!(result.errors.len() >= 2);
let strong_password = FieldValue::Text("SecurePass123".to_string());
assert!(system.validate_field("password", &strong_password).is_valid);
}
#[test]
fn test_conditional_required_field() {
let mut system = FormValidationSystem::new();
let info = RequiredFieldInfo {
field_name: "shipping_address".to_string(),
error_message: "Shipping address required when different from billing".to_string(),
group: Some("shipping".to_string()),
condition: Some(RequirementCondition::IfFieldNotEmpty {
field: "different_shipping".to_string(),
}),
};
system.add_required_field(info);
let _result = system.validate_field("shipping_address", &FieldValue::Empty);
}
#[test]
fn test_validation_cache_advanced() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "cached_field".to_string(),
rules: vec![ValidationRule::Required],
format_mask: None,
error_message: None,
});
let value = FieldValue::Text("test".to_string());
let _result1 = system.validate_field("cached_field", &value);
assert!(system.validation_cache.contains_key("cached_field"));
system.clear_cache();
assert!(system.validation_cache.is_empty());
}
#[test]
fn test_custom_validator_function() {
let mut system = FormValidationSystem::new();
fn is_even_number(value: &FieldValue) -> bool {
match value {
FieldValue::Number(n) => (*n as i32) % 2 == 0,
_ => false,
}
}
system.add_validator(FieldValidator {
field_name: "even_number".to_string(),
rules: vec![ValidationRule::Custom {
name: "even_check".to_string(),
validator: is_even_number,
}],
format_mask: None,
error_message: Some("Must be an even number".to_string()),
});
assert!(
system
.validate_field("even_number", &FieldValue::Number(4.0))
.is_valid
);
assert!(
!system
.validate_field("even_number", &FieldValue::Number(5.0))
.is_valid
);
}
#[test]
fn test_percentage_format() {
let system = FormValidationSystem::new();
let mask = FormatMask::Number {
decimals: 1,
thousands_separator: false,
allow_negative: false,
prefix: None,
suffix: Some("%".to_string()),
};
let result = system.apply_format_mask(&FieldValue::Number(0.856), &mask);
assert!(result.is_ok());
}
#[test]
fn test_clear_validation_errors() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "test".to_string(),
rules: vec![ValidationRule::Required],
format_mask: None,
error_message: None,
});
let _ = system.validate_field("test", &FieldValue::Empty);
assert!(system.validation_cache.contains_key("test"));
system.clear_cache();
assert!(system.validation_cache.is_empty());
}
#[test]
fn test_batch_validation() {
let mut system = FormValidationSystem::new();
for i in 0..5 {
system.add_validator(FieldValidator {
field_name: format!("field_{}", i),
rules: vec![ValidationRule::Required],
format_mask: None,
error_message: None,
});
}
let mut fields = HashMap::new();
for i in 0..5 {
fields.insert(
format!("field_{}", i),
FieldValue::Text(format!("value_{}", i)),
);
}
let results = system.validate_all(&fields);
assert_eq!(results.len(), 5);
assert!(results.iter().all(|r| r.is_valid));
}
#[test]
fn test_unicode_text_validation() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "unicode_text".to_string(),
rules: vec![ValidationRule::Length {
min: Some(1),
max: Some(100),
}],
format_mask: None,
error_message: None,
});
let test_cases = vec![
("Hello World", true), ("Café münü", true), ("🚀 Rocket ship", true), ("こんにちは", true), ("مرحبا", true), ("Привет", true), ("🏳️⚧️🏳️🌈", true), ("𝒯𝒽𝒾𝓈 𝒾𝓈 𝓂𝒶𝓉𝒽", true), ("ℌ𝔢𝔩𝔩𝔬 𝔚𝔬𝔯𝔩𝔡", true), ("\u{200B}\u{FEFF}hidden\u{200C}", true), ];
for (text, should_be_valid) in test_cases {
let value = FieldValue::Text(text.to_string());
let result = system.validate_field("unicode_text", &value);
assert_eq!(
result.is_valid, should_be_valid,
"Failed for text: {}",
text
);
}
}
#[test]
fn test_unicode_length_calculation() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "emoji_text".to_string(),
rules: vec![ValidationRule::Length {
min: Some(1),
max: Some(5),
}],
format_mask: None,
error_message: None,
});
let emoji_text = FieldValue::Text("🚀".to_string());
let result = system.validate_field("emoji_text", &emoji_text);
assert!(result.is_valid);
let multi_emoji = FieldValue::Text("🚀🌟".to_string());
let result = system.validate_field("emoji_text", &multi_emoji);
assert!(!result.is_valid); }
#[test]
fn test_unicode_pattern_matching() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "unicode_pattern".to_string(),
rules: vec![ValidationRule::Pattern(r"^[\p{L}\p{N}\s]+$".to_string())],
format_mask: None,
error_message: None,
});
let test_cases = vec![
("Hello World", true), ("Café123", true), ("こんにちは123", true), ("Hello@World", false), ("🚀 Test", false), ];
for (text, should_be_valid) in test_cases {
let value = FieldValue::Text(text.to_string());
let result = system.validate_field("unicode_pattern", &value);
assert_eq!(
result.is_valid, should_be_valid,
"Pattern failed for text: {}",
text
);
}
}
#[test]
fn test_unicode_email_validation() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "international_email".to_string(),
rules: vec![ValidationRule::Email],
format_mask: None,
error_message: None,
});
let test_cases = vec![
("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), ];
for (email, should_be_valid) in test_cases {
let value = FieldValue::Text(email.to_string());
let result = system.validate_field("international_email", &value);
assert_eq!(
result.is_valid, should_be_valid,
"Email validation failed for: {}",
email
);
}
}
#[test]
fn test_unicode_normalization() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "normalized_text".to_string(),
rules: vec![ValidationRule::Pattern(r"^café$".to_string())],
format_mask: None,
error_message: None,
});
let nfc_form = "café"; let nfd_form = "cafe\u{0301}";
let nfc_value = FieldValue::Text(nfc_form.to_string());
let nfd_value = FieldValue::Text(nfd_form.to_string());
let nfc_result = system.validate_field("normalized_text", &nfc_value);
let nfd_result = system.validate_field("normalized_text", &nfd_value);
assert!(nfc_result.is_valid);
assert!(!nfd_result.is_valid);
}
#[test]
fn test_sql_injection_patterns() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "user_input".to_string(),
rules: vec![
ValidationRule::Length {
min: Some(1),
max: Some(100),
},
ValidationRule::Pattern(r#"^[^';\-]+$"#.to_string()),
],
format_mask: None,
error_message: Some("Invalid characters detected".to_string()),
});
let malicious_inputs = vec![
"'; DROP TABLE users; --",
"' OR '1'='1",
"admin'/*",
"'; SELECT * FROM users WHERE 't' = 't",
"' UNION SELECT * FROM passwords--",
"\\\\\\\'; SELECT 1; --",
];
for input in malicious_inputs {
let value = FieldValue::Text(input.to_string());
let result = system.validate_field("user_input", &value);
assert!(
!result.is_valid,
"Should reject SQL injection pattern: {}",
input
);
assert_eq!(result.errors[0].message, "Invalid characters detected");
}
let valid_inputs = vec![
"john.doe",
"valid_username",
"123456789",
"normal text input",
];
for input in valid_inputs {
let value = FieldValue::Text(input.to_string());
let result = system.validate_field("user_input", &value);
assert!(result.is_valid, "Should accept valid input: {}", input);
}
}
#[test]
fn test_xss_prevention_patterns() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "comment".to_string(),
rules: vec![
ValidationRule::Pattern(r#"^[^<>"'&]+$"#.to_string()),
],
format_mask: None,
error_message: Some("HTML and script tags not allowed".to_string()),
});
let xss_attempts = vec![
"<script>alert('xss')</script>",
"<img src='x' onerror='alert(1)'>",
"javascript:alert('xss')",
"<iframe src='javascript:alert(1)'></iframe>",
"\"onmouseover=\"alert(1)\"",
"'onload='alert(1)'",
"<script>alert('xss')</script>",
];
for input in xss_attempts {
let value = FieldValue::Text(input.to_string());
let result = system.validate_field("comment", &value);
assert!(!result.is_valid, "Should reject XSS attempt: {}", input);
}
let valid_comments = vec![
"This is a normal comment",
"Great post! Thanks for sharing",
"I agree with your points",
];
for comment in valid_comments {
let value = FieldValue::Text(comment.to_string());
let result = system.validate_field("comment", &value);
assert!(result.is_valid, "Should accept valid comment: {}", comment);
}
}
#[test]
fn test_buffer_overflow_protection() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "limited_input".to_string(),
rules: vec![ValidationRule::Length {
min: Some(1),
max: Some(256),
}],
format_mask: None,
error_message: None,
});
let very_long_input = "A".repeat(10000);
let value = FieldValue::Text(very_long_input);
let result = system.validate_field("limited_input", &value);
assert!(!result.is_valid);
assert!(result
.errors
.iter()
.any(|e| e.error_type == ValidationErrorType::Length));
}
#[test]
fn test_malicious_regex_patterns() {
let mut system = FormValidationSystem::new();
let invalid_patterns = vec![
"[", "(?", "*", "(?P<>)", ];
for pattern in invalid_patterns {
let validator = FieldValidator {
field_name: "test_field".to_string(),
rules: vec![ValidationRule::Pattern(pattern.to_string())],
format_mask: None,
error_message: None,
};
system.add_validator(validator);
let value = FieldValue::Text("test".to_string());
let result = system.validate_field("test_field", &value);
assert!(!result.is_valid);
assert!(result
.errors
.iter()
.any(|e| e.error_type == ValidationErrorType::Pattern));
}
}
#[test]
fn test_path_traversal_prevention() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "filename".to_string(),
rules: vec![
ValidationRule::Pattern(r"^[a-zA-Z0-9._-]+$".to_string()),
ValidationRule::Length {
min: Some(1),
max: Some(255),
},
],
format_mask: None,
error_message: Some("Invalid filename".to_string()),
});
let path_traversal_attempts = vec![
"../../../etc/passwd",
"..\\..\\..\\windows\\system32",
"../../../../root/.ssh/id_rsa",
"file/../../sensitive.txt",
"./../config/database.yml",
"....//....//....//etc/passwd",
];
for attempt in path_traversal_attempts {
let value = FieldValue::Text(attempt.to_string());
let result = system.validate_field("filename", &value);
assert!(
!result.is_valid,
"Should reject path traversal: {}",
attempt
);
}
let valid_filenames = vec![
"document.pdf",
"image_123.jpg",
"report-2024.docx",
"data.csv",
];
for filename in valid_filenames {
let value = FieldValue::Text(filename.to_string());
let result = system.validate_field("filename", &value);
assert!(
result.is_valid,
"Should accept valid filename: {}",
filename
);
}
}
#[test]
fn test_numeric_boundary_conditions() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "bounded_number".to_string(),
rules: vec![ValidationRule::Range {
min: Some(f64::MIN),
max: Some(f64::MAX),
}],
format_mask: None,
error_message: None,
});
let extreme_values = vec![
(f64::MIN, true),
(f64::MAX, true),
(f64::INFINITY, false), (f64::NEG_INFINITY, false), (f64::NAN, false), (0.0, true),
(-0.0, true),
(f64::MIN_POSITIVE, true),
(f64::EPSILON, true),
];
for (value, should_be_valid) in extreme_values {
let field_value = FieldValue::Number(value);
let result = system.validate_field("bounded_number", &field_value);
if value.is_nan() {
assert!(
result.is_valid,
"NaN passes range validation due to comparison behavior"
);
} else if value.is_infinite() {
assert!(!result.is_valid, "Should reject infinite number: {}", value);
} else {
assert_eq!(
result.is_valid, should_be_valid,
"Failed for value: {}",
value
);
}
}
}
#[test]
fn test_date_boundary_conditions() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "date_field".to_string(),
rules: vec![ValidationRule::Date {
min: Some(NaiveDate::from_ymd_opt(1900, 1, 1).unwrap()),
max: Some(NaiveDate::from_ymd_opt(2100, 12, 31).unwrap()),
}],
format_mask: None,
error_message: None,
});
let boundary_dates = vec![
("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), ];
for (date_str, should_be_valid) in boundary_dates {
let value = FieldValue::Text(date_str.to_string());
let result = system.validate_field("date_field", &value);
assert_eq!(
result.is_valid, should_be_valid,
"Date validation failed for: {}",
date_str
);
}
}
#[test]
fn test_time_boundary_conditions() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "time_field".to_string(),
rules: vec![ValidationRule::Time {
min: Some(NaiveTime::from_hms_opt(0, 0, 0).unwrap()),
max: Some(NaiveTime::from_hms_opt(23, 59, 59).unwrap()),
}],
format_mask: None,
error_message: None,
});
let boundary_times = vec![
("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), ];
for (time_str, should_be_valid) in boundary_times {
let value = FieldValue::Text(time_str.to_string());
let result = system.validate_field("time_field", &value);
assert_eq!(
result.is_valid, should_be_valid,
"Time validation failed for: {}",
time_str
);
}
}
#[test]
fn test_string_length_boundaries() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "length_test".to_string(),
rules: vec![ValidationRule::Length {
min: Some(5),
max: Some(10),
}],
format_mask: None,
error_message: None,
});
let test_cases = vec![
("", false), ("1234", false), ("12345", true), ("123456789", true), ("1234567890", true), ("12345678901", false), ];
for (text, should_be_valid) in test_cases {
let value = FieldValue::Text(text.to_string());
let result = system.validate_field("length_test", &value);
assert_eq!(
result.is_valid,
should_be_valid,
"Length validation failed for '{}' (len={})",
text,
text.len()
);
}
}
#[test]
fn test_empty_and_null_value_handling() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "nullable_field".to_string(),
rules: vec![ValidationRule::Length {
min: Some(1),
max: Some(100),
}],
format_mask: None,
error_message: None,
});
let empty_values = vec![
FieldValue::Empty,
FieldValue::Text("".to_string()),
FieldValue::Text(" ".to_string()), ];
for value in empty_values {
let result = system.validate_field("nullable_field", &value);
if matches!(value, FieldValue::Empty) || value.to_string().is_empty() {
assert!(
!result.is_valid,
"Empty value should fail length validation"
);
} else {
assert!(result.is_valid || !result.is_valid); }
}
}
#[test]
fn test_conditional_required_fields() {
let mut system = FormValidationSystem::new();
let conditional_info = RequiredFieldInfo {
field_name: "billing_address".to_string(),
error_message: "Billing address is required when payment method is credit card"
.to_string(),
group: Some("payment".to_string()),
condition: Some(RequirementCondition::IfFieldEquals {
field: "payment_method".to_string(),
value: FieldValue::Text("credit_card".to_string()),
}),
};
system.add_required_field(conditional_info);
let result = system.validate_field("billing_address", &FieldValue::Empty);
assert!(
result.is_valid,
"Conditional requirements are not yet implemented"
);
}
#[test]
fn test_field_group_validation() {
let mut system = FormValidationSystem::new();
let fields = vec![
("contact_phone", "Phone number"),
("contact_email", "Email address"),
("contact_address", "Mailing address"),
];
for (field_name, error_msg) in fields {
let info = RequiredFieldInfo {
field_name: field_name.to_string(),
error_message: format!("{} is required in contact group", error_msg),
group: Some("contact".to_string()),
condition: Some(RequirementCondition::IfGroupHasValue {
group: "contact".to_string(),
}),
};
system.add_required_field(info);
}
for (field_name, _) in &[
("contact_phone", ""),
("contact_email", ""),
("contact_address", ""),
] {
let _result = system.validate_field(field_name, &FieldValue::Empty);
}
}
#[test]
fn test_field_dependency_chain() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "country".to_string(),
rules: vec![ValidationRule::Required],
format_mask: None,
error_message: Some("Country is required".to_string()),
});
system.add_validator(FieldValidator {
field_name: "state".to_string(),
rules: vec![
ValidationRule::Required,
ValidationRule::Length {
min: Some(2),
max: Some(50),
},
],
format_mask: None,
error_message: Some("State is required when country is selected".to_string()),
});
system.add_validator(FieldValidator {
field_name: "city".to_string(),
rules: vec![
ValidationRule::Required,
ValidationRule::Length {
min: Some(1),
max: Some(100),
},
],
format_mask: None,
error_message: Some("City is required when state is selected".to_string()),
});
let mut fields = HashMap::new();
fields.insert("country".to_string(), FieldValue::Text("USA".to_string()));
fields.insert("state".to_string(), FieldValue::Text("CA".to_string()));
fields.insert(
"city".to_string(),
FieldValue::Text("San Francisco".to_string()),
);
let results = system.validate_all(&fields);
assert_eq!(results.len(), 3);
assert!(
results.iter().all(|r| r.is_valid),
"All dependent fields should be valid"
);
let mut incomplete_fields = HashMap::new();
incomplete_fields.insert("country".to_string(), FieldValue::Text("USA".to_string()));
incomplete_fields.insert("state".to_string(), FieldValue::Empty);
incomplete_fields.insert(
"city".to_string(),
FieldValue::Text("Some City".to_string()),
);
let incomplete_results = system.validate_all(&incomplete_fields);
let state_result = incomplete_results
.iter()
.find(|r| r.field_name == "state")
.unwrap();
assert!(
!state_result.is_valid,
"State should fail validation when empty"
);
}
#[test]
fn test_cache_memory_management() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "cached_field".to_string(),
rules: vec![ValidationRule::Required],
format_mask: None,
error_message: None,
});
for i in 0..1000 {
let field_name = format!("field_{}", i);
let value = FieldValue::Text(format!("value_{}", i));
system.add_validator(FieldValidator {
field_name: field_name.clone(),
rules: vec![ValidationRule::Required],
format_mask: None,
error_message: None,
});
let _result = system.validate_field(&field_name, &value);
}
assert!(
system.validation_cache.len() > 900,
"Cache should contain many entries"
);
system.clear_cache();
assert_eq!(
system.validation_cache.len(),
0,
"Cache should be empty after clear"
);
}
#[test]
fn test_cache_invalidation_scenarios() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "test_field".to_string(),
rules: vec![ValidationRule::Required],
format_mask: None,
error_message: None,
});
let value = FieldValue::Text("test_value".to_string());
let result1 = system.validate_field("test_field", &value);
assert!(result1.is_valid);
assert!(system.validation_cache.contains_key("test_field"));
let cached = system.get_cached_result("test_field");
assert!(cached.is_some());
assert!(cached.unwrap().is_valid);
let result2 = system.validate_field("test_field", &value);
assert!(result2.is_valid);
system.clear_cache();
assert!(system.get_cached_result("test_field").is_none());
}
#[test]
fn test_concurrent_validation_safety() {
let mut system = FormValidationSystem::new();
for i in 0..10 {
system.add_validator(FieldValidator {
field_name: format!("field_{}", i),
rules: vec![
ValidationRule::Required,
ValidationRule::Length {
min: Some(1),
max: Some(100),
},
],
format_mask: None,
error_message: None,
});
}
let mut results = Vec::new();
for i in 0..10 {
let field_name = format!("field_{}", i);
let value = FieldValue::Text(format!("value_{}", i));
let result = system.validate_field(&field_name, &value);
results.push(result);
}
assert_eq!(results.len(), 10);
assert!(results.iter().all(|r| r.is_valid));
for i in 0..10 {
let field_name = format!("field_{}", i);
assert!(system.get_cached_result(&field_name).is_some());
}
}
#[test]
fn test_malformed_date_handling() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "date_field".to_string(),
rules: vec![ValidationRule::Date {
min: Some(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap()),
max: Some(NaiveDate::from_ymd_opt(2030, 12, 31).unwrap()),
}],
format_mask: None,
error_message: None,
});
let malformed_dates = vec![
"not-a-date",
"2023-13-45", "2023/02/29", "2023-02-30", "2023-04-31", "2023", "2023-", "2023-02", "", "2023-02-29T14:30:00", ];
for date_str in malformed_dates {
let value = FieldValue::Text(date_str.to_string());
let result = system.validate_field("date_field", &value);
assert!(
!result.is_valid,
"Should reject malformed date: {}",
date_str
);
assert!(
!result.errors.is_empty(),
"Should have error for malformed date: {}",
date_str
);
}
}
#[test]
fn test_malformed_time_handling() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "time_field".to_string(),
rules: vec![ValidationRule::Time {
min: Some(NaiveTime::from_hms_opt(9, 0, 0).unwrap()),
max: Some(NaiveTime::from_hms_opt(17, 0, 0).unwrap()),
}],
format_mask: None,
error_message: None,
});
let malformed_times = vec![
"not-a-time",
"25:00:00", "12:60:00", "12:30:60", "12", "12:", "", "12:30 PM EST", "noon", ];
for time_str in malformed_times {
let value = FieldValue::Text(time_str.to_string());
let result = system.validate_field("time_field", &value);
assert!(
!result.is_valid,
"Should reject malformed time: {}",
time_str
);
assert!(
!result.errors.is_empty(),
"Should have error for malformed time: {}",
time_str
);
}
}
#[test]
fn test_regex_compilation_errors() {
let mut system = FormValidationSystem::new();
let bad_patterns = vec![
"[unclosed", "(?incomplete", "*quantifier", "\\k<unknown>", "(?:", ];
for (i, pattern) in bad_patterns.iter().enumerate() {
let validator = FieldValidator {
field_name: format!("regex_test_{}", i),
rules: vec![ValidationRule::Pattern((*pattern).to_string())],
format_mask: None,
error_message: Some("Custom regex error".to_string()),
};
system.add_validator(validator);
let value = FieldValue::Text("test_string".to_string());
let result = system.validate_field(&format!("regex_test_{}", i), &value);
assert!(
!result.is_valid,
"Should fail for bad regex pattern: {}",
pattern
);
assert_eq!(result.errors[0].error_type, ValidationErrorType::Pattern);
}
}
#[test]
fn test_validation_with_different_field_types() {
let mut system = FormValidationSystem::new();
system.add_validator(FieldValidator {
field_name: "mixed_field".to_string(),
rules: vec![
ValidationRule::Range {
min: Some(0.0),
max: Some(100.0),
},
ValidationRule::Length {
min: Some(1),
max: Some(10),
},
],
format_mask: None,
error_message: None,
});
let test_values = vec![
(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), ];
for (value, should_be_valid) in test_values {
let result = system.validate_field("mixed_field", &value);
assert_eq!(
result.is_valid, should_be_valid,
"Failed for value: {:?}",
value
);
}
}
#[test]
fn test_format_mask_edge_cases() {
let system = FormValidationSystem::new();
let number_mask = FormatMask::Number {
decimals: 2,
thousands_separator: true,
allow_negative: true,
prefix: Some("$".to_string()),
suffix: Some(" USD".to_string()),
};
let edge_cases = vec![
(0.0, "$0.00 USD"),
(-0.0, "$-0.00 USD"), (0.001, "$0.00 USD"), (0.009, "$0.01 USD"), (1000000.0, "$1,000,000.00 USD"),
(-1000000.0, "$-1,000,000.00 USD"),
];
for (input, expected) in edge_cases {
let value = FieldValue::Number(input);
let result = system.apply_format_mask(&value, &number_mask);
assert!(result.is_ok(), "Should format number: {}", input);
assert_eq!(
result.unwrap(),
expected,
"Formatting failed for: {}",
input
);
}
}
#[test]
fn test_date_format_edge_cases() {
let system = FormValidationSystem::new();
let date_mask = FormatMask::Date {
format: DateFormat::MDY,
};
let edge_cases = vec![
("01012000", true), ("20000101", true), ("1212000", false), ("0101200", false), ("13012000", true), ("01322000", true), ("", false), ];
for (input, should_succeed) in edge_cases {
let value = FieldValue::Text(input.to_string());
let result = system.apply_format_mask(&value, &date_mask);
if should_succeed {
assert!(result.is_ok(), "Should format date: {}", input);
} else {
assert!(result.is_err(), "Should fail to format date: {}", input);
}
}
}
#[test]
fn test_custom_mask_edge_cases() {
let system = FormValidationSystem::new();
let custom_mask = FormatMask::Custom {
pattern: "(###) ###-####".to_string(),
placeholder: '#',
};
let test_cases = vec![
("1234567890", "(123) 456-7890"), ("123456789", "(123) 456-789"), ("12345678901", "(123) 456-7890"), ("123", "(123) "), ("", "("), ];
for (input, expected) in test_cases {
let value = FieldValue::Text(input.to_string());
let result = system.apply_format_mask(&value, &custom_mask);
assert!(result.is_ok(), "Should apply custom mask to: {}", input);
assert_eq!(
result.unwrap(),
expected,
"Custom mask failed for: {}",
input
);
}
}
#[test]
fn test_phone_validation_comprehensive() {
let mut system = FormValidationSystem::new();
let phone_tests = vec![
(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), ];
for (country, phone, should_be_valid) in phone_tests {
let field_name = format!("phone_{:?}", country);
system.add_validator(FieldValidator {
field_name: field_name.clone(),
rules: vec![ValidationRule::PhoneNumber { country }],
format_mask: None,
error_message: None,
});
let value = FieldValue::Text(phone.to_string());
let result = system.validate_field(&field_name, &value);
assert_eq!(
result.is_valid, should_be_valid,
"Phone validation failed for {:?} format: {}",
country, phone
);
}
}
#[test]
fn test_phone_format_mask_edge_cases() {
let system = FormValidationSystem::new();
let us_mask = FormatMask::Phone {
country: PhoneCountry::US,
};
let us_cases = vec![
("1234567890", Some("(123) 456-7890")), ("123456789", None), ("12345678901", Some("(123) 456-7890")), ];
for (input, expected_result) in us_cases {
let value = FieldValue::Text(input.to_string());
let result = system.apply_format_mask(&value, &us_mask);
match expected_result {
None => assert!(
result.is_err(),
"Should error for invalid US phone: {}",
input
),
Some(expected) => {
assert!(result.is_ok(), "Should format US phone: {}", input);
assert_eq!(
result.unwrap(),
expected,
"US phone format failed for: {}",
input
);
}
}
}
let uk_mask = FormatMask::Phone {
country: PhoneCountry::UK,
};
let uk_value = FieldValue::Text("441234567890".to_string());
let uk_result = system.apply_format_mask(&uk_value, &uk_mask);
assert!(uk_result.is_ok(), "Should format UK phone");
assert_eq!(uk_result.unwrap(), "+44 1234 567890");
}
}