use rand::seq::SliceRandom;
use rand::Rng;
const LOWERCASE: &[u8] = b"abcdefghijklmnopqrstuvwxyz";
const UPPERCASE: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const NUMBERS: &[u8] = b"0123456789";
const SYMBOLS: &[u8] = b"!@#$%^&*()_+-=[]{}|;:,.<>?";
pub fn generate_password() -> String {
generate_password_with_length(16)
}
pub fn generate_password_with_length(length: usize) -> String {
let length = length.max(4);
let mut rng = rand::thread_rng();
let mut password = Vec::with_capacity(length);
password.push(*LOWERCASE.choose(&mut rng).unwrap());
password.push(*UPPERCASE.choose(&mut rng).unwrap());
password.push(*NUMBERS.choose(&mut rng).unwrap());
password.push(*SYMBOLS.choose(&mut rng).unwrap());
let all_chars: Vec<u8> = [LOWERCASE, UPPERCASE, NUMBERS, SYMBOLS]
.iter()
.flat_map(|&set| set.iter())
.copied()
.collect();
while password.len() < length {
password.push(*all_chars.choose(&mut rng).unwrap());
}
for i in (1..password.len()).rev() {
let j = rng.gen_range(0..=i);
password.swap(i, j);
}
String::from_utf8(password).unwrap()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_password_default_length() {
let password = generate_password();
assert_eq!(password.len(), 16);
}
#[test]
fn test_generate_password_custom_length() {
let password = generate_password_with_length(20);
assert_eq!(password.len(), 20);
}
#[test]
fn test_generate_password_minimum_length() {
let password = generate_password_with_length(2);
assert_eq!(password.len(), 4);
}
#[test]
fn test_password_contains_all_categories() {
let password = generate_password();
let has_lowercase = password.chars().any(|c| c.is_ascii_lowercase());
let has_uppercase = password.chars().any(|c| c.is_ascii_uppercase());
let has_number = password.chars().any(|c| c.is_ascii_digit());
let has_symbol = password.chars().any(|c| SYMBOLS.contains(&(c as u8)));
assert!(has_lowercase, "Password should contain lowercase");
assert!(has_uppercase, "Password should contain uppercase");
assert!(has_number, "Password should contain number");
assert!(has_symbol, "Password should contain symbol");
}
#[test]
fn test_passwords_are_different() {
let p1 = generate_password();
let p2 = generate_password();
let p3 = generate_password();
assert!(!(p1 == p2 && p2 == p3));
}
}