1use once_cell::sync::Lazy;
3use regex::Regex;
4use reinhardt_core::exception::Result;
5use reinhardt_core::validators::{
6 self as validators_crate, OrmValidator, ValidationError as BaseValidationError,
7 ValidationResult,
8};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ValidationError {
15 pub field: String,
17 pub message: String,
19 pub code: String,
21}
22
23impl ValidationError {
24 pub fn new(
40 field: impl Into<String>,
41 message: impl Into<String>,
42 code: impl Into<String>,
43 ) -> Self {
44 Self {
45 field: field.into(),
46 message: message.into(),
47 code: code.into(),
48 }
49 }
50}
51
52impl std::fmt::Display for ValidationError {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 write!(f, "{}: {} ({})", self.field, self.message, self.code)
55 }
56}
57
58impl std::error::Error for ValidationError {}
59
60pub trait Validator: Send + Sync {
65 fn validate(&self, value: &str) -> Result<()>;
67 fn message(&self) -> String;
69}
70
71#[derive(Debug, Clone)]
73pub struct RequiredValidator {
74 pub message: String,
76}
77
78impl RequiredValidator {
79 pub fn new() -> Self {
92 Self {
93 message: "This field is required".to_string(),
94 }
95 }
96 pub fn with_message(message: impl Into<String>) -> Self {
107 Self {
108 message: message.into(),
109 }
110 }
111}
112
113impl Default for RequiredValidator {
114 fn default() -> Self {
115 Self::new()
116 }
117}
118
119impl Validator for RequiredValidator {
120 fn validate(&self, value: &str) -> Result<()> {
121 if value.trim().is_empty() {
122 return Err(reinhardt_core::exception::Error::Validation(
123 self.message.clone(),
124 ));
125 }
126 Ok(())
127 }
128
129 fn message(&self) -> String {
130 self.message.clone()
131 }
132}
133
134impl validators_crate::Validator<str> for RequiredValidator {
135 fn validate(&self, value: &str) -> ValidationResult<()> {
136 if value.trim().is_empty() {
137 return Err(BaseValidationError::Custom(self.message.clone()));
138 }
139 Ok(())
140 }
141}
142
143impl OrmValidator for RequiredValidator {
144 fn message(&self) -> String {
145 self.message.clone()
146 }
147}
148
149#[derive(Debug, Clone)]
151pub struct MaxLengthValidator {
152 pub max_length: usize,
154 pub message: String,
156}
157
158impl MaxLengthValidator {
159 pub fn new(max_length: usize) -> Self {
171 Self {
172 max_length,
173 message: format!("Ensure this value has at most {} characters", max_length),
174 }
175 }
176 pub fn with_message(max_length: usize, message: impl Into<String>) -> Self {
187 Self {
188 max_length,
189 message: message.into(),
190 }
191 }
192}
193
194impl Validator for MaxLengthValidator {
195 fn validate(&self, value: &str) -> Result<()> {
196 if value.len() > self.max_length {
197 return Err(reinhardt_core::exception::Error::Validation(
198 self.message.clone(),
199 ));
200 }
201 Ok(())
202 }
203
204 fn message(&self) -> String {
205 self.message.clone()
206 }
207}
208
209impl validators_crate::Validator<str> for MaxLengthValidator {
210 fn validate(&self, value: &str) -> ValidationResult<()> {
211 if value.len() > self.max_length {
212 return Err(BaseValidationError::Custom(self.message.clone()));
213 }
214 Ok(())
215 }
216}
217
218impl OrmValidator for MaxLengthValidator {
219 fn message(&self) -> String {
220 self.message.clone()
221 }
222}
223
224#[derive(Debug, Clone)]
226pub struct MinLengthValidator {
227 pub min_length: usize,
229 pub message: String,
231}
232
233impl MinLengthValidator {
234 pub fn new(min_length: usize) -> Self {
246 Self {
247 min_length,
248 message: format!("Ensure this value has at least {} characters", min_length),
249 }
250 }
251 pub fn with_message(min_length: usize, message: impl Into<String>) -> Self {
262 Self {
263 min_length,
264 message: message.into(),
265 }
266 }
267}
268
269impl Validator for MinLengthValidator {
270 fn validate(&self, value: &str) -> Result<()> {
271 if value.len() < self.min_length {
272 return Err(reinhardt_core::exception::Error::Validation(
273 self.message.clone(),
274 ));
275 }
276 Ok(())
277 }
278
279 fn message(&self) -> String {
280 self.message.clone()
281 }
282}
283
284impl validators_crate::Validator<str> for MinLengthValidator {
285 fn validate(&self, value: &str) -> ValidationResult<()> {
286 if value.len() < self.min_length {
287 return Err(BaseValidationError::Custom(self.message.clone()));
288 }
289 Ok(())
290 }
291}
292
293impl OrmValidator for MinLengthValidator {
294 fn message(&self) -> String {
295 self.message.clone()
296 }
297}
298
299#[derive(Debug, Clone)]
301pub struct EmailValidator {
302 pub message: String,
304}
305
306impl EmailValidator {
307 pub fn new() -> Self {
321 Self {
322 message: "Enter a valid email address".to_string(),
323 }
324 }
325 pub fn with_message(message: impl Into<String>) -> Self {
336 Self {
337 message: message.into(),
338 }
339 }
340}
341
342impl Default for EmailValidator {
343 fn default() -> Self {
344 Self::new()
345 }
346}
347
348impl Validator for EmailValidator {
349 fn validate(&self, value: &str) -> Result<()> {
350 let parts: Vec<&str> = value.rsplitn(2, '@').collect();
356 if parts.len() != 2 {
357 return Err(reinhardt_core::exception::Error::Validation(
358 self.message.clone(),
359 ));
360 }
361
362 let domain = parts[0];
363 let local = parts[1];
364
365 if !Self::validate_local_part(local) {
367 return Err(reinhardt_core::exception::Error::Validation(
368 self.message.clone(),
369 ));
370 }
371
372 if !Self::validate_domain_part(domain) {
374 return Err(reinhardt_core::exception::Error::Validation(
375 self.message.clone(),
376 ));
377 }
378
379 Ok(())
380 }
381
382 fn message(&self) -> String {
383 self.message.clone()
384 }
385}
386
387impl validators_crate::Validator<str> for EmailValidator {
388 fn validate(&self, value: &str) -> ValidationResult<()> {
389 let parts: Vec<&str> = value.rsplitn(2, '@').collect();
390 if parts.len() != 2 {
391 return Err(BaseValidationError::Custom(self.message.clone()));
392 }
393
394 let domain = parts[0];
395 let local = parts[1];
396
397 if !Self::validate_local_part(local) || !Self::validate_domain_part(domain) {
398 return Err(BaseValidationError::Custom(self.message.clone()));
399 }
400
401 Ok(())
402 }
403}
404
405impl OrmValidator for EmailValidator {
406 fn message(&self) -> String {
407 self.message.clone()
408 }
409}
410
411impl EmailValidator {
412 fn validate_local_part(local: &str) -> bool {
415 if local.is_empty() || local.len() > 64 {
416 return false;
417 }
418
419 if local.starts_with('"') && local.ends_with('"') {
421 return Self::validate_quoted_string(local);
422 }
423
424 Self::validate_dot_atom(local)
426 }
427
428 fn validate_quoted_string(s: &str) -> bool {
430 if s.len() < 2 {
431 return false;
432 }
433
434 let inner = &s[1..s.len() - 1];
435 let mut escaped = false;
436
437 for ch in inner.chars() {
438 if escaped {
439 if !ch.is_ascii() {
441 return false;
442 }
443 escaped = false;
444 } else if ch == '\\' {
445 escaped = true;
446 } else if ch == '"' {
447 return false;
449 } else if !Self::is_qtext(ch) {
450 return false;
451 }
452 }
453
454 !escaped
456 }
457
458 fn is_qtext(ch: char) -> bool {
460 ch == ' ' || ch == '\t' || (('!'..='~').contains(&ch) && ch != '"' && ch != '\\')
461 }
462
463 fn validate_dot_atom(s: &str) -> bool {
465 if s.is_empty() || s.starts_with('.') || s.ends_with('.') {
466 return false;
467 }
468
469 if s.contains("..") {
471 return false;
472 }
473
474 s.split('.').all(Self::validate_atom)
476 }
477
478 fn validate_atom(atom: &str) -> bool {
480 if atom.is_empty() {
481 return false;
482 }
483
484 atom.chars().all(Self::is_atext)
485 }
486
487 fn is_atext(ch: char) -> bool {
489 ch.is_ascii_alphanumeric() || "!#$%&'*+-/=?^_`{|}~".contains(ch)
490 }
491
492 fn validate_domain_part(domain: &str) -> bool {
495 if domain.is_empty() || domain.len() > 255 {
496 return false;
497 }
498
499 if domain.starts_with('[') && domain.ends_with(']') {
501 return Self::validate_domain_literal(domain);
502 }
503
504 Self::validate_domain_name(domain)
506 }
507
508 fn validate_domain_literal(domain: &str) -> bool {
510 if domain.len() < 3 {
511 return false;
512 }
513
514 let inner = &domain[1..domain.len() - 1];
515
516 if inner.to_lowercase().starts_with("ipv6:") {
518 return inner.len() > 5 && inner[5..].contains(':');
520 }
521
522 Self::validate_ipv4(inner)
524 }
525
526 fn validate_ipv4(s: &str) -> bool {
528 let parts: Vec<&str> = s.split('.').collect();
529 if parts.len() != 4 {
530 return false;
531 }
532
533 parts.iter().all(|part| part.parse::<u8>().is_ok())
534 }
535
536 fn validate_domain_name(domain: &str) -> bool {
538 if domain.starts_with('.') || domain.ends_with('.') || domain.contains("..") {
539 return false;
540 }
541
542 let labels: Vec<&str> = domain.split('.').collect();
543
544 if labels.len() < 2 {
546 return false;
547 }
548
549 labels
551 .iter()
552 .all(|label| Self::validate_domain_label(label))
553 }
554
555 fn validate_domain_label(label: &str) -> bool {
557 if label.is_empty() || label.len() > 63 {
558 return false;
559 }
560
561 if label.starts_with('-') || label.ends_with('-') {
563 return false;
564 }
565
566 label
568 .chars()
569 .all(|ch| ch.is_ascii_alphanumeric() || ch == '-')
570 }
571}
572
573#[derive(Debug, Clone)]
575pub struct URLValidator {
576 pub message: String,
578}
579
580impl URLValidator {
581 pub fn new() -> Self {
595 Self {
596 message: "Enter a valid URL".to_string(),
597 }
598 }
599 pub fn with_message(message: impl Into<String>) -> Self {
610 Self {
611 message: message.into(),
612 }
613 }
614}
615
616impl Default for URLValidator {
617 fn default() -> Self {
618 Self::new()
619 }
620}
621
622impl Validator for URLValidator {
623 fn validate(&self, value: &str) -> Result<()> {
624 static URL_REGEX: Lazy<Regex> = Lazy::new(|| {
626 Regex::new(r"^https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(/.*)?$")
627 .expect("Invalid URL regex pattern")
628 });
629
630 if !URL_REGEX.is_match(value) {
631 return Err(reinhardt_core::exception::Error::Validation(
632 self.message.clone(),
633 ));
634 }
635 Ok(())
636 }
637
638 fn message(&self) -> String {
639 self.message.clone()
640 }
641}
642
643impl validators_crate::Validator<str> for URLValidator {
644 fn validate(&self, value: &str) -> ValidationResult<()> {
645 static URL_REGEX: Lazy<Regex> = Lazy::new(|| {
646 Regex::new(r"^https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(/.*)?$")
647 .expect("Invalid URL regex pattern")
648 });
649
650 if !URL_REGEX.is_match(value) {
651 return Err(BaseValidationError::Custom(self.message.clone()));
652 }
653 Ok(())
654 }
655}
656
657impl OrmValidator for URLValidator {
658 fn message(&self) -> String {
659 self.message.clone()
660 }
661}
662
663#[derive(Debug)]
665pub struct RegexValidator {
666 pattern: String,
667 compiled_regex: Regex,
668 message: String,
669}
670
671impl Clone for RegexValidator {
672 fn clone(&self) -> Self {
673 Self {
674 pattern: self.pattern.clone(),
675 compiled_regex: Regex::new(&self.pattern)
676 .expect("Regex should be valid since it was validated at construction"),
677 message: self.message.clone(),
678 }
679 }
680}
681
682impl RegexValidator {
683 pub fn new(pattern: impl Into<String>) -> Self {
699 let pattern = pattern.into();
700 let compiled_regex = Regex::new(&pattern)
701 .unwrap_or_else(|e| panic!("Invalid regex pattern '{}': {}", pattern, e));
702
703 Self {
704 message: format!("Value does not match pattern: {}", pattern),
705 pattern,
706 compiled_regex,
707 }
708 }
709 pub fn with_message(pattern: impl Into<String>, message: impl Into<String>) -> Self {
725 let pattern = pattern.into();
726 let compiled_regex = Regex::new(&pattern)
727 .unwrap_or_else(|e| panic!("Invalid regex pattern '{}': {}", pattern, e));
728
729 Self {
730 pattern,
731 compiled_regex,
732 message: message.into(),
733 }
734 }
735 pub fn try_new(pattern: impl Into<String>) -> std::result::Result<Self, regex::Error> {
751 let pattern = pattern.into();
752 let compiled_regex = Regex::new(&pattern)?;
753
754 Ok(Self {
755 message: format!("Value does not match pattern: {}", pattern),
756 pattern,
757 compiled_regex,
758 })
759 }
760 pub fn pattern(&self) -> &str {
771 &self.pattern
772 }
773}
774
775impl Validator for RegexValidator {
776 fn validate(&self, value: &str) -> Result<()> {
777 if !self.compiled_regex.is_match(value) {
778 return Err(reinhardt_core::exception::Error::Validation(
779 self.message.clone(),
780 ));
781 }
782 Ok(())
783 }
784
785 fn message(&self) -> String {
786 self.message.clone()
787 }
788}
789
790impl validators_crate::Validator<str> for RegexValidator {
791 fn validate(&self, value: &str) -> ValidationResult<()> {
792 if !self.compiled_regex.is_match(value) {
793 return Err(BaseValidationError::Custom(self.message.clone()));
794 }
795 Ok(())
796 }
797}
798
799impl OrmValidator for RegexValidator {
800 fn message(&self) -> String {
801 self.message.clone()
802 }
803}
804
805#[derive(Debug, Clone)]
807pub struct RangeValidator {
808 pub min: Option<i64>,
810 pub max: Option<i64>,
812 pub message: String,
814}
815
816impl RangeValidator {
817 pub fn new(min: Option<i64>, max: Option<i64>) -> Self {
832 let message = match (min, max) {
833 (Some(min), Some(max)) => format!("Value must be between {} and {}", min, max),
834 (Some(min), None) => format!("Value must be at least {}", min),
835 (None, Some(max)) => format!("Value must be at most {}", max),
836 (None, None) => "Invalid range".to_string(),
837 };
838 Self { min, max, message }
839 }
840 pub fn with_message(min: Option<i64>, max: Option<i64>, message: impl Into<String>) -> Self {
851 Self {
852 min,
853 max,
854 message: message.into(),
855 }
856 }
857}
858
859impl Validator for RangeValidator {
860 fn validate(&self, value: &str) -> Result<()> {
861 let num: i64 = value.parse().map_err(|_| {
862 reinhardt_core::exception::Error::Validation("Invalid number".to_string())
863 })?;
864
865 if let Some(min) = self.min
866 && num < min
867 {
868 return Err(reinhardt_core::exception::Error::Validation(
869 self.message.clone(),
870 ));
871 }
872
873 if let Some(max) = self.max
874 && num > max
875 {
876 return Err(reinhardt_core::exception::Error::Validation(
877 self.message.clone(),
878 ));
879 }
880
881 Ok(())
882 }
883
884 fn message(&self) -> String {
885 self.message.clone()
886 }
887}
888
889impl validators_crate::Validator<str> for RangeValidator {
890 fn validate(&self, value: &str) -> ValidationResult<()> {
891 let num: i64 = value
892 .parse()
893 .map_err(|_| BaseValidationError::Custom("Invalid number".to_string()))?;
894
895 if let Some(min) = self.min
896 && num < min
897 {
898 return Err(BaseValidationError::Custom(self.message.clone()));
899 }
900
901 if let Some(max) = self.max
902 && num > max
903 {
904 return Err(BaseValidationError::Custom(self.message.clone()));
905 }
906
907 Ok(())
908 }
909}
910
911impl OrmValidator for RangeValidator {
912 fn message(&self) -> String {
913 self.message.clone()
914 }
915}
916
917pub struct FieldValidators {
919 pub validators: Vec<Box<dyn Validator>>,
921}
922
923impl FieldValidators {
924 pub fn new() -> Self {
937 Self {
938 validators: Vec::new(),
939 }
940 }
941 pub fn with_validator(mut self, validator: Box<dyn Validator>) -> Self {
956 self.validators.push(validator);
957 self
958 }
959 pub fn validate(&self, value: &str) -> Result<()> {
975 for validator in &self.validators {
976 validator.validate(value)?;
977 }
978 Ok(())
979 }
980}
981
982impl Default for FieldValidators {
983 fn default() -> Self {
984 Self::new()
985 }
986}
987
988pub struct ModelValidators {
990 pub field_validators: HashMap<String, FieldValidators>,
992}
993
994impl ModelValidators {
995 pub fn new() -> Self {
1006 Self {
1007 field_validators: HashMap::new(),
1008 }
1009 }
1010 pub fn add_field_validator(&mut self, field: String, validators: FieldValidators) {
1025 self.field_validators.insert(field, validators);
1026 }
1027 pub fn validate(&self, field: &str, value: &str) -> Result<()> {
1043 if let Some(validators) = self.field_validators.get(field) {
1044 validators.validate(value)?;
1045 }
1046 Ok(())
1047 }
1048 pub fn validate_all(&self, data: &HashMap<String, String>) -> Vec<ValidationError> {
1074 let mut errors = Vec::new();
1075
1076 for (field, validators) in &self.field_validators {
1077 if let Some(value) = data.get(field)
1078 && let Err(e) = validators.validate(value)
1079 {
1080 errors.push(ValidationError::new(
1081 field,
1082 e.to_string(),
1083 "validation_error",
1084 ));
1085 }
1086 }
1087
1088 errors
1089 }
1090}
1091
1092impl Default for ModelValidators {
1093 fn default() -> Self {
1094 Self::new()
1095 }
1096}
1097
1098#[cfg(test)]
1099mod tests {
1100 use super::*;
1101
1102 #[test]
1103 fn test_orm_validators_required() {
1104 let validator = RequiredValidator::new();
1105 assert!(validator.validate("test").is_ok());
1106 assert!(validator.validate("").is_err());
1107 assert!(validator.validate(" ").is_err());
1108 }
1109
1110 #[test]
1111 fn test_orm_validators_max_length() {
1112 let validator = MaxLengthValidator::new(5);
1113 assert!(validator.validate("test").is_ok());
1114 assert!(validator.validate("test1").is_ok());
1115 assert!(validator.validate("test12").is_err());
1116 }
1117
1118 #[test]
1119 fn test_orm_validators_min_length() {
1120 let validator = MinLengthValidator::new(3);
1121 assert!(validator.validate("test").is_ok());
1122 assert!(validator.validate("te").is_err());
1123 }
1124
1125 #[test]
1126 fn test_email_validator() {
1127 let validator = EmailValidator::new();
1128
1129 assert!(validator.validate("test@example.com").is_ok());
1131 assert!(validator.validate("user.name@example.com").is_ok());
1132 assert!(validator.validate("user+tag@example.co.uk").is_ok());
1133 assert!(validator.validate("first.last@sub.example.com").is_ok());
1134
1135 assert!(validator.validate("user!tag@example.com").is_ok());
1137 assert!(validator.validate("user#tag@example.com").is_ok());
1138 assert!(validator.validate("user$tag@example.com").is_ok());
1139 assert!(validator.validate("user%tag@example.com").is_ok());
1140 assert!(validator.validate("user&tag@example.com").is_ok());
1141 assert!(validator.validate("user*tag@example.com").is_ok());
1142 assert!(validator.validate("user=tag@example.com").is_ok());
1143 assert!(validator.validate("user?tag@example.com").is_ok());
1144 assert!(validator.validate("user^tag@example.com").is_ok());
1145 assert!(validator.validate("user_tag@example.com").is_ok());
1146 assert!(validator.validate("user`tag@example.com").is_ok());
1147 assert!(validator.validate("user{tag@example.com").is_ok());
1148 assert!(validator.validate("user|tag@example.com").is_ok());
1149 assert!(validator.validate("user}tag@example.com").is_ok());
1150 assert!(validator.validate("user~tag@example.com").is_ok());
1151
1152 assert!(validator.validate(r#""user name"@example.com"#).is_ok());
1154 assert!(validator.validate(r#""user@name"@example.com"#).is_ok());
1155 assert!(validator.validate(r#""user\"name"@example.com"#).is_ok());
1156
1157 assert!(validator.validate("user@[192.168.1.1]").is_ok());
1159 assert!(validator.validate("user@[127.0.0.1]").is_ok());
1160
1161 assert!(validator.validate("user@[IPv6:2001:db8::1]").is_ok());
1163
1164 assert!(validator.validate("invalid").is_err());
1166 assert!(validator.validate("no-at-sign.com").is_err());
1167 assert!(validator.validate("@example.com").is_err());
1168 assert!(validator.validate("user@").is_err());
1169 assert!(validator.validate("user @example.com").is_err());
1170
1171 assert!(validator.validate(".user@example.com").is_err());
1173 assert!(validator.validate("user.@example.com").is_err());
1174 assert!(validator.validate("user..name@example.com").is_err());
1175
1176 assert!(validator.validate("user@.example.com").is_err());
1178 assert!(validator.validate("user@example.com.").is_err());
1179 assert!(validator.validate("user@example..com").is_err());
1180 assert!(validator.validate("user@-example.com").is_err());
1181 assert!(validator.validate("user@example-.com").is_err());
1182 assert!(validator.validate("user@example").is_err()); let long_local = "a".repeat(65);
1186 assert!(
1187 validator
1188 .validate(&format!("{}@example.com", long_local))
1189 .is_err()
1190 );
1191
1192 assert!(validator.validate(r#""user"name"@example.com"#).is_err()); assert!(validator.validate(r#""user\"@example.com"#).is_err()); }
1196
1197 #[test]
1198 fn test_url_validator() {
1199 let validator = URLValidator::new();
1200 assert!(validator.validate("http://example.com").is_ok());
1202 assert!(validator.validate("https://example.com").is_ok());
1203 assert!(validator.validate("https://example.com/path").is_ok());
1204 assert!(validator.validate("http://sub.example.com").is_ok());
1205
1206 assert!(validator.validate("example.com").is_err());
1208 assert!(validator.validate("ftp://example.com").is_err());
1209 assert!(validator.validate("http://").is_err());
1210 }
1211
1212 #[test]
1213 fn test_regex_validator_valid_pattern() {
1214 let validator = RegexValidator::new(r"^\d{3}-\d{4}$");
1215 assert!(validator.validate("123-4567").is_ok());
1216 assert!(validator.validate("999-0000").is_ok());
1217 assert!(validator.validate("12-4567").is_err());
1218 assert!(validator.validate("123-45678").is_err());
1219 assert!(validator.validate("abc-defg").is_err());
1220 }
1221
1222 #[test]
1223 fn test_regex_validator_alphanumeric() {
1224 let validator = RegexValidator::new(r"^[a-zA-Z0-9]+$");
1225 assert!(validator.validate("abc123").is_ok());
1226 assert!(validator.validate("ABC").is_ok());
1227 assert!(validator.validate("123").is_ok());
1228 assert!(validator.validate("abc-123").is_err());
1229 assert!(validator.validate("abc 123").is_err());
1230 }
1231
1232 #[test]
1233 #[should_panic(expected = "Invalid regex pattern")]
1234 fn test_regex_validator_invalid_pattern() {
1235 RegexValidator::new(r"[invalid(regex");
1237 }
1238
1239 #[test]
1240 fn test_regex_validator_try_new_valid() {
1241 let result = RegexValidator::try_new(r"^\d+$");
1242 let validator = result.unwrap();
1243 assert!(validator.validate("123").is_ok());
1244 assert!(validator.validate("abc").is_err());
1245 }
1246
1247 #[test]
1248 fn test_regex_validator_try_new_invalid() {
1249 let result = RegexValidator::try_new(r"[invalid(regex");
1250 assert!(result.is_err());
1251 }
1252
1253 #[test]
1254 fn test_regex_validator_with_message() {
1255 use reinhardt_core::validators::OrmValidator;
1256 let validator = RegexValidator::with_message(r"^\d{5}$", "ZIP code must be 5 digits");
1257 assert_eq!(
1258 OrmValidator::message(&validator),
1259 "ZIP code must be 5 digits"
1260 );
1261 assert!(validator.validate("12345").is_ok());
1262 assert!(validator.validate("1234").is_err());
1263 }
1264
1265 #[test]
1266 fn test_regex_validator_pattern() {
1267 let validator = RegexValidator::new(r"^\d+$");
1268 assert_eq!(validator.pattern(), r"^\d+$");
1269 }
1270
1271 #[test]
1272 fn test_orm_range_validator() {
1273 let validator = RangeValidator::new(Some(0), Some(100));
1274 assert!(validator.validate("50").is_ok());
1275 assert!(validator.validate("0").is_ok());
1276 assert!(validator.validate("100").is_ok());
1277 assert!(validator.validate("-1").is_err());
1278 assert!(validator.validate("101").is_err());
1279 }
1280
1281 #[test]
1282 fn test_field_validators() {
1283 let validators = FieldValidators::new()
1284 .with_validator(Box::new(RequiredValidator::new()))
1285 .with_validator(Box::new(MaxLengthValidator::new(10)));
1286
1287 assert!(validators.validate("test").is_ok());
1288 assert!(validators.validate("").is_err());
1289 assert!(validators.validate("12345678901").is_err());
1290 }
1291
1292 #[test]
1293 fn test_model_validators() {
1294 let mut model_validators = ModelValidators::new();
1295
1296 let email_validators = FieldValidators::new()
1297 .with_validator(Box::new(RequiredValidator::new()))
1298 .with_validator(Box::new(EmailValidator::new()));
1299
1300 model_validators.add_field_validator("email".to_string(), email_validators);
1301
1302 assert!(
1303 model_validators
1304 .validate("email", "test@example.com")
1305 .is_ok()
1306 );
1307 assert!(model_validators.validate("email", "invalid").is_err());
1308 }
1309
1310 #[test]
1311 fn test_validate_all() {
1312 let mut model_validators = ModelValidators::new();
1313
1314 let username_validators = FieldValidators::new()
1315 .with_validator(Box::new(MinLengthValidator::new(3)))
1316 .with_validator(Box::new(MaxLengthValidator::new(20)));
1317
1318 let email_validators =
1319 FieldValidators::new().with_validator(Box::new(EmailValidator::new()));
1320
1321 model_validators.add_field_validator("username".to_string(), username_validators);
1322 model_validators.add_field_validator("email".to_string(), email_validators);
1323
1324 let mut data = HashMap::new();
1325 data.insert("username".to_string(), "ab".to_string());
1326 data.insert("email".to_string(), "invalid".to_string());
1327
1328 let errors = model_validators.validate_all(&data);
1329 assert_eq!(errors.len(), 2);
1330 }
1331}