use regex::Regex;
use std::sync::LazyLock;
pub(crate) static EMAIL_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(
r"^(?i)[a-z0-9]([a-z0-9._%+-]*[a-z0-9])?@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*\.[a-z]{2,}$",
)
.expect("EMAIL_REGEX: Invalid regex pattern")
});
pub(crate) static URL_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(
r"^https?://[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)*(:[0-9]{1,5})?(/[^\s?#]*)?(\?[^\s#]*)?(#[^\s]*)?$",
)
.expect("URL_REGEX: Invalid regex pattern")
});
pub(crate) static PHONE_E164_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^\+([1-9]\d{0,2})[\s.\-()]*\d+[\s.\-\d()]*$")
.expect("PHONE_E164_REGEX: Invalid regex pattern")
});
pub(crate) static PHONE_EXTENSION_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^(.+?)(?:\s*(?:ext\.?|x|extension)\s*(\d+))?$")
.expect("PHONE_EXTENSION_REGEX: Invalid regex pattern")
});
pub(crate) static SLUG_ASCII_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^[-a-zA-Z0-9_]+$").expect("SLUG_ASCII_REGEX: Invalid regex pattern")
});
pub(crate) static SLUG_UNICODE_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^[-\w]+$").expect("SLUG_UNICODE_REGEX: Invalid regex pattern"));
pub(crate) static UUID_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
.expect("UUID_REGEX: Invalid regex pattern")
});
pub(crate) static COLOR_HEX_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$")
.expect("COLOR_HEX_REGEX: Invalid regex pattern")
});
pub(crate) static COLOR_RGB_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(
r"^rgb\(\s*([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\s*,\s*([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\s*,\s*([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\s*\)$",
)
.expect("COLOR_RGB_REGEX: Invalid regex pattern")
});
pub(crate) static COLOR_RGBA_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(
r"^rgba\(\s*([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\s*,\s*([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\s*,\s*([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\s*,\s*(0|1|0?\.\d+)\s*\)$",
)
.expect("COLOR_RGBA_REGEX: Invalid regex pattern")
});
pub(crate) static COLOR_HSL_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(
r"^hsl\(\s*([0-9]|[1-9][0-9]|[1-2][0-9]{2}|3[0-5][0-9]|360)\s*,\s*([0-9]|[1-9][0-9]|100)%\s*,\s*([0-9]|[1-9][0-9]|100)%\s*\)$",
)
.expect("COLOR_HSL_REGEX: Invalid regex pattern")
});
pub(crate) static COLOR_HSLA_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(
r"^hsla\(\s*([0-9]|[1-9][0-9]|[1-2][0-9]{2}|3[0-5][0-9]|360)\s*,\s*([0-9]|[1-9][0-9]|100)%\s*,\s*([0-9]|[1-9][0-9]|100)%\s*,\s*(0|1|0?\.\d+)\s*\)$",
)
.expect("COLOR_HSLA_REGEX: Invalid regex pattern")
});
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_email_regex() {
assert!(EMAIL_REGEX.is_match("test@example.com"));
assert!(EMAIL_REGEX.is_match("user.name@sub.example.co.uk"));
assert!(!EMAIL_REGEX.is_match("invalid"));
assert!(!EMAIL_REGEX.is_match("@example.com"));
}
#[test]
fn test_url_regex() {
assert!(URL_REGEX.is_match("http://example.com"));
assert!(URL_REGEX.is_match("https://example.com:8080/path?query=value#section"));
assert!(!URL_REGEX.is_match("ftp://example.com"));
assert!(!URL_REGEX.is_match("invalid"));
}
#[test]
fn test_phone_e164_regex() {
assert!(PHONE_E164_REGEX.is_match("+1234567890"));
assert!(PHONE_E164_REGEX.is_match("+81-90-1234-5678"));
assert!(!PHONE_E164_REGEX.is_match("1234567890"));
assert!(!PHONE_E164_REGEX.is_match("+0123456789"));
}
#[test]
fn test_phone_extension_regex() {
let caps = PHONE_EXTENSION_REGEX
.captures("+1234567890 ext. 123")
.unwrap();
assert_eq!(caps.get(1).map(|m| m.as_str()), Some("+1234567890"));
assert_eq!(caps.get(2).map(|m| m.as_str()), Some("123"));
}
#[test]
fn test_slug_ascii_regex() {
assert!(SLUG_ASCII_REGEX.is_match("valid-slug"));
assert!(SLUG_ASCII_REGEX.is_match("valid_slug_123"));
assert!(!SLUG_ASCII_REGEX.is_match("invalid slug"));
assert!(!SLUG_ASCII_REGEX.is_match("日本語"));
}
#[test]
fn test_slug_unicode_regex() {
assert!(SLUG_UNICODE_REGEX.is_match("valid-slug"));
assert!(SLUG_UNICODE_REGEX.is_match("日本語-slug"));
}
#[test]
fn test_uuid_regex() {
assert!(UUID_REGEX.is_match("550e8400-e29b-41d4-a716-446655440000"));
assert!(!UUID_REGEX.is_match("invalid-uuid"));
assert!(!UUID_REGEX.is_match("550E8400-E29B-41D4-A716-446655440000")); }
#[test]
fn test_color_hex_regex() {
assert!(COLOR_HEX_REGEX.is_match("#FFF"));
assert!(COLOR_HEX_REGEX.is_match("#FF0000"));
assert!(COLOR_HEX_REGEX.is_match("#FF0000FF"));
assert!(!COLOR_HEX_REGEX.is_match("FF0000"));
}
#[test]
fn test_color_rgb_regex() {
assert!(COLOR_RGB_REGEX.is_match("rgb(255, 0, 0)"));
assert!(COLOR_RGB_REGEX.is_match("rgb(0,0,0)"));
assert!(!COLOR_RGB_REGEX.is_match("rgb(256, 0, 0)"));
}
#[test]
fn test_color_rgba_regex() {
assert!(COLOR_RGBA_REGEX.is_match("rgba(255, 0, 0, 1)"));
assert!(COLOR_RGBA_REGEX.is_match("rgba(255, 0, 0, 0.5)"));
assert!(!COLOR_RGBA_REGEX.is_match("rgba(255, 0, 0)"));
}
#[test]
fn test_color_hsl_regex() {
assert!(COLOR_HSL_REGEX.is_match("hsl(0, 100%, 50%)"));
assert!(COLOR_HSL_REGEX.is_match("hsl(360, 0%, 0%)"));
assert!(!COLOR_HSL_REGEX.is_match("hsl(361, 100%, 50%)"));
}
#[test]
fn test_color_hsla_regex() {
assert!(COLOR_HSLA_REGEX.is_match("hsla(0, 100%, 50%, 1)"));
assert!(COLOR_HSLA_REGEX.is_match("hsla(0, 100%, 50%, 0.5)"));
assert!(!COLOR_HSLA_REGEX.is_match("hsla(0, 100%, 50%)"));
}
}