1use crate::v2::context::ValidationContext;
4use crate::v2::error::{RuleError, ValidationErrors};
5use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7use std::fmt::Debug;
8
9pub trait Validate {
40 fn validate(&self) -> Result<(), ValidationErrors> {
42 self.validate_with_group(crate::v2::group::ValidationGroup::Default)
43 }
44
45 fn validate_with_group(
47 &self,
48 group: crate::v2::group::ValidationGroup,
49 ) -> Result<(), ValidationErrors>;
50
51 fn validated(self) -> Result<Self, ValidationErrors>
53 where
54 Self: Sized,
55 {
56 self.validate()?;
57 Ok(self)
58 }
59
60 fn validated_with_group(
62 self,
63 group: crate::v2::group::ValidationGroup,
64 ) -> Result<Self, ValidationErrors>
65 where
66 Self: Sized,
67 {
68 self.validate_with_group(group)?;
69 Ok(self)
70 }
71}
72
73#[async_trait]
104pub trait AsyncValidate: Validate + Send + Sync {
105 async fn validate_async(&self, ctx: &ValidationContext) -> Result<(), ValidationErrors> {
107 self.validate_async_with_group(ctx, crate::v2::group::ValidationGroup::Default)
108 .await
109 }
110
111 async fn validate_async_with_group(
113 &self,
114 ctx: &ValidationContext,
115 group: crate::v2::group::ValidationGroup,
116 ) -> Result<(), ValidationErrors>;
117
118 async fn validate_full(&self, ctx: &ValidationContext) -> Result<(), ValidationErrors> {
120 self.validate_full_with_group(ctx, crate::v2::group::ValidationGroup::Default)
121 .await
122 }
123
124 async fn validate_full_with_group(
126 &self,
127 ctx: &ValidationContext,
128 group: crate::v2::group::ValidationGroup,
129 ) -> Result<(), ValidationErrors> {
130 self.validate_with_group(group.clone())?;
132 self.validate_async_with_group(ctx, group).await
134 }
135
136 async fn validated_async(self, ctx: &ValidationContext) -> Result<Self, ValidationErrors>
138 where
139 Self: Sized,
140 {
141 self.validate_full(ctx).await?;
142 Ok(self)
143 }
144
145 async fn validated_async_with_group(
147 self,
148 ctx: &ValidationContext,
149 group: crate::v2::group::ValidationGroup,
150 ) -> Result<Self, ValidationErrors>
151 where
152 Self: Sized,
153 {
154 self.validate_full_with_group(ctx, group).await?;
155 Ok(self)
156 }
157}
158
159pub trait ValidationRule<T: ?Sized>: Debug + Send + Sync {
186 fn validate(&self, value: &T) -> Result<(), RuleError>;
188
189 fn rule_name(&self) -> &'static str;
191
192 fn default_message(&self) -> String {
194 format!("Validation failed for rule '{}'", self.rule_name())
195 }
196}
197
198#[async_trait]
202pub trait AsyncValidationRule<T: ?Sized + Sync>: Debug + Send + Sync {
203 async fn validate_async(&self, value: &T, ctx: &ValidationContext) -> Result<(), RuleError>;
205
206 fn rule_name(&self) -> &'static str;
208
209 fn default_message(&self) -> String {
211 format!("Async validation failed for rule '{}'", self.rule_name())
212 }
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
220#[serde(tag = "type", rename_all = "snake_case")]
221pub enum SerializableRule {
222 Email {
224 #[serde(skip_serializing_if = "Option::is_none")]
225 message: Option<String>,
226 },
227 Length {
229 #[serde(skip_serializing_if = "Option::is_none")]
230 min: Option<usize>,
231 #[serde(skip_serializing_if = "Option::is_none")]
232 max: Option<usize>,
233 #[serde(skip_serializing_if = "Option::is_none")]
234 message: Option<String>,
235 },
236 Range {
238 #[serde(skip_serializing_if = "Option::is_none")]
239 min: Option<f64>,
240 #[serde(skip_serializing_if = "Option::is_none")]
241 max: Option<f64>,
242 #[serde(skip_serializing_if = "Option::is_none")]
243 message: Option<String>,
244 },
245 Regex {
247 pattern: String,
248 #[serde(skip_serializing_if = "Option::is_none")]
249 message: Option<String>,
250 },
251 Url {
253 #[serde(skip_serializing_if = "Option::is_none")]
254 message: Option<String>,
255 },
256 Required {
258 #[serde(skip_serializing_if = "Option::is_none")]
259 message: Option<String>,
260 },
261 AsyncUnique {
263 table: String,
264 column: String,
265 #[serde(skip_serializing_if = "Option::is_none")]
266 message: Option<String>,
267 },
268 AsyncExists {
270 table: String,
271 column: String,
272 #[serde(skip_serializing_if = "Option::is_none")]
273 message: Option<String>,
274 },
275 AsyncApi {
277 endpoint: String,
278 #[serde(skip_serializing_if = "Option::is_none")]
279 message: Option<String>,
280 },
281 CreditCard {
283 #[serde(skip_serializing_if = "Option::is_none")]
284 message: Option<String>,
285 },
286 Ip {
288 #[serde(skip_serializing_if = "Option::is_none")]
289 v4: Option<bool>,
290 #[serde(skip_serializing_if = "Option::is_none")]
291 v6: Option<bool>,
292 #[serde(skip_serializing_if = "Option::is_none")]
293 message: Option<String>,
294 },
295 Phone {
297 #[serde(skip_serializing_if = "Option::is_none")]
298 message: Option<String>,
299 },
300 Contains {
302 needle: String,
303 #[serde(skip_serializing_if = "Option::is_none")]
304 message: Option<String>,
305 },
306 CustomAsync {
308 function: String,
309 #[serde(skip_serializing_if = "Option::is_none")]
310 message: Option<String>,
311 },
312}
313
314impl SerializableRule {
315 pub fn pretty_print(&self) -> String {
317 match self {
318 SerializableRule::Email { message } => {
319 let msg = message
320 .as_ref()
321 .map(|m| format!(", message = \"{}\"", m))
322 .unwrap_or_default();
323 format!("#[validate(email{})]", msg)
324 }
325 SerializableRule::Length { min, max, message } => {
326 let mut parts = Vec::new();
327 if let Some(min) = min {
328 parts.push(format!("min = {}", min));
329 }
330 if let Some(max) = max {
331 parts.push(format!("max = {}", max));
332 }
333 if let Some(msg) = message {
334 parts.push(format!("message = \"{}\"", msg));
335 }
336 format!("#[validate(length({}))]", parts.join(", "))
337 }
338 SerializableRule::Range { min, max, message } => {
339 let mut parts = Vec::new();
340 if let Some(min) = min {
341 parts.push(format!("min = {}", min));
342 }
343 if let Some(max) = max {
344 parts.push(format!("max = {}", max));
345 }
346 if let Some(msg) = message {
347 parts.push(format!("message = \"{}\"", msg));
348 }
349 format!("#[validate(range({}))]", parts.join(", "))
350 }
351 SerializableRule::Regex { pattern, message } => {
352 let msg = message
353 .as_ref()
354 .map(|m| format!(", message = \"{}\"", m))
355 .unwrap_or_default();
356 format!("#[validate(regex = \"{}\"{})]", pattern, msg)
357 }
358 SerializableRule::Url { message } => {
359 let msg = message
360 .as_ref()
361 .map(|m| format!(", message = \"{}\"", m))
362 .unwrap_or_default();
363 format!("#[validate(url{})]", msg)
364 }
365 SerializableRule::Required { message } => {
366 let msg = message
367 .as_ref()
368 .map(|m| format!(", message = \"{}\"", m))
369 .unwrap_or_default();
370 format!("#[validate(required{})]", msg)
371 }
372 SerializableRule::AsyncUnique {
373 table,
374 column,
375 message,
376 } => {
377 let msg = message
378 .as_ref()
379 .map(|m| format!(", message = \"{}\"", m))
380 .unwrap_or_default();
381 format!(
382 "#[validate(async_unique(table = \"{}\", column = \"{}\"{}))]",
383 table, column, msg
384 )
385 }
386 SerializableRule::AsyncExists {
387 table,
388 column,
389 message,
390 } => {
391 let msg = message
392 .as_ref()
393 .map(|m| format!(", message = \"{}\"", m))
394 .unwrap_or_default();
395 format!(
396 "#[validate(async_exists(table = \"{}\", column = \"{}\"{}))]",
397 table, column, msg
398 )
399 }
400 SerializableRule::AsyncApi { endpoint, message } => {
401 let msg = message
402 .as_ref()
403 .map(|m| format!(", message = \"{}\"", m))
404 .unwrap_or_default();
405 format!("#[validate(async_api(endpoint = \"{}\"{}))]", endpoint, msg)
406 }
407 SerializableRule::CreditCard { message } => {
408 let msg = message
409 .as_ref()
410 .map(|m| format!(", message = \"{}\"", m))
411 .unwrap_or_default();
412 format!("#[validate(credit_card{})]", msg)
413 }
414 SerializableRule::Ip { v4, v6, message } => {
415 let mut parts = Vec::new();
416 if let Some(true) = v4 {
417 parts.push("v4".to_string());
418 }
419 if let Some(true) = v6 {
420 parts.push("v6".to_string());
421 }
422 if let Some(msg) = message {
423 parts.push(format!("message = \"{}\"", msg));
424 }
425 if parts.is_empty() {
426 "#[validate(ip)]".to_string()
427 } else {
428 format!("#[validate(ip({}))]", parts.join(", "))
429 }
430 }
431 SerializableRule::Phone { message } => {
432 let msg = message
433 .as_ref()
434 .map(|m| format!(", message = \"{}\"", m))
435 .unwrap_or_default();
436 format!("#[validate(phone{})]", msg)
437 }
438 SerializableRule::Contains { needle, message } => {
439 let msg = message
440 .as_ref()
441 .map(|m| format!(", message = \"{}\"", m))
442 .unwrap_or_default();
443 format!("#[validate(contains(needle = \"{}\"{}))]", needle, msg)
444 }
445 SerializableRule::CustomAsync { function, message } => {
446 let msg = message
447 .as_ref()
448 .map(|m| format!(", message = \"{}\"", m))
449 .unwrap_or_default();
450 format!("#[validate(custom_async = \"{}\"{})]", function, msg)
451 }
452 }
453 }
454
455 pub fn parse(s: &str) -> Option<Self> {
460 let s = s.trim();
461
462 if !s.starts_with("#[validate(") || !s.ends_with(")]") {
464 return None;
465 }
466
467 let inner = &s[11..s.len() - 2];
469
470 if inner == "email" || inner.starts_with("email,") {
472 let message = Self::extract_message(inner);
473 return Some(SerializableRule::Email { message });
474 }
475
476 if inner == "url" || inner.starts_with("url,") {
477 let message = Self::extract_message(inner);
478 return Some(SerializableRule::Url { message });
479 }
480
481 if inner == "required" || inner.starts_with("required,") {
482 let message = Self::extract_message(inner);
483 return Some(SerializableRule::Required { message });
484 }
485
486 if inner.starts_with("length(") {
487 return Self::parse_length(inner);
488 }
489
490 if inner.starts_with("range(") {
491 return Self::parse_range(inner);
492 }
493
494 if inner.starts_with("regex") {
495 return Self::parse_regex(inner);
496 }
497
498 if inner.starts_with("async_unique(") {
499 return Self::parse_async_unique(inner);
500 }
501
502 if inner.starts_with("async_exists(") {
503 return Self::parse_async_exists(inner);
504 }
505
506 if inner.starts_with("async_api(") {
507 return Self::parse_async_api(inner);
508 }
509
510 if inner == "credit_card" || inner.starts_with("credit_card,") {
511 let message = Self::extract_message(inner);
512 return Some(SerializableRule::CreditCard { message });
513 }
514
515 if inner == "ip" {
516 return Some(SerializableRule::Ip {
517 v4: None,
518 v6: None,
519 message: None,
520 });
521 }
522
523 if inner.starts_with("ip(") {
524 return Self::parse_ip(inner);
525 }
526
527 if inner == "phone" || inner.starts_with("phone,") {
528 let message = Self::extract_message(inner);
529 return Some(SerializableRule::Phone { message });
530 }
531
532 if inner.starts_with("contains(") {
533 return Self::parse_contains(inner);
534 }
535
536 if inner.starts_with("custom_async") {
537 return Self::parse_custom_async(inner);
538 }
539
540 None
541 }
542
543 fn extract_message(s: &str) -> Option<String> {
544 if let Some(idx) = s.find("message = \"") {
545 let start = idx + 11;
546 if let Some(end) = s[start..].find('"') {
547 return Some(s[start..start + end].to_string());
548 }
549 }
550 None
551 }
552
553 fn extract_param(s: &str, param: &str) -> Option<String> {
554 let pattern = format!("{} = ", param);
555 if let Some(idx) = s.find(&pattern) {
556 let start = idx + pattern.len();
557 let rest = &s[start..];
558
559 if let Some(stripped) = rest.strip_prefix('"') {
561 if let Some(end) = stripped.find('"') {
562 return Some(stripped[..end].to_string());
563 }
564 } else {
565 let end = rest.find([',', ')']).unwrap_or(rest.len());
567 return Some(rest[..end].trim().to_string());
568 }
569 }
570 None
571 }
572
573 fn parse_length(s: &str) -> Option<Self> {
574 let min = Self::extract_param(s, "min").and_then(|v| v.parse().ok());
575 let max = Self::extract_param(s, "max").and_then(|v| v.parse().ok());
576 let message = Self::extract_message(s);
577 Some(SerializableRule::Length { min, max, message })
578 }
579
580 fn parse_range(s: &str) -> Option<Self> {
581 let min = Self::extract_param(s, "min").and_then(|v| v.parse().ok());
582 let max = Self::extract_param(s, "max").and_then(|v| v.parse().ok());
583 let message = Self::extract_message(s);
584 Some(SerializableRule::Range { min, max, message })
585 }
586
587 fn parse_regex(s: &str) -> Option<Self> {
588 let pattern =
589 Self::extract_param(s, "regex").or_else(|| Self::extract_param(s, "pattern"))?;
590 let message = Self::extract_message(s);
591 Some(SerializableRule::Regex { pattern, message })
592 }
593
594 fn parse_async_unique(s: &str) -> Option<Self> {
595 let table = Self::extract_param(s, "table")?;
596 let column = Self::extract_param(s, "column")?;
597 let message = Self::extract_message(s);
598 Some(SerializableRule::AsyncUnique {
599 table,
600 column,
601 message,
602 })
603 }
604
605 fn parse_async_exists(s: &str) -> Option<Self> {
606 let table = Self::extract_param(s, "table")?;
607 let column = Self::extract_param(s, "column")?;
608 let message = Self::extract_message(s);
609 Some(SerializableRule::AsyncExists {
610 table,
611 column,
612 message,
613 })
614 }
615
616 fn parse_async_api(s: &str) -> Option<Self> {
617 let endpoint = Self::extract_param(s, "endpoint")?;
618 let message = Self::extract_message(s);
619 Some(SerializableRule::AsyncApi { endpoint, message })
620 }
621
622 fn parse_ip(s: &str) -> Option<Self> {
623 let v4 = if s.contains("v4") { Some(true) } else { None };
624 let v6 = if s.contains("v6") { Some(true) } else { None };
625 let message = Self::extract_message(s);
626 Some(SerializableRule::Ip { v4, v6, message })
627 }
628
629 fn parse_contains(s: &str) -> Option<Self> {
630 let needle = Self::extract_param(s, "needle")?;
631 let message = Self::extract_message(s);
632 Some(SerializableRule::Contains { needle, message })
633 }
634
635 fn parse_custom_async(s: &str) -> Option<Self> {
636 let function = Self::extract_param(s, "custom_async")
638 .or_else(|| Self::extract_param(s, "function"))?;
639 let message = Self::extract_message(s);
640 Some(SerializableRule::CustomAsync { function, message })
641 }
642}
643
644use crate::v2::rules::{
646 AsyncApiRule, AsyncExistsRule, AsyncUniqueRule, ContainsRule, CreditCardRule, EmailRule,
647 IpRule, LengthRule, PhoneRule, RegexRule, RequiredRule, UrlRule,
648};
649
650impl From<EmailRule> for SerializableRule {
651 fn from(rule: EmailRule) -> Self {
652 SerializableRule::Email {
653 message: rule.message,
654 }
655 }
656}
657
658impl From<LengthRule> for SerializableRule {
659 fn from(rule: LengthRule) -> Self {
660 SerializableRule::Length {
661 min: rule.min,
662 max: rule.max,
663 message: rule.message,
664 }
665 }
666}
667
668impl From<RegexRule> for SerializableRule {
669 fn from(rule: RegexRule) -> Self {
670 SerializableRule::Regex {
671 pattern: rule.pattern,
672 message: rule.message,
673 }
674 }
675}
676
677impl From<UrlRule> for SerializableRule {
678 fn from(rule: UrlRule) -> Self {
679 SerializableRule::Url {
680 message: rule.message,
681 }
682 }
683}
684
685impl From<RequiredRule> for SerializableRule {
686 fn from(rule: RequiredRule) -> Self {
687 SerializableRule::Required {
688 message: rule.message,
689 }
690 }
691}
692
693impl From<AsyncUniqueRule> for SerializableRule {
694 fn from(rule: AsyncUniqueRule) -> Self {
695 SerializableRule::AsyncUnique {
696 table: rule.table,
697 column: rule.column,
698 message: rule.message,
699 }
700 }
701}
702
703impl From<AsyncExistsRule> for SerializableRule {
704 fn from(rule: AsyncExistsRule) -> Self {
705 SerializableRule::AsyncExists {
706 table: rule.table,
707 column: rule.column,
708 message: rule.message,
709 }
710 }
711}
712
713impl From<AsyncApiRule> for SerializableRule {
714 fn from(rule: AsyncApiRule) -> Self {
715 SerializableRule::AsyncApi {
716 endpoint: rule.endpoint,
717 message: rule.message,
718 }
719 }
720}
721
722impl From<CreditCardRule> for SerializableRule {
723 fn from(rule: CreditCardRule) -> Self {
724 SerializableRule::CreditCard {
725 message: rule.message,
726 }
727 }
728}
729
730impl From<IpRule> for SerializableRule {
731 fn from(rule: IpRule) -> Self {
732 SerializableRule::Ip {
733 v4: rule.v4,
734 v6: rule.v6,
735 message: rule.message,
736 }
737 }
738}
739
740impl From<PhoneRule> for SerializableRule {
741 fn from(rule: PhoneRule) -> Self {
742 SerializableRule::Phone {
743 message: rule.message,
744 }
745 }
746}
747
748impl From<ContainsRule> for SerializableRule {
749 fn from(rule: ContainsRule) -> Self {
750 SerializableRule::Contains {
751 needle: rule.needle,
752 message: rule.message,
753 }
754 }
755}
756
757#[cfg(test)]
758mod tests {
759 use super::*;
760
761 #[test]
762 fn serializable_rule_email_pretty_print() {
763 let rule = SerializableRule::Email { message: None };
764 assert_eq!(rule.pretty_print(), "#[validate(email)]");
765
766 let rule = SerializableRule::Email {
767 message: Some("Invalid email".to_string()),
768 };
769 assert_eq!(
770 rule.pretty_print(),
771 "#[validate(email, message = \"Invalid email\")]"
772 );
773 }
774
775 #[test]
776 fn serializable_rule_length_pretty_print() {
777 let rule = SerializableRule::Length {
778 min: Some(3),
779 max: Some(50),
780 message: None,
781 };
782 assert_eq!(
783 rule.pretty_print(),
784 "#[validate(length(min = 3, max = 50))]"
785 );
786 }
787
788 #[test]
789 fn serializable_rule_roundtrip() {
790 let rule = SerializableRule::Range {
791 min: Some(18.0),
792 max: Some(120.0),
793 message: Some("Age must be between 18 and 120".to_string()),
794 };
795
796 let json = serde_json::to_string(&rule).unwrap();
797 let parsed: SerializableRule = serde_json::from_str(&json).unwrap();
798 assert_eq!(rule, parsed);
799 }
800
801 #[test]
802 fn serializable_rule_pretty_print_roundtrip_email() {
803 let rule = SerializableRule::Email { message: None };
804 let pretty = rule.pretty_print();
805 let parsed = SerializableRule::parse(&pretty).unwrap();
806 assert_eq!(rule, parsed);
807
808 let rule = SerializableRule::Email {
809 message: Some("Invalid email".to_string()),
810 };
811 let pretty = rule.pretty_print();
812 let parsed = SerializableRule::parse(&pretty).unwrap();
813 assert_eq!(rule, parsed);
814 }
815
816 #[test]
817 fn serializable_rule_pretty_print_roundtrip_length() {
818 let rule = SerializableRule::Length {
819 min: Some(3),
820 max: Some(50),
821 message: None,
822 };
823 let pretty = rule.pretty_print();
824 let parsed = SerializableRule::parse(&pretty).unwrap();
825 assert_eq!(rule, parsed);
826 }
827
828 #[test]
829 fn serializable_rule_pretty_print_roundtrip_range() {
830 let rule = SerializableRule::Range {
831 min: Some(18.0),
832 max: Some(120.0),
833 message: None,
834 };
835 let pretty = rule.pretty_print();
836 let parsed = SerializableRule::parse(&pretty).unwrap();
837 assert_eq!(rule, parsed);
838 }
839
840 #[test]
841 fn serializable_rule_pretty_print_roundtrip_url() {
842 let rule = SerializableRule::Url { message: None };
843 let pretty = rule.pretty_print();
844 let parsed = SerializableRule::parse(&pretty).unwrap();
845 assert_eq!(rule, parsed);
846 }
847
848 #[test]
849 fn serializable_rule_pretty_print_roundtrip_required() {
850 let rule = SerializableRule::Required { message: None };
851 let pretty = rule.pretty_print();
852 let parsed = SerializableRule::parse(&pretty).unwrap();
853 assert_eq!(rule, parsed);
854 }
855
856 #[test]
857 fn serializable_rule_pretty_print_roundtrip_async_unique() {
858 let rule = SerializableRule::AsyncUnique {
859 table: "users".to_string(),
860 column: "email".to_string(),
861 message: None,
862 };
863 let pretty = rule.pretty_print();
864 let parsed = SerializableRule::parse(&pretty).unwrap();
865 assert_eq!(rule, parsed);
866 }
867
868 #[test]
869 fn serializable_rule_pretty_print_roundtrip_async_exists() {
870 let rule = SerializableRule::AsyncExists {
871 table: "categories".to_string(),
872 column: "id".to_string(),
873 message: Some("Category not found".to_string()),
874 };
875 let pretty = rule.pretty_print();
876 let parsed = SerializableRule::parse(&pretty).unwrap();
877 assert_eq!(rule, parsed);
878 }
879
880 #[test]
881 fn serializable_rule_pretty_print_roundtrip_async_api() {
882 let rule = SerializableRule::AsyncApi {
883 endpoint: "https://api.example.com/validate".to_string(),
884 message: None,
885 };
886 let pretty = rule.pretty_print();
887 let parsed = SerializableRule::parse(&pretty).unwrap();
888 assert_eq!(rule, parsed);
889 }
890
891 #[test]
892 fn from_email_rule() {
893 let rule = EmailRule::new().with_message("Invalid email");
894 let serializable: SerializableRule = rule.into();
895 assert_eq!(
896 serializable,
897 SerializableRule::Email {
898 message: Some("Invalid email".to_string())
899 }
900 );
901 }
902
903 #[test]
904 fn from_length_rule() {
905 let rule = LengthRule::new(3, 50).with_message("Invalid length");
906 let serializable: SerializableRule = rule.into();
907 assert_eq!(
908 serializable,
909 SerializableRule::Length {
910 min: Some(3),
911 max: Some(50),
912 message: Some("Invalid length".to_string())
913 }
914 );
915 }
916
917 #[test]
918 fn from_async_unique_rule() {
919 let rule = AsyncUniqueRule::new("users", "email").with_message("Email taken");
920 let serializable: SerializableRule = rule.into();
921 assert_eq!(
922 serializable,
923 SerializableRule::AsyncUnique {
924 table: "users".to_string(),
925 column: "email".to_string(),
926 message: Some("Email taken".to_string())
927 }
928 );
929 }
930}