1use crate::v2::error::RuleError;
6use crate::v2::traits::ValidationRule;
7use regex::Regex;
8use serde::{Deserialize, Serialize};
9use std::sync::OnceLock;
10
11static EMAIL_REGEX: OnceLock<Regex> = OnceLock::new();
13static URL_REGEX: OnceLock<Regex> = OnceLock::new();
14static PHONE_REGEX: OnceLock<Regex> = OnceLock::new();
15
16fn email_regex() -> &'static Regex {
17 EMAIL_REGEX.get_or_init(|| {
18 Regex::new(
20 r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
21 ).unwrap()
22 })
23}
24
25fn url_regex() -> &'static Regex {
26 URL_REGEX.get_or_init(|| Regex::new(r"^(https?|ftp)://[^\s/$.?#].[^\s]*$").unwrap())
27}
28
29fn phone_regex() -> &'static Regex {
30 PHONE_REGEX.get_or_init(|| Regex::new(r"^\+[1-9]\d{1,14}$").unwrap())
32}
33
34#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
38pub struct EmailRule {
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub message: Option<String>,
42}
43
44impl EmailRule {
45 pub fn new() -> Self {
47 Self::default()
48 }
49
50 pub fn with_message(mut self, message: impl Into<String>) -> Self {
52 self.message = Some(message.into());
53 self
54 }
55}
56
57impl ValidationRule<str> for EmailRule {
58 fn validate(&self, value: &str) -> Result<(), RuleError> {
59 if email_regex().is_match(value) {
60 Ok(())
61 } else {
62 let message = self
63 .message
64 .clone()
65 .unwrap_or_else(|| "validation.email.invalid".to_string());
66 Err(RuleError::new("email", message))
67 }
68 }
69
70 fn rule_name(&self) -> &'static str {
71 "email"
72 }
73}
74
75impl ValidationRule<String> for EmailRule {
76 fn validate(&self, value: &String) -> Result<(), RuleError> {
77 <Self as ValidationRule<str>>::validate(self, value.as_str())
78 }
79
80 fn rule_name(&self) -> &'static str {
81 "email"
82 }
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
89pub struct LengthRule {
90 #[serde(skip_serializing_if = "Option::is_none")]
92 pub min: Option<usize>,
93 #[serde(skip_serializing_if = "Option::is_none")]
95 pub max: Option<usize>,
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub message: Option<String>,
99}
100
101impl LengthRule {
102 pub fn new(min: usize, max: usize) -> Self {
104 Self {
105 min: Some(min),
106 max: Some(max),
107 message: None,
108 }
109 }
110
111 pub fn min(min: usize) -> Self {
113 Self {
114 min: Some(min),
115 max: None,
116 message: None,
117 }
118 }
119
120 pub fn max(max: usize) -> Self {
122 Self {
123 min: None,
124 max: Some(max),
125 message: None,
126 }
127 }
128
129 pub fn with_message(mut self, message: impl Into<String>) -> Self {
131 self.message = Some(message.into());
132 self
133 }
134}
135
136impl ValidationRule<str> for LengthRule {
137 fn validate(&self, value: &str) -> Result<(), RuleError> {
138 let len = value.chars().count();
139
140 if let Some(min) = self.min {
141 if len < min {
142 let message = self
143 .message
144 .clone()
145 .unwrap_or_else(|| "validation.length.min".to_string());
146 return Err(RuleError::new("length", message)
147 .param("min", min)
148 .param("max", self.max)
149 .param("actual", len));
150 }
151 }
152
153 if let Some(max) = self.max {
154 if len > max {
155 let message = self
156 .message
157 .clone()
158 .unwrap_or_else(|| "validation.length.max".to_string());
159 return Err(RuleError::new("length", message)
160 .param("min", self.min)
161 .param("max", max)
162 .param("actual", len));
163 }
164 }
165
166 Ok(())
167 }
168
169 fn rule_name(&self) -> &'static str {
170 "length"
171 }
172}
173
174impl ValidationRule<String> for LengthRule {
175 fn validate(&self, value: &String) -> Result<(), RuleError> {
176 <Self as ValidationRule<str>>::validate(self, value.as_str())
177 }
178
179 fn rule_name(&self) -> &'static str {
180 "length"
181 }
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
188pub struct RangeRule<T> {
189 #[serde(skip_serializing_if = "Option::is_none")]
191 pub min: Option<T>,
192 #[serde(skip_serializing_if = "Option::is_none")]
194 pub max: Option<T>,
195 #[serde(skip_serializing_if = "Option::is_none")]
197 pub message: Option<String>,
198}
199
200impl<T> RangeRule<T> {
201 pub fn new(min: T, max: T) -> Self {
203 Self {
204 min: Some(min),
205 max: Some(max),
206 message: None,
207 }
208 }
209
210 pub fn min(min: T) -> Self {
212 Self {
213 min: Some(min),
214 max: None,
215 message: None,
216 }
217 }
218
219 pub fn max(max: T) -> Self {
221 Self {
222 min: None,
223 max: Some(max),
224 message: None,
225 }
226 }
227
228 pub fn with_message(mut self, message: impl Into<String>) -> Self {
230 self.message = Some(message.into());
231 self
232 }
233}
234
235impl<T> ValidationRule<T> for RangeRule<T>
236where
237 T: PartialOrd + std::fmt::Display + Copy + Send + Sync + std::fmt::Debug + Serialize,
238{
239 fn validate(&self, value: &T) -> Result<(), RuleError> {
240 if let Some(ref min) = self.min {
241 if value < min {
242 let message = self
243 .message
244 .clone()
245 .unwrap_or_else(|| "validation.range.min".to_string());
246 return Err(RuleError::new("range", message)
247 .param("min", *min)
248 .param("max", self.max)
249 .param("actual", *value));
250 }
251 }
252
253 if let Some(ref max) = self.max {
254 if value > max {
255 let message = self
256 .message
257 .clone()
258 .unwrap_or_else(|| "validation.range.max".to_string());
259 return Err(RuleError::new("range", message)
260 .param("min", self.min)
261 .param("max", *max)
262 .param("actual", *value));
263 }
264 }
265
266 Ok(())
267 }
268
269 fn rule_name(&self) -> &'static str {
270 "range"
271 }
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct RegexRule {
279 pub pattern: String,
281 #[serde(skip)]
283 compiled: OnceLock<Result<Regex, String>>,
284 #[serde(skip_serializing_if = "Option::is_none")]
286 pub message: Option<String>,
287}
288
289impl PartialEq for RegexRule {
290 fn eq(&self, other: &Self) -> bool {
291 self.pattern == other.pattern && self.message == other.message
292 }
293}
294
295impl RegexRule {
296 pub fn new(pattern: impl Into<String>) -> Self {
298 Self {
299 pattern: pattern.into(),
300 compiled: OnceLock::new(),
301 message: None,
302 }
303 }
304
305 pub fn with_message(mut self, message: impl Into<String>) -> Self {
307 self.message = Some(message.into());
308 self
309 }
310
311 fn get_regex(&self) -> Result<&Regex, RuleError> {
312 let result = self.compiled.get_or_init(|| {
313 Regex::new(&self.pattern)
314 .map_err(|_| format!("Invalid regex pattern: {}", self.pattern))
315 });
316
317 match result {
318 Ok(regex) => Ok(regex),
319 Err(msg) => Err(RuleError::new("regex", msg.clone())),
320 }
321 }
322}
323
324impl ValidationRule<str> for RegexRule {
325 fn validate(&self, value: &str) -> Result<(), RuleError> {
326 let regex = self.get_regex()?;
327
328 if regex.is_match(value) {
329 Ok(())
330 } else {
331 let message = self
332 .message
333 .clone()
334 .unwrap_or_else(|| "validation.regex.mismatch".to_string());
335 Err(RuleError::new("regex", message).param("pattern", self.pattern.clone()))
336 }
337 }
338
339 fn rule_name(&self) -> &'static str {
340 "regex"
341 }
342}
343
344impl ValidationRule<String> for RegexRule {
345 fn validate(&self, value: &String) -> Result<(), RuleError> {
346 <Self as ValidationRule<str>>::validate(self, value.as_str())
347 }
348
349 fn rule_name(&self) -> &'static str {
350 "regex"
351 }
352}
353
354#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
358pub struct UrlRule {
359 #[serde(skip_serializing_if = "Option::is_none")]
361 pub message: Option<String>,
362}
363
364impl UrlRule {
365 pub fn new() -> Self {
367 Self::default()
368 }
369
370 pub fn with_message(mut self, message: impl Into<String>) -> Self {
372 self.message = Some(message.into());
373 self
374 }
375}
376
377impl ValidationRule<str> for UrlRule {
378 fn validate(&self, value: &str) -> Result<(), RuleError> {
379 if url_regex().is_match(value) {
380 Ok(())
381 } else {
382 let message = self
383 .message
384 .clone()
385 .unwrap_or_else(|| "validation.url.invalid".to_string());
386 Err(RuleError::new("url", message))
387 }
388 }
389
390 fn rule_name(&self) -> &'static str {
391 "url"
392 }
393}
394
395impl ValidationRule<String> for UrlRule {
396 fn validate(&self, value: &String) -> Result<(), RuleError> {
397 <Self as ValidationRule<str>>::validate(self, value.as_str())
398 }
399
400 fn rule_name(&self) -> &'static str {
401 "url"
402 }
403}
404
405#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
409pub struct RequiredRule {
410 #[serde(skip_serializing_if = "Option::is_none")]
412 pub message: Option<String>,
413}
414
415impl RequiredRule {
416 pub fn new() -> Self {
418 Self::default()
419 }
420
421 pub fn with_message(mut self, message: impl Into<String>) -> Self {
423 self.message = Some(message.into());
424 self
425 }
426}
427
428impl ValidationRule<str> for RequiredRule {
429 fn validate(&self, value: &str) -> Result<(), RuleError> {
430 if !value.trim().is_empty() {
431 Ok(())
432 } else {
433 let message = self
434 .message
435 .clone()
436 .unwrap_or_else(|| "validation.required.missing".to_string());
437 Err(RuleError::new("required", message))
438 }
439 }
440
441 fn rule_name(&self) -> &'static str {
442 "required"
443 }
444}
445
446impl ValidationRule<String> for RequiredRule {
447 fn validate(&self, value: &String) -> Result<(), RuleError> {
448 <Self as ValidationRule<str>>::validate(self, value.as_str())
449 }
450
451 fn rule_name(&self) -> &'static str {
452 "required"
453 }
454}
455
456impl<T> ValidationRule<Option<T>> for RequiredRule
457where
458 T: std::fmt::Debug + Send + Sync,
459{
460 fn validate(&self, value: &Option<T>) -> Result<(), RuleError> {
461 if value.is_some() {
462 Ok(())
463 } else {
464 let message = self
465 .message
466 .clone()
467 .unwrap_or_else(|| "validation.required.missing".to_string());
468 Err(RuleError::new("required", message))
469 }
470 }
471
472 fn rule_name(&self) -> &'static str {
473 "required"
474 }
475}
476
477#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
479pub struct CreditCardRule {
480 #[serde(skip_serializing_if = "Option::is_none")]
482 pub message: Option<String>,
483}
484
485impl CreditCardRule {
486 pub fn new() -> Self {
488 Self::default()
489 }
490
491 pub fn with_message(mut self, message: impl Into<String>) -> Self {
493 self.message = Some(message.into());
494 self
495 }
496}
497
498impl ValidationRule<str> for CreditCardRule {
499 fn validate(&self, value: &str) -> Result<(), RuleError> {
500 let mut sum = 0;
501 let mut double = false;
502
503 for c in value.chars().rev() {
505 if !c.is_ascii_digit() {
506 let message = self
507 .message
508 .clone()
509 .unwrap_or_else(|| "validation.credit_card.invalid_format".to_string());
510 return Err(RuleError::new("credit_card", message));
511 }
512
513 let mut digit = c.to_digit(10).unwrap();
514
515 if double {
516 digit *= 2;
517 if digit > 9 {
518 digit -= 9;
519 }
520 }
521
522 sum += digit;
523 double = !double;
524 }
525
526 if sum > 0 && sum % 10 == 0 {
527 Ok(())
528 } else {
529 let message = self
530 .message
531 .clone()
532 .unwrap_or_else(|| "validation.credit_card.invalid".to_string());
533 Err(RuleError::new("credit_card", message))
534 }
535 }
536
537 fn rule_name(&self) -> &'static str {
538 "credit_card"
539 }
540}
541
542impl ValidationRule<String> for CreditCardRule {
543 fn validate(&self, value: &String) -> Result<(), RuleError> {
544 <Self as ValidationRule<str>>::validate(self, value.as_str())
545 }
546
547 fn rule_name(&self) -> &'static str {
548 "credit_card"
549 }
550}
551
552#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
554pub struct IpRule {
555 #[serde(skip_serializing_if = "Option::is_none")]
557 pub v4: Option<bool>,
558 #[serde(skip_serializing_if = "Option::is_none")]
560 pub v6: Option<bool>,
561 #[serde(skip_serializing_if = "Option::is_none")]
563 pub message: Option<String>,
564}
565
566impl IpRule {
567 pub fn new() -> Self {
569 Self::default()
570 }
571
572 pub fn v4() -> Self {
574 Self {
575 v4: Some(true),
576 v6: None,
577 message: None,
578 }
579 }
580
581 pub fn v6() -> Self {
583 Self {
584 v4: None,
585 v6: Some(true),
586 message: None,
587 }
588 }
589
590 pub fn with_message(mut self, message: impl Into<String>) -> Self {
592 self.message = Some(message.into());
593 self
594 }
595}
596
597impl ValidationRule<str> for IpRule {
598 fn validate(&self, value: &str) -> Result<(), RuleError> {
599 use std::net::IpAddr;
600
601 match value.parse::<IpAddr>() {
602 Ok(ip) => {
603 if let Some(true) = self.v4 {
604 if !ip.is_ipv4() {
605 let message = self
606 .message
607 .clone()
608 .unwrap_or_else(|| "validation.ip.v4_required".to_string());
609 return Err(RuleError::new("ip", message));
610 }
611 }
612 if let Some(true) = self.v6 {
613 if !ip.is_ipv6() {
614 let message = self
615 .message
616 .clone()
617 .unwrap_or_else(|| "validation.ip.v6_required".to_string());
618 return Err(RuleError::new("ip", message));
619 }
620 }
621 Ok(())
622 }
623 Err(_) => {
624 let message = self
625 .message
626 .clone()
627 .unwrap_or_else(|| "validation.ip.invalid".to_string());
628 Err(RuleError::new("ip", message))
629 }
630 }
631 }
632
633 fn rule_name(&self) -> &'static str {
634 "ip"
635 }
636}
637
638impl ValidationRule<String> for IpRule {
639 fn validate(&self, value: &String) -> Result<(), RuleError> {
640 <Self as ValidationRule<str>>::validate(self, value.as_str())
641 }
642
643 fn rule_name(&self) -> &'static str {
644 "ip"
645 }
646}
647
648#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
650pub struct PhoneRule {
651 #[serde(skip_serializing_if = "Option::is_none")]
653 pub message: Option<String>,
654}
655
656impl PhoneRule {
657 pub fn new() -> Self {
659 Self::default()
660 }
661
662 pub fn with_message(mut self, message: impl Into<String>) -> Self {
664 self.message = Some(message.into());
665 self
666 }
667}
668
669impl ValidationRule<str> for PhoneRule {
670 fn validate(&self, value: &str) -> Result<(), RuleError> {
671 if phone_regex().is_match(value) {
672 Ok(())
673 } else {
674 let message = self
675 .message
676 .clone()
677 .unwrap_or_else(|| "validation.phone.invalid".to_string());
678 Err(RuleError::new("phone", message))
679 }
680 }
681
682 fn rule_name(&self) -> &'static str {
683 "phone"
684 }
685}
686
687impl ValidationRule<String> for PhoneRule {
688 fn validate(&self, value: &String) -> Result<(), RuleError> {
689 <Self as ValidationRule<str>>::validate(self, value.as_str())
690 }
691
692 fn rule_name(&self) -> &'static str {
693 "phone"
694 }
695}
696
697#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
699pub struct ContainsRule {
700 pub needle: String,
702 #[serde(skip_serializing_if = "Option::is_none")]
704 pub message: Option<String>,
705}
706
707impl ContainsRule {
708 pub fn new(needle: impl Into<String>) -> Self {
710 Self {
711 needle: needle.into(),
712 message: None,
713 }
714 }
715
716 pub fn with_message(mut self, message: impl Into<String>) -> Self {
718 self.message = Some(message.into());
719 self
720 }
721}
722
723impl ValidationRule<str> for ContainsRule {
724 fn validate(&self, value: &str) -> Result<(), RuleError> {
725 if value.contains(&self.needle) {
726 Ok(())
727 } else {
728 let message = self
729 .message
730 .clone()
731 .unwrap_or_else(|| "validation.contains.missing".to_string());
732 Err(RuleError::new("contains", message).param("needle", self.needle.clone()))
733 }
734 }
735
736 fn rule_name(&self) -> &'static str {
737 "contains"
738 }
739}
740
741impl ValidationRule<String> for ContainsRule {
742 fn validate(&self, value: &String) -> Result<(), RuleError> {
743 <Self as ValidationRule<str>>::validate(self, value.as_str())
744 }
745
746 fn rule_name(&self) -> &'static str {
747 "contains"
748 }
749}
750
751#[cfg(test)]
752mod tests {
753 use super::*;
754
755 #[test]
756 fn email_rule_valid() {
757 let rule = EmailRule::new();
758 assert!(rule.validate("test@example.com").is_ok());
759 assert!(rule.validate("user.name+tag@domain.co.uk").is_ok());
760 }
761
762 #[test]
763 fn email_rule_invalid() {
764 let rule = EmailRule::new();
765 assert!(rule.validate("invalid").is_err());
766 assert!(rule.validate("@domain.com").is_err());
767 assert!(rule.validate("user@").is_err());
768 }
769
770 #[test]
771 fn email_rule_custom_message() {
772 let rule = EmailRule::new().with_message("Please enter a valid email");
773 let err = rule.validate("invalid").unwrap_err();
774 assert_eq!(err.message, "Please enter a valid email");
775 }
776
777 #[test]
778 fn length_rule_valid() {
779 let rule = LengthRule::new(3, 10);
780 assert!(rule.validate("abc").is_ok());
781 assert!(rule.validate("abcdefghij").is_ok());
782 }
783
784 #[test]
785 fn length_rule_too_short() {
786 let rule = LengthRule::new(3, 10);
787 let err = rule.validate("ab").unwrap_err();
788 assert_eq!(err.code, "length");
789 }
790
791 #[test]
792 fn length_rule_too_long() {
793 let rule = LengthRule::new(3, 10);
794 let err = rule.validate("abcdefghijk").unwrap_err();
795 assert_eq!(err.code, "length");
796 }
797
798 #[test]
799 fn range_rule_valid() {
800 let rule = RangeRule::new(18, 120);
801 assert!(rule.validate(&18).is_ok());
802 assert!(rule.validate(&50).is_ok());
803 assert!(rule.validate(&120).is_ok());
804 }
805
806 #[test]
807 fn range_rule_too_low() {
808 let rule = RangeRule::new(18, 120);
809 let err = rule.validate(&17).unwrap_err();
810 assert_eq!(err.code, "range");
811 }
812
813 #[test]
814 fn range_rule_too_high() {
815 let rule = RangeRule::new(18, 120);
816 let err = rule.validate(&121).unwrap_err();
817 assert_eq!(err.code, "range");
818 }
819
820 #[test]
821 fn regex_rule_valid() {
822 let rule = RegexRule::new(r"^\d{3}-\d{4}$");
823 assert!(rule.validate("123-4567").is_ok());
824 }
825
826 #[test]
827 fn regex_rule_invalid() {
828 let rule = RegexRule::new(r"^\d{3}-\d{4}$");
829 assert!(rule.validate("1234567").is_err());
830 }
831
832 #[test]
833 fn url_rule_valid() {
834 let rule = UrlRule::new();
835 assert!(rule.validate("https://example.com").is_ok());
836 assert!(rule.validate("http://example.com/path?query=1").is_ok());
837 }
838
839 #[test]
840 fn url_rule_invalid() {
841 let rule = UrlRule::new();
842 assert!(rule.validate("not-a-url").is_err());
843 assert!(rule.validate("ftp://").is_err());
844 }
845
846 #[test]
847 fn required_rule_valid() {
848 let rule = RequiredRule::new();
849 assert!(rule.validate("value").is_ok());
850 assert!(rule.validate(" value ").is_ok());
851 }
852
853 #[test]
854 fn required_rule_empty() {
855 let rule = RequiredRule::new();
856 assert!(rule.validate("").is_err());
857 assert!(rule.validate(" ").is_err());
858 }
859
860 #[test]
861 fn required_rule_option() {
862 let rule = RequiredRule::new();
863 assert!(ValidationRule::<Option<i32>>::validate(&rule, &Some(42)).is_ok());
864 assert!(ValidationRule::<Option<i32>>::validate(&rule, &None).is_err());
865 }
866
867 #[test]
868 fn rule_serialization_roundtrip() {
869 let rule = LengthRule::new(3, 50).with_message("Custom message");
870 let json = serde_json::to_string(&rule).unwrap();
871 let parsed: LengthRule = serde_json::from_str(&json).unwrap();
872 assert_eq!(rule, parsed);
873 }
874}