#[cfg(feature = "regex")]
use regex::Regex;
#[cfg(feature = "regex")]
pub fn matches_regex(value: &str, pattern: &str) -> bool {
Regex::new(pattern)
.map(|re| re.is_match(value))
.unwrap_or(false)
}
#[cfg(feature = "regex")]
pub fn not_matches_regex(value: &str, pattern: &str) -> bool {
!matches_regex(value, pattern)
}
#[cfg(feature = "email")]
pub fn is_email(value: &str) -> bool {
email_address::EmailAddress::is_valid(value)
}
#[cfg(feature = "url-parse")]
pub fn is_url(value: &str) -> bool {
match url::Url::parse(value) {
Ok(u) => u.scheme() == "http" || u.scheme() == "https",
Err(_) => false,
}
}
#[cfg(feature = "uuid-parse")]
pub fn is_uuid(value: &str) -> bool {
uuid::Uuid::parse_str(value).is_ok()
}
#[cfg(feature = "ulid-parse")]
pub fn is_ulid(value: &str) -> bool {
value.parse::<ulid::Ulid>().is_ok()
}
#[cfg(feature = "chrono")]
pub fn is_iso_date(value: &str) -> bool {
chrono::NaiveDate::parse_from_str(value, "%Y-%m-%d").is_ok()
}
#[cfg(feature = "chrono")]
pub fn is_iso_datetime(value: &str) -> bool {
if chrono::DateTime::parse_from_rfc3339(value).is_ok() {
return true;
}
chrono::NaiveDateTime::parse_from_str(value, "%Y-%m-%dT%H:%M:%S").is_ok()
|| chrono::NaiveDateTime::parse_from_str(value, "%Y-%m-%dT%H:%M:%S%.f").is_ok()
}
#[cfg(feature = "chrono")]
fn compare_dates(a: &str, b: &str) -> Option<std::cmp::Ordering> {
let a_str = a.split('T').next().unwrap_or(a);
let b_str = b.split('T').next().unwrap_or(b);
let a_date = chrono::NaiveDate::parse_from_str(a_str, "%Y-%m-%d").ok()?;
let b_date = chrono::NaiveDate::parse_from_str(b_str, "%Y-%m-%d").ok()?;
Some(a_date.cmp(&b_date))
}
#[cfg(feature = "chrono")]
pub fn is_before(value: &str, other: &str) -> bool {
compare_dates(value, other) == Some(std::cmp::Ordering::Less)
}
#[cfg(feature = "chrono")]
pub fn is_after(value: &str, other: &str) -> bool {
compare_dates(value, other) == Some(std::cmp::Ordering::Greater)
}
#[cfg(feature = "chrono")]
pub fn is_before_or_equal(value: &str, other: &str) -> bool {
matches!(
compare_dates(value, other),
Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
)
}
#[cfg(feature = "chrono")]
pub fn is_after_or_equal(value: &str, other: &str) -> bool {
matches!(
compare_dates(value, other),
Some(std::cmp::Ordering::Greater | std::cmp::Ordering::Equal)
)
}
#[cfg(feature = "chrono")]
pub fn is_date_equals(value: &str, other: &str) -> bool {
compare_dates(value, other) == Some(std::cmp::Ordering::Equal)
}
pub fn is_ip(value: &str) -> bool {
value.parse::<std::net::IpAddr>().is_ok()
}
pub fn is_ipv4(value: &str) -> bool {
value.parse::<std::net::Ipv4Addr>().is_ok()
}
pub fn is_ipv6(value: &str) -> bool {
value.parse::<std::net::Ipv6Addr>().is_ok()
}
pub fn is_mac_address(value: &str) -> bool {
let parts: Vec<&str> = if value.contains(':') {
value.split(':').collect()
} else if value.contains('-') {
value.split('-').collect()
} else {
return false;
};
parts.len() == 6
&& parts
.iter()
.all(|p| p.len() == 2 && p.chars().all(|c| c.is_ascii_hexdigit()))
}
#[cfg(feature = "serde_json")]
pub fn is_json(value: &str) -> bool {
serde_json::from_str::<serde_json::Value>(value).is_ok()
}
pub fn is_ascii(value: &str) -> bool {
value.is_ascii()
}
pub fn is_hex_color(value: &str) -> bool {
if !value.starts_with('#') {
return false;
}
let hex = &value[1..];
matches!(hex.len(), 3 | 6 | 8) && hex.chars().all(|c| c.is_ascii_hexdigit())
}
#[cfg(feature = "timezone")]
pub fn is_timezone(value: &str) -> bool {
value.parse::<chrono_tz::Tz>().is_ok()
}