use rand::rng;
use rand::seq::IndexedRandom;
use rand::Rng;
use strum::IntoEnumIterator;
use crate::{hkid_check_digit::calculate_check_digit, hkid_prefix::HKIDPrefix};
fn random_uppercase_letter<R: Rng + ?Sized>(rng: &mut R) -> char {
rng.random_range(b'A'..=b'Z') as char
}
pub fn generate_hkid(
prefix: Option<&str>,
must_exist_in_enum: bool,
) -> Result<String, String> {
let mut rng = rng();
let prefix_str = match prefix {
Some(px) => {
let parsed_prefix = HKIDPrefix::parse(px);
if must_exist_in_enum && !parsed_prefix.is_known() {
return Err(format!("Prefix '{px}' is not recognized"));
}
parsed_prefix.as_str()
}
None => {
if must_exist_in_enum {
let valid_prefixes: Vec<String> = HKIDPrefix::iter()
.filter(HKIDPrefix::is_known)
.map(|variant| variant.as_str())
.collect();
valid_prefixes
.choose(&mut rng)
.cloned() .ok_or_else(|| "No valid prefixes in HKIDPrefix enum".to_string())?
} else {
if rng.random_range(0..2) == 0 {
random_uppercase_letter(&mut rng).to_string()
} else {
let prefix: String = (0..2)
.map(|_| random_uppercase_letter(&mut rng))
.collect();
prefix
}
}
}
};
let mut hkid_body = prefix_str;
for _ in 0..6 {
hkid_body.push_str(&rng.random_range(0..10).to_string());
}
let check_digit = calculate_check_digit(&hkid_body);
Ok(format!("{hkid_body}({check_digit})"))
}
#[cfg(test)]
mod tests {
use rand::{rng};
use super::{generate_hkid, random_uppercase_letter};
#[test]
fn test_random_uppercase_letter() {
let mut rng = rng();
for _ in 0..100 {
let c = random_uppercase_letter(&mut rng);
assert!(
c.is_ascii_uppercase(),
"Generated character '{}' is not an ASCII uppercase letter",
c
);
}
}
fn is_valid_format(hkid: &str) -> bool {
let parts: Vec<_> = hkid.split(['(', ')']).collect();
if parts.len() != 3 {
return false;
}
let prefix_digits = parts[0];
let check_digit = parts[1];
if check_digit.len() != 1 {
return false;
}
let prefix_len = prefix_digits.len();
if prefix_len < 7 || prefix_len > 8 {
return false;
}
prefix_digits
.chars()
.rev()
.take(6)
.all(|c| c.is_ascii_digit())
}
#[test]
fn test_generate_hkid_with_known_prefix() {
let prefix = "A";
let result = generate_hkid(Some(prefix), true);
assert!(result.is_ok());
let hkid = result.unwrap();
assert!(hkid.starts_with(prefix));
assert!(is_valid_format(&hkid));
}
#[test]
fn test_generate_hkid_with_two_letter_known_prefix() {
let prefix = "WX";
let result = generate_hkid(Some(prefix), true);
assert!(result.is_ok());
let hkid = result.unwrap();
assert!(hkid.starts_with(prefix));
assert!(is_valid_format(&hkid));
}
#[test]
fn test_generate_hkid_with_custom_prefix_allowed() {
let prefix = "ZZ";
let result = generate_hkid(Some(prefix), false);
assert!(result.is_ok());
let hkid = result.unwrap();
assert!(hkid.starts_with(prefix));
assert!(is_valid_format(&hkid));
}
#[test]
fn test_generate_hkid_with_random_known_prefix() {
let result = generate_hkid(None, true);
assert!(result.is_ok());
let hkid = result.unwrap();
assert!(is_valid_format(&hkid));
}
#[test]
fn test_generate_hkid_with_random_any_prefix() {
let result = generate_hkid(None, false);
assert!(result.is_ok());
let hkid = result.unwrap();
assert!(is_valid_format(&hkid));
let prefix_len = hkid.find(|c: char| c.is_ascii_digit()).unwrap();
assert!(prefix_len == 1 || prefix_len == 2);
let prefix = &hkid[..prefix_len];
for c in prefix.chars() {
assert!(c.is_ascii_uppercase());
}
}
#[test]
fn test_generate_hkid_with_empty_prefix() {
let result = generate_hkid(Some(""), true);
assert!(result.is_err());
}
#[test]
fn test_generate_hkid_with_lowercase_prefix() {
let result = generate_hkid(Some("a"), true);
assert!(result.is_err());
}
}