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 BusinessCombination = 0x43,
129 SegmentReport = 0x44,
131 ExpectedCreditLoss = 0x45,
133 Pension = 0x46,
135 Provision = 0x47,
137 StockCompensation = 0x48,
139 IndustryBenchmark = 0x49,
141 Governance = 0x4A,
143 OrganizationalProfile = 0x4B,
145 ItControls = 0x4C,
147 ManagementReport = 0x4D,
149 PriorYear = 0x4E,
151 LegalDocument = 0x4F,
153 ExternalExpectation = 0x50,
155 EvidenceAnchor = 0x51,
157}
158
159#[derive(Debug)]
176pub struct DeterministicUuidFactory {
177 seed: u64,
178 generator_type: GeneratorType,
179 counter: AtomicU64,
180 sub_discriminator: u8,
182}
183
184impl DeterministicUuidFactory {
185 pub fn new(seed: u64, generator_type: GeneratorType) -> Self {
201 Self {
202 seed,
203 generator_type,
204 counter: AtomicU64::new(0),
205 sub_discriminator: 0,
206 }
207 }
208
209 pub fn with_sub_discriminator(
213 seed: u64,
214 generator_type: GeneratorType,
215 sub_discriminator: u8,
216 ) -> Self {
217 Self {
218 seed,
219 generator_type,
220 counter: AtomicU64::new(0),
221 sub_discriminator,
222 }
223 }
224
225 pub fn with_counter(seed: u64, generator_type: GeneratorType, start_counter: u64) -> Self {
230 Self {
231 seed,
232 generator_type,
233 counter: AtomicU64::new(start_counter),
234 sub_discriminator: 0,
235 }
236 }
237
238 pub fn for_partition(seed: u64, generator_type: GeneratorType, partition_index: u8) -> Self {
244 Self {
245 seed,
246 generator_type,
247 counter: AtomicU64::new(0),
248 sub_discriminator: partition_index,
249 }
250 }
251
252 #[inline]
256 pub fn next(&self) -> Uuid {
257 let counter = self.counter.fetch_add(1, Ordering::Relaxed);
258 self.generate_uuid(counter)
259 }
260
261 pub fn generate_at(&self, counter: u64) -> Uuid {
265 self.generate_uuid(counter)
266 }
267
268 pub fn current_counter(&self) -> u64 {
270 self.counter.load(Ordering::Relaxed)
271 }
272
273 pub fn reset(&self) {
275 self.counter.store(0, Ordering::Relaxed);
276 }
277
278 pub fn set_counter(&self, value: u64) {
280 self.counter.store(value, Ordering::Relaxed);
281 }
282
283 #[inline]
289 fn generate_uuid(&self, counter: u64) -> Uuid {
290 let mut hash: u64 = 14695981039346656037; for byte in self.seed.to_le_bytes() {
296 hash ^= byte as u64;
297 hash = hash.wrapping_mul(1099511628211); }
299
300 hash ^= self.generator_type as u64;
302 hash = hash.wrapping_mul(1099511628211);
303
304 hash ^= self.sub_discriminator as u64;
306 hash = hash.wrapping_mul(1099511628211);
307
308 for byte in counter.to_le_bytes() {
310 hash ^= byte as u64;
311 hash = hash.wrapping_mul(1099511628211);
312 }
313
314 let mut hash2: u64 = hash;
316 hash2 ^= self.seed.rotate_left(32);
317 hash2 = hash2.wrapping_mul(1099511628211);
318 hash2 ^= counter.rotate_left(32);
319 hash2 = hash2.wrapping_mul(1099511628211);
320
321 let mut bytes = [0u8; 16];
322
323 bytes[0..8].copy_from_slice(&hash.to_le_bytes());
325 bytes[8..16].copy_from_slice(&hash2.to_le_bytes());
327
328 bytes[6] = (bytes[6] & 0x0f) | 0x40;
331
332 bytes[8] = (bytes[8] & 0x3f) | 0x80;
335
336 Uuid::from_bytes(bytes)
337 }
338}
339
340impl Clone for DeterministicUuidFactory {
341 fn clone(&self) -> Self {
342 Self {
343 seed: self.seed,
344 generator_type: self.generator_type,
345 counter: AtomicU64::new(self.counter.load(Ordering::Relaxed)),
346 sub_discriminator: self.sub_discriminator,
347 }
348 }
349}
350
351#[derive(Debug)]
355pub struct UuidFactoryRegistry {
356 seed: u64,
357 factories: std::collections::HashMap<GeneratorType, DeterministicUuidFactory>,
358}
359
360impl UuidFactoryRegistry {
361 pub fn new(seed: u64) -> Self {
363 Self {
364 seed,
365 factories: std::collections::HashMap::new(),
366 }
367 }
368
369 pub fn get_factory(&mut self, generator_type: GeneratorType) -> &DeterministicUuidFactory {
371 self.factories
372 .entry(generator_type)
373 .or_insert_with(|| DeterministicUuidFactory::new(self.seed, generator_type))
374 }
375
376 pub fn next_uuid(&mut self, generator_type: GeneratorType) -> Uuid {
378 self.get_factory(generator_type).next()
379 }
380
381 pub fn reset_all(&self) {
383 for factory in self.factories.values() {
384 factory.reset();
385 }
386 }
387
388 pub fn get_counter(&self, generator_type: GeneratorType) -> Option<u64> {
390 self.factories
391 .get(&generator_type)
392 .map(DeterministicUuidFactory::current_counter)
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399 use std::collections::HashSet;
400 use std::thread;
401
402 #[test]
403 fn test_uuid_uniqueness_same_generator() {
404 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
405
406 let mut uuids = HashSet::new();
407 for _ in 0..10000 {
408 let uuid = factory.next();
409 assert!(uuids.insert(uuid), "Duplicate UUID generated");
410 }
411 }
412
413 #[test]
414 fn test_uuid_uniqueness_different_generators() {
415 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
416 let factory2 = DeterministicUuidFactory::new(12345, GeneratorType::DocumentFlow);
417
418 let mut uuids = HashSet::new();
419
420 for _ in 0..5000 {
421 let uuid1 = factory1.next();
422 let uuid2 = factory2.next();
423 assert!(uuids.insert(uuid1), "Duplicate UUID from JE generator");
424 assert!(uuids.insert(uuid2), "Duplicate UUID from DocFlow generator");
425 }
426 }
427
428 #[test]
429 fn test_uuid_determinism() {
430 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
431 let factory2 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
432
433 for _ in 0..100 {
434 assert_eq!(factory1.next(), factory2.next());
435 }
436 }
437
438 #[test]
439 fn test_uuid_different_seeds() {
440 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
441 let factory2 = DeterministicUuidFactory::new(67890, GeneratorType::JournalEntry);
442
443 assert_ne!(factory1.next(), factory2.next());
445 }
446
447 #[test]
448 fn test_thread_safety() {
449 use std::sync::Arc;
450
451 let factory = Arc::new(DeterministicUuidFactory::new(
452 12345,
453 GeneratorType::JournalEntry,
454 ));
455 let mut handles = vec![];
456
457 for _ in 0..4 {
458 let factory_clone = Arc::clone(&factory);
459 handles.push(thread::spawn(move || {
460 let mut uuids = Vec::new();
461 for _ in 0..1000 {
462 uuids.push(factory_clone.next());
463 }
464 uuids
465 }));
466 }
467
468 let mut all_uuids = HashSet::new();
469 for handle in handles {
470 let uuids = handle.join().unwrap();
471 for uuid in uuids {
472 assert!(all_uuids.insert(uuid), "Thread-generated UUID collision");
473 }
474 }
475
476 assert_eq!(all_uuids.len(), 4000);
477 }
478
479 #[test]
480 fn test_sub_discriminator() {
481 let factory1 =
482 DeterministicUuidFactory::with_sub_discriminator(12345, GeneratorType::JournalEntry, 0);
483 let factory2 =
484 DeterministicUuidFactory::with_sub_discriminator(12345, GeneratorType::JournalEntry, 1);
485
486 let uuid1 = factory1.next();
488 factory1.reset();
489 let uuid2 = factory2.next();
490
491 assert_ne!(uuid1, uuid2);
492 }
493
494 #[test]
495 fn test_generate_at() {
496 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
497
498 let uuid_at_5 = factory.generate_at(5);
500
501 for _ in 0..5 {
503 factory.next();
504 }
505 let _uuid_sequential = factory.next();
506
507 assert_eq!(uuid_at_5, factory.generate_at(5));
509 }
510
511 #[test]
512 fn test_registry() {
513 let mut registry = UuidFactoryRegistry::new(12345);
514
515 let uuid1 = registry.next_uuid(GeneratorType::JournalEntry);
516 let uuid2 = registry.next_uuid(GeneratorType::JournalEntry);
517 let uuid3 = registry.next_uuid(GeneratorType::DocumentFlow);
518
519 assert_ne!(uuid1, uuid2);
521 assert_ne!(uuid1, uuid3);
522 assert_ne!(uuid2, uuid3);
523
524 assert_eq!(registry.get_counter(GeneratorType::JournalEntry), Some(2));
526 assert_eq!(registry.get_counter(GeneratorType::DocumentFlow), Some(1));
527 }
528
529 #[test]
530 fn test_uuid_is_valid_v4() {
531 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
532 let uuid = factory.next();
533
534 assert_eq!(uuid.get_version_num(), 4);
536
537 assert_eq!(uuid.get_variant(), uuid::Variant::RFC4122);
539 }
540}