nano_id/
lib.rs

1//! A tiny, secure, URL-friendly, unique string ID generator
2
3#[macro_export]
4macro_rules! gen {
5    ($mod:tt, $len:tt, $alphabet:tt) => {
6        #[doc = concat!(" Nanoid with alphabet table `", stringify!($alphabet), "`")]
7        mod $mod {
8            pub const MASK: usize = ($len as usize).next_power_of_two() - 1;
9            pub const ALPHABET: &'static [u8; $len] = $alphabet;
10        }
11
12        #[doc = concat!(" Nanoid with ", stringify!($mod))]
13        #[must_use]
14        pub fn $mod<const N: usize>() -> String {
15            let mut bytes = vec![0u8; 8 * N / 5];
16            let mut id = String::with_capacity(N);
17
18            loop {
19                ::getrandom::getrandom(&mut bytes)
20                    .unwrap_or_else(|err| panic!("could not retreive random bytes: {err}"));
21
22                for byte in &bytes {
23                    let idx = *byte as usize & $mod::MASK;
24                    if idx < $len {
25                        id.push($mod::ALPHABET[idx] as char)
26                    }
27                    if id.len() == N {
28                        return id;
29                    }
30                }
31            }
32        }
33    };
34}
35
36#[cfg(feature = "base58")]
37gen!(
38    base58,
39    58,
40    b"ModueSymbhaswnPr123456789ABCDEFGHNRVfgctiUvzKqYTJkLxpZXjQW"
41);
42
43#[cfg(feature = "base62")]
44gen!(
45    base62,
46    62,
47    b"ModuleSymbhasOwnPr0123456789ABCDEFGHNRVfgctiUvzKqYTJkLxpZXIjQW"
48);
49
50#[cfg(feature = "base64")]
51gen!(
52    base64,
53    64,
54    b"ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW"
55);
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    #[cfg(feature = "base58")]
63    fn generates_base58() {
64        let id = base58::<21>();
65        println!("{}", &id);
66        assert_eq!(id.len(), 21);
67    }
68
69    #[test]
70    #[cfg(feature = "base62")]
71    fn generates_base62() {
72        let id = base62::<21>();
73        println!("{}", &id);
74        assert_eq!(id.len(), 21);
75    }
76
77    #[test]
78    #[cfg(feature = "base64")]
79    fn generates_base64() {
80        let id = base64::<21>();
81        println!("{}", &id);
82        assert_eq!(id.len(), 21);
83    }
84
85    #[test]
86    fn generates_uid() {
87        gen!(
88            uid,
89            64,
90            b"_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
91        );
92
93        let id = uid::<21>();
94        println!("{}", &id);
95        assert_eq!(id.len(), 21);
96    }
97
98    #[test]
99    #[cfg(feature = "base62")]
100    fn symbols() {
101        use std::collections::BTreeMap;
102
103        let mut counts = BTreeMap::new();
104
105        for _ in 0..1_000_000 {
106            let id = base62::<10>();
107            for c in id.chars() {
108                *counts.entry(c).or_insert(0) += 1;
109            }
110        }
111
112        println!("{} symbols generated", counts.len());
113        for (c, count) in &counts {
114            println!("{}: {}", c, count);
115        }
116
117        assert_eq!(counts.len(), 62);
118    }
119}