1use std::fmt::{self, Display};
17use std::ops::Deref;
18use std::str;
19use std::sync::Mutex;
20
21use rand::distr::Alphanumeric;
22use rand::Rng;
23
24const BASE: usize = 62;
25const ALPHABET: [u8; BASE] = *b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
26
27const PRE_LEN: usize = 12;
28const MAX_SEQ: u64 = 839_299_365_868_340_224; const MIN_INC: u64 = 33;
30const MAX_INC: u64 = 333;
31
32pub const TOTAL_LEN: usize = 22;
34
35static GLOBAL_NUID: Mutex<NUID> = Mutex::new(NUID::new());
36
37#[allow(clippy::missing_panics_doc)]
39#[must_use]
40pub fn next() -> NUIDStr {
41 GLOBAL_NUID.lock().unwrap().next()
42}
43
44pub struct NUID {
49 pre: [u8; PRE_LEN],
50 seq: u64,
51 inc: u64,
52}
53
54pub struct NUIDStr(
59 [u8; TOTAL_LEN],
61);
62
63impl Default for NUID {
64 fn default() -> Self {
65 Self::new()
66 }
67}
68
69impl NUID {
70 #[must_use]
72 pub const fn new() -> Self {
73 Self {
74 pre: [0; PRE_LEN],
75 seq: MAX_SEQ,
77 inc: 0,
78 }
79 }
80
81 pub fn randomize_prefix(&mut self) {
82 for (i, n) in rand::rng()
83 .sample_iter(&Alphanumeric)
84 .take(PRE_LEN)
85 .enumerate()
86 {
87 self.pre[i] = ALPHABET[n as usize % BASE];
88 }
89 }
90
91 #[allow(clippy::should_implement_trait)]
93 #[must_use]
94 pub fn next(&mut self) -> NUIDStr {
95 let mut buffer = [0u8; TOTAL_LEN];
96
97 self.seq += self.inc;
98 if self.seq >= MAX_SEQ {
99 self.randomize_prefix();
100 self.reset_sequential();
101 }
102 #[allow(clippy::cast_possible_truncation)]
103 let seq = self.seq as usize;
104
105 for (i, n) in self.pre.iter().enumerate() {
106 buffer[i] = *n;
107 }
108
109 let mut l = seq;
110 for i in (PRE_LEN..TOTAL_LEN).rev() {
111 buffer[i] = ALPHABET[l % BASE];
112 l /= BASE;
113 }
114
115 NUIDStr(buffer)
117 }
118
119 fn reset_sequential(&mut self) {
120 let mut rng = rand::rng();
121 self.seq = rng.random_range(0..MAX_SEQ);
122 self.inc = rng.random_range(MIN_INC..MAX_INC);
123 }
124}
125
126impl NUIDStr {
127 #[must_use]
129 pub fn as_str(&self) -> &str {
130 unsafe { str::from_utf8_unchecked(&self.0) }
132 }
133
134 #[must_use]
136 pub fn into_bytes(self) -> [u8; TOTAL_LEN] {
137 self.0
138 }
139}
140
141impl Display for NUIDStr {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 f.write_str(self.as_str())
144 }
145}
146
147impl Deref for NUIDStr {
148 type Target = str;
149
150 fn deref(&self) -> &Self::Target {
152 self.as_str()
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use std::collections::HashSet;
160
161 #[test]
162 fn alphabet_size() {
163 assert_eq!(ALPHABET.len(), BASE);
164 }
165
166 #[test]
167 fn global_nuid_init() {
168 assert_eq!(GLOBAL_NUID.lock().unwrap().pre.len(), PRE_LEN);
169 assert_ne!(GLOBAL_NUID.lock().unwrap().seq, 0);
170 }
171
172 #[test]
173 fn nuid_rollover() {
174 let mut n = NUID::new();
175 n.seq = MAX_SEQ;
176 let old = n.pre.to_vec();
177 let _ = n.next();
178 assert_ne!(n.pre.to_vec(), old);
179
180 let mut n = NUID::new();
181 n.seq = 1;
182 let old = n.pre.to_vec();
183 let _ = n.next();
184 assert_eq!(n.pre.to_vec(), old);
185 }
186
187 #[test]
188 fn nuid_len() {
189 let id = next();
190 assert_eq!(id.len(), TOTAL_LEN);
191 }
192
193 #[test]
194 fn proper_prefix() {
195 let mut min: u8 = 255;
196 let mut max: u8 = 0;
197
198 for nn in &ALPHABET {
199 let n = *nn;
200 if n < min {
201 min = n;
202 }
203 if n > max {
204 max = n;
205 }
206 }
207
208 for _ in 0..100_000 {
209 let nuid = NUID::new();
210 for j in 0..PRE_LEN {
211 assert!(nuid.pre[j] >= min || nuid.pre[j] <= max);
212 }
213 }
214 }
215
216 #[test]
217 fn unique() {
218 let mut set = HashSet::new();
219 for _ in 0..10_000_000 {
220 assert!(set.insert(next().to_string()));
221 }
222 }
223}