1use std::fmt::Write;
16use std::sync::atomic::{AtomicU64, Ordering};
17use std::time::{SystemTime, UNIX_EPOCH};
18
19static TIMESTAMP_COUNTER: AtomicU64 = AtomicU64::new(0);
21
22static ENTROPY_COUNTER: AtomicU64 = AtomicU64::new(0);
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum IdFormat {
28 Timestamp,
30 RandomHex,
32 Short,
34 Prefixed,
36}
37
38#[must_use]
48pub fn generate_id(format: IdFormat) -> String {
49 match format {
50 IdFormat::RandomHex => generate_random_hex(),
51 IdFormat::Short => generate_short_id(),
52 IdFormat::Timestamp | IdFormat::Prefixed => generate_timestamp_id(),
53 }
54}
55
56#[must_use]
66pub fn generate_prefixed_id(prefix: &str) -> String {
67 format!("{prefix}_{}", generate_short_id())
68}
69
70#[must_use]
75pub fn generate_timestamp_id() -> String {
76 let timestamp = current_timestamp_millis();
77 let counter = TIMESTAMP_COUNTER.fetch_add(1, Ordering::SeqCst) % 10_000_000;
78 format!("{timestamp:013}{counter:07}")
79}
80
81#[must_use]
83pub fn generate_random_hex() -> String {
84 let mut bytes = [0u8; 16];
85 fill_random_bytes(&mut bytes);
86 bytes.iter().fold(String::with_capacity(32), |mut s, b| {
87 let _ = write!(s, "{b:02x}");
88 s
89 })
90}
91
92#[must_use]
94pub fn generate_short_id() -> String {
95 const CHARS: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
96 let mut bytes = [0u8; 12];
97 fill_random_bytes(&mut bytes);
98
99 bytes
100 .iter()
101 .map(|b| CHARS[(*b as usize) % CHARS.len()] as char)
102 .collect()
103}
104
105#[must_use]
110pub fn generate_uuid_like() -> String {
111 let mut bytes = [0u8; 16];
112 fill_random_bytes(&mut bytes);
113
114 bytes[6] = (bytes[6] & 0x0f) | 0x40;
116 bytes[8] = (bytes[8] & 0x3f) | 0x80;
117
118 format!(
119 "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
120 bytes[0],
121 bytes[1],
122 bytes[2],
123 bytes[3],
124 bytes[4],
125 bytes[5],
126 bytes[6],
127 bytes[7],
128 bytes[8],
129 bytes[9],
130 bytes[10],
131 bytes[11],
132 bytes[12],
133 bytes[13],
134 bytes[14],
135 bytes[15]
136 )
137}
138
139#[must_use]
141#[allow(clippy::cast_possible_truncation)]
142pub fn current_timestamp_millis() -> u64 {
143 SystemTime::now()
144 .duration_since(UNIX_EPOCH)
145 .unwrap_or_default()
146 .as_millis() as u64
147}
148
149fn fill_random_bytes(bytes: &mut [u8]) {
151 use std::collections::hash_map::DefaultHasher;
152 use std::hash::{Hash, Hasher};
153
154 let counter = ENTROPY_COUNTER.fetch_add(1, Ordering::SeqCst);
155 let timestamp = current_timestamp_millis();
156
157 let mut hasher = DefaultHasher::new();
159 timestamp.hash(&mut hasher);
160 counter.hash(&mut hasher);
161 std::process::id().hash(&mut hasher);
162 std::thread::current().id().hash(&mut hasher);
163
164 let mut seed = hasher.finish();
165
166 for byte in bytes.iter_mut() {
168 seed ^= seed << 13;
169 seed ^= seed >> 7;
170 seed ^= seed << 17;
171 *byte = (seed & 0xff) as u8;
172 seed = seed.wrapping_add(counter);
174 }
175}
176
177#[derive(Debug, Clone)]
179pub struct IdGenerator {
180 prefix: Option<String>,
181 format: IdFormat,
182}
183
184impl IdGenerator {
185 #[must_use]
187 pub fn new() -> Self {
188 Self::default()
189 }
190
191 #[must_use]
193 pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
194 self.prefix = Some(prefix.into());
195 self
196 }
197
198 #[must_use]
200 pub const fn with_format(mut self, format: IdFormat) -> Self {
201 self.format = format;
202 self
203 }
204
205 #[must_use]
207 pub fn generate(&self) -> String {
208 let id = generate_id(self.format);
209 match &self.prefix {
210 Some(p) => format!("{p}_{id}"),
211 None => id,
212 }
213 }
214}
215
216impl Default for IdGenerator {
217 fn default() -> Self {
218 Self {
219 prefix: None,
220 format: IdFormat::Short,
221 }
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use std::collections::HashSet;
229
230 #[test]
231 fn test_timestamp_id_format() {
232 let id = generate_timestamp_id();
233 assert_eq!(id.len(), 20);
234 assert!(id.chars().all(|c| c.is_ascii_digit()));
235 }
236
237 #[test]
238 fn test_timestamp_ids_are_sortable() {
239 let id1 = generate_timestamp_id();
240 std::thread::sleep(std::time::Duration::from_millis(1));
241 let id2 = generate_timestamp_id();
242 assert!(id1 < id2);
243 }
244
245 #[test]
246 fn test_timestamp_ids_unique_in_same_ms() {
247 let ids: Vec<String> = (0..100).map(|_| generate_timestamp_id()).collect();
248 let unique: HashSet<_> = ids.iter().collect();
249 assert_eq!(ids.len(), unique.len());
250 }
251
252 #[test]
253 fn test_random_hex_format() {
254 let id = generate_random_hex();
255 assert_eq!(id.len(), 32);
256 assert!(id.chars().all(|c| c.is_ascii_hexdigit()));
257 }
258
259 #[test]
260 fn test_short_id_format() {
261 let id = generate_short_id();
262 assert_eq!(id.len(), 12);
263 assert!(id.chars().all(|c| c.is_ascii_alphanumeric()));
264 }
265
266 #[test]
267 fn test_uuid_like_format() {
268 let id = generate_uuid_like();
269 assert_eq!(id.len(), 36);
270 assert_eq!(id.chars().filter(|&c| c == '-').count(), 4);
271 }
272
273 #[test]
274 fn test_prefixed_id() {
275 let id = generate_prefixed_id("usr");
276 assert!(id.starts_with("usr_"));
277 assert_eq!(id.len(), 4 + 12); }
279
280 #[test]
281 fn test_id_generator() {
282 let generator = IdGenerator::new()
283 .with_prefix("order")
284 .with_format(IdFormat::Short);
285
286 let id = generator.generate();
287 assert!(id.starts_with("order_"));
288 }
289
290 #[test]
291 fn test_uniqueness() {
292 let ids: HashSet<String> = (0..1000).map(|_| generate_short_id()).collect();
293 assert_eq!(ids.len(), 1000);
294 }
295
296 #[test]
297 fn test_counter_isolation() {
298 let _random_ids: Vec<String> = (0..1000).map(|_| generate_random_hex()).collect();
300
301 let ts_ids: HashSet<String> = (0..100).map(|_| generate_timestamp_id()).collect();
303 assert_eq!(ts_ids.len(), 100);
304 }
305}