use rand::Rng;
const LOWER_LETTERS: &str = "qwertyuiopasdfghjklzxcvbnm";
const UPPER_LETTERS: &str = "QWERTYUIOPASDFGHJKLZXCVBNM";
const DIGITS: &str = "1234567890";
const SPECIAL_SYMBOLS: &str = "!@#$%^&*()_+-={}[];:|,.<>?~";
#[derive(Debug, Clone)]
pub struct PasswordOptions {
pub lowercase: bool,
pub uppercase: bool,
pub digits: bool,
pub special: bool,
pub length: usize,
}
impl Default for PasswordOptions {
fn default() -> Self {
Self {
lowercase: true,
uppercase: true,
digits: true,
special: false,
length: 16,
}
}
}
pub fn generate_password(options: &PasswordOptions) -> String {
let mut rng = rand::rng();
let mut char_pool = String::new();
if options.lowercase {
char_pool.push_str(LOWER_LETTERS);
char_pool.push_str(LOWER_LETTERS);
char_pool.push_str(LOWER_LETTERS);
}
if options.uppercase {
char_pool.push_str(UPPER_LETTERS);
char_pool.push_str(UPPER_LETTERS);
char_pool.push_str(UPPER_LETTERS);
}
if options.digits {
char_pool.push_str(DIGITS);
char_pool.push_str(DIGITS);
}
if options.special {
char_pool.push_str(SPECIAL_SYMBOLS);
}
if char_pool.is_empty() {
char_pool.push_str(LOWER_LETTERS);
}
let chars: Vec<char> = char_pool.chars().collect();
let mut password = String::with_capacity(options.length);
for _ in 0..options.length {
let idx = rng.random_range(0..chars.len());
password.push(chars[idx]);
}
password
}
pub fn generate_clever_password(pattern: &str) -> String {
let mut rng = rand::rng();
let all_symbols = format!("{}{}{}{}", LOWER_LETTERS, UPPER_LETTERS, DIGITS, SPECIAL_SYMBOLS);
let all_chars: Vec<char> = all_symbols.chars().collect();
let lower_chars: Vec<char> = LOWER_LETTERS.chars().collect();
let upper_chars: Vec<char> = UPPER_LETTERS.chars().collect();
let digit_chars: Vec<char> = DIGITS.chars().collect();
let special_chars: Vec<char> = SPECIAL_SYMBOLS.chars().collect();
let mut password = String::with_capacity(pattern.len());
for ch in pattern.chars() {
let generated_char = if LOWER_LETTERS.contains(ch) {
lower_chars[rng.random_range(0..lower_chars.len())]
} else if UPPER_LETTERS.contains(ch) {
upper_chars[rng.random_range(0..upper_chars.len())]
} else if DIGITS.contains(ch) {
digit_chars[rng.random_range(0..digit_chars.len())]
} else if SPECIAL_SYMBOLS.contains(ch) {
special_chars[rng.random_range(0..special_chars.len())]
} else {
all_chars[rng.random_range(0..all_chars.len())]
};
password.push(generated_char);
}
password
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_password_default() {
let options = PasswordOptions::default();
let password = generate_password(&options);
assert_eq!(password.len(), 16);
}
#[test]
fn test_generate_password_length() {
let options = PasswordOptions {
length: 32,
..Default::default()
};
let password = generate_password(&options);
assert_eq!(password.len(), 32);
}
#[test]
fn test_generate_password_lowercase_only() {
let options = PasswordOptions {
lowercase: true,
uppercase: false,
digits: false,
special: false,
length: 20,
};
let password = generate_password(&options);
assert_eq!(password.len(), 20);
assert!(password.chars().all(|c| c.is_ascii_lowercase()));
}
#[test]
fn test_generate_password_uppercase_only() {
let options = PasswordOptions {
lowercase: false,
uppercase: true,
digits: false,
special: false,
length: 20,
};
let password = generate_password(&options);
assert_eq!(password.len(), 20);
assert!(password.chars().all(|c| c.is_ascii_uppercase()));
}
#[test]
fn test_generate_password_digits_only() {
let options = PasswordOptions {
lowercase: false,
uppercase: false,
digits: true,
special: false,
length: 20,
};
let password = generate_password(&options);
assert_eq!(password.len(), 20);
assert!(password.chars().all(|c| c.is_ascii_digit()));
}
#[test]
fn test_generate_password_special_only() {
let options = PasswordOptions {
lowercase: false,
uppercase: false,
digits: false,
special: true,
length: 20,
};
let password = generate_password(&options);
assert_eq!(password.len(), 20);
assert!(password.chars().all(|c| SPECIAL_SYMBOLS.contains(c)));
}
#[test]
fn test_generate_password_all_types() {
let options = PasswordOptions {
lowercase: true,
uppercase: true,
digits: true,
special: true,
length: 100,
};
let password = generate_password(&options);
assert_eq!(password.len(), 100);
}
#[test]
fn test_generate_password_empty_options_fallback() {
let options = PasswordOptions {
lowercase: false,
uppercase: false,
digits: false,
special: false,
length: 10,
};
let password = generate_password(&options);
assert_eq!(password.len(), 10);
assert!(password.chars().all(|c| c.is_ascii_lowercase()));
}
#[test]
fn test_generate_password_uniqueness() {
let options = PasswordOptions::default();
let p1 = generate_password(&options);
let p2 = generate_password(&options);
assert_ne!(p1, p2);
}
#[test]
fn test_generate_clever_password_length() {
let password = generate_clever_password("Aaaa0000");
assert_eq!(password.len(), 8);
}
#[test]
fn test_generate_clever_password_pattern() {
let password = generate_clever_password("aaaa");
assert!(password.chars().all(|c| c.is_ascii_lowercase()));
let password = generate_clever_password("AAAA");
assert!(password.chars().all(|c| c.is_ascii_uppercase()));
let password = generate_clever_password("0000");
assert!(password.chars().all(|c| c.is_ascii_digit()));
}
#[test]
fn test_generate_clever_password_mixed() {
let password = generate_clever_password("Aa00");
assert_eq!(password.len(), 4);
let chars: Vec<char> = password.chars().collect();
assert!(chars[0].is_ascii_uppercase());
assert!(chars[1].is_ascii_lowercase());
assert!(chars[2].is_ascii_digit());
assert!(chars[3].is_ascii_digit());
}
#[test]
fn test_generate_clever_password_special() {
let password = generate_clever_password("!@#$");
assert_eq!(password.len(), 4);
assert!(password.chars().all(|c| SPECIAL_SYMBOLS.contains(c)));
}
#[test]
fn test_generate_clever_password_unknown_chars() {
let password = generate_clever_password(" "); assert_eq!(password.len(), 4);
}
#[test]
fn test_generate_clever_password_empty() {
let password = generate_clever_password("");
assert!(password.is_empty());
}
#[test]
fn test_generate_clever_password_uniqueness() {
let p1 = generate_clever_password("Aaaa0000@@");
let p2 = generate_clever_password("Aaaa0000@@");
assert_ne!(p1, p2);
}
}