use rand::{TryRngCore, rngs::OsRng};
pub fn generate_prefixed_id(prefix: &str) -> String {
let mut bytes = [0u8; 12];
OsRng.try_fill_bytes(&mut bytes).unwrap();
let encoded = b58::encode(&bytes);
format!("{prefix}_{encoded}")
}
pub fn generate_prefixed_id_with_bytes(prefix: &str, bytes: usize) -> String {
if bytes < 12 {
panic!("Minimum 12 bytes (96 bits) of entropy required");
}
let mut random_bytes = vec![0u8; bytes];
OsRng.try_fill_bytes(&mut random_bytes).unwrap();
let encoded = b58::encode(&random_bytes);
format!("{prefix}_{encoded}")
}
pub fn validate_prefixed_id(id: &str, expected_prefix: &str) -> bool {
if !id.starts_with(&format!("{expected_prefix}_")) {
return false;
}
let random_part = &id[expected_prefix.len() + 1..];
match b58::decode(random_part) {
Ok(decoded) => decoded.len() >= 12, Err(_) => false,
}
}
pub fn extract_prefix(id: &str) -> Option<&str> {
id.split_once('_').map(|(prefix, _)| prefix)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_prefixed_id() {
let id = generate_prefixed_id("usr");
assert!(id.starts_with("usr_"));
assert!(id.len() > 4);
let id2 = generate_prefixed_id("usr");
assert_ne!(id, id2);
}
#[test]
fn test_generate_prefixed_id_with_bytes() {
let id = generate_prefixed_id_with_bytes("sess", 16);
assert!(id.starts_with("sess_"));
let random_part = &id[5..]; let decoded = b58::decode(random_part).unwrap();
assert_eq!(decoded.len(), 16);
}
#[test]
#[should_panic(expected = "Minimum 12 bytes")]
fn test_generate_prefixed_id_insufficient_entropy() {
generate_prefixed_id_with_bytes("usr", 8);
}
#[test]
fn test_validate_prefixed_id() {
let id = generate_prefixed_id("usr");
assert!(validate_prefixed_id(&id, "usr"));
assert!(!validate_prefixed_id(&id, "sess"));
assert!(!validate_prefixed_id("usr", "usr"));
assert!(!validate_prefixed_id("usr_", "usr"));
assert!(!validate_prefixed_id("usr_invalid!", "usr"));
}
#[test]
fn test_extract_prefix() {
assert_eq!(extract_prefix("usr_abc123"), Some("usr"));
assert_eq!(extract_prefix("sess_xyz789"), Some("sess"));
assert_eq!(extract_prefix("no_underscore"), Some("no"));
assert_eq!(extract_prefix("noprefix"), None);
}
#[test]
fn test_id_is_url_safe() {
let id = generate_prefixed_id("usr");
assert!(
id.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '-')
);
}
}