1use std::sync::atomic::{AtomicU64, Ordering};
9use uuid::Uuid;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13#[repr(u8)]
14pub enum GeneratorType {
15 JournalEntry = 0x01,
17 DocumentFlow = 0x02,
19 Vendor = 0x03,
21 Customer = 0x04,
23 Material = 0x05,
25 Asset = 0x06,
27 Employee = 0x07,
29 ARSubledger = 0x08,
31 APSubledger = 0x09,
33 FASubledger = 0x0A,
35 InventorySubledger = 0x0B,
37 Intercompany = 0x0C,
39 Anomaly = 0x0D,
41 PeriodClose = 0x0E,
43 FxRate = 0x0F,
45 Accrual = 0x10,
47 Depreciation = 0x11,
49 Control = 0x12,
51 OpeningBalance = 0x13,
53 TrialBalance = 0x14,
55 PurchaseOrder = 0x20,
57 GoodsReceipt = 0x21,
59 VendorInvoice = 0x22,
61 Payment = 0x23,
63 SalesOrder = 0x24,
65 Delivery = 0x25,
67 CustomerInvoice = 0x26,
69 CustomerReceipt = 0x27,
71
72 SourcingProject = 0x28,
75 RfxEvent = 0x29,
77 SupplierBid = 0x2A,
79 ProcurementContract = 0x2B,
81 CatalogItem = 0x2C,
83 BankReconciliation = 0x2D,
85 FinancialStatement = 0x2E,
87 PayrollRun = 0x2F,
89 TimeEntry = 0x30,
91 ExpenseReport = 0x31,
93 ProductionOrder = 0x32,
95 CycleCount = 0x33,
97 QualityInspection = 0x34,
99 SalesQuote = 0x35,
101 BudgetLine = 0x36,
103 RevenueRecognition = 0x37,
105 ImpairmentTest = 0x38,
107 Kpi = 0x39,
109 Tax = 0x3A,
111 ProjectAccounting = 0x3B,
113 Esg = 0x3C,
115 SupplierQualification = 0x3D,
117 SupplierScorecard = 0x3E,
119 BomComponent = 0x3F,
121 InventoryMovement = 0x40,
123 BenefitEnrollment = 0x41,
125 Disruption = 0x42,
127}
128
129#[derive(Debug)]
146pub struct DeterministicUuidFactory {
147 seed: u64,
148 generator_type: GeneratorType,
149 counter: AtomicU64,
150 sub_discriminator: u8,
152}
153
154impl DeterministicUuidFactory {
155 pub fn new(seed: u64, generator_type: GeneratorType) -> Self {
171 Self {
172 seed,
173 generator_type,
174 counter: AtomicU64::new(0),
175 sub_discriminator: 0,
176 }
177 }
178
179 pub fn with_sub_discriminator(
183 seed: u64,
184 generator_type: GeneratorType,
185 sub_discriminator: u8,
186 ) -> Self {
187 Self {
188 seed,
189 generator_type,
190 counter: AtomicU64::new(0),
191 sub_discriminator,
192 }
193 }
194
195 pub fn with_counter(seed: u64, generator_type: GeneratorType, start_counter: u64) -> Self {
200 Self {
201 seed,
202 generator_type,
203 counter: AtomicU64::new(start_counter),
204 sub_discriminator: 0,
205 }
206 }
207
208 pub fn for_partition(seed: u64, generator_type: GeneratorType, partition_index: u8) -> Self {
214 Self {
215 seed,
216 generator_type,
217 counter: AtomicU64::new(0),
218 sub_discriminator: partition_index,
219 }
220 }
221
222 #[inline]
226 pub fn next(&self) -> Uuid {
227 let counter = self.counter.fetch_add(1, Ordering::Relaxed);
228 self.generate_uuid(counter)
229 }
230
231 pub fn generate_at(&self, counter: u64) -> Uuid {
235 self.generate_uuid(counter)
236 }
237
238 pub fn current_counter(&self) -> u64 {
240 self.counter.load(Ordering::Relaxed)
241 }
242
243 pub fn reset(&self) {
245 self.counter.store(0, Ordering::Relaxed);
246 }
247
248 pub fn set_counter(&self, value: u64) {
250 self.counter.store(value, Ordering::Relaxed);
251 }
252
253 #[inline]
259 fn generate_uuid(&self, counter: u64) -> Uuid {
260 let mut hash: u64 = 14695981039346656037; for byte in self.seed.to_le_bytes() {
266 hash ^= byte as u64;
267 hash = hash.wrapping_mul(1099511628211); }
269
270 hash ^= self.generator_type as u64;
272 hash = hash.wrapping_mul(1099511628211);
273
274 hash ^= self.sub_discriminator as u64;
276 hash = hash.wrapping_mul(1099511628211);
277
278 for byte in counter.to_le_bytes() {
280 hash ^= byte as u64;
281 hash = hash.wrapping_mul(1099511628211);
282 }
283
284 let mut hash2: u64 = hash;
286 hash2 ^= self.seed.rotate_left(32);
287 hash2 = hash2.wrapping_mul(1099511628211);
288 hash2 ^= counter.rotate_left(32);
289 hash2 = hash2.wrapping_mul(1099511628211);
290
291 let mut bytes = [0u8; 16];
292
293 bytes[0..8].copy_from_slice(&hash.to_le_bytes());
295 bytes[8..16].copy_from_slice(&hash2.to_le_bytes());
297
298 bytes[6] = (bytes[6] & 0x0f) | 0x40;
301
302 bytes[8] = (bytes[8] & 0x3f) | 0x80;
305
306 Uuid::from_bytes(bytes)
307 }
308}
309
310impl Clone for DeterministicUuidFactory {
311 fn clone(&self) -> Self {
312 Self {
313 seed: self.seed,
314 generator_type: self.generator_type,
315 counter: AtomicU64::new(self.counter.load(Ordering::Relaxed)),
316 sub_discriminator: self.sub_discriminator,
317 }
318 }
319}
320
321#[derive(Debug)]
325pub struct UuidFactoryRegistry {
326 seed: u64,
327 factories: std::collections::HashMap<GeneratorType, DeterministicUuidFactory>,
328}
329
330impl UuidFactoryRegistry {
331 pub fn new(seed: u64) -> Self {
333 Self {
334 seed,
335 factories: std::collections::HashMap::new(),
336 }
337 }
338
339 pub fn get_factory(&mut self, generator_type: GeneratorType) -> &DeterministicUuidFactory {
341 self.factories
342 .entry(generator_type)
343 .or_insert_with(|| DeterministicUuidFactory::new(self.seed, generator_type))
344 }
345
346 pub fn next_uuid(&mut self, generator_type: GeneratorType) -> Uuid {
348 self.get_factory(generator_type).next()
349 }
350
351 pub fn reset_all(&self) {
353 for factory in self.factories.values() {
354 factory.reset();
355 }
356 }
357
358 pub fn get_counter(&self, generator_type: GeneratorType) -> Option<u64> {
360 self.factories
361 .get(&generator_type)
362 .map(DeterministicUuidFactory::current_counter)
363 }
364}
365
366#[cfg(test)]
367#[allow(clippy::unwrap_used)]
368mod tests {
369 use super::*;
370 use std::collections::HashSet;
371 use std::thread;
372
373 #[test]
374 fn test_uuid_uniqueness_same_generator() {
375 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
376
377 let mut uuids = HashSet::new();
378 for _ in 0..10000 {
379 let uuid = factory.next();
380 assert!(uuids.insert(uuid), "Duplicate UUID generated");
381 }
382 }
383
384 #[test]
385 fn test_uuid_uniqueness_different_generators() {
386 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
387 let factory2 = DeterministicUuidFactory::new(12345, GeneratorType::DocumentFlow);
388
389 let mut uuids = HashSet::new();
390
391 for _ in 0..5000 {
392 let uuid1 = factory1.next();
393 let uuid2 = factory2.next();
394 assert!(uuids.insert(uuid1), "Duplicate UUID from JE generator");
395 assert!(uuids.insert(uuid2), "Duplicate UUID from DocFlow generator");
396 }
397 }
398
399 #[test]
400 fn test_uuid_determinism() {
401 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
402 let factory2 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
403
404 for _ in 0..100 {
405 assert_eq!(factory1.next(), factory2.next());
406 }
407 }
408
409 #[test]
410 fn test_uuid_different_seeds() {
411 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
412 let factory2 = DeterministicUuidFactory::new(67890, GeneratorType::JournalEntry);
413
414 assert_ne!(factory1.next(), factory2.next());
416 }
417
418 #[test]
419 fn test_thread_safety() {
420 use std::sync::Arc;
421
422 let factory = Arc::new(DeterministicUuidFactory::new(
423 12345,
424 GeneratorType::JournalEntry,
425 ));
426 let mut handles = vec![];
427
428 for _ in 0..4 {
429 let factory_clone = Arc::clone(&factory);
430 handles.push(thread::spawn(move || {
431 let mut uuids = Vec::new();
432 for _ in 0..1000 {
433 uuids.push(factory_clone.next());
434 }
435 uuids
436 }));
437 }
438
439 let mut all_uuids = HashSet::new();
440 for handle in handles {
441 let uuids = handle.join().unwrap();
442 for uuid in uuids {
443 assert!(all_uuids.insert(uuid), "Thread-generated UUID collision");
444 }
445 }
446
447 assert_eq!(all_uuids.len(), 4000);
448 }
449
450 #[test]
451 fn test_sub_discriminator() {
452 let factory1 =
453 DeterministicUuidFactory::with_sub_discriminator(12345, GeneratorType::JournalEntry, 0);
454 let factory2 =
455 DeterministicUuidFactory::with_sub_discriminator(12345, GeneratorType::JournalEntry, 1);
456
457 let uuid1 = factory1.next();
459 factory1.reset();
460 let uuid2 = factory2.next();
461
462 assert_ne!(uuid1, uuid2);
463 }
464
465 #[test]
466 fn test_generate_at() {
467 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
468
469 let uuid_at_5 = factory.generate_at(5);
471
472 for _ in 0..5 {
474 factory.next();
475 }
476 let _uuid_sequential = factory.next();
477
478 assert_eq!(uuid_at_5, factory.generate_at(5));
480 }
481
482 #[test]
483 fn test_registry() {
484 let mut registry = UuidFactoryRegistry::new(12345);
485
486 let uuid1 = registry.next_uuid(GeneratorType::JournalEntry);
487 let uuid2 = registry.next_uuid(GeneratorType::JournalEntry);
488 let uuid3 = registry.next_uuid(GeneratorType::DocumentFlow);
489
490 assert_ne!(uuid1, uuid2);
492 assert_ne!(uuid1, uuid3);
493 assert_ne!(uuid2, uuid3);
494
495 assert_eq!(registry.get_counter(GeneratorType::JournalEntry), Some(2));
497 assert_eq!(registry.get_counter(GeneratorType::DocumentFlow), Some(1));
498 }
499
500 #[test]
501 fn test_uuid_is_valid_v4() {
502 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
503 let uuid = factory.next();
504
505 assert_eq!(uuid.get_version_num(), 4);
507
508 assert_eq!(uuid.get_variant(), uuid::Variant::RFC4122);
510 }
511}