karbon_framework/validation/constraints/string/
email.rs1use crate::validation::constraints::{Constraint, ConstraintResult, ConstraintViolation};
2
3pub struct Email {
7 pub message: String,
8}
9
10impl Default for Email {
11 fn default() -> Self {
12 Self {
13 message: "This value is not a valid email address.".to_string(),
14 }
15 }
16}
17
18impl Email {
19 pub fn new() -> Self {
20 Self::default()
21 }
22
23 pub fn with_message(mut self, message: impl Into<String>) -> Self {
24 self.message = message.into();
25 self
26 }
27
28 fn is_valid_email(value: &str) -> bool {
29 if value.is_empty() {
30 return false;
31 }
32
33 let parts: Vec<&str> = value.splitn(2, '@').collect();
34 if parts.len() != 2 {
35 return false;
36 }
37
38 let local = parts[0];
39 let domain = parts[1];
40
41 if local.is_empty() || local.len() > 64 {
43 return false;
44 }
45
46 if domain.is_empty() || domain.len() > 255 {
48 return false;
49 }
50
51 if !domain.contains('.') {
53 return false;
54 }
55
56 for part in domain.split('.') {
58 if part.is_empty() || part.len() > 63 {
59 return false;
60 }
61 if part.starts_with('-') || part.ends_with('-') {
62 return false;
63 }
64 if !part.chars().all(|c| c.is_alphanumeric() || c == '-') {
65 return false;
66 }
67 }
68
69 let valid_local = local.chars().all(|c| {
71 c.is_alphanumeric() || ".!#$%&'*+/=?^_`{|}~-".contains(c)
72 });
73
74 if !valid_local {
75 return false;
76 }
77
78 if local.contains("..") {
80 return false;
81 }
82
83 true
84 }
85}
86
87impl Constraint for Email {
88 fn validate(&self, value: &str) -> ConstraintResult {
89 if !Self::is_valid_email(value) {
90 return Err(ConstraintViolation::new(
91 self.name(),
92 &self.message,
93 value,
94 ));
95 }
96 Ok(())
97 }
98
99 fn name(&self) -> &'static str {
100 "Email"
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn test_valid_emails() {
110 let constraint = Email::new();
111 assert!(constraint.validate("user@example.com").is_ok());
112 assert!(constraint.validate("user.name@example.com").is_ok());
113 assert!(constraint.validate("user+tag@example.com").is_ok());
114 assert!(constraint.validate("user@sub.domain.com").is_ok());
115 assert!(constraint.validate("u@example.co").is_ok());
116 }
117
118 #[test]
119 fn test_invalid_emails() {
120 let constraint = Email::new();
121 assert!(constraint.validate("").is_err());
122 assert!(constraint.validate("not-an-email").is_err());
123 assert!(constraint.validate("@example.com").is_err());
124 assert!(constraint.validate("user@").is_err());
125 assert!(constraint.validate("user@.com").is_err());
126 assert!(constraint.validate("user@domain").is_err());
127 assert!(constraint.validate("user..name@example.com").is_err());
128 assert!(constraint.validate("user@-domain.com").is_err());
129 }
130
131 #[test]
132 fn test_custom_message() {
133 let constraint = Email::new().with_message("Email invalide.");
134 let err = constraint.validate("invalid").unwrap_err();
135 assert_eq!(err.message, "Email invalide.");
136 }
137}