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}
140
141#[derive(Debug)]
158pub struct DeterministicUuidFactory {
159 seed: u64,
160 generator_type: GeneratorType,
161 counter: AtomicU64,
162 sub_discriminator: u8,
164}
165
166impl DeterministicUuidFactory {
167 pub fn new(seed: u64, generator_type: GeneratorType) -> Self {
183 Self {
184 seed,
185 generator_type,
186 counter: AtomicU64::new(0),
187 sub_discriminator: 0,
188 }
189 }
190
191 pub fn with_sub_discriminator(
195 seed: u64,
196 generator_type: GeneratorType,
197 sub_discriminator: u8,
198 ) -> Self {
199 Self {
200 seed,
201 generator_type,
202 counter: AtomicU64::new(0),
203 sub_discriminator,
204 }
205 }
206
207 pub fn with_counter(seed: u64, generator_type: GeneratorType, start_counter: u64) -> Self {
212 Self {
213 seed,
214 generator_type,
215 counter: AtomicU64::new(start_counter),
216 sub_discriminator: 0,
217 }
218 }
219
220 pub fn for_partition(seed: u64, generator_type: GeneratorType, partition_index: u8) -> Self {
226 Self {
227 seed,
228 generator_type,
229 counter: AtomicU64::new(0),
230 sub_discriminator: partition_index,
231 }
232 }
233
234 #[inline]
238 pub fn next(&self) -> Uuid {
239 let counter = self.counter.fetch_add(1, Ordering::Relaxed);
240 self.generate_uuid(counter)
241 }
242
243 pub fn generate_at(&self, counter: u64) -> Uuid {
247 self.generate_uuid(counter)
248 }
249
250 pub fn current_counter(&self) -> u64 {
252 self.counter.load(Ordering::Relaxed)
253 }
254
255 pub fn reset(&self) {
257 self.counter.store(0, Ordering::Relaxed);
258 }
259
260 pub fn set_counter(&self, value: u64) {
262 self.counter.store(value, Ordering::Relaxed);
263 }
264
265 #[inline]
271 fn generate_uuid(&self, counter: u64) -> Uuid {
272 let mut hash: u64 = 14695981039346656037; for byte in self.seed.to_le_bytes() {
278 hash ^= byte as u64;
279 hash = hash.wrapping_mul(1099511628211); }
281
282 hash ^= self.generator_type as u64;
284 hash = hash.wrapping_mul(1099511628211);
285
286 hash ^= self.sub_discriminator as u64;
288 hash = hash.wrapping_mul(1099511628211);
289
290 for byte in counter.to_le_bytes() {
292 hash ^= byte as u64;
293 hash = hash.wrapping_mul(1099511628211);
294 }
295
296 let mut hash2: u64 = hash;
298 hash2 ^= self.seed.rotate_left(32);
299 hash2 = hash2.wrapping_mul(1099511628211);
300 hash2 ^= counter.rotate_left(32);
301 hash2 = hash2.wrapping_mul(1099511628211);
302
303 let mut bytes = [0u8; 16];
304
305 bytes[0..8].copy_from_slice(&hash.to_le_bytes());
307 bytes[8..16].copy_from_slice(&hash2.to_le_bytes());
309
310 bytes[6] = (bytes[6] & 0x0f) | 0x40;
313
314 bytes[8] = (bytes[8] & 0x3f) | 0x80;
317
318 Uuid::from_bytes(bytes)
319 }
320}
321
322impl Clone for DeterministicUuidFactory {
323 fn clone(&self) -> Self {
324 Self {
325 seed: self.seed,
326 generator_type: self.generator_type,
327 counter: AtomicU64::new(self.counter.load(Ordering::Relaxed)),
328 sub_discriminator: self.sub_discriminator,
329 }
330 }
331}
332
333#[derive(Debug)]
337pub struct UuidFactoryRegistry {
338 seed: u64,
339 factories: std::collections::HashMap<GeneratorType, DeterministicUuidFactory>,
340}
341
342impl UuidFactoryRegistry {
343 pub fn new(seed: u64) -> Self {
345 Self {
346 seed,
347 factories: std::collections::HashMap::new(),
348 }
349 }
350
351 pub fn get_factory(&mut self, generator_type: GeneratorType) -> &DeterministicUuidFactory {
353 self.factories
354 .entry(generator_type)
355 .or_insert_with(|| DeterministicUuidFactory::new(self.seed, generator_type))
356 }
357
358 pub fn next_uuid(&mut self, generator_type: GeneratorType) -> Uuid {
360 self.get_factory(generator_type).next()
361 }
362
363 pub fn reset_all(&self) {
365 for factory in self.factories.values() {
366 factory.reset();
367 }
368 }
369
370 pub fn get_counter(&self, generator_type: GeneratorType) -> Option<u64> {
372 self.factories
373 .get(&generator_type)
374 .map(DeterministicUuidFactory::current_counter)
375 }
376}
377
378#[cfg(test)]
379#[allow(clippy::unwrap_used)]
380mod tests {
381 use super::*;
382 use std::collections::HashSet;
383 use std::thread;
384
385 #[test]
386 fn test_uuid_uniqueness_same_generator() {
387 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
388
389 let mut uuids = HashSet::new();
390 for _ in 0..10000 {
391 let uuid = factory.next();
392 assert!(uuids.insert(uuid), "Duplicate UUID generated");
393 }
394 }
395
396 #[test]
397 fn test_uuid_uniqueness_different_generators() {
398 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
399 let factory2 = DeterministicUuidFactory::new(12345, GeneratorType::DocumentFlow);
400
401 let mut uuids = HashSet::new();
402
403 for _ in 0..5000 {
404 let uuid1 = factory1.next();
405 let uuid2 = factory2.next();
406 assert!(uuids.insert(uuid1), "Duplicate UUID from JE generator");
407 assert!(uuids.insert(uuid2), "Duplicate UUID from DocFlow generator");
408 }
409 }
410
411 #[test]
412 fn test_uuid_determinism() {
413 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
414 let factory2 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
415
416 for _ in 0..100 {
417 assert_eq!(factory1.next(), factory2.next());
418 }
419 }
420
421 #[test]
422 fn test_uuid_different_seeds() {
423 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
424 let factory2 = DeterministicUuidFactory::new(67890, GeneratorType::JournalEntry);
425
426 assert_ne!(factory1.next(), factory2.next());
428 }
429
430 #[test]
431 fn test_thread_safety() {
432 use std::sync::Arc;
433
434 let factory = Arc::new(DeterministicUuidFactory::new(
435 12345,
436 GeneratorType::JournalEntry,
437 ));
438 let mut handles = vec![];
439
440 for _ in 0..4 {
441 let factory_clone = Arc::clone(&factory);
442 handles.push(thread::spawn(move || {
443 let mut uuids = Vec::new();
444 for _ in 0..1000 {
445 uuids.push(factory_clone.next());
446 }
447 uuids
448 }));
449 }
450
451 let mut all_uuids = HashSet::new();
452 for handle in handles {
453 let uuids = handle.join().unwrap();
454 for uuid in uuids {
455 assert!(all_uuids.insert(uuid), "Thread-generated UUID collision");
456 }
457 }
458
459 assert_eq!(all_uuids.len(), 4000);
460 }
461
462 #[test]
463 fn test_sub_discriminator() {
464 let factory1 =
465 DeterministicUuidFactory::with_sub_discriminator(12345, GeneratorType::JournalEntry, 0);
466 let factory2 =
467 DeterministicUuidFactory::with_sub_discriminator(12345, GeneratorType::JournalEntry, 1);
468
469 let uuid1 = factory1.next();
471 factory1.reset();
472 let uuid2 = factory2.next();
473
474 assert_ne!(uuid1, uuid2);
475 }
476
477 #[test]
478 fn test_generate_at() {
479 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
480
481 let uuid_at_5 = factory.generate_at(5);
483
484 for _ in 0..5 {
486 factory.next();
487 }
488 let _uuid_sequential = factory.next();
489
490 assert_eq!(uuid_at_5, factory.generate_at(5));
492 }
493
494 #[test]
495 fn test_registry() {
496 let mut registry = UuidFactoryRegistry::new(12345);
497
498 let uuid1 = registry.next_uuid(GeneratorType::JournalEntry);
499 let uuid2 = registry.next_uuid(GeneratorType::JournalEntry);
500 let uuid3 = registry.next_uuid(GeneratorType::DocumentFlow);
501
502 assert_ne!(uuid1, uuid2);
504 assert_ne!(uuid1, uuid3);
505 assert_ne!(uuid2, uuid3);
506
507 assert_eq!(registry.get_counter(GeneratorType::JournalEntry), Some(2));
509 assert_eq!(registry.get_counter(GeneratorType::DocumentFlow), Some(1));
510 }
511
512 #[test]
513 fn test_uuid_is_valid_v4() {
514 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
515 let uuid = factory.next();
516
517 assert_eq!(uuid.get_version_num(), 4);
519
520 assert_eq!(uuid.get_variant(), uuid::Variant::RFC4122);
522 }
523}