Skip to main content

wasm_dbms_api/dbms/validate/
email.rs

1use lazy_regex::{Lazy, Regex, lazy_regex};
2
3use crate::prelude::{Validate, Value};
4
5/// Email address pattern.
6///
7/// - The local part starts with a letter or digit and is a sequence of dot-separated atoms,
8///   so it never contains consecutive dots or ends with a dot.
9/// - Each domain label starts and ends with a letter or digit; hyphens are only allowed inside.
10/// - The top-level domain has at least two letters.
11static EMAIL_REGEX: Lazy<Regex> = lazy_regex!(
12    r"^[A-Za-z0-9][A-Za-z0-9_%+-]*(\.[A-Za-z0-9_%+-]+)*@([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?\.)+[A-Za-z]{2,}$"
13);
14
15/// A validator for email addresses.
16///
17/// This validator checks if a given text value conforms to a standard email format.
18///
19/// # Examples
20///
21/// ```rust
22/// use wasm_dbms_api::prelude::{EmailValidator, Validate, Value};
23///
24/// let validator = EmailValidator;
25/// let valid_email = Value::Text("christian.visintin@gmail.com".into());
26/// let invalid_email = Value::Text("invalid-email".into());
27///
28/// assert!(validator.validate(&valid_email).is_ok());
29/// assert!(validator.validate(&invalid_email).is_err());
30/// ```
31pub struct EmailValidator;
32
33impl Validate for EmailValidator {
34    fn validate(&self, value: &Value) -> crate::prelude::DbmsResult<()> {
35        let Value::Text(text) = value else {
36            return Err(crate::prelude::DbmsError::Validation(
37                "EmailValidator can only be applied to Text values".to_string(),
38            ));
39        };
40
41        if EMAIL_REGEX.is_match(text.as_str()) {
42            Ok(())
43        } else {
44            Err(crate::prelude::DbmsError::Validation(format!(
45                "Value '{text}' is not a valid email address",
46            )))
47        }
48    }
49}
50
51#[cfg(test)]
52mod tests {
53
54    use super::*;
55
56    #[test]
57    fn test_email_validator() {
58        let validator = EmailValidator;
59        let valid_emails = vec![
60            "christian.visintin1997@yahoo.com",
61            "nome.cognome@gmail.com",
62            "user123@outlook.com",
63            "user.name+tag@gmail.com",
64            "info@azienda.it",
65            "support@sub.domain.com",
66            "hello-world@my-site.org",
67            "a@b.co",
68            "test_email@domain.travel",
69            "user99@domain.co.uk",
70            "a.b.c@x-y.z-w.io",
71            "user_name.last-name@a1.b2.example",
72            "u@1-2.example.com",
73        ];
74        let invalid_emails = vec![
75            "",
76            "plainaddress",
77            "@gmail.com",
78            "user@",
79            "user@gmail",
80            "user@gmail.",
81            "user@.com",
82            "user@@gmail.com",
83            "user@gmail..com",
84            "user..name@gmail.com",
85            ".user@gmail.com",
86            "user.@gmail.com",
87            "user@-gmail.com",
88            "user@gmail-.com",
89            "user@sub.-domain.com",
90            "user@sub-.domain.com",
91            "user@domain.-com",
92            "user@111.222.333.444",
93            "user@[127.0.0.1]",
94            "\"user\"@gmail.com",
95            "user name@gmail.com",
96        ];
97
98        for email in valid_emails {
99            let value = Value::Text(email.into());
100            assert!(
101                validator.validate(&value).is_ok(),
102                "Expected '{}' to be valid",
103                email
104            );
105        }
106        for email in invalid_emails {
107            let value = Value::Text(email.into());
108            assert!(
109                validator.validate(&value).is_err(),
110                "Expected '{}' to be invalid",
111                email
112            );
113        }
114    }
115}