1use rand::Rng;
8use std::collections::HashSet;
9
10pub const ALPHABET: &[u8] = b"ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
11pub const LENGTH: usize = 8;
12const MAX_ATTEMPTS: usize = 500;
13
14pub fn random() -> String {
15 let mut rng = rand::thread_rng();
16 (0..LENGTH)
17 .map(|_| ALPHABET[rng.gen_range(0..ALPHABET.len())] as char)
18 .collect()
19}
20
21pub fn generate_unique(existing: &HashSet<String>) -> Result<String, &'static str> {
27 for _ in 0..MAX_ATTEMPTS {
28 let candidate = random();
29 if !existing.contains(&candidate) {
30 return Ok(candidate);
31 }
32 }
33 Err("failed to generate unique pairing code after 500 attempts")
34}
35
36#[cfg(test)]
37mod tests {
38 use super::*;
39
40 #[test]
41 fn alphabet_excludes_ambiguous_chars() {
42 for c in [b'0', b'O', b'1', b'I'] {
46 assert!(
47 !ALPHABET.contains(&c),
48 "ambiguous char {} in alphabet",
49 c as char
50 );
51 }
52 }
53
54 #[test]
55 fn generated_code_uses_only_alphabet() {
56 for _ in 0..50 {
57 let c = random();
58 assert_eq!(c.len(), LENGTH);
59 assert!(c.chars().all(|ch| ALPHABET.contains(&(ch as u8))));
60 }
61 }
62
63 #[test]
64 fn generate_unique_avoids_collision() {
65 let mut existing = HashSet::new();
66 for _ in 0..200 {
67 let c = generate_unique(&existing).unwrap();
68 assert!(!existing.contains(&c));
69 existing.insert(c);
70 }
71 }
72}