Skip to main content

reinhardt_db/orm/
validators.rs

1/// Field validators similar to Django's validators
2use 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/// Validation error
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ValidationError {
15	/// The field.
16	pub field: String,
17	/// The message.
18	pub message: String,
19	/// The code.
20	pub code: String,
21}
22
23impl ValidationError {
24	/// Create a new validation error
25	///
26	/// # Examples
27	///
28	/// ```
29	/// use reinhardt_db::orm::validators::ValidationError;
30	///
31	/// let error = ValidationError::new(
32	///     "email",
33	///     "Enter a valid email address",
34	///     "invalid_email"
35	/// );
36	/// assert_eq!(error.field, "email");
37	/// assert_eq!(error.code, "invalid_email");
38	/// ```
39	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
60/// Base trait for validators
61///
62/// This is the ORM-specific validator trait. For the base validator trait,
63/// see `reinhardt_core::validators::Validator`.
64pub trait Validator: Send + Sync {
65	/// Validates the given value and returns an error if invalid.
66	fn validate(&self, value: &str) -> Result<()>;
67	/// Returns the error message for this validator.
68	fn message(&self) -> String;
69}
70
71/// Required field validator
72#[derive(Debug, Clone)]
73pub struct RequiredValidator {
74	/// The message.
75	pub message: String,
76}
77
78impl RequiredValidator {
79	/// Create a new required field validator
80	///
81	/// # Examples
82	///
83	/// ```
84	/// use reinhardt_db::orm::validators::{RequiredValidator, Validator};
85	///
86	/// let validator = RequiredValidator::new();
87	/// assert!(validator.validate("some text").is_ok());
88	/// assert!(validator.validate("").is_err());
89	/// assert!(validator.validate("   ").is_err());
90	/// ```
91	pub fn new() -> Self {
92		Self {
93			message: "This field is required".to_string(),
94		}
95	}
96	/// Create a required validator with custom error message
97	///
98	/// # Examples
99	///
100	/// ```
101	/// use reinhardt_db::orm::validators::{RequiredValidator, Validator};
102	///
103	/// let validator = RequiredValidator::with_message("Username is required");
104	/// assert_eq!(validator.message(), "Username is required");
105	/// ```
106	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/// Max length validator
150#[derive(Debug, Clone)]
151pub struct MaxLengthValidator {
152	/// The max length.
153	pub max_length: usize,
154	/// The message.
155	pub message: String,
156}
157
158impl MaxLengthValidator {
159	/// Create a new max length validator
160	///
161	/// # Examples
162	///
163	/// ```
164	/// use reinhardt_db::orm::validators::{MaxLengthValidator, Validator};
165	///
166	/// let validator = MaxLengthValidator::new(10);
167	/// assert!(validator.validate("hello").is_ok());
168	/// assert!(validator.validate("hello world").is_err());
169	/// ```
170	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	/// Create a max length validator with custom error message
177	///
178	/// # Examples
179	///
180	/// ```
181	/// use reinhardt_db::orm::validators::{MaxLengthValidator, Validator};
182	///
183	/// let validator = MaxLengthValidator::with_message(20, "Name too long");
184	/// assert_eq!(validator.message(), "Name too long");
185	/// ```
186	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/// Min length validator
225#[derive(Debug, Clone)]
226pub struct MinLengthValidator {
227	/// The min length.
228	pub min_length: usize,
229	/// The message.
230	pub message: String,
231}
232
233impl MinLengthValidator {
234	/// Create a new min length validator
235	///
236	/// # Examples
237	///
238	/// ```
239	/// use reinhardt_db::orm::validators::{MinLengthValidator, Validator};
240	///
241	/// let validator = MinLengthValidator::new(3);
242	/// assert!(validator.validate("hello").is_ok());
243	/// assert!(validator.validate("hi").is_err());
244	/// ```
245	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	/// Create a min length validator with custom error message
252	///
253	/// # Examples
254	///
255	/// ```
256	/// use reinhardt_db::orm::validators::{MinLengthValidator, Validator};
257	///
258	/// let validator = MinLengthValidator::with_message(8, "Password too short");
259	/// assert_eq!(validator.message(), "Password too short");
260	/// ```
261	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/// Email validator
300#[derive(Debug, Clone)]
301pub struct EmailValidator {
302	/// The message.
303	pub message: String,
304}
305
306impl EmailValidator {
307	/// Create a new RFC 5322 compliant email validator
308	///
309	/// # Examples
310	///
311	/// ```
312	/// use reinhardt_db::orm::validators::{EmailValidator, Validator};
313	///
314	/// let validator = EmailValidator::new();
315	/// assert!(validator.validate("user@example.com").is_ok());
316	/// assert!(validator.validate("user.name+tag@example.co.uk").is_ok());
317	/// assert!(validator.validate("invalid-email").is_err());
318	/// assert!(validator.validate("@example.com").is_err());
319	/// ```
320	pub fn new() -> Self {
321		Self {
322			message: "Enter a valid email address".to_string(),
323		}
324	}
325	/// Create an email validator with custom error message
326	///
327	/// # Examples
328	///
329	/// ```
330	/// use reinhardt_db::orm::validators::{EmailValidator, Validator};
331	///
332	/// let validator = EmailValidator::with_message("Please provide a valid email");
333	/// assert_eq!(validator.message(), "Please provide a valid email");
334	/// ```
335	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		// RFC 5322 compliant email validation
351		// This implementation follows the addr-spec format from RFC 5322
352		// See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.4.1
353
354		// Split email into local and domain parts
355		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		// Validate local part (before @)
366		if !Self::validate_local_part(local) {
367			return Err(reinhardt_core::exception::Error::Validation(
368				self.message.clone(),
369			));
370		}
371
372		// Validate domain part (after @)
373		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	/// Validate the local part of an email (before @)
413	/// RFC 5322: local-part = dot-atom / quoted-string / obs-local-part
414	fn validate_local_part(local: &str) -> bool {
415		if local.is_empty() || local.len() > 64 {
416			return false;
417		}
418
419		// Check for quoted strings
420		if local.starts_with('"') && local.ends_with('"') {
421			return Self::validate_quoted_string(local);
422		}
423
424		// Validate dot-atom format
425		Self::validate_dot_atom(local)
426	}
427
428	/// Validate quoted string format
429	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				// After backslash, any ASCII char is allowed
440				if !ch.is_ascii() {
441					return false;
442				}
443				escaped = false;
444			} else if ch == '\\' {
445				escaped = true;
446			} else if ch == '"' {
447				// Unescaped quote inside quoted string is invalid
448				return false;
449			} else if !Self::is_qtext(ch) {
450				return false;
451			}
452		}
453
454		// Must not end with an escape character
455		!escaped
456	}
457
458	/// Check if character is valid qtext (RFC 5322)
459	fn is_qtext(ch: char) -> bool {
460		ch == ' ' || ch == '\t' || (('!'..='~').contains(&ch) && ch != '"' && ch != '\\')
461	}
462
463	/// Validate dot-atom format (RFC 5322)
464	fn validate_dot_atom(s: &str) -> bool {
465		if s.is_empty() || s.starts_with('.') || s.ends_with('.') {
466			return false;
467		}
468
469		// Check for consecutive dots
470		if s.contains("..") {
471			return false;
472		}
473
474		// Each atom must be valid
475		s.split('.').all(Self::validate_atom)
476	}
477
478	/// Validate atom characters (RFC 5322)
479	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	/// Check if character is valid atext (RFC 5322)
488	fn is_atext(ch: char) -> bool {
489		ch.is_ascii_alphanumeric() || "!#$%&'*+-/=?^_`{|}~".contains(ch)
490	}
491
492	/// Validate domain part (after @)
493	/// RFC 5322: domain = dot-atom / domain-literal / obs-domain
494	fn validate_domain_part(domain: &str) -> bool {
495		if domain.is_empty() || domain.len() > 255 {
496			return false;
497		}
498
499		// Check for domain literal [IPv4 or IPv6]
500		if domain.starts_with('[') && domain.ends_with(']') {
501			return Self::validate_domain_literal(domain);
502		}
503
504		// Validate dot-atom domain format
505		Self::validate_domain_name(domain)
506	}
507
508	/// Validate domain literal (IP address in brackets)
509	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		// Check for IPv6 prefix
517		if inner.to_lowercase().starts_with("ipv6:") {
518			// Basic IPv6 validation - can be enhanced
519			return inner.len() > 5 && inner[5..].contains(':');
520		}
521
522		// IPv4 validation
523		Self::validate_ipv4(inner)
524	}
525
526	/// Basic IPv4 validation
527	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	/// Validate domain name (dot-separated labels)
537	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		// Must have at least 2 labels (e.g., example.com)
545		if labels.len() < 2 {
546			return false;
547		}
548
549		// Each label must be valid
550		labels
551			.iter()
552			.all(|label| Self::validate_domain_label(label))
553	}
554
555	/// Validate a single domain label
556	fn validate_domain_label(label: &str) -> bool {
557		if label.is_empty() || label.len() > 63 {
558			return false;
559		}
560
561		// Label cannot start or end with hyphen
562		if label.starts_with('-') || label.ends_with('-') {
563			return false;
564		}
565
566		// Label must contain only alphanumeric and hyphen
567		label
568			.chars()
569			.all(|ch| ch.is_ascii_alphanumeric() || ch == '-')
570	}
571}
572
573/// URL validator
574#[derive(Debug, Clone)]
575pub struct URLValidator {
576	/// The message.
577	pub message: String,
578}
579
580impl URLValidator {
581	/// Create a new URL validator
582	///
583	/// # Examples
584	///
585	/// ```
586	/// use reinhardt_db::orm::validators::{URLValidator, Validator};
587	///
588	/// let validator = URLValidator::new();
589	/// assert!(validator.validate("https://example.com").is_ok());
590	/// assert!(validator.validate("http://example.com/path").is_ok());
591	/// assert!(validator.validate("ftp://example.com").is_err());
592	/// assert!(validator.validate("example.com").is_err());
593	/// ```
594	pub fn new() -> Self {
595		Self {
596			message: "Enter a valid URL".to_string(),
597		}
598	}
599	/// Create a URL validator with custom error message
600	///
601	/// # Examples
602	///
603	/// ```
604	/// use reinhardt_db::orm::validators::{URLValidator, Validator};
605	///
606	/// let validator = URLValidator::with_message("Please provide a valid URL");
607	/// assert_eq!(validator.message(), "Please provide a valid URL");
608	/// ```
609	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		// URL validation with proper scheme, domain, and optional path
625		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/// Regex validator with compile-time pattern validation and runtime caching
664#[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	/// Create a new RegexValidator with compile-time pattern validation
684	///
685	/// # Panics
686	/// Panics if the regex pattern is invalid. This is intentional to catch
687	/// regex errors at initialization time rather than at validation time.
688	///
689	/// # Examples
690	///
691	/// ```
692	/// use reinhardt_db::orm::validators::{RegexValidator, Validator};
693	///
694	/// let validator = RegexValidator::new(r"^\d{3}-\d{4}$");
695	/// assert!(validator.validate("123-4567").is_ok());
696	/// assert!(validator.validate("abc-defg").is_err());
697	/// ```
698	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	/// Create a new RegexValidator with a custom error message
710	///
711	/// # Panics
712	/// Panics if the regex pattern is invalid.
713	///
714	/// # Examples
715	///
716	/// ```
717	/// use reinhardt_db::orm::validators::{RegexValidator, Validator};
718	///
719	/// let validator = RegexValidator::with_message(r"^\d{5}$", "ZIP code must be 5 digits");
720	/// assert_eq!(validator.message(), "ZIP code must be 5 digits");
721	/// assert!(validator.validate("12345").is_ok());
722	/// assert!(validator.validate("1234").is_err());
723	/// ```
724	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	/// Try to create a new RegexValidator, returning an error if the pattern is invalid
736	///
737	/// # Examples
738	///
739	/// ```
740	/// use reinhardt_db::orm::validators::RegexValidator;
741	///
742	/// // Valid pattern
743	/// let result = RegexValidator::try_new(r"^\d+$");
744	/// assert!(result.is_ok());
745	///
746	/// // Invalid pattern
747	/// let result = RegexValidator::try_new(r"[invalid(regex");
748	/// assert!(result.is_err());
749	/// ```
750	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	/// Get the regex pattern
761	///
762	/// # Examples
763	///
764	/// ```
765	/// use reinhardt_db::orm::validators::RegexValidator;
766	///
767	/// let validator = RegexValidator::new(r"^\d{3}-\d{4}$");
768	/// assert_eq!(validator.pattern(), r"^\d{3}-\d{4}$");
769	/// ```
770	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/// Numeric range validator
806#[derive(Debug, Clone)]
807pub struct RangeValidator {
808	/// The min.
809	pub min: Option<i64>,
810	/// The max.
811	pub max: Option<i64>,
812	/// The message.
813	pub message: String,
814}
815
816impl RangeValidator {
817	/// Create a new numeric range validator
818	///
819	/// # Examples
820	///
821	/// ```
822	/// use reinhardt_db::orm::validators::{RangeValidator, Validator};
823	///
824	/// let validator = RangeValidator::new(Some(0), Some(100));
825	/// assert!(validator.validate("50").is_ok());
826	/// assert!(validator.validate("0").is_ok());
827	/// assert!(validator.validate("100").is_ok());
828	/// assert!(validator.validate("-1").is_err());
829	/// assert!(validator.validate("101").is_err());
830	/// ```
831	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	/// Create a range validator with custom error message
841	///
842	/// # Examples
843	///
844	/// ```
845	/// use reinhardt_db::orm::validators::{RangeValidator, Validator};
846	///
847	/// let validator = RangeValidator::with_message(Some(18), Some(65), "Age must be 18-65");
848	/// assert_eq!(validator.message(), "Age must be 18-65");
849	/// ```
850	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
917/// Validator collection for a field
918pub struct FieldValidators {
919	/// The validators.
920	pub validators: Vec<Box<dyn Validator>>,
921}
922
923impl FieldValidators {
924	/// Create a new empty field validators collection
925	///
926	/// # Examples
927	///
928	/// ```
929	/// use reinhardt_db::orm::validators::{FieldValidators, RequiredValidator};
930	///
931	/// let validators = FieldValidators::new()
932	///     .with_validator(Box::new(RequiredValidator::new()));
933	/// // Verify validator was added (type check passes)
934	/// let _: FieldValidators = validators;
935	/// ```
936	pub fn new() -> Self {
937		Self {
938			validators: Vec::new(),
939		}
940	}
941	/// Add a validator to this field's validator chain
942	///
943	/// # Examples
944	///
945	/// ```
946	/// use reinhardt_db::orm::validators::{FieldValidators, RequiredValidator, MaxLengthValidator};
947	///
948	/// let validators = FieldValidators::new()
949	///     .with_validator(Box::new(RequiredValidator::new()))
950	///     .with_validator(Box::new(MaxLengthValidator::new(100)));
951	/// // Verify both validators were added successfully
952	/// assert!(validators.validate("hello").is_ok());
953	/// assert!(validators.validate("").is_err()); // Fails RequiredValidator
954	/// ```
955	pub fn with_validator(mut self, validator: Box<dyn Validator>) -> Self {
956		self.validators.push(validator);
957		self
958	}
959	/// Validate a value against all validators in this collection
960	///
961	/// # Examples
962	///
963	/// ```
964	/// use reinhardt_db::orm::validators::{FieldValidators, RequiredValidator, MaxLengthValidator, Validator};
965	///
966	/// let validators = FieldValidators::new()
967	///     .with_validator(Box::new(RequiredValidator::new()))
968	///     .with_validator(Box::new(MaxLengthValidator::new(10)));
969	///
970	/// assert!(validators.validate("hello").is_ok());
971	/// assert!(validators.validate("").is_err()); // Required
972	/// assert!(validators.validate("hello world long text").is_err()); // Too long
973	/// ```
974	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
988/// Model validators collection
989pub struct ModelValidators {
990	/// The field validators.
991	pub field_validators: HashMap<String, FieldValidators>,
992}
993
994impl ModelValidators {
995	/// Create a new model validators collection
996	///
997	/// # Examples
998	///
999	/// ```
1000	/// use reinhardt_db::orm::validators::ModelValidators;
1001	///
1002	/// let mut model_validators = ModelValidators::new();
1003	/// assert_eq!(model_validators.field_validators.len(), 0);
1004	/// ```
1005	pub fn new() -> Self {
1006		Self {
1007			field_validators: HashMap::new(),
1008		}
1009	}
1010	/// Register validators for a specific field
1011	///
1012	/// # Examples
1013	///
1014	/// ```
1015	/// use reinhardt_db::orm::validators::{ModelValidators, FieldValidators, EmailValidator};
1016	///
1017	/// let mut model_validators = ModelValidators::new();
1018	/// let email_validators = FieldValidators::new()
1019	///     .with_validator(Box::new(EmailValidator::new()));
1020	/// model_validators.add_field_validator("email".to_string(), email_validators);
1021	/// assert_eq!(model_validators.field_validators.len(), 1);
1022	/// assert!(model_validators.field_validators.contains_key("email"));
1023	/// ```
1024	pub fn add_field_validator(&mut self, field: String, validators: FieldValidators) {
1025		self.field_validators.insert(field, validators);
1026	}
1027	/// Validate a single field's value
1028	///
1029	/// # Examples
1030	///
1031	/// ```
1032	/// use reinhardt_db::orm::validators::{ModelValidators, FieldValidators, EmailValidator};
1033	///
1034	/// let mut model_validators = ModelValidators::new();
1035	/// let email_validators = FieldValidators::new()
1036	///     .with_validator(Box::new(EmailValidator::new()));
1037	/// model_validators.add_field_validator("email".to_string(), email_validators);
1038	///
1039	/// assert!(model_validators.validate("email", "test@example.com").is_ok());
1040	/// assert!(model_validators.validate("email", "invalid").is_err());
1041	/// ```
1042	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	/// Validate all fields in a data map and return all validation errors
1049	///
1050	/// # Examples
1051	///
1052	/// ```
1053	/// use std::collections::HashMap;
1054	/// use reinhardt_db::orm::validators::{ModelValidators, FieldValidators, MinLengthValidator, EmailValidator};
1055	///
1056	/// let mut model_validators = ModelValidators::new();
1057	///
1058	/// let username_validators = FieldValidators::new()
1059	///     .with_validator(Box::new(MinLengthValidator::new(3)));
1060	/// let email_validators = FieldValidators::new()
1061	///     .with_validator(Box::new(EmailValidator::new()));
1062	///
1063	/// model_validators.add_field_validator("username".to_string(), username_validators);
1064	/// model_validators.add_field_validator("email".to_string(), email_validators);
1065	///
1066	/// let mut data = HashMap::new();
1067	/// data.insert("username".to_string(), "ab".to_string());
1068	/// data.insert("email".to_string(), "invalid".to_string());
1069	///
1070	/// let errors = model_validators.validate_all(&data);
1071	/// assert_eq!(errors.len(), 2);
1072	/// ```
1073	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		// Valid emails - standard format
1130		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		// Valid emails - special characters allowed in local part
1136		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		// Valid emails - quoted strings
1153		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		// Valid emails - IP addresses
1158		assert!(validator.validate("user@[192.168.1.1]").is_ok());
1159		assert!(validator.validate("user@[127.0.0.1]").is_ok());
1160
1161		// Valid emails - IPv6 (basic)
1162		assert!(validator.validate("user@[IPv6:2001:db8::1]").is_ok());
1163
1164		// Invalid emails - basic format errors
1165		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		// Invalid emails - dot-atom errors
1172		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		// Invalid emails - domain errors
1177		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()); // Must have at least 2 labels
1183
1184		// Invalid emails - length errors
1185		let long_local = "a".repeat(65);
1186		assert!(
1187			validator
1188				.validate(&format!("{}@example.com", long_local))
1189				.is_err()
1190		);
1191
1192		// Invalid emails - quoted string errors
1193		assert!(validator.validate(r#""user"name"@example.com"#).is_err()); // Unescaped quote
1194		assert!(validator.validate(r#""user\"@example.com"#).is_err()); // Unclosed quote
1195	}
1196
1197	#[test]
1198	fn test_url_validator() {
1199		let validator = URLValidator::new();
1200		// Valid URLs
1201		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		// Invalid URLs
1207		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		// This should panic at construction time
1236		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}