use rand::{seq::SliceRandom, thread_rng}; // Make IDs that: // // * Do not require escaping in URLs // * Do not require escaping in hostnames // * Are unique up to case conversion // * Are relatively unlikely to contain cursewords // * Are relatively unlikely to contain visually similar characters in most typefaces // * Are not sequential // // This leaves 23 ASCII characters, or about 4.52 bits of entropy per character // if generated with uniform probability. pub const ALPHABET: [char; 23] = [ '1', '2', '3', '4', '6', '7', '8', '9', 'b', 'c', 'd', 'f', 'h', 'j', 'k', 'n', 'p', 'r', 's', 't', 'w', 'x', 'y', ]; // Pick enough characters per ID to make accidental collisions "acceptably" unlikely // without also making them _too_ unwieldy. This gives a fraction under 68 bits per ID. pub const ID_SIZE: usize = 15; // Intended to be wrapped in a newtype that provides both type-based separation // from other identifier types, and a unique prefix to allow the intended type // of an ID to be determined by eyeball when debugging. // // By convention, the prefix should be UPPERCASE - note that the alphabet for this // is entirely lowercase. #[derive(Debug, Hash, PartialEq, Eq, sqlx::Type)] #[sqlx(transparent)] pub struct Id(String); impl Id { pub fn generate(prefix: &str) -> T where T: From, { let mut rng = thread_rng(); let id = prefix .chars() .chain( (0..ID_SIZE) .flat_map(|_| ALPHABET.choose(&mut rng)) /* usize -> &char */ .cloned(), /* &char -> char */ ) .collect::(); T::from(Self(id)) } }