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}
120
121#[derive(Debug)]
138pub struct DeterministicUuidFactory {
139 seed: u64,
140 generator_type: GeneratorType,
141 counter: AtomicU64,
142 sub_discriminator: u8,
144}
145
146impl DeterministicUuidFactory {
147 pub fn new(seed: u64, generator_type: GeneratorType) -> Self {
163 Self {
164 seed,
165 generator_type,
166 counter: AtomicU64::new(0),
167 sub_discriminator: 0,
168 }
169 }
170
171 pub fn with_sub_discriminator(
175 seed: u64,
176 generator_type: GeneratorType,
177 sub_discriminator: u8,
178 ) -> Self {
179 Self {
180 seed,
181 generator_type,
182 counter: AtomicU64::new(0),
183 sub_discriminator,
184 }
185 }
186
187 pub fn with_counter(seed: u64, generator_type: GeneratorType, start_counter: u64) -> Self {
192 Self {
193 seed,
194 generator_type,
195 counter: AtomicU64::new(start_counter),
196 sub_discriminator: 0,
197 }
198 }
199
200 pub fn for_partition(seed: u64, generator_type: GeneratorType, partition_index: u8) -> Self {
206 Self {
207 seed,
208 generator_type,
209 counter: AtomicU64::new(0),
210 sub_discriminator: partition_index,
211 }
212 }
213
214 #[inline]
218 pub fn next(&self) -> Uuid {
219 let counter = self.counter.fetch_add(1, Ordering::Relaxed);
220 self.generate_uuid(counter)
221 }
222
223 pub fn generate_at(&self, counter: u64) -> Uuid {
227 self.generate_uuid(counter)
228 }
229
230 pub fn current_counter(&self) -> u64 {
232 self.counter.load(Ordering::Relaxed)
233 }
234
235 pub fn reset(&self) {
237 self.counter.store(0, Ordering::Relaxed);
238 }
239
240 pub fn set_counter(&self, value: u64) {
242 self.counter.store(value, Ordering::Relaxed);
243 }
244
245 #[inline]
251 fn generate_uuid(&self, counter: u64) -> Uuid {
252 let mut hash: u64 = 14695981039346656037; for byte in self.seed.to_le_bytes() {
258 hash ^= byte as u64;
259 hash = hash.wrapping_mul(1099511628211); }
261
262 hash ^= self.generator_type as u64;
264 hash = hash.wrapping_mul(1099511628211);
265
266 hash ^= self.sub_discriminator as u64;
268 hash = hash.wrapping_mul(1099511628211);
269
270 for byte in counter.to_le_bytes() {
272 hash ^= byte as u64;
273 hash = hash.wrapping_mul(1099511628211);
274 }
275
276 let mut hash2: u64 = hash;
278 hash2 ^= self.seed.rotate_left(32);
279 hash2 = hash2.wrapping_mul(1099511628211);
280 hash2 ^= counter.rotate_left(32);
281 hash2 = hash2.wrapping_mul(1099511628211);
282
283 let mut bytes = [0u8; 16];
284
285 bytes[0..8].copy_from_slice(&hash.to_le_bytes());
287 bytes[8..16].copy_from_slice(&hash2.to_le_bytes());
289
290 bytes[6] = (bytes[6] & 0x0f) | 0x40;
293
294 bytes[8] = (bytes[8] & 0x3f) | 0x80;
297
298 Uuid::from_bytes(bytes)
299 }
300}
301
302impl Clone for DeterministicUuidFactory {
303 fn clone(&self) -> Self {
304 Self {
305 seed: self.seed,
306 generator_type: self.generator_type,
307 counter: AtomicU64::new(self.counter.load(Ordering::Relaxed)),
308 sub_discriminator: self.sub_discriminator,
309 }
310 }
311}
312
313#[derive(Debug)]
317pub struct UuidFactoryRegistry {
318 seed: u64,
319 factories: std::collections::HashMap<GeneratorType, DeterministicUuidFactory>,
320}
321
322impl UuidFactoryRegistry {
323 pub fn new(seed: u64) -> Self {
325 Self {
326 seed,
327 factories: std::collections::HashMap::new(),
328 }
329 }
330
331 pub fn get_factory(&mut self, generator_type: GeneratorType) -> &DeterministicUuidFactory {
333 self.factories
334 .entry(generator_type)
335 .or_insert_with(|| DeterministicUuidFactory::new(self.seed, generator_type))
336 }
337
338 pub fn next_uuid(&mut self, generator_type: GeneratorType) -> Uuid {
340 self.get_factory(generator_type).next()
341 }
342
343 pub fn reset_all(&self) {
345 for factory in self.factories.values() {
346 factory.reset();
347 }
348 }
349
350 pub fn get_counter(&self, generator_type: GeneratorType) -> Option<u64> {
352 self.factories
353 .get(&generator_type)
354 .map(|f| f.current_counter())
355 }
356}
357
358#[cfg(test)]
359#[allow(clippy::unwrap_used)]
360mod tests {
361 use super::*;
362 use std::collections::HashSet;
363 use std::thread;
364
365 #[test]
366 fn test_uuid_uniqueness_same_generator() {
367 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
368
369 let mut uuids = HashSet::new();
370 for _ in 0..10000 {
371 let uuid = factory.next();
372 assert!(uuids.insert(uuid), "Duplicate UUID generated");
373 }
374 }
375
376 #[test]
377 fn test_uuid_uniqueness_different_generators() {
378 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
379 let factory2 = DeterministicUuidFactory::new(12345, GeneratorType::DocumentFlow);
380
381 let mut uuids = HashSet::new();
382
383 for _ in 0..5000 {
384 let uuid1 = factory1.next();
385 let uuid2 = factory2.next();
386 assert!(uuids.insert(uuid1), "Duplicate UUID from JE generator");
387 assert!(uuids.insert(uuid2), "Duplicate UUID from DocFlow generator");
388 }
389 }
390
391 #[test]
392 fn test_uuid_determinism() {
393 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
394 let factory2 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
395
396 for _ in 0..100 {
397 assert_eq!(factory1.next(), factory2.next());
398 }
399 }
400
401 #[test]
402 fn test_uuid_different_seeds() {
403 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
404 let factory2 = DeterministicUuidFactory::new(67890, GeneratorType::JournalEntry);
405
406 assert_ne!(factory1.next(), factory2.next());
408 }
409
410 #[test]
411 fn test_thread_safety() {
412 use std::sync::Arc;
413
414 let factory = Arc::new(DeterministicUuidFactory::new(
415 12345,
416 GeneratorType::JournalEntry,
417 ));
418 let mut handles = vec![];
419
420 for _ in 0..4 {
421 let factory_clone = Arc::clone(&factory);
422 handles.push(thread::spawn(move || {
423 let mut uuids = Vec::new();
424 for _ in 0..1000 {
425 uuids.push(factory_clone.next());
426 }
427 uuids
428 }));
429 }
430
431 let mut all_uuids = HashSet::new();
432 for handle in handles {
433 let uuids = handle.join().unwrap();
434 for uuid in uuids {
435 assert!(all_uuids.insert(uuid), "Thread-generated UUID collision");
436 }
437 }
438
439 assert_eq!(all_uuids.len(), 4000);
440 }
441
442 #[test]
443 fn test_sub_discriminator() {
444 let factory1 =
445 DeterministicUuidFactory::with_sub_discriminator(12345, GeneratorType::JournalEntry, 0);
446 let factory2 =
447 DeterministicUuidFactory::with_sub_discriminator(12345, GeneratorType::JournalEntry, 1);
448
449 let uuid1 = factory1.next();
451 factory1.reset();
452 let uuid2 = factory2.next();
453
454 assert_ne!(uuid1, uuid2);
455 }
456
457 #[test]
458 fn test_generate_at() {
459 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
460
461 let uuid_at_5 = factory.generate_at(5);
463
464 for _ in 0..5 {
466 factory.next();
467 }
468 let _uuid_sequential = factory.next();
469
470 assert_eq!(uuid_at_5, factory.generate_at(5));
472 }
473
474 #[test]
475 fn test_registry() {
476 let mut registry = UuidFactoryRegistry::new(12345);
477
478 let uuid1 = registry.next_uuid(GeneratorType::JournalEntry);
479 let uuid2 = registry.next_uuid(GeneratorType::JournalEntry);
480 let uuid3 = registry.next_uuid(GeneratorType::DocumentFlow);
481
482 assert_ne!(uuid1, uuid2);
484 assert_ne!(uuid1, uuid3);
485 assert_ne!(uuid2, uuid3);
486
487 assert_eq!(registry.get_counter(GeneratorType::JournalEntry), Some(2));
489 assert_eq!(registry.get_counter(GeneratorType::DocumentFlow), Some(1));
490 }
491
492 #[test]
493 fn test_uuid_is_valid_v4() {
494 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
495 let uuid = factory.next();
496
497 assert_eq!(uuid.get_version_num(), 4);
499
500 assert_eq!(uuid.get_variant(), uuid::Variant::RFC4122);
502 }
503}