1use std::collections::HashMap;
10use std::fmt;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum ValidationResult {
15 Valid,
17 Invalid(String),
19 Pending,
21}
22
23impl ValidationResult {
24 pub fn is_valid(&self) -> bool {
26 matches!(self, Self::Valid)
27 }
28
29 pub fn is_invalid(&self) -> bool {
31 matches!(self, Self::Invalid(_))
32 }
33
34 pub fn is_pending(&self) -> bool {
36 matches!(self, Self::Pending)
37 }
38
39 pub fn error(&self) -> Option<&str> {
41 match self {
42 Self::Invalid(msg) => Some(msg),
43 _ => None,
44 }
45 }
46}
47
48pub trait Validator: Send + Sync {
50 fn validate(&self, value: &str) -> ValidationResult;
52
53 fn name(&self) -> &str;
55}
56
57#[derive(Debug, Clone)]
59pub struct Required {
60 message: String,
61}
62
63impl Required {
64 pub fn new() -> Self {
66 Self {
67 message: "This field is required".to_string(),
68 }
69 }
70
71 pub fn with_message(message: &str) -> Self {
73 Self {
74 message: message.to_string(),
75 }
76 }
77}
78
79impl Default for Required {
80 fn default() -> Self {
81 Self::new()
82 }
83}
84
85impl Validator for Required {
86 fn validate(&self, value: &str) -> ValidationResult {
87 if value.trim().is_empty() {
88 ValidationResult::Invalid(self.message.clone())
89 } else {
90 ValidationResult::Valid
91 }
92 }
93
94 fn name(&self) -> &'static str {
95 "required"
96 }
97}
98
99#[derive(Debug, Clone)]
101pub struct MinLength {
102 min: usize,
103 message: String,
104}
105
106impl MinLength {
107 pub fn new(min: usize) -> Self {
109 Self {
110 min,
111 message: format!("Must be at least {min} characters"),
112 }
113 }
114
115 pub fn with_message(min: usize, message: &str) -> Self {
117 Self {
118 min,
119 message: message.to_string(),
120 }
121 }
122}
123
124impl Validator for MinLength {
125 fn validate(&self, value: &str) -> ValidationResult {
126 if value.chars().count() < self.min {
127 ValidationResult::Invalid(self.message.clone())
128 } else {
129 ValidationResult::Valid
130 }
131 }
132
133 fn name(&self) -> &'static str {
134 "minLength"
135 }
136}
137
138#[derive(Debug, Clone)]
140pub struct MaxLength {
141 max: usize,
142 message: String,
143}
144
145impl MaxLength {
146 pub fn new(max: usize) -> Self {
148 Self {
149 max,
150 message: format!("Must be at most {max} characters"),
151 }
152 }
153
154 pub fn with_message(max: usize, message: &str) -> Self {
156 Self {
157 max,
158 message: message.to_string(),
159 }
160 }
161}
162
163impl Validator for MaxLength {
164 fn validate(&self, value: &str) -> ValidationResult {
165 if value.chars().count() > self.max {
166 ValidationResult::Invalid(self.message.clone())
167 } else {
168 ValidationResult::Valid
169 }
170 }
171
172 fn name(&self) -> &'static str {
173 "maxLength"
174 }
175}
176
177#[derive(Debug, Clone)]
179pub struct Range {
180 min: f64,
181 max: f64,
182 message: String,
183}
184
185impl Range {
186 pub fn new(min: f64, max: f64) -> Self {
188 Self {
189 min,
190 max,
191 message: format!("Must be between {min} and {max}"),
192 }
193 }
194
195 pub fn with_message(min: f64, max: f64, message: &str) -> Self {
197 Self {
198 min,
199 max,
200 message: message.to_string(),
201 }
202 }
203}
204
205impl Validator for Range {
206 fn validate(&self, value: &str) -> ValidationResult {
207 match value.parse::<f64>() {
208 Ok(num) if num >= self.min && num <= self.max => ValidationResult::Valid,
209 Ok(_) => ValidationResult::Invalid(self.message.clone()),
210 Err(_) => ValidationResult::Invalid("Must be a valid number".to_string()),
211 }
212 }
213
214 fn name(&self) -> &'static str {
215 "range"
216 }
217}
218
219#[derive(Debug, Clone)]
222pub struct Pattern {
223 pattern: PatternType,
224 message: String,
225}
226
227#[derive(Debug, Clone)]
229pub enum PatternType {
230 Email,
232 Url,
234 Phone,
236 Alphanumeric,
238 Digits,
240 Custom(String),
242}
243
244impl Pattern {
245 pub fn email() -> Self {
247 Self {
248 pattern: PatternType::Email,
249 message: "Must be a valid email address".to_string(),
250 }
251 }
252
253 pub fn url() -> Self {
255 Self {
256 pattern: PatternType::Url,
257 message: "Must be a valid URL".to_string(),
258 }
259 }
260
261 pub fn phone() -> Self {
263 Self {
264 pattern: PatternType::Phone,
265 message: "Must be a valid phone number".to_string(),
266 }
267 }
268
269 pub fn alphanumeric() -> Self {
271 Self {
272 pattern: PatternType::Alphanumeric,
273 message: "Must contain only letters and numbers".to_string(),
274 }
275 }
276
277 pub fn digits() -> Self {
279 Self {
280 pattern: PatternType::Digits,
281 message: "Must contain only digits".to_string(),
282 }
283 }
284
285 pub fn with_message(mut self, message: &str) -> Self {
287 self.message = message.to_string();
288 self
289 }
290
291 fn matches(&self, value: &str) -> bool {
292 match &self.pattern {
293 PatternType::Email => {
294 let parts: Vec<&str> = value.split('@').collect();
296 parts.len() == 2
297 && !parts[0].is_empty()
298 && parts[1].contains('.')
299 && !parts[1].starts_with('.')
300 && !parts[1].ends_with('.')
301 }
302 PatternType::Url => {
303 value.starts_with("http://")
304 || value.starts_with("https://")
305 || value.starts_with("ftp://")
306 }
307 PatternType::Phone => {
308 value.chars().all(|c| {
309 c.is_ascii_digit() || c == ' ' || c == '-' || c == '(' || c == ')' || c == '+'
310 }) && value.chars().filter(char::is_ascii_digit).count() >= 7
311 }
312 PatternType::Alphanumeric => value.chars().all(char::is_alphanumeric),
313 PatternType::Digits => value.chars().all(|c| c.is_ascii_digit()),
314 PatternType::Custom(pattern) => {
315 if pattern.is_empty() {
317 return true;
318 }
319 let parts: Vec<&str> = pattern.split('*').collect();
320 if parts.len() == 1 {
321 return value == pattern;
322 }
323 let mut remaining = value;
324 for (i, part) in parts.iter().enumerate() {
325 if part.is_empty() {
326 continue;
327 }
328 if i == 0 {
329 if !remaining.starts_with(part) {
330 return false;
331 }
332 remaining = &remaining[part.len()..];
333 } else if i == parts.len() - 1 {
334 if !remaining.ends_with(part) {
335 return false;
336 }
337 } else if let Some(pos) = remaining.find(part) {
338 remaining = &remaining[pos + part.len()..];
339 } else {
340 return false;
341 }
342 }
343 true
344 }
345 }
346 }
347}
348
349impl Validator for Pattern {
350 fn validate(&self, value: &str) -> ValidationResult {
351 if value.is_empty() {
352 return ValidationResult::Valid;
354 }
355
356 if self.matches(value) {
357 ValidationResult::Valid
358 } else {
359 ValidationResult::Invalid(self.message.clone())
360 }
361 }
362
363 fn name(&self) -> &'static str {
364 "pattern"
365 }
366}
367
368pub struct Custom<F>
370where
371 F: Fn(&str) -> ValidationResult + Send + Sync,
372{
373 validator: F,
374 name: String,
375}
376
377impl<F> Custom<F>
378where
379 F: Fn(&str) -> ValidationResult + Send + Sync,
380{
381 pub fn new(name: &str, validator: F) -> Self {
383 Self {
384 validator,
385 name: name.to_string(),
386 }
387 }
388}
389
390impl<F> Validator for Custom<F>
391where
392 F: Fn(&str) -> ValidationResult + Send + Sync,
393{
394 fn validate(&self, value: &str) -> ValidationResult {
395 (self.validator)(value)
396 }
397
398 fn name(&self) -> &str {
399 &self.name
400 }
401}
402
403impl<F> fmt::Debug for Custom<F>
404where
405 F: Fn(&str) -> ValidationResult + Send + Sync,
406{
407 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
408 f.debug_struct("Custom").field("name", &self.name).finish()
409 }
410}
411
412#[derive(Debug, Clone, Default)]
414pub struct FieldState {
415 pub value: String,
417 pub result: Option<ValidationResult>,
419 pub touched: bool,
421 pub dirty: bool,
423 pub errors: Vec<String>,
425}
426
427impl FieldState {
428 pub fn new() -> Self {
430 Self::default()
431 }
432
433 pub fn with_value(value: &str) -> Self {
435 Self {
436 value: value.to_string(),
437 ..Default::default()
438 }
439 }
440
441 pub fn is_valid(&self) -> bool {
443 self.result
444 .as_ref()
445 .map_or(true, ValidationResult::is_valid)
446 }
447
448 pub fn has_errors(&self) -> bool {
450 !self.errors.is_empty()
451 }
452
453 pub fn first_error(&self) -> Option<&str> {
455 self.errors.first().map(std::string::String::as_str)
456 }
457
458 pub fn touch(&mut self) {
460 self.touched = true;
461 }
462
463 pub fn set_value(&mut self, value: &str) {
465 if self.value != value {
466 self.value = value.to_string();
467 self.dirty = true;
468 }
469 }
470}
471
472#[derive(Default)]
474pub struct FieldConfig {
475 validators: Vec<Box<dyn Validator>>,
477 validate_on: ValidateOn,
479}
480
481impl std::fmt::Debug for FieldConfig {
482 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
483 f.debug_struct("FieldConfig")
484 .field("validator_count", &self.validators.len())
485 .field("validate_on", &self.validate_on)
486 .finish()
487 }
488}
489
490#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
492pub enum ValidateOn {
493 #[default]
495 Change,
496 Blur,
498 Submit,
500}
501
502impl FieldConfig {
503 pub fn new() -> Self {
505 Self::default()
506 }
507
508 pub fn add_validator<V: Validator + 'static>(mut self, validator: V) -> Self {
510 self.validators.push(Box::new(validator));
511 self
512 }
513
514 pub fn required(self) -> Self {
516 self.add_validator(Required::new())
517 }
518
519 pub fn min_length(self, min: usize) -> Self {
521 self.add_validator(MinLength::new(min))
522 }
523
524 pub fn max_length(self, max: usize) -> Self {
526 self.add_validator(MaxLength::new(max))
527 }
528
529 pub fn range(self, min: f64, max: f64) -> Self {
531 self.add_validator(Range::new(min, max))
532 }
533
534 pub fn email(self) -> Self {
536 self.add_validator(Pattern::email())
537 }
538
539 pub fn validate_on(mut self, trigger: ValidateOn) -> Self {
541 self.validate_on = trigger;
542 self
543 }
544
545 pub fn validate(&self, value: &str) -> Vec<String> {
547 let mut errors = Vec::new();
548 for validator in &self.validators {
549 if let ValidationResult::Invalid(msg) = validator.validate(value) {
550 errors.push(msg);
551 }
552 }
553 errors
554 }
555}
556
557#[derive(Debug, Default)]
559pub struct FormValidator {
560 configs: HashMap<String, FieldConfig>,
562 states: HashMap<String, FieldState>,
564 submitted: bool,
566}
567
568impl FormValidator {
569 pub fn new() -> Self {
571 Self::default()
572 }
573
574 pub fn register(&mut self, name: &str, config: FieldConfig) {
576 self.configs.insert(name.to_string(), config);
577 self.states.insert(name.to_string(), FieldState::new());
578 }
579
580 pub fn register_field(&mut self, name: &str) {
582 self.register(name, FieldConfig::new());
583 }
584
585 pub fn set_value(&mut self, name: &str, value: &str) {
587 if let Some(state) = self.states.get_mut(name) {
588 state.set_value(value);
589
590 if let Some(config) = self.configs.get(name) {
592 if config.validate_on == ValidateOn::Change {
593 state.errors = config.validate(value);
594 state.result = if state.errors.is_empty() {
595 Some(ValidationResult::Valid)
596 } else {
597 Some(ValidationResult::Invalid(state.errors[0].clone()))
598 };
599 }
600 }
601 }
602 }
603
604 pub fn touch(&mut self, name: &str) {
606 if let Some(state) = self.states.get_mut(name) {
607 state.touch();
608
609 if let Some(config) = self.configs.get(name) {
611 if config.validate_on == ValidateOn::Blur {
612 state.errors = config.validate(&state.value);
613 state.result = if state.errors.is_empty() {
614 Some(ValidationResult::Valid)
615 } else {
616 Some(ValidationResult::Invalid(state.errors[0].clone()))
617 };
618 }
619 }
620 }
621 }
622
623 pub fn field(&self, name: &str) -> Option<&FieldState> {
625 self.states.get(name)
626 }
627
628 pub fn value(&self, name: &str) -> Option<&str> {
630 self.states.get(name).map(|s| s.value.as_str())
631 }
632
633 pub fn errors(&self, name: &str) -> &[String] {
635 self.states.get(name).map_or(&[], |s| s.errors.as_slice())
636 }
637
638 pub fn field_is_valid(&self, name: &str) -> bool {
640 self.states.get(name).is_some_and(FieldState::is_valid)
641 }
642
643 pub fn validate(&mut self) -> bool {
645 let mut all_valid = true;
646
647 for (name, config) in &self.configs {
648 if let Some(state) = self.states.get_mut(name) {
649 state.errors = config.validate(&state.value);
650 state.result = if state.errors.is_empty() {
651 Some(ValidationResult::Valid)
652 } else {
653 Some(ValidationResult::Invalid(state.errors[0].clone()))
654 };
655
656 if !state.errors.is_empty() {
657 all_valid = false;
658 }
659 }
660 }
661
662 self.submitted = true;
663 all_valid
664 }
665
666 pub fn is_valid(&self) -> bool {
668 self.states.values().all(FieldState::is_valid)
669 }
670
671 pub fn is_submitted(&self) -> bool {
673 self.submitted
674 }
675
676 pub fn is_dirty(&self) -> bool {
678 self.states.values().any(|s| s.dirty)
679 }
680
681 pub fn all_errors(&self) -> HashMap<&str, &[String]> {
683 self.states
684 .iter()
685 .filter(|(_, s)| !s.errors.is_empty())
686 .map(|(k, s)| (k.as_str(), s.errors.as_slice()))
687 .collect()
688 }
689
690 pub fn reset(&mut self) {
692 for state in self.states.values_mut() {
693 state.value.clear();
694 state.result = None;
695 state.touched = false;
696 state.dirty = false;
697 state.errors.clear();
698 }
699 self.submitted = false;
700 }
701
702 pub fn field_count(&self) -> usize {
704 self.configs.len()
705 }
706}
707
708#[cfg(test)]
709mod tests {
710 use super::*;
711
712 #[test]
714 fn test_validation_result_valid() {
715 let result = ValidationResult::Valid;
716 assert!(result.is_valid());
717 assert!(!result.is_invalid());
718 assert!(!result.is_pending());
719 assert!(result.error().is_none());
720 }
721
722 #[test]
723 fn test_validation_result_invalid() {
724 let result = ValidationResult::Invalid("Error message".to_string());
725 assert!(!result.is_valid());
726 assert!(result.is_invalid());
727 assert!(!result.is_pending());
728 assert_eq!(result.error(), Some("Error message"));
729 }
730
731 #[test]
732 fn test_validation_result_pending() {
733 let result = ValidationResult::Pending;
734 assert!(!result.is_valid());
735 assert!(!result.is_invalid());
736 assert!(result.is_pending());
737 }
738
739 #[test]
741 fn test_required_validator() {
742 let validator = Required::new();
743 assert_eq!(validator.name(), "required");
744
745 assert!(validator.validate("hello").is_valid());
746 assert!(validator.validate(" content ").is_valid());
747 assert!(validator.validate("").is_invalid());
748 assert!(validator.validate(" ").is_invalid());
749 }
750
751 #[test]
752 fn test_required_custom_message() {
753 let validator = Required::with_message("Custom error");
754 let result = validator.validate("");
755 assert_eq!(result.error(), Some("Custom error"));
756 }
757
758 #[test]
760 fn test_min_length_validator() {
761 let validator = MinLength::new(3);
762 assert_eq!(validator.name(), "minLength");
763
764 assert!(validator.validate("abc").is_valid());
765 assert!(validator.validate("abcd").is_valid());
766 assert!(validator.validate("ab").is_invalid());
767 assert!(validator.validate("").is_invalid());
768 }
769
770 #[test]
771 fn test_min_length_unicode() {
772 let validator = MinLength::new(3);
773 assert!(validator.validate("日本語").is_valid()); assert!(validator.validate("日本").is_invalid()); }
776
777 #[test]
779 fn test_max_length_validator() {
780 let validator = MaxLength::new(5);
781 assert_eq!(validator.name(), "maxLength");
782
783 assert!(validator.validate("").is_valid());
784 assert!(validator.validate("abc").is_valid());
785 assert!(validator.validate("abcde").is_valid());
786 assert!(validator.validate("abcdef").is_invalid());
787 }
788
789 #[test]
791 fn test_range_validator() {
792 let validator = Range::new(0.0, 100.0);
793 assert_eq!(validator.name(), "range");
794
795 assert!(validator.validate("0").is_valid());
796 assert!(validator.validate("50").is_valid());
797 assert!(validator.validate("100").is_valid());
798 assert!(validator.validate("-1").is_invalid());
799 assert!(validator.validate("101").is_invalid());
800 }
801
802 #[test]
803 fn test_range_with_decimals() {
804 let validator = Range::new(0.0, 1.0);
805
806 assert!(validator.validate("0.5").is_valid());
807 assert!(validator.validate("0.99").is_valid());
808 assert!(validator.validate("1.01").is_invalid());
809 }
810
811 #[test]
812 fn test_range_invalid_number() {
813 let validator = Range::new(0.0, 100.0);
814 let result = validator.validate("not a number");
815 assert!(result.is_invalid());
816 assert_eq!(result.error(), Some("Must be a valid number"));
817 }
818
819 #[test]
821 fn test_pattern_email() {
822 let validator = Pattern::email();
823
824 assert!(validator.validate("test@example.com").is_valid());
825 assert!(validator.validate("user.name@domain.co.uk").is_valid());
826 assert!(validator.validate("").is_valid()); assert!(validator.validate("invalid").is_invalid());
829 assert!(validator.validate("@missing.com").is_invalid());
830 assert!(validator.validate("missing@").is_invalid());
831 assert!(validator.validate("missing@.com").is_invalid());
832 }
833
834 #[test]
835 fn test_pattern_url() {
836 let validator = Pattern::url();
837
838 assert!(validator.validate("http://example.com").is_valid());
839 assert!(validator.validate("https://example.com").is_valid());
840 assert!(validator.validate("ftp://files.example.com").is_valid());
841
842 assert!(validator.validate("example.com").is_invalid());
843 assert!(validator.validate("www.example.com").is_invalid());
844 }
845
846 #[test]
847 fn test_pattern_phone() {
848 let validator = Pattern::phone();
849
850 assert!(validator.validate("1234567").is_valid());
851 assert!(validator.validate("123-456-7890").is_valid());
852 assert!(validator.validate("(123) 456-7890").is_valid());
853 assert!(validator.validate("+1 234 567 8900").is_valid());
854
855 assert!(validator.validate("123").is_invalid()); assert!(validator.validate("abc-def-ghij").is_invalid());
857 }
858
859 #[test]
860 fn test_pattern_alphanumeric() {
861 let validator = Pattern::alphanumeric();
862
863 assert!(validator.validate("abc123").is_valid());
864 assert!(validator.validate("ABC").is_valid());
865 assert!(validator.validate("123").is_valid());
866
867 assert!(validator.validate("abc-123").is_invalid());
868 assert!(validator.validate("hello world").is_invalid());
869 }
870
871 #[test]
872 fn test_pattern_digits() {
873 let validator = Pattern::digits();
874
875 assert!(validator.validate("123456").is_valid());
876 assert!(validator.validate("0").is_valid());
877
878 assert!(validator.validate("123a").is_invalid());
879 assert!(validator.validate("12.34").is_invalid());
880 }
881
882 #[test]
884 fn test_custom_validator() {
885 let validator = Custom::new("even_length", |value| {
886 if value.len() % 2 == 0 {
887 ValidationResult::Valid
888 } else {
889 ValidationResult::Invalid("Length must be even".to_string())
890 }
891 });
892
893 assert_eq!(validator.name(), "even_length");
894 assert!(validator.validate("ab").is_valid());
895 assert!(validator.validate("abcd").is_valid());
896 assert!(validator.validate("abc").is_invalid());
897 }
898
899 #[test]
901 fn test_field_state_new() {
902 let state = FieldState::new();
903 assert!(state.value.is_empty());
904 assert!(!state.touched);
905 assert!(!state.dirty);
906 assert!(state.errors.is_empty());
907 }
908
909 #[test]
910 fn test_field_state_with_value() {
911 let state = FieldState::with_value("initial");
912 assert_eq!(state.value, "initial");
913 }
914
915 #[test]
916 fn test_field_state_touch() {
917 let mut state = FieldState::new();
918 assert!(!state.touched);
919 state.touch();
920 assert!(state.touched);
921 }
922
923 #[test]
924 fn test_field_state_set_value() {
925 let mut state = FieldState::new();
926 assert!(!state.dirty);
927 state.set_value("new value");
928 assert!(state.dirty);
929 assert_eq!(state.value, "new value");
930 }
931
932 #[test]
933 fn test_field_state_first_error() {
934 let mut state = FieldState::new();
935 assert!(state.first_error().is_none());
936
937 state.errors.push("First error".to_string());
938 state.errors.push("Second error".to_string());
939 assert_eq!(state.first_error(), Some("First error"));
940 }
941
942 #[test]
944 fn test_field_config_builder() {
945 let config = FieldConfig::new()
946 .required()
947 .min_length(3)
948 .max_length(10)
949 .email();
950
951 assert_eq!(config.validators.len(), 4);
952 }
953
954 #[test]
955 fn test_field_config_validate() {
956 let config = FieldConfig::new().required().min_length(3);
957
958 let errors = config.validate("");
959 assert_eq!(errors.len(), 2); let errors = config.validate("ab");
962 assert_eq!(errors.len(), 1); let errors = config.validate("abc");
965 assert!(errors.is_empty());
966 }
967
968 #[test]
970 fn test_form_validator_new() {
971 let form = FormValidator::new();
972 assert_eq!(form.field_count(), 0);
973 assert!(!form.is_submitted());
974 }
975
976 #[test]
977 fn test_form_validator_register() {
978 let mut form = FormValidator::new();
979 form.register("email", FieldConfig::new().required().email());
980 form.register("name", FieldConfig::new().required());
981
982 assert_eq!(form.field_count(), 2);
983 }
984
985 #[test]
986 fn test_form_validator_set_value() {
987 let mut form = FormValidator::new();
988 form.register("name", FieldConfig::new());
989
990 form.set_value("name", "John");
991 assert_eq!(form.value("name"), Some("John"));
992 assert!(form.is_dirty());
993 }
994
995 #[test]
996 fn test_form_validator_validate_on_change() {
997 let mut form = FormValidator::new();
998 form.register(
999 "email",
1000 FieldConfig::new()
1001 .required()
1002 .email()
1003 .validate_on(ValidateOn::Change),
1004 );
1005
1006 form.set_value("email", "invalid");
1007 assert!(!form.field_is_valid("email"));
1008
1009 form.set_value("email", "valid@example.com");
1010 assert!(form.field_is_valid("email"));
1011 }
1012
1013 #[test]
1014 fn test_form_validator_validate_on_blur() {
1015 let mut form = FormValidator::new();
1016 form.register(
1017 "email",
1018 FieldConfig::new().required().validate_on(ValidateOn::Blur),
1019 );
1020
1021 form.set_value("email", "");
1022 assert!(form.errors("email").is_empty());
1024
1025 form.touch("email");
1026 assert!(!form.errors("email").is_empty());
1028 }
1029
1030 #[test]
1031 fn test_form_validator_validate_all() {
1032 let mut form = FormValidator::new();
1033 form.register("name", FieldConfig::new().required());
1034 form.register("email", FieldConfig::new().required().email());
1035
1036 form.set_value("name", "John");
1037 form.set_value("email", "invalid");
1038
1039 let valid = form.validate();
1040 assert!(!valid);
1041 assert!(form.is_submitted());
1042
1043 let errors = form.all_errors();
1044 assert_eq!(errors.len(), 1); }
1046
1047 #[test]
1048 fn test_form_validator_reset() {
1049 let mut form = FormValidator::new();
1050 form.register("name", FieldConfig::new().required());
1051
1052 form.set_value("name", "John");
1053 form.touch("name");
1054 form.validate();
1055
1056 form.reset();
1057
1058 assert!(!form.is_submitted());
1059 assert!(!form.is_dirty());
1060 assert_eq!(form.value("name"), Some(""));
1061 }
1062
1063 #[test]
1064 fn test_form_validator_is_valid() {
1065 let mut form = FormValidator::new();
1066 form.register("name", FieldConfig::new().required());
1067
1068 form.set_value("name", "");
1069 form.validate();
1070 assert!(!form.is_valid());
1071
1072 form.set_value("name", "John");
1073 form.validate();
1074 assert!(form.is_valid());
1075 }
1076
1077 #[test]
1082 fn test_validation_result_clone() {
1083 let result = ValidationResult::Invalid("error".to_string());
1084 let cloned = result.clone();
1085 assert_eq!(result, cloned);
1086 }
1087
1088 #[test]
1089 fn test_validation_result_debug() {
1090 let result = ValidationResult::Valid;
1091 let debug = format!("{:?}", result);
1092 assert!(debug.contains("Valid"));
1093 }
1094
1095 #[test]
1096 fn test_validation_result_partial_eq() {
1097 assert_eq!(ValidationResult::Valid, ValidationResult::Valid);
1098 assert_eq!(ValidationResult::Pending, ValidationResult::Pending);
1099 assert_ne!(ValidationResult::Valid, ValidationResult::Pending);
1100 assert_ne!(
1101 ValidationResult::Invalid("a".to_string()),
1102 ValidationResult::Invalid("b".to_string())
1103 );
1104 }
1105
1106 #[test]
1111 fn test_required_default() {
1112 let validator = Required::default();
1113 assert!(validator.validate("").is_invalid());
1114 }
1115
1116 #[test]
1117 fn test_required_whitespace_only() {
1118 let validator = Required::new();
1119 assert!(validator.validate("\t\n").is_invalid());
1120 assert!(validator.validate(" \t ").is_invalid());
1121 }
1122
1123 #[test]
1124 fn test_required_single_char() {
1125 let validator = Required::new();
1126 assert!(validator.validate("a").is_valid());
1127 assert!(validator.validate(" a ").is_valid());
1128 }
1129
1130 #[test]
1131 fn test_required_clone() {
1132 let validator = Required::with_message("custom");
1133 let cloned = validator.clone();
1134 assert_eq!(cloned.message, "custom");
1135 }
1136
1137 #[test]
1142 fn test_min_length_boundary_zero() {
1143 let validator = MinLength::new(0);
1144 assert!(validator.validate("").is_valid());
1145 assert!(validator.validate("a").is_valid());
1146 }
1147
1148 #[test]
1149 fn test_min_length_boundary_one() {
1150 let validator = MinLength::new(1);
1151 assert!(validator.validate("").is_invalid());
1152 assert!(validator.validate("a").is_valid());
1153 assert!(validator.validate("ab").is_valid());
1154 }
1155
1156 #[test]
1157 fn test_min_length_boundary_exact() {
1158 let validator = MinLength::new(5);
1159 assert!(validator.validate("1234").is_invalid()); assert!(validator.validate("12345").is_valid()); assert!(validator.validate("123456").is_valid()); }
1163
1164 #[test]
1165 fn test_min_length_custom_message() {
1166 let validator = MinLength::with_message(3, "Too short!");
1167 let result = validator.validate("ab");
1168 assert_eq!(result.error(), Some("Too short!"));
1169 }
1170
1171 #[test]
1172 fn test_min_length_clone() {
1173 let validator = MinLength::new(5);
1174 let cloned = validator.clone();
1175 assert_eq!(cloned.min, 5);
1176 }
1177
1178 #[test]
1183 fn test_max_length_boundary_zero() {
1184 let validator = MaxLength::new(0);
1185 assert!(validator.validate("").is_valid());
1186 assert!(validator.validate("a").is_invalid());
1187 }
1188
1189 #[test]
1190 fn test_max_length_boundary_one() {
1191 let validator = MaxLength::new(1);
1192 assert!(validator.validate("").is_valid());
1193 assert!(validator.validate("a").is_valid());
1194 assert!(validator.validate("ab").is_invalid());
1195 }
1196
1197 #[test]
1198 fn test_max_length_boundary_exact() {
1199 let validator = MaxLength::new(5);
1200 assert!(validator.validate("1234").is_valid()); assert!(validator.validate("12345").is_valid()); assert!(validator.validate("123456").is_invalid()); }
1204
1205 #[test]
1206 fn test_max_length_custom_message() {
1207 let validator = MaxLength::with_message(3, "Too long!");
1208 let result = validator.validate("abcd");
1209 assert_eq!(result.error(), Some("Too long!"));
1210 }
1211
1212 #[test]
1213 fn test_max_length_unicode() {
1214 let validator = MaxLength::new(3);
1215 assert!(validator.validate("日本語").is_valid()); assert!(validator.validate("日本語字").is_invalid()); }
1218
1219 #[test]
1220 fn test_max_length_clone() {
1221 let validator = MaxLength::new(10);
1222 let cloned = validator.clone();
1223 assert_eq!(cloned.max, 10);
1224 }
1225
1226 #[test]
1231 fn test_range_boundary_exact() {
1232 let validator = Range::new(10.0, 20.0);
1233 assert!(validator.validate("9.99").is_invalid());
1234 assert!(validator.validate("10.0").is_valid());
1235 assert!(validator.validate("20.0").is_valid());
1236 assert!(validator.validate("20.01").is_invalid());
1237 }
1238
1239 #[test]
1240 fn test_range_negative_values() {
1241 let validator = Range::new(-100.0, -10.0);
1242 assert!(validator.validate("-50").is_valid());
1243 assert!(validator.validate("-100").is_valid());
1244 assert!(validator.validate("-10").is_valid());
1245 assert!(validator.validate("-101").is_invalid());
1246 assert!(validator.validate("-9").is_invalid());
1247 }
1248
1249 #[test]
1250 fn test_range_scientific_notation() {
1251 let validator = Range::new(0.0, 1e10);
1252 assert!(validator.validate("1e5").is_valid());
1253 assert!(validator.validate("1E9").is_valid());
1254 assert!(validator.validate("1e11").is_invalid());
1255 }
1256
1257 #[test]
1258 fn test_range_float_precision() {
1259 let validator = Range::new(0.1, 0.3);
1260 assert!(validator.validate("0.2").is_valid());
1261 assert!(validator.validate("0.1").is_valid());
1263 assert!(validator.validate("0.3").is_valid());
1264 }
1265
1266 #[test]
1267 fn test_range_custom_message() {
1268 let validator = Range::with_message(1.0, 10.0, "Out of range!");
1269 let result = validator.validate("0");
1270 assert_eq!(result.error(), Some("Out of range!"));
1271 }
1272
1273 #[test]
1274 fn test_range_clone() {
1275 let validator = Range::new(0.0, 100.0);
1276 let cloned = validator.clone();
1277 assert_eq!(cloned.min, 0.0);
1278 assert_eq!(cloned.max, 100.0);
1279 }
1280
1281 #[test]
1282 fn test_range_empty_string() {
1283 let validator = Range::new(0.0, 100.0);
1284 let result = validator.validate("");
1285 assert!(result.is_invalid());
1286 assert_eq!(result.error(), Some("Must be a valid number"));
1287 }
1288
1289 #[test]
1294 fn test_pattern_email_edge_cases() {
1295 let validator = Pattern::email();
1296
1297 assert!(validator.validate("a@b.c").is_valid());
1299 assert!(validator.validate("test+tag@example.com").is_valid());
1300 assert!(validator.validate("test.name@sub.domain.com").is_valid());
1301
1302 assert!(validator.validate("test@@example.com").is_invalid());
1304 assert!(validator.validate("test@.example.com").is_invalid());
1305 assert!(validator.validate("test@example.").is_invalid());
1306 }
1307
1308 #[test]
1309 fn test_pattern_url_protocols() {
1310 let validator = Pattern::url();
1311
1312 assert!(validator.validate("http://localhost").is_valid());
1313 assert!(validator.validate("https://127.0.0.1").is_valid());
1314 assert!(validator.validate("ftp://ftp.example.com").is_valid());
1315
1316 assert!(validator.validate("file://local").is_invalid());
1317 assert!(validator.validate("mailto:test@example.com").is_invalid());
1318 }
1319
1320 #[test]
1321 fn test_pattern_phone_international() {
1322 let validator = Pattern::phone();
1323
1324 assert!(validator.validate("+44 20 7946 0958").is_valid());
1325 assert!(validator.validate("+1-800-555-1234").is_valid());
1326 assert!(validator.validate("(555) 123-4567").is_valid());
1327 }
1328
1329 #[test]
1330 fn test_pattern_phone_too_short() {
1331 let validator = Pattern::phone();
1332 assert!(validator.validate("123456").is_invalid()); }
1334
1335 #[test]
1336 fn test_pattern_alphanumeric_unicode() {
1337 let validator = Pattern::alphanumeric();
1338 assert!(validator.validate("日本語").is_valid());
1340 assert!(validator.validate("Café").is_valid());
1341 }
1342
1343 #[test]
1344 fn test_pattern_digits_edge_cases() {
1345 let validator = Pattern::digits();
1346
1347 assert!(validator.validate("0").is_valid());
1348 assert!(validator.validate("0123456789").is_valid());
1349
1350 assert!(validator.validate("-1").is_invalid());
1351 assert!(validator.validate("+1").is_invalid());
1352 assert!(validator.validate("1.0").is_invalid());
1353 }
1354
1355 #[test]
1356 fn test_pattern_custom_message() {
1357 let validator = Pattern::email().with_message("Invalid email format");
1358 let result = validator.validate("invalid");
1359 assert_eq!(result.error(), Some("Invalid email format"));
1360 }
1361
1362 #[test]
1363 fn test_pattern_clone() {
1364 let validator = Pattern::email();
1365 let cloned = validator.clone();
1366 assert!(cloned.validate("test@example.com").is_valid());
1367 }
1368
1369 #[test]
1370 fn test_pattern_custom_glob_exact() {
1371 let pattern = Pattern {
1372 pattern: PatternType::Custom("hello".to_string()),
1373 message: "Must be hello".to_string(),
1374 };
1375 assert!(pattern.validate("hello").is_valid());
1376 assert!(pattern.validate("hello!").is_invalid());
1377 assert!(pattern.validate("hellp").is_invalid());
1378 }
1379
1380 #[test]
1381 fn test_pattern_custom_glob_prefix() {
1382 let pattern = Pattern {
1383 pattern: PatternType::Custom("test*".to_string()),
1384 message: "Must start with test".to_string(),
1385 };
1386 assert!(pattern.validate("test").is_valid());
1387 assert!(pattern.validate("testing").is_valid());
1388 assert!(pattern.validate("TEST").is_invalid());
1389 }
1390
1391 #[test]
1392 fn test_pattern_custom_glob_suffix() {
1393 let pattern = Pattern {
1394 pattern: PatternType::Custom("*.txt".to_string()),
1395 message: "Must end with .txt".to_string(),
1396 };
1397 assert!(pattern.validate("file.txt").is_valid());
1398 assert!(pattern.validate(".txt").is_valid());
1399 assert!(pattern.validate("file.doc").is_invalid());
1400 }
1401
1402 #[test]
1403 fn test_pattern_custom_glob_middle() {
1404 let pattern = Pattern {
1405 pattern: PatternType::Custom("pre*suf".to_string()),
1406 message: "error".to_string(),
1407 };
1408 assert!(pattern.validate("presuf").is_valid());
1409 assert!(pattern.validate("pre123suf").is_valid());
1410 assert!(pattern.validate("prefix_suffix").is_invalid());
1411 }
1412
1413 #[test]
1414 fn test_pattern_custom_glob_empty() {
1415 let pattern = Pattern {
1416 pattern: PatternType::Custom("".to_string()),
1417 message: "error".to_string(),
1418 };
1419 assert!(pattern.validate("anything").is_valid());
1421 assert!(pattern.validate("").is_valid());
1422 }
1423
1424 #[test]
1425 fn test_pattern_custom_glob_multiple_wildcards() {
1426 let pattern = Pattern {
1427 pattern: PatternType::Custom("a*b*c".to_string()),
1428 message: "error".to_string(),
1429 };
1430 assert!(pattern.validate("abc").is_valid());
1431 assert!(pattern.validate("a123b456c").is_valid());
1432 assert!(pattern.validate("axbxc").is_valid());
1433 assert!(pattern.validate("axbx").is_invalid());
1434 }
1435
1436 #[test]
1441 fn test_custom_validator_debug() {
1442 let validator = Custom::new("test", |_| ValidationResult::Valid);
1443 let debug = format!("{:?}", validator);
1444 assert!(debug.contains("Custom"));
1445 assert!(debug.contains("test"));
1446 }
1447
1448 #[test]
1449 fn test_custom_validator_pending() {
1450 let validator = Custom::new("async_check", |_| ValidationResult::Pending);
1451 assert!(validator.validate("anything").is_pending());
1452 }
1453
1454 #[test]
1459 fn test_field_state_is_valid_no_result() {
1460 let state = FieldState::new();
1461 assert!(state.is_valid()); }
1463
1464 #[test]
1465 fn test_field_state_is_valid_with_result() {
1466 let mut state = FieldState::new();
1467 state.result = Some(ValidationResult::Invalid("error".to_string()));
1468 assert!(!state.is_valid());
1469
1470 state.result = Some(ValidationResult::Valid);
1471 assert!(state.is_valid());
1472 }
1473
1474 #[test]
1475 fn test_field_state_has_errors() {
1476 let mut state = FieldState::new();
1477 assert!(!state.has_errors());
1478
1479 state.errors.push("error".to_string());
1480 assert!(state.has_errors());
1481 }
1482
1483 #[test]
1484 fn test_field_state_set_value_same_value() {
1485 let mut state = FieldState::with_value("test");
1486 state.set_value("test"); assert!(!state.dirty); }
1489
1490 #[test]
1491 fn test_field_state_default() {
1492 let state = FieldState::default();
1493 assert!(state.value.is_empty());
1494 assert!(state.result.is_none());
1495 assert!(!state.touched);
1496 assert!(!state.dirty);
1497 assert!(state.errors.is_empty());
1498 }
1499
1500 #[test]
1501 fn test_field_state_clone() {
1502 let mut state = FieldState::with_value("test");
1503 state.touched = true;
1504 state.dirty = true;
1505 state.errors.push("error".to_string());
1506
1507 let cloned = state.clone();
1508 assert_eq!(cloned.value, "test");
1509 assert!(cloned.touched);
1510 assert!(cloned.dirty);
1511 assert_eq!(cloned.errors.len(), 1);
1512 }
1513
1514 #[test]
1519 fn test_field_config_validate_on() {
1520 let config = FieldConfig::new().validate_on(ValidateOn::Blur);
1521 assert_eq!(config.validate_on, ValidateOn::Blur);
1522 }
1523
1524 #[test]
1525 fn test_field_config_range() {
1526 let config = FieldConfig::new().range(0.0, 100.0);
1527
1528 let errors = config.validate("50");
1529 assert!(errors.is_empty());
1530
1531 let errors = config.validate("150");
1532 assert!(!errors.is_empty());
1533 }
1534
1535 #[test]
1536 fn test_field_config_debug() {
1537 let config = FieldConfig::new().required().min_length(3);
1538 let debug = format!("{:?}", config);
1539 assert!(debug.contains("FieldConfig"));
1540 assert!(debug.contains("validator_count"));
1541 }
1542
1543 #[test]
1544 fn test_field_config_default() {
1545 let config = FieldConfig::default();
1546 assert!(config.validators.is_empty());
1547 assert_eq!(config.validate_on, ValidateOn::Change);
1548 }
1549
1550 #[test]
1551 fn test_field_config_multiple_validators_all_fail() {
1552 let config = FieldConfig::new().required().min_length(5).max_length(3); let errors = config.validate("");
1555 assert_eq!(errors.len(), 2); }
1557
1558 #[test]
1563 fn test_validate_on_default() {
1564 assert_eq!(ValidateOn::default(), ValidateOn::Change);
1565 }
1566
1567 #[test]
1568 fn test_validate_on_debug() {
1569 let trigger = ValidateOn::Submit;
1570 let debug = format!("{:?}", trigger);
1571 assert!(debug.contains("Submit"));
1572 }
1573
1574 #[test]
1575 fn test_validate_on_clone() {
1576 let trigger = ValidateOn::Blur;
1577 let cloned = trigger;
1578 assert_eq!(trigger, cloned);
1579 }
1580
1581 #[test]
1586 fn test_form_validator_register_field() {
1587 let mut form = FormValidator::new();
1588 form.register_field("simple");
1589 assert_eq!(form.field_count(), 1);
1590 assert!(form.field("simple").is_some());
1591 }
1592
1593 #[test]
1594 fn test_form_validator_field_nonexistent() {
1595 let form = FormValidator::new();
1596 assert!(form.field("nonexistent").is_none());
1597 assert!(form.value("nonexistent").is_none());
1598 }
1599
1600 #[test]
1601 fn test_form_validator_errors_nonexistent() {
1602 let form = FormValidator::new();
1603 assert!(form.errors("nonexistent").is_empty());
1604 }
1605
1606 #[test]
1607 fn test_form_validator_set_value_nonexistent() {
1608 let mut form = FormValidator::new();
1609 form.set_value("nonexistent", "value"); }
1611
1612 #[test]
1613 fn test_form_validator_touch_nonexistent() {
1614 let mut form = FormValidator::new();
1615 form.touch("nonexistent"); }
1617
1618 #[test]
1619 fn test_form_validator_field_is_valid_nonexistent() {
1620 let form = FormValidator::new();
1621 assert!(!form.field_is_valid("nonexistent")); }
1623
1624 #[test]
1625 fn test_form_validator_validate_submit_only() {
1626 let mut form = FormValidator::new();
1627 form.register(
1628 "field",
1629 FieldConfig::new()
1630 .required()
1631 .validate_on(ValidateOn::Submit),
1632 );
1633
1634 form.set_value("field", "");
1635 assert!(form.errors("field").is_empty());
1637
1638 form.touch("field");
1639 assert!(form.errors("field").is_empty());
1641
1642 form.validate();
1643 assert!(!form.errors("field").is_empty());
1645 }
1646
1647 #[test]
1648 fn test_form_validator_all_errors_empty() {
1649 let mut form = FormValidator::new();
1650 form.register("valid", FieldConfig::new().required());
1651 form.set_value("valid", "value");
1652 form.validate();
1653
1654 let errors = form.all_errors();
1655 assert!(errors.is_empty());
1656 }
1657
1658 #[test]
1659 fn test_form_validator_all_errors_multiple() {
1660 let mut form = FormValidator::new();
1661 form.register("field1", FieldConfig::new().required());
1662 form.register("field2", FieldConfig::new().required());
1663 form.validate();
1664
1665 let errors = form.all_errors();
1666 assert_eq!(errors.len(), 2);
1667 }
1668
1669 #[test]
1670 fn test_form_validator_default() {
1671 let form = FormValidator::default();
1672 assert_eq!(form.field_count(), 0);
1673 }
1674
1675 #[test]
1676 fn test_form_validator_debug() {
1677 let form = FormValidator::new();
1678 let debug = format!("{:?}", form);
1679 assert!(debug.contains("FormValidator"));
1680 }
1681
1682 #[test]
1683 fn test_form_validator_is_dirty_multiple_fields() {
1684 let mut form = FormValidator::new();
1685 form.register_field("a");
1686 form.register_field("b");
1687
1688 assert!(!form.is_dirty());
1689
1690 form.set_value("a", "value");
1691 assert!(form.is_dirty());
1692 }
1693
1694 #[test]
1695 fn test_form_validator_reset_clears_all() {
1696 let mut form = FormValidator::new();
1697 form.register("a", FieldConfig::new().required());
1698 form.register("b", FieldConfig::new().required());
1699
1700 form.set_value("a", "value1");
1701 form.set_value("b", "value2");
1702 form.touch("a");
1703 form.validate();
1704
1705 form.reset();
1706
1707 assert!(!form.is_submitted());
1708 assert!(!form.is_dirty());
1709 assert_eq!(form.value("a"), Some(""));
1710 assert_eq!(form.value("b"), Some(""));
1711 }
1712
1713 #[test]
1714 fn test_form_validator_validate_returns_true_when_valid() {
1715 let mut form = FormValidator::new();
1716 form.register("name", FieldConfig::new().required());
1717 form.set_value("name", "John");
1718
1719 assert!(form.validate());
1720 }
1721
1722 #[test]
1723 fn test_form_validator_complex_scenario() {
1724 let mut form = FormValidator::new();
1725
1726 form.register(
1727 "email",
1728 FieldConfig::new()
1729 .required()
1730 .email()
1731 .validate_on(ValidateOn::Change),
1732 );
1733
1734 form.register(
1735 "password",
1736 FieldConfig::new()
1737 .required()
1738 .min_length(8)
1739 .validate_on(ValidateOn::Blur),
1740 );
1741
1742 form.register(
1743 "age",
1744 FieldConfig::new()
1745 .range(18.0, 120.0)
1746 .validate_on(ValidateOn::Submit),
1747 );
1748
1749 form.set_value("email", "invalid");
1751 assert!(!form.field_is_valid("email"));
1752
1753 form.set_value("email", "test@example.com");
1754 assert!(form.field_is_valid("email"));
1755
1756 form.set_value("password", "short");
1758 assert!(form.errors("password").is_empty());
1759
1760 form.touch("password");
1761 assert!(!form.errors("password").is_empty());
1762
1763 form.set_value("password", "longpassword123");
1764 form.touch("password");
1765 assert!(form.errors("password").is_empty());
1766
1767 form.set_value("age", "15");
1769 assert!(form.errors("age").is_empty());
1770
1771 assert!(!form.validate()); form.set_value("age", "25");
1775 assert!(form.validate());
1776 assert!(form.is_valid());
1777 }
1778}