rustq_nanoid/
lib.rs

1#![doc(
2    html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png",
3    html_favicon_url = "https://www.rust-lang.org/favicon.ico",
4    html_root_url = "https://docs.rs/rustq-nanoid"
5)]
6
7#[cfg(feature = "smartstring")]
8use smartstring::alias::String;
9
10pub mod alphabet;
11pub mod rngs;
12
13use std::sync::Mutex;
14
15const POOL_SIZE_MULTIPLIER: usize = 128;
16const DEFAULT_SIZE: usize = 21;
17const POOL_SIZE: usize = DEFAULT_SIZE * POOL_SIZE_MULTIPLIER;
18
19lazy_static! {
20    static ref POOL: Mutex<[u8; POOL_SIZE]> = Mutex::new([0; POOL_SIZE]);
21    static ref POOL_OFFSET: Mutex<usize> = Mutex::new(POOL_SIZE);
22}
23
24pub fn format(random: fn(usize) -> Vec<u8>, alphabet: &[char], size: usize) -> String {
25    assert!(
26        alphabet.len() <= u8::max_value() as usize,
27        "The alphabet cannot be longer than a `u8` (to comply with the `random` function)"
28    );
29
30    assert!(size <= POOL_SIZE, "The size should smaller than pool size");
31
32    let bytes = &mut POOL.lock().unwrap();
33    let mask = alphabet.len().next_power_of_two() - 1;
34    // Assert that the masking does not truncate the alphabet. (See #9)
35    debug_assert!(alphabet.len() <= mask + 1);
36
37    let mut pointer = *POOL_OFFSET.lock().unwrap();
38
39    let mut id = String::with_capacity(size);
40
41    while id.len() < size {
42        if pointer == POOL_SIZE {
43            let buf = random(POOL_SIZE);
44            for i in 0..POOL_SIZE {
45                bytes[i] = buf[i];
46            }
47            pointer = 0;
48        }
49        let byte = bytes[pointer] as usize & mask;
50        if alphabet.len() > byte {
51            id.push(alphabet[byte]);
52        }
53        pointer += 1;
54    }
55
56    *POOL_OFFSET.lock().unwrap() = pointer;
57
58    id
59}
60
61#[cfg(test)]
62mod test_format {
63    use super::*;
64
65    // #[test]
66    // fn generates_random_string() {
67    //     fn random(size: usize) -> Vec<u8> {
68    //         [2, 255, 0, 1].iter().cloned().cycle().take(size).collect()
69    //     }
70
71    //     assert_eq!(format(random, &['a', 'b', 'c'], 4), "cabc");
72    // }
73
74    #[test]
75    #[should_panic]
76    fn bad_alphabet() {
77        let alphabet: Vec<char> = (0..32_u8).cycle().map(|i| i as char).take(1000).collect();
78        nanoid!(21, &alphabet);
79    }
80
81    #[test]
82    fn non_power_2() {
83        let id: String = nanoid!(42, &alphabet::SAFE[0..62]);
84
85        assert_eq!(id.len(), 42);
86    }
87}
88
89#[macro_export]
90macro_rules! nanoid {
91    // simple
92    () => {
93        $crate::format($crate::rngs::default, &$crate::alphabet::SAFE, 21)
94    };
95
96    // generate
97    ($size:expr) => {
98        $crate::format($crate::rngs::default, &$crate::alphabet::SAFE, $size)
99    };
100
101    // custom
102    ($size:expr, $alphabet:expr) => {
103        $crate::format($crate::rngs::default, $alphabet, $size)
104    };
105
106    // complex
107    ($size:expr, $alphabet:expr, $random:expr) => {
108        $crate::format($random, $alphabet, $size)
109    };
110}
111
112#[cfg(test)]
113mod test_macros {
114    use super::*;
115
116    #[test]
117    fn simple() {
118        let id: String = nanoid!();
119
120        assert_eq!(id.len(), 21);
121    }
122
123    #[test]
124    fn generate() {
125        let id: String = nanoid!(42);
126
127        assert_eq!(id.len(), 42);
128    }
129
130    #[test]
131    fn custom() {
132        let id: String = nanoid!(42, &alphabet::SAFE);
133
134        assert_eq!(id.len(), 42);
135    }
136
137    #[test]
138    fn complex() {
139        let id: String = nanoid!(4, &alphabet::SAFE, rngs::default);
140
141        assert_eq!(id.len(), 4);
142    }
143
144    #[test]
145    fn simple_expression() {
146        let id: String = nanoid!(42 / 2);
147
148        assert_eq!(id.len(), 21);
149    }
150}
151
152#[cfg(doctest)]
153doc_comment::doctest!("../README.md");
154
155#[macro_use]
156extern crate lazy_static;
157// pub mod prefill;