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