use rand::{rngs::OsRng, RngCore};
const ALNUM: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const ALPHA: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const NUMERIC: &[u8] = b"0123456789";
const HEX: &[u8] = b"0123456789abcdef";
const SYMBOL: &[u8] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+[]{}|;:,.<>?";
const AMBIGUOUS: &[u8] = b"0Ol1I";
fn resolve_charset(charset: &str) -> &'static [u8] {
match charset {
"alpha" => ALPHA,
"numeric" => NUMERIC,
"hex" => HEX,
"symbol" => SYMBOL,
_ => ALNUM,
}
}
pub fn generate(length: usize, charset: &str) -> String {
let chars = resolve_charset(charset);
let mut buf = [0u8; 4];
(0..length)
.map(|_| {
OsRng.fill_bytes(&mut buf);
let idx = u32::from_le_bytes(buf) as usize % chars.len();
chars[idx] as char
})
.collect()
}
pub fn generate_unambiguous(length: usize, charset: &str) -> String {
let chars: Vec<u8> = resolve_charset(charset)
.iter()
.copied()
.filter(|c| !AMBIGUOUS.contains(c))
.collect();
if chars.is_empty() {
return generate(length, charset);
}
let mut buf = [0u8; 4];
(0..length)
.map(|_| {
OsRng.fill_bytes(&mut buf);
let idx = u32::from_le_bytes(buf) as usize % chars.len();
chars[idx] as char
})
.collect()
}
pub fn effective_pool_size(charset: &str, exclude_ambiguous: bool) -> usize {
let chars = resolve_charset(charset);
if exclude_ambiguous {
chars.iter().filter(|c| !AMBIGUOUS.contains(c)).count()
} else {
chars.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn length_is_exact() {
assert_eq!(generate(32, "alnum").len(), 32);
assert_eq!(generate(0, "alnum").len(), 0);
assert_eq!(generate(128, "hex").len(), 128);
}
#[test]
fn charset_alnum_contains_only_valid_chars() {
let s = generate(1000, "alnum");
assert!(
s.chars().all(|c| c.is_ascii_alphanumeric()),
"unexpected char in alnum output"
);
}
#[test]
fn charset_numeric_is_all_digits() {
let s = generate(64, "numeric");
assert!(s.chars().all(|c| c.is_ascii_digit()));
}
#[test]
fn charset_hex_is_lowercase() {
let s = generate(64, "hex");
assert!(s.chars().all(|c| "0123456789abcdef".contains(c)));
}
#[test]
fn unknown_charset_falls_back_to_alnum() {
let s = generate(64, "garbage");
assert!(s.chars().all(|c| c.is_ascii_alphanumeric()));
}
}