use lazy_static::lazy_static;
use regex::Regex;
pub fn validate_email(email: &str) -> Result<(), String> {
if email.is_empty() {
return Err("Email cannot be empty".to_string());
}
if email.len() > 255 {
return Err("Email must be less than 255 characters".to_string());
}
lazy_static! {
static ref EMAIL_REGEX: Regex = Regex::new(
r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[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])?)*$"
).unwrap();
}
if !EMAIL_REGEX.is_match(email) {
return Err("Invalid email format".to_string());
}
Ok(())
}
pub fn validate_password(password: &str) -> Result<(), String> {
if password.is_empty() {
return Err("Password cannot be empty".to_string());
}
if password.len() < 8 {
return Err("Password must be at least 8 characters long".to_string());
}
if password.len() > 128 {
return Err("Password must be less than 128 characters".to_string());
}
if !password.chars().any(|c| c.is_uppercase()) {
return Err("Password must contain at least one uppercase letter".to_string());
}
if !password.chars().any(|c| c.is_lowercase()) {
return Err("Password must contain at least one lowercase letter".to_string());
}
if !password.chars().any(|c| c.is_numeric()) {
return Err("Password must contain at least one number".to_string());
}
let special_chars = "!@#$%^&*()_+-=[]{}|;:,.<>?";
if !password.chars().any(|c| special_chars.contains(c)) {
return Err(
"Password must contain at least one special character (!@#$%^&*()_+-=[]{}|;:,.<>?)"
.to_string(),
);
}
Ok(())
}
pub fn validate_name(name: &str) -> Result<(), String> {
if name.is_empty() {
return Err("Name cannot be empty".to_string());
}
if name.len() > 50 {
return Err("Name must be less than 50 characters".to_string());
}
Ok(())
}
pub fn validate_tenant_name(name: &str) -> Result<(), String> {
if name.is_empty() {
return Err("Tenant name cannot be empty".to_string());
}
if name.len() > 64 {
return Err("Tenant name must be less than 64 characters".to_string());
}
if name.contains("..") || name.contains('/') || name.contains('\\') {
return Err("Tenant name contains invalid characters".to_string());
}
lazy_static! {
static ref TENANT_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9][a-zA-Z0-9_-]*$").unwrap();
}
if !TENANT_REGEX.is_match(name) {
return Err(
"Tenant name must start with alphanumeric and contain only letters, numbers, underscores, and hyphens"
.to_string(),
);
}
Ok(())
}
pub fn validate_hostname(name: &str) -> Result<(), String> {
if name.is_empty() {
return Err("Name cannot be empty".to_string());
}
if name.len() > 255 {
return Err("Name must be less than 255 characters".to_string());
}
lazy_static! {
static ref NAME_REGEX: Regex =
Regex::new(r"^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$").unwrap();
}
if name.len() == 1 && name.chars().next().map(|c| c.is_alphanumeric()).unwrap_or(false) {
return Ok(());
}
if !NAME_REGEX.is_match(name) {
return Err("Name must start and end with alphanumeric characters and contain only letters, numbers, and hyphens".to_string());
}
Ok(())
}
pub fn validate_required_string(value: &str, field_name: &str, max_length: usize) -> Result<(), String> {
if value.is_empty() {
return Err(format!("{} cannot be empty", field_name));
}
if value.len() > max_length {
return Err(format!("{} must be less than {} characters", field_name, max_length));
}
Ok(())
}
pub fn validate_optional_string(value: Option<&str>, field_name: &str, max_length: usize) -> Result<(), String> {
if let Some(v) = value {
if v.len() > max_length {
return Err(format!("{} must be less than {} characters", field_name, max_length));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_email_valid() {
assert!(validate_email("user@example.com").is_ok());
assert!(validate_email("user.name@example.co.uk").is_ok());
assert!(validate_email("user+tag@example.com").is_ok());
}
#[test]
fn test_validate_email_invalid() {
assert!(validate_email("").is_err());
assert!(validate_email("invalid").is_err());
assert!(validate_email("@example.com").is_err());
assert!(validate_email("user@").is_err());
}
#[test]
fn test_validate_password_valid() {
assert!(validate_password("SecurePass123!").is_ok());
assert!(validate_password("MyP@ssw0rd").is_ok());
}
#[test]
fn test_validate_password_invalid() {
assert!(validate_password("").is_err()); assert!(validate_password("short").is_err()); assert!(validate_password("nouppercase123!").is_err()); assert!(validate_password("NOLOWERCASE123!").is_err()); assert!(validate_password("NoNumbers!").is_err()); assert!(validate_password("NoSpecial123").is_err()); }
#[test]
fn test_validate_tenant_name_valid() {
assert!(validate_tenant_name("tenant").is_ok());
assert!(validate_tenant_name("my_tenant").is_ok());
assert!(validate_tenant_name("tenant-123").is_ok());
assert!(validate_tenant_name("Tenant1").is_ok());
}
#[test]
fn test_validate_tenant_name_invalid() {
assert!(validate_tenant_name("").is_err()); assert!(validate_tenant_name("_tenant").is_err()); assert!(validate_tenant_name("-tenant").is_err()); assert!(validate_tenant_name("tenant/../admin").is_err()); assert!(validate_tenant_name("tenant/admin").is_err()); assert!(validate_tenant_name("tenant; DROP TABLE").is_err()); }
#[test]
fn test_validate_hostname_valid() {
assert!(validate_hostname("a").is_ok());
assert!(validate_hostname("server-1").is_ok());
assert!(validate_hostname("web-server-01").is_ok());
}
#[test]
fn test_validate_hostname_invalid() {
assert!(validate_hostname("").is_err());
assert!(validate_hostname("-server").is_err());
assert!(validate_hostname("server-").is_err());
}
}