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}
126
127#[derive(Debug)]
144pub struct DeterministicUuidFactory {
145 seed: u64,
146 generator_type: GeneratorType,
147 counter: AtomicU64,
148 sub_discriminator: u8,
150}
151
152impl DeterministicUuidFactory {
153 pub fn new(seed: u64, generator_type: GeneratorType) -> Self {
169 Self {
170 seed,
171 generator_type,
172 counter: AtomicU64::new(0),
173 sub_discriminator: 0,
174 }
175 }
176
177 pub fn with_sub_discriminator(
181 seed: u64,
182 generator_type: GeneratorType,
183 sub_discriminator: u8,
184 ) -> Self {
185 Self {
186 seed,
187 generator_type,
188 counter: AtomicU64::new(0),
189 sub_discriminator,
190 }
191 }
192
193 pub fn with_counter(seed: u64, generator_type: GeneratorType, start_counter: u64) -> Self {
198 Self {
199 seed,
200 generator_type,
201 counter: AtomicU64::new(start_counter),
202 sub_discriminator: 0,
203 }
204 }
205
206 pub fn for_partition(seed: u64, generator_type: GeneratorType, partition_index: u8) -> Self {
212 Self {
213 seed,
214 generator_type,
215 counter: AtomicU64::new(0),
216 sub_discriminator: partition_index,
217 }
218 }
219
220 #[inline]
224 pub fn next(&self) -> Uuid {
225 let counter = self.counter.fetch_add(1, Ordering::Relaxed);
226 self.generate_uuid(counter)
227 }
228
229 pub fn generate_at(&self, counter: u64) -> Uuid {
233 self.generate_uuid(counter)
234 }
235
236 pub fn current_counter(&self) -> u64 {
238 self.counter.load(Ordering::Relaxed)
239 }
240
241 pub fn reset(&self) {
243 self.counter.store(0, Ordering::Relaxed);
244 }
245
246 pub fn set_counter(&self, value: u64) {
248 self.counter.store(value, Ordering::Relaxed);
249 }
250
251 #[inline]
257 fn generate_uuid(&self, counter: u64) -> Uuid {
258 let mut hash: u64 = 14695981039346656037; for byte in self.seed.to_le_bytes() {
264 hash ^= byte as u64;
265 hash = hash.wrapping_mul(1099511628211); }
267
268 hash ^= self.generator_type as u64;
270 hash = hash.wrapping_mul(1099511628211);
271
272 hash ^= self.sub_discriminator as u64;
274 hash = hash.wrapping_mul(1099511628211);
275
276 for byte in counter.to_le_bytes() {
278 hash ^= byte as u64;
279 hash = hash.wrapping_mul(1099511628211);
280 }
281
282 let mut hash2: u64 = hash;
284 hash2 ^= self.seed.rotate_left(32);
285 hash2 = hash2.wrapping_mul(1099511628211);
286 hash2 ^= counter.rotate_left(32);
287 hash2 = hash2.wrapping_mul(1099511628211);
288
289 let mut bytes = [0u8; 16];
290
291 bytes[0..8].copy_from_slice(&hash.to_le_bytes());
293 bytes[8..16].copy_from_slice(&hash2.to_le_bytes());
295
296 bytes[6] = (bytes[6] & 0x0f) | 0x40;
299
300 bytes[8] = (bytes[8] & 0x3f) | 0x80;
303
304 Uuid::from_bytes(bytes)
305 }
306}
307
308impl Clone for DeterministicUuidFactory {
309 fn clone(&self) -> Self {
310 Self {
311 seed: self.seed,
312 generator_type: self.generator_type,
313 counter: AtomicU64::new(self.counter.load(Ordering::Relaxed)),
314 sub_discriminator: self.sub_discriminator,
315 }
316 }
317}
318
319#[derive(Debug)]
323pub struct UuidFactoryRegistry {
324 seed: u64,
325 factories: std::collections::HashMap<GeneratorType, DeterministicUuidFactory>,
326}
327
328impl UuidFactoryRegistry {
329 pub fn new(seed: u64) -> Self {
331 Self {
332 seed,
333 factories: std::collections::HashMap::new(),
334 }
335 }
336
337 pub fn get_factory(&mut self, generator_type: GeneratorType) -> &DeterministicUuidFactory {
339 self.factories
340 .entry(generator_type)
341 .or_insert_with(|| DeterministicUuidFactory::new(self.seed, generator_type))
342 }
343
344 pub fn next_uuid(&mut self, generator_type: GeneratorType) -> Uuid {
346 self.get_factory(generator_type).next()
347 }
348
349 pub fn reset_all(&self) {
351 for factory in self.factories.values() {
352 factory.reset();
353 }
354 }
355
356 pub fn get_counter(&self, generator_type: GeneratorType) -> Option<u64> {
358 self.factories
359 .get(&generator_type)
360 .map(|f| f.current_counter())
361 }
362}
363
364#[cfg(test)]
365#[allow(clippy::unwrap_used)]
366mod tests {
367 use super::*;
368 use std::collections::HashSet;
369 use std::thread;
370
371 #[test]
372 fn test_uuid_uniqueness_same_generator() {
373 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
374
375 let mut uuids = HashSet::new();
376 for _ in 0..10000 {
377 let uuid = factory.next();
378 assert!(uuids.insert(uuid), "Duplicate UUID generated");
379 }
380 }
381
382 #[test]
383 fn test_uuid_uniqueness_different_generators() {
384 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
385 let factory2 = DeterministicUuidFactory::new(12345, GeneratorType::DocumentFlow);
386
387 let mut uuids = HashSet::new();
388
389 for _ in 0..5000 {
390 let uuid1 = factory1.next();
391 let uuid2 = factory2.next();
392 assert!(uuids.insert(uuid1), "Duplicate UUID from JE generator");
393 assert!(uuids.insert(uuid2), "Duplicate UUID from DocFlow generator");
394 }
395 }
396
397 #[test]
398 fn test_uuid_determinism() {
399 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
400 let factory2 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
401
402 for _ in 0..100 {
403 assert_eq!(factory1.next(), factory2.next());
404 }
405 }
406
407 #[test]
408 fn test_uuid_different_seeds() {
409 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
410 let factory2 = DeterministicUuidFactory::new(67890, GeneratorType::JournalEntry);
411
412 assert_ne!(factory1.next(), factory2.next());
414 }
415
416 #[test]
417 fn test_thread_safety() {
418 use std::sync::Arc;
419
420 let factory = Arc::new(DeterministicUuidFactory::new(
421 12345,
422 GeneratorType::JournalEntry,
423 ));
424 let mut handles = vec![];
425
426 for _ in 0..4 {
427 let factory_clone = Arc::clone(&factory);
428 handles.push(thread::spawn(move || {
429 let mut uuids = Vec::new();
430 for _ in 0..1000 {
431 uuids.push(factory_clone.next());
432 }
433 uuids
434 }));
435 }
436
437 let mut all_uuids = HashSet::new();
438 for handle in handles {
439 let uuids = handle.join().unwrap();
440 for uuid in uuids {
441 assert!(all_uuids.insert(uuid), "Thread-generated UUID collision");
442 }
443 }
444
445 assert_eq!(all_uuids.len(), 4000);
446 }
447
448 #[test]
449 fn test_sub_discriminator() {
450 let factory1 =
451 DeterministicUuidFactory::with_sub_discriminator(12345, GeneratorType::JournalEntry, 0);
452 let factory2 =
453 DeterministicUuidFactory::with_sub_discriminator(12345, GeneratorType::JournalEntry, 1);
454
455 let uuid1 = factory1.next();
457 factory1.reset();
458 let uuid2 = factory2.next();
459
460 assert_ne!(uuid1, uuid2);
461 }
462
463 #[test]
464 fn test_generate_at() {
465 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
466
467 let uuid_at_5 = factory.generate_at(5);
469
470 for _ in 0..5 {
472 factory.next();
473 }
474 let _uuid_sequential = factory.next();
475
476 assert_eq!(uuid_at_5, factory.generate_at(5));
478 }
479
480 #[test]
481 fn test_registry() {
482 let mut registry = UuidFactoryRegistry::new(12345);
483
484 let uuid1 = registry.next_uuid(GeneratorType::JournalEntry);
485 let uuid2 = registry.next_uuid(GeneratorType::JournalEntry);
486 let uuid3 = registry.next_uuid(GeneratorType::DocumentFlow);
487
488 assert_ne!(uuid1, uuid2);
490 assert_ne!(uuid1, uuid3);
491 assert_ne!(uuid2, uuid3);
492
493 assert_eq!(registry.get_counter(GeneratorType::JournalEntry), Some(2));
495 assert_eq!(registry.get_counter(GeneratorType::DocumentFlow), Some(1));
496 }
497
498 #[test]
499 fn test_uuid_is_valid_v4() {
500 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
501 let uuid = factory.next();
502
503 assert_eq!(uuid.get_version_num(), 4);
505
506 assert_eq!(uuid.get_variant(), uuid::Variant::RFC4122);
508 }
509}