use serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicU64, Ordering};
use uuid::Uuid;
#[derive(Debug, Serialize, Deserialize)]
pub struct UuidGenerator {
namespace: Uuid,
counter: AtomicU64,
}
impl UuidGenerator {
pub fn new(namespace: Uuid) -> Self {
Self {
namespace,
counter: AtomicU64::new(0),
}
}
pub fn next(&self) -> Uuid {
let counter = self.counter.fetch_add(1, Ordering::SeqCst);
let name = counter.to_string();
Uuid::new_v5(&self.namespace, name.as_bytes())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
use std::sync::{Arc, Barrier};
use std::thread;
fn create_test_namespace() -> Uuid {
Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap()
}
#[test]
fn test_uuid_generator_creation() {
let namespace = create_test_namespace();
let generator = UuidGenerator::new(namespace);
assert_eq!(generator.namespace, namespace);
assert_eq!(generator.counter.load(Ordering::SeqCst), 0);
}
#[test]
fn test_uuid_generator_next() {
let generator = UuidGenerator::new(create_test_namespace());
let uuid1 = generator.next();
assert_eq!(generator.counter.load(Ordering::SeqCst), 1);
let uuid2 = generator.next();
assert_eq!(generator.counter.load(Ordering::SeqCst), 2);
assert_ne!(uuid1, uuid2);
assert_eq!(uuid1.get_version(), Some(uuid::Version::Sha1));
assert_eq!(uuid2.get_version(), Some(uuid::Version::Sha1));
}
#[test]
fn test_uuid_generator_deterministic() {
let namespace = create_test_namespace();
let generator1 = UuidGenerator::new(namespace);
let generator2 = UuidGenerator::new(namespace);
assert_eq!(generator1.next(), generator2.next());
assert_eq!(generator1.next(), generator2.next());
}
#[test]
fn test_uuid_generator_different_namespaces() {
let namespace1 = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap();
let namespace2 = Uuid::parse_str("6ba7b811-9dad-11d1-80b4-00c04fd430c8").unwrap();
let generator1 = UuidGenerator::new(namespace1);
let generator2 = UuidGenerator::new(namespace2);
assert_ne!(generator1.next(), generator2.next());
assert_ne!(generator1.next(), generator2.next());
}
#[test]
fn test_uuid_generator_sequential() {
let generator = UuidGenerator::new(create_test_namespace());
let mut uuids = Vec::new();
for _ in 0..100 {
uuids.push(generator.next());
}
let unique_uuids: HashSet<_> = uuids.iter().collect();
assert_eq!(unique_uuids.len(), 100);
assert_eq!(generator.counter.load(Ordering::SeqCst), 100);
}
#[test]
fn test_uuid_generator_thread_safety() {
let generator = Arc::new(UuidGenerator::new(create_test_namespace()));
let num_threads = 10;
let uuids_per_thread = 100;
let total_uuids = num_threads * uuids_per_thread;
let barrier = Arc::new(Barrier::new(num_threads));
let all_uuids = Arc::new(std::sync::Mutex::new(Vec::with_capacity(total_uuids)));
let mut handles = vec![];
for _ in 0..num_threads {
let thread_generator = Arc::clone(&generator);
let thread_barrier = Arc::clone(&barrier);
let thread_uuids = Arc::clone(&all_uuids);
let handle = thread::spawn(move || {
thread_barrier.wait();
let mut local_uuids = Vec::with_capacity(uuids_per_thread);
for _ in 0..uuids_per_thread {
local_uuids.push(thread_generator.next());
}
let mut all = thread_uuids.lock().unwrap();
all.extend(local_uuids);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let all_uuids = all_uuids.lock().unwrap();
let unique_uuids: HashSet<_> = all_uuids.iter().collect();
assert_eq!(
unique_uuids.len(),
total_uuids,
"All generated UUIDs should be unique"
);
assert_eq!(
generator.counter.load(Ordering::SeqCst),
total_uuids as u64,
"Counter should match the total number of generated UUIDs"
);
}
#[test]
fn test_uuid_generator_with_initial_counter() {
let namespace = create_test_namespace();
let initial_counter = 1000;
let mut generator = UuidGenerator::new(namespace);
generator.counter = AtomicU64::new(initial_counter);
let _ = generator.next();
assert_eq!(
generator.counter.load(Ordering::SeqCst),
initial_counter + 1
);
let mut generator2 = UuidGenerator::new(namespace);
generator2.counter = AtomicU64::new(initial_counter + 1);
assert_eq!(generator.next(), generator2.next());
}
}