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
73#[derive(Debug)]
90pub struct DeterministicUuidFactory {
91 seed: u64,
92 generator_type: GeneratorType,
93 counter: AtomicU64,
94 sub_discriminator: u8,
96}
97
98impl DeterministicUuidFactory {
99 pub fn new(seed: u64, generator_type: GeneratorType) -> Self {
115 Self {
116 seed,
117 generator_type,
118 counter: AtomicU64::new(0),
119 sub_discriminator: 0,
120 }
121 }
122
123 pub fn with_sub_discriminator(
127 seed: u64,
128 generator_type: GeneratorType,
129 sub_discriminator: u8,
130 ) -> Self {
131 Self {
132 seed,
133 generator_type,
134 counter: AtomicU64::new(0),
135 sub_discriminator,
136 }
137 }
138
139 pub fn with_counter(seed: u64, generator_type: GeneratorType, start_counter: u64) -> Self {
143 Self {
144 seed,
145 generator_type,
146 counter: AtomicU64::new(start_counter),
147 sub_discriminator: 0,
148 }
149 }
150
151 pub fn next(&self) -> Uuid {
155 let counter = self.counter.fetch_add(1, Ordering::Relaxed);
156 self.generate_uuid(counter)
157 }
158
159 pub fn generate_at(&self, counter: u64) -> Uuid {
163 self.generate_uuid(counter)
164 }
165
166 pub fn current_counter(&self) -> u64 {
168 self.counter.load(Ordering::Relaxed)
169 }
170
171 pub fn reset(&self) {
173 self.counter.store(0, Ordering::Relaxed);
174 }
175
176 pub fn set_counter(&self, value: u64) {
178 self.counter.store(value, Ordering::Relaxed);
179 }
180
181 fn generate_uuid(&self, counter: u64) -> Uuid {
187 let mut hash: u64 = 14695981039346656037; for byte in self.seed.to_le_bytes() {
193 hash ^= byte as u64;
194 hash = hash.wrapping_mul(1099511628211); }
196
197 hash ^= self.generator_type as u64;
199 hash = hash.wrapping_mul(1099511628211);
200
201 hash ^= self.sub_discriminator as u64;
203 hash = hash.wrapping_mul(1099511628211);
204
205 for byte in counter.to_le_bytes() {
207 hash ^= byte as u64;
208 hash = hash.wrapping_mul(1099511628211);
209 }
210
211 let mut hash2: u64 = hash;
213 hash2 ^= self.seed.rotate_left(32);
214 hash2 = hash2.wrapping_mul(1099511628211);
215 hash2 ^= counter.rotate_left(32);
216 hash2 = hash2.wrapping_mul(1099511628211);
217
218 let mut bytes = [0u8; 16];
219
220 bytes[0..8].copy_from_slice(&hash.to_le_bytes());
222 bytes[8..16].copy_from_slice(&hash2.to_le_bytes());
224
225 bytes[6] = (bytes[6] & 0x0f) | 0x40;
228
229 bytes[8] = (bytes[8] & 0x3f) | 0x80;
232
233 Uuid::from_bytes(bytes)
234 }
235}
236
237impl Clone for DeterministicUuidFactory {
238 fn clone(&self) -> Self {
239 Self {
240 seed: self.seed,
241 generator_type: self.generator_type,
242 counter: AtomicU64::new(self.counter.load(Ordering::Relaxed)),
243 sub_discriminator: self.sub_discriminator,
244 }
245 }
246}
247
248#[derive(Debug)]
252pub struct UuidFactoryRegistry {
253 seed: u64,
254 factories: std::collections::HashMap<GeneratorType, DeterministicUuidFactory>,
255}
256
257impl UuidFactoryRegistry {
258 pub fn new(seed: u64) -> Self {
260 Self {
261 seed,
262 factories: std::collections::HashMap::new(),
263 }
264 }
265
266 pub fn get_factory(&mut self, generator_type: GeneratorType) -> &DeterministicUuidFactory {
268 self.factories
269 .entry(generator_type)
270 .or_insert_with(|| DeterministicUuidFactory::new(self.seed, generator_type))
271 }
272
273 pub fn next_uuid(&mut self, generator_type: GeneratorType) -> Uuid {
275 self.get_factory(generator_type).next()
276 }
277
278 pub fn reset_all(&self) {
280 for factory in self.factories.values() {
281 factory.reset();
282 }
283 }
284
285 pub fn get_counter(&self, generator_type: GeneratorType) -> Option<u64> {
287 self.factories
288 .get(&generator_type)
289 .map(|f| f.current_counter())
290 }
291}
292
293#[cfg(test)]
294#[allow(clippy::unwrap_used)]
295mod tests {
296 use super::*;
297 use std::collections::HashSet;
298 use std::thread;
299
300 #[test]
301 fn test_uuid_uniqueness_same_generator() {
302 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
303
304 let mut uuids = HashSet::new();
305 for _ in 0..10000 {
306 let uuid = factory.next();
307 assert!(uuids.insert(uuid), "Duplicate UUID generated");
308 }
309 }
310
311 #[test]
312 fn test_uuid_uniqueness_different_generators() {
313 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
314 let factory2 = DeterministicUuidFactory::new(12345, GeneratorType::DocumentFlow);
315
316 let mut uuids = HashSet::new();
317
318 for _ in 0..5000 {
319 let uuid1 = factory1.next();
320 let uuid2 = factory2.next();
321 assert!(uuids.insert(uuid1), "Duplicate UUID from JE generator");
322 assert!(uuids.insert(uuid2), "Duplicate UUID from DocFlow generator");
323 }
324 }
325
326 #[test]
327 fn test_uuid_determinism() {
328 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
329 let factory2 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
330
331 for _ in 0..100 {
332 assert_eq!(factory1.next(), factory2.next());
333 }
334 }
335
336 #[test]
337 fn test_uuid_different_seeds() {
338 let factory1 = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
339 let factory2 = DeterministicUuidFactory::new(67890, GeneratorType::JournalEntry);
340
341 assert_ne!(factory1.next(), factory2.next());
343 }
344
345 #[test]
346 fn test_thread_safety() {
347 use std::sync::Arc;
348
349 let factory = Arc::new(DeterministicUuidFactory::new(
350 12345,
351 GeneratorType::JournalEntry,
352 ));
353 let mut handles = vec![];
354
355 for _ in 0..4 {
356 let factory_clone = Arc::clone(&factory);
357 handles.push(thread::spawn(move || {
358 let mut uuids = Vec::new();
359 for _ in 0..1000 {
360 uuids.push(factory_clone.next());
361 }
362 uuids
363 }));
364 }
365
366 let mut all_uuids = HashSet::new();
367 for handle in handles {
368 let uuids = handle.join().unwrap();
369 for uuid in uuids {
370 assert!(all_uuids.insert(uuid), "Thread-generated UUID collision");
371 }
372 }
373
374 assert_eq!(all_uuids.len(), 4000);
375 }
376
377 #[test]
378 fn test_sub_discriminator() {
379 let factory1 =
380 DeterministicUuidFactory::with_sub_discriminator(12345, GeneratorType::JournalEntry, 0);
381 let factory2 =
382 DeterministicUuidFactory::with_sub_discriminator(12345, GeneratorType::JournalEntry, 1);
383
384 let uuid1 = factory1.next();
386 factory1.reset();
387 let uuid2 = factory2.next();
388
389 assert_ne!(uuid1, uuid2);
390 }
391
392 #[test]
393 fn test_generate_at() {
394 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
395
396 let uuid_at_5 = factory.generate_at(5);
398
399 for _ in 0..5 {
401 factory.next();
402 }
403 let _uuid_sequential = factory.next();
404
405 assert_eq!(uuid_at_5, factory.generate_at(5));
407 }
408
409 #[test]
410 fn test_registry() {
411 let mut registry = UuidFactoryRegistry::new(12345);
412
413 let uuid1 = registry.next_uuid(GeneratorType::JournalEntry);
414 let uuid2 = registry.next_uuid(GeneratorType::JournalEntry);
415 let uuid3 = registry.next_uuid(GeneratorType::DocumentFlow);
416
417 assert_ne!(uuid1, uuid2);
419 assert_ne!(uuid1, uuid3);
420 assert_ne!(uuid2, uuid3);
421
422 assert_eq!(registry.get_counter(GeneratorType::JournalEntry), Some(2));
424 assert_eq!(registry.get_counter(GeneratorType::DocumentFlow), Some(1));
425 }
426
427 #[test]
428 fn test_uuid_is_valid_v4() {
429 let factory = DeterministicUuidFactory::new(12345, GeneratorType::JournalEntry);
430 let uuid = factory.next();
431
432 assert_eq!(uuid.get_version_num(), 4);
434
435 assert_eq!(uuid.get_variant(), uuid::Variant::RFC4122);
437 }
438}