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}
116
117#[derive(Debug)]
134pub struct DeterministicUuidFactory {
135 seed: u64,
136 generator_type: GeneratorType,
137 counter: AtomicU64,
138 sub_discriminator: u8,
140}
141
142impl DeterministicUuidFactory {
143 pub fn new(seed: u64, generator_type: GeneratorType) -> Self {
159 Self {
160 seed,
161 generator_type,
162 counter: AtomicU64::new(0),
163 sub_discriminator: 0,
164 }
165 }
166
167 pub fn with_sub_discriminator(
171 seed: u64,
172 generator_type: GeneratorType,
173 sub_discriminator: u8,
174 ) -> Self {
175 Self {
176 seed,
177 generator_type,
178 counter: AtomicU64::new(0),
179 sub_discriminator,
180 }
181 }
182
183 pub fn with_counter(seed: u64, generator_type: GeneratorType, start_counter: u64) -> Self {
187 Self {
188 seed,
189 generator_type,
190 counter: AtomicU64::new(start_counter),
191 sub_discriminator: 0,
192 }
193 }
194
195 pub fn next(&self) -> Uuid {
199 let counter = self.counter.fetch_add(1, Ordering::Relaxed);
200 self.generate_uuid(counter)
201 }
202
203 pub fn generate_at(&self, counter: u64) -> Uuid {
207 self.generate_uuid(counter)
208 }
209
210 pub fn current_counter(&self) -> u64 {
212 self.counter.load(Ordering::Relaxed)
213 }
214
215 pub fn reset(&self) {
217 self.counter.store(0, Ordering::Relaxed);
218 }
219
220 pub fn set_counter(&self, value: u64) {
222 self.counter.store(value, Ordering::Relaxed);
223 }
224
225 fn generate_uuid(&self, counter: u64) -> Uuid {
231 let mut hash: u64 = 14695981039346656037; for byte in self.seed.to_le_bytes() {
237 hash ^= byte as u64;
238 hash = hash.wrapping_mul(1099511628211); }
240
241 hash ^= self.generator_type as u64;
243 hash = hash.wrapping_mul(1099511628211);
244
245 hash ^= self.sub_discriminator as u64;
247 hash = hash.wrapping_mul(1099511628211);
248
249 for byte in counter.to_le_bytes() {
251 hash ^= byte as u64;
252 hash = hash.wrapping_mul(1099511628211);
253 }
254
255 let mut hash2: u64 = hash;
257 hash2 ^= self.seed.rotate_left(32);
258 hash2 = hash2.wrapping_mul(1099511628211);
259 hash2 ^= counter.rotate_left(32);
260 hash2 = hash2.wrapping_mul(1099511628211);
261
262 let mut bytes = [0u8; 16];
263
264 bytes[0..8].copy_from_slice(&hash.to_le_bytes());
266 bytes[8..16].copy_from_slice(&hash2.to_le_bytes());
268
269 bytes[6] = (bytes[6] & 0x0f) | 0x40;
272
273 bytes[8] = (bytes[8] & 0x3f) | 0x80;
276
277 Uuid::from_bytes(bytes)
278 }
279}
280
281impl Clone for DeterministicUuidFactory {
282 fn clone(&self) -> Self {
283 Self {
284 seed: self.seed,
285 generator_type: self.generator_type,
286 counter: AtomicU64::new(self.counter.load(Ordering::Relaxed)),
287 sub_discriminator: self.sub_discriminator,
288 }
289 }
290}
291
292#[derive(Debug)]
296pub struct UuidFactoryRegistry {
297 seed: u64,
298 factories: std::collections::HashMap<GeneratorType, DeterministicUuidFactory>,
299}
300
301impl UuidFactoryRegistry {
302 pub fn new(seed: u64) -> Self {
304 Self {
305 seed,
306 factories: std::collections::HashMap::new(),
307 }
308 }
309
310 pub fn get_factory(&mut self, generator_type: GeneratorType) -> &DeterministicUuidFactory {
312 self.factories
313 .entry(generator_type)
314 .or_insert_with(|| DeterministicUuidFactory::new(self.seed, generator_type))
315 }
316
317 pub fn next_uuid(&mut self, generator_type: GeneratorType) -> Uuid {
319 self.get_factory(generator_type).next()
320 }
321
322 pub fn reset_all(&self) {
324 for factory in self.factories.values() {
325 factory.reset();
326 }
327 }
328
329 pub fn get_counter(&self, generator_type: GeneratorType) -> Option<u64> {
331 self.factories
332 .get(&generator_type)
333 .map(|f| f.current_counter())
334 }
335}
336
337#[cfg(test)]
338#[allow(clippy::unwrap_used)]
339mod tests {
340 use super::*;
341 use std::collections::HashSet;
342 use std::thread;
343
344 #[test]
345 fn test_uuid_uniqueness_same_generator() {
346 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
347
348 let mut uuids = HashSet::new();
349 for _ in 0..10000 {
350 let uuid = factory.next();
351 assert!(uuids.insert(uuid), "Duplicate UUID generated");
352 }
353 }
354
355 #[test]
356 fn test_uuid_uniqueness_different_generators() {
357 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
358 let factory2 = DeterministicUuidFactory::new(12345, GeneratorType::DocumentFlow);
359
360 let mut uuids = HashSet::new();
361
362 for _ in 0..5000 {
363 let uuid1 = factory1.next();
364 let uuid2 = factory2.next();
365 assert!(uuids.insert(uuid1), "Duplicate UUID from JE generator");
366 assert!(uuids.insert(uuid2), "Duplicate UUID from DocFlow generator");
367 }
368 }
369
370 #[test]
371 fn test_uuid_determinism() {
372 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
373 let factory2 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
374
375 for _ in 0..100 {
376 assert_eq!(factory1.next(), factory2.next());
377 }
378 }
379
380 #[test]
381 fn test_uuid_different_seeds() {
382 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
383 let factory2 = DeterministicUuidFactory::new(67890, GeneratorType::JournalEntry);
384
385 assert_ne!(factory1.next(), factory2.next());
387 }
388
389 #[test]
390 fn test_thread_safety() {
391 use std::sync::Arc;
392
393 let factory = Arc::new(DeterministicUuidFactory::new(
394 12345,
395 GeneratorType::JournalEntry,
396 ));
397 let mut handles = vec![];
398
399 for _ in 0..4 {
400 let factory_clone = Arc::clone(&factory);
401 handles.push(thread::spawn(move || {
402 let mut uuids = Vec::new();
403 for _ in 0..1000 {
404 uuids.push(factory_clone.next());
405 }
406 uuids
407 }));
408 }
409
410 let mut all_uuids = HashSet::new();
411 for handle in handles {
412 let uuids = handle.join().unwrap();
413 for uuid in uuids {
414 assert!(all_uuids.insert(uuid), "Thread-generated UUID collision");
415 }
416 }
417
418 assert_eq!(all_uuids.len(), 4000);
419 }
420
421 #[test]
422 fn test_sub_discriminator() {
423 let factory1 =
424 DeterministicUuidFactory::with_sub_discriminator(12345, GeneratorType::JournalEntry, 0);
425 let factory2 =
426 DeterministicUuidFactory::with_sub_discriminator(12345, GeneratorType::JournalEntry, 1);
427
428 let uuid1 = factory1.next();
430 factory1.reset();
431 let uuid2 = factory2.next();
432
433 assert_ne!(uuid1, uuid2);
434 }
435
436 #[test]
437 fn test_generate_at() {
438 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
439
440 let uuid_at_5 = factory.generate_at(5);
442
443 for _ in 0..5 {
445 factory.next();
446 }
447 let _uuid_sequential = factory.next();
448
449 assert_eq!(uuid_at_5, factory.generate_at(5));
451 }
452
453 #[test]
454 fn test_registry() {
455 let mut registry = UuidFactoryRegistry::new(12345);
456
457 let uuid1 = registry.next_uuid(GeneratorType::JournalEntry);
458 let uuid2 = registry.next_uuid(GeneratorType::JournalEntry);
459 let uuid3 = registry.next_uuid(GeneratorType::DocumentFlow);
460
461 assert_ne!(uuid1, uuid2);
463 assert_ne!(uuid1, uuid3);
464 assert_ne!(uuid2, uuid3);
465
466 assert_eq!(registry.get_counter(GeneratorType::JournalEntry), Some(2));
468 assert_eq!(registry.get_counter(GeneratorType::DocumentFlow), Some(1));
469 }
470
471 #[test]
472 fn test_uuid_is_valid_v4() {
473 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
474 let uuid = factory.next();
475
476 assert_eq!(uuid.get_version_num(), 4);
478
479 assert_eq!(uuid.get_variant(), uuid::Variant::RFC4122);
481 }
482}