use regex::Regex;
use std::sync::LazyLock;
macro_rules! static_regex {
($name:ident, $pattern:expr) => {
static $name: LazyLock<Regex> = LazyLock::new(|| Regex::new($pattern).unwrap());
};
}
static_regex!(
EMAIL_RE,
r#"(?i)^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$"#
);
static_regex!(LANG_RE, r#"^[a-z]{2}(-[A-Z]{2})?$"#);
static_regex!(
URL_RE,
r#"^(https?://)([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)+)(:[0-9]{1,5})?(/[^\s?#]*)?(\?[^\s#]*)?(\#[^\s]*)?$"#
);
static_regex!(NAME_RE, r#"^[\p{L}\p{M}\p{N} ._''\-]{2,128}$"#);
pub fn is_valid_password(
password: &str,
min_length: usize,
require_upper: bool,
require_lower: bool,
require_digit: bool,
require_special: bool,
) -> bool {
if password.len() < min_length {
return false;
}
if require_upper && !password.chars().any(|c| c.is_ascii_uppercase()) {
return false;
}
if require_lower && !password.chars().any(|c| c.is_ascii_lowercase()) {
return false;
}
if require_digit && !password.chars().any(|c| c.is_ascii_digit()) {
return false;
}
if require_special && !password.chars().any(|c| !c.is_alphanumeric()) {
return false;
}
true
}
pub fn is_valid_email(email: &str) -> bool {
if email.len() > 254 {
return false;
}
EMAIL_RE.is_match(email)
}
pub fn is_valid_language_code(code: &str) -> bool {
if code.len() != 2 && code.len() != 5 {
return false;
}
LANG_RE.is_match(code)
}
pub fn is_valid_country_code(code: &str) -> bool {
let len = code.len();
(len == 2 || len == 3) && code.chars().all(|c| c.is_ascii_uppercase())
}
pub fn is_valid_url_format(url: &str) -> bool {
if url.len() > 2048 {
return false;
}
URL_RE.is_match(url.trim())
}
pub fn is_valid_name(name: &str) -> bool {
NAME_RE.is_match(name)
}
pub fn is_valid_otp_code(code: &str, length: u8) -> bool {
code.len() == length as usize && code.chars().all(|c| c.is_ascii_digit())
}
pub const MAX_PASSWORD_BYTES: usize = 1024;
pub const MAX_OTP_CODE_BYTES: usize = 64;
pub const MAX_IDENTIFIER_BYTES: usize = 256;
pub const MAX_DISPLAY_NAME_BYTES: usize = 256;
pub const MAX_OAUTH_PARAM_BYTES: usize = 4096;
pub fn is_printable(s: &str) -> bool {
s.chars()
.all(|c| c == ' ' || (!c.is_control() && c != '\u{FEFF}'))
}
pub fn compare_secrets(a: &[u8], b: &[u8]) -> bool {
use subtle::ConstantTimeEq;
a.ct_eq(b).into()
}
#[cfg(test)]
mod validation_tests;