use crate::credit_card::CreditCardOutputFormat;
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_formatted(format: CreditCardOutputFormat) -> String {
let password = generate_password();
format_password(&password, format)
}
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()
}
pub fn generate_password_with_length_formatted(
length: usize,
format: CreditCardOutputFormat,
) -> String {
let password = generate_password_with_length(length);
format_password(&password, format)
}
fn format_password(password: &str, format: CreditCardOutputFormat) -> String {
match format {
CreditCardOutputFormat::Plain => password.to_string(),
CreditCardOutputFormat::Json => format!("{{\"password\":\"{}\"}}", password),
CreditCardOutputFormat::JsonLd => format!(
"{{\"@context\":\"https://schema.org\",\"@type\":\"PropertyValue\",\"name\":\"password\",\"value\":\"{}\"}}",
password
),
}
}
#[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));
}
#[test]
fn test_generate_password_formatted_json() {
let output = generate_password_formatted(CreditCardOutputFormat::Json);
assert!(output.contains("\"password\":\""));
}
#[test]
fn test_generate_password_formatted_jsonld() {
let output = generate_password_formatted(CreditCardOutputFormat::JsonLd);
assert!(output.contains("\"@context\":\"https://schema.org\""));
assert!(output.contains("\"name\":\"password\""));
}
}