use crate::error::{AppError, Result};
use crate::types::UserPoolId;
const MIN_PASSWORD_LENGTH: usize = 6;
const MAX_PASSWORD_LENGTH: usize = 256;
const MAX_USERNAME_LENGTH: usize = 128;
pub fn validate_username(username: &str) -> Result<()> {
let trimmed = username.trim();
if trimmed.is_empty() {
return Err(AppError::InvalidParameter(
"Username cannot be empty".to_string(),
));
}
if trimmed.len() > MAX_USERNAME_LENGTH {
return Err(AppError::InvalidParameter(format!(
"Username cannot exceed {} characters",
MAX_USERNAME_LENGTH
)));
}
if trimmed.contains(' ') {
return Err(AppError::InvalidParameter(
"Username cannot contain spaces".to_string(),
));
}
Ok(())
}
pub fn validate_password(password: &str) -> Result<()> {
if password.is_empty() {
return Err(AppError::InvalidParameter(
"Password cannot be empty".to_string(),
));
}
if password.len() < MIN_PASSWORD_LENGTH {
return Err(AppError::InvalidParameter(format!(
"Password must be at least {} characters",
MIN_PASSWORD_LENGTH
)));
}
if password.len() > MAX_PASSWORD_LENGTH {
return Err(AppError::InvalidParameter(format!(
"Password cannot exceed {} characters",
MAX_PASSWORD_LENGTH
)));
}
Ok(())
}
pub fn validate_email(email: &str) -> Result<()> {
let trimmed = email.trim();
if trimmed.is_empty() {
return Err(AppError::InvalidParameter(
"Email cannot be empty".to_string(),
));
}
let parts: Vec<&str> = trimmed.split('@').collect();
if parts.len() != 2 {
return Err(AppError::InvalidParameter(
"Invalid email format".to_string(),
));
}
let (local, domain) = (parts[0], parts[1]);
if local.is_empty() || domain.is_empty() {
return Err(AppError::InvalidParameter(
"Invalid email format".to_string(),
));
}
if !domain.contains('.') {
return Err(AppError::InvalidParameter(
"Invalid email format".to_string(),
));
}
let domain_parts: Vec<&str> = domain.split('.').collect();
if domain_parts.iter().any(|p| p.is_empty()) {
return Err(AppError::InvalidParameter(
"Invalid email format".to_string(),
));
}
Ok(())
}
pub fn validate_callback_url(url: &str) -> Result<()> {
let trimmed = url.trim();
if trimmed.is_empty() {
return Err(AppError::InvalidParameter(
"Callback URL cannot be empty".to_string(),
));
}
if !trimmed.starts_with("http://") && !trimmed.starts_with("https://") {
return Err(AppError::InvalidParameter(
"Callback URL must start with http:// or https://".to_string(),
));
}
if trimmed.starts_with("http://") {
let host_part = trimmed.trim_start_matches("http://");
let host = host_part.split('/').next().unwrap_or("");
let host_without_port = host.split(':').next().unwrap_or("");
if host_without_port != "localhost" && host_without_port != "127.0.0.1" {
tracing::warn!("HTTP callback URL used for non-localhost: {}", url);
}
}
Ok(())
}
pub fn validate_pool_name(name: &str) -> Result<()> {
let trimmed = name.trim();
if trimmed.is_empty() {
return Err(AppError::InvalidParameter(
"Pool name cannot be empty".to_string(),
));
}
if trimmed.len() > 128 {
return Err(AppError::InvalidParameter(
"Pool name cannot exceed 128 characters".to_string(),
));
}
Ok(())
}
pub fn validate_client_name(name: &str) -> Result<()> {
let trimmed = name.trim();
if trimmed.is_empty() {
return Err(AppError::InvalidParameter(
"Client name cannot be empty".to_string(),
));
}
if trimmed.len() > 128 {
return Err(AppError::InvalidParameter(
"Client name cannot exceed 128 characters".to_string(),
));
}
Ok(())
}
pub fn validate_group_name(name: &str) -> Result<()> {
let trimmed = name.trim();
if trimmed.is_empty() {
return Err(AppError::InvalidParameter(
"Group name cannot be empty".to_string(),
));
}
if trimmed.len() > 128 {
return Err(AppError::InvalidParameter(
"Group name cannot exceed 128 characters".to_string(),
));
}
Ok(())
}
pub fn parse_user_pool_id(value: &str) -> Result<UserPoolId> {
UserPoolId::new(value).map_err(|e| AppError::InvalidParameter(e.to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_username_valid() {
assert!(validate_username("testuser").is_ok());
assert!(validate_username("user123").is_ok());
assert!(validate_username("user_name").is_ok());
}
#[test]
fn test_validate_username_empty() {
assert!(validate_username("").is_err());
assert!(validate_username(" ").is_err());
}
#[test]
fn test_validate_username_with_spaces() {
assert!(validate_username("user name").is_err());
}
#[test]
fn test_validate_password_valid() {
assert!(validate_password("password123").is_ok());
assert!(validate_password("123456").is_ok());
}
#[test]
fn test_validate_password_too_short() {
assert!(validate_password("12345").is_err());
assert!(validate_password("").is_err());
}
#[test]
fn test_validate_email_valid() {
assert!(validate_email("user@example.com").is_ok());
assert!(validate_email("user.name@example.co.uk").is_ok());
}
#[test]
fn test_validate_email_invalid() {
assert!(validate_email("notanemail").is_err());
assert!(validate_email("user@").is_err());
assert!(validate_email("@example.com").is_err());
assert!(validate_email("user@example").is_err());
assert!(validate_email("").is_err());
}
#[test]
fn test_validate_callback_url_valid() {
assert!(validate_callback_url("https://example.com/callback").is_ok());
assert!(validate_callback_url("http://localhost:3000/callback").is_ok());
assert!(validate_callback_url("http://127.0.0.1:3000/callback").is_ok());
}
#[test]
fn test_validate_callback_url_invalid() {
assert!(validate_callback_url("").is_err());
assert!(validate_callback_url("not-a-url").is_err());
assert!(validate_callback_url("ftp://example.com").is_err());
}
}