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 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]
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 () => {
93 $crate::format($crate::rngs::default, &$crate::alphabet::SAFE, 21)
94 };
95
96 ($size:expr) => {
98 $crate::format($crate::rngs::default, &$crate::alphabet::SAFE, $size)
99 };
100
101 ($size:expr, $alphabet:expr) => {
103 $crate::format($crate::rngs::default, $alphabet, $size)
104 };
105
106 ($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