use rand::rng;
use rand::seq::IndexedRandom;
use rand::Rng;
use strum::IntoEnumIterator;
use crate::{hkid_check_digit::calculate_check_digit, hkid_prefix::HKIDPrefix};
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 '{}' is not recognized", px));
}
parsed_prefix.as_str()
}
None => {
if must_exist_in_enum {
let valid_prefixes: Vec<String> = HKIDPrefix::iter()
.filter(|variant| variant.is_known())
.map(|variant| variant.as_str())
.collect();
valid_prefixes
.choose(&mut rng)
.expect("No valid prefixes in HKIDPrefix enum")
.clone()
} else {
if rng.random_range(0..2) == 0 {
let letter = rng.random_range(b'A'..=b'Z') as char;
letter.to_string()
} else {
let letter1 = rng.random_range(b'A'..=b'Z') as char;
let letter2 = rng.random_range(b'A'..=b'Z') as char;
format!("{}{}", letter1, letter2)
}
}
}
};
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 super::generate_hkid;
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());
}
}