Skip to main content

pricelevel/utils/
uuid.rs

1use serde::{Deserialize, Serialize};
2use std::sync::atomic::{AtomicU64, Ordering};
3use uuid::Uuid;
4
5/// # UuidGenerator
6///
7/// A utility for generating sequential UUIDs within a namespace.
8///
9/// This struct provides a thread-safe way to generate UUIDs using the UUID v5 algorithm,
10/// which creates name-based UUIDs. Each generated UUID is unique within the given namespace
11/// and derived from an incrementing counter.
12///
13/// ## Example
14///
15/// ```
16/// use uuid::Uuid;
17/// use pricelevel::UuidGenerator;
18///
19/// let namespace = Uuid::new_v4(); // Create a random namespace
20/// let generator = UuidGenerator::new(namespace);
21///
22/// let id1 = generator.next(); // Generate first UUID
23/// let id2 = generator.next(); // Generate second UUID
24/// ```
25///
26/// This is useful for applications that need deterministic but unique identifiers
27/// within a specific namespace context.
28#[derive(Debug, Serialize, Deserialize)]
29pub struct UuidGenerator {
30    namespace: Uuid,
31    counter: AtomicU64,
32}
33
34/// A generator for creating sequential UUIDs based on a namespace.
35///
36/// This struct provides functionality to generate deterministic UUIDs in sequence
37/// by combining a namespace UUID with an incrementing counter value. Each generated
38/// UUID is created using the UUID v5 algorithm (SHA-1 hash-based).
39///
40/// # Examples
41///
42/// ```
43/// use uuid::Uuid;
44/// use pricelevel::UuidGenerator;
45///
46/// let namespace = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap();
47/// let generator = UuidGenerator::new(namespace);
48///
49/// let id1 = generator.next(); // First UUID
50/// let id2 = generator.next(); // Second UUID (different from first)
51/// ```
52impl UuidGenerator {
53    /// Creates a new `UuidGenerator` with the specified namespace.
54    ///
55    /// The namespace is used as a base for all generated UUIDs.
56    ///
57    /// # Arguments
58    ///
59    /// * `namespace` - The UUID to use as the namespace for generating v5 UUIDs
60    ///
61    /// # Returns
62    ///
63    /// A new `UuidGenerator` instance initialized with the provided namespace and a counter set to 0.
64    pub fn new(namespace: Uuid) -> Self {
65        Self {
66            namespace,
67            counter: AtomicU64::new(0),
68        }
69    }
70
71    /// Generates the next UUID in sequence.
72    ///
73    /// This method atomically increments an internal counter and uses its string representation
74    /// as the name to generate a UUID v5 combined with the namespace.
75    ///
76    /// # Returns
77    ///
78    /// A new UUID that is deterministically derived from the namespace and the current counter value.
79    pub fn next(&self) -> Uuid {
80        let counter = self.counter.fetch_add(1, Ordering::SeqCst);
81        let name = counter.to_string();
82        // Generate a UUID v5 (name-based) using the namespace and counter
83        Uuid::new_v5(&self.namespace, name.as_bytes())
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use std::collections::HashSet;
91    use std::sync::{Arc, Barrier};
92    use std::thread;
93
94    // Helper function to create a test namespace
95    fn create_test_namespace() -> Uuid {
96        Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap()
97    }
98
99    #[test]
100    fn test_uuid_generator_creation() {
101        let namespace = create_test_namespace();
102        let generator = UuidGenerator::new(namespace);
103
104        assert_eq!(generator.namespace, namespace);
105        assert_eq!(generator.counter.load(Ordering::SeqCst), 0);
106    }
107
108    #[test]
109    fn test_uuid_generator_next() {
110        let generator = UuidGenerator::new(create_test_namespace());
111
112        // Generate first UUID
113        let uuid1 = generator.next();
114        assert_eq!(generator.counter.load(Ordering::SeqCst), 1);
115
116        // Generate second UUID
117        let uuid2 = generator.next();
118        assert_eq!(generator.counter.load(Ordering::SeqCst), 2);
119
120        // UUIDs should be different
121        assert_ne!(uuid1, uuid2);
122
123        // Both should be version 5 (name-based) UUIDs
124        assert_eq!(uuid1.get_version(), Some(uuid::Version::Sha1));
125        assert_eq!(uuid2.get_version(), Some(uuid::Version::Sha1));
126    }
127
128    #[test]
129    fn test_uuid_generator_deterministic() {
130        // Create two generators with the same namespace
131        let namespace = create_test_namespace();
132        let generator1 = UuidGenerator::new(namespace);
133        let generator2 = UuidGenerator::new(namespace);
134
135        // They should generate the same UUIDs for the same counter values
136        assert_eq!(generator1.next(), generator2.next());
137        assert_eq!(generator1.next(), generator2.next());
138    }
139
140    #[test]
141    fn test_uuid_generator_different_namespaces() {
142        // Create two generators with different namespaces
143        let namespace1 = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap();
144        let namespace2 = Uuid::parse_str("6ba7b811-9dad-11d1-80b4-00c04fd430c8").unwrap();
145
146        let generator1 = UuidGenerator::new(namespace1);
147        let generator2 = UuidGenerator::new(namespace2);
148
149        // They should generate different UUIDs for the same counter values
150        assert_ne!(generator1.next(), generator2.next());
151        assert_ne!(generator1.next(), generator2.next());
152    }
153
154    #[test]
155    fn test_uuid_generator_sequential() {
156        let generator = UuidGenerator::new(create_test_namespace());
157        let mut uuids = Vec::new();
158
159        // Generate 100 UUIDs
160        for _ in 0..100 {
161            uuids.push(generator.next());
162        }
163
164        // Check they're all unique
165        let unique_uuids: HashSet<_> = uuids.iter().collect();
166        assert_eq!(unique_uuids.len(), 100);
167
168        // Check that the counter is properly incremented
169        assert_eq!(generator.counter.load(Ordering::SeqCst), 100);
170    }
171
172    #[test]
173    fn test_uuid_generator_thread_safety() {
174        let generator = Arc::new(UuidGenerator::new(create_test_namespace()));
175        let num_threads = 10;
176        let uuids_per_thread = 100;
177        let total_uuids = num_threads * uuids_per_thread;
178
179        // Use a barrier to ensure all threads start at the same time
180        let barrier = Arc::new(Barrier::new(num_threads));
181
182        // Shared container to collect all generated UUIDs
183        let all_uuids = Arc::new(std::sync::Mutex::new(Vec::with_capacity(total_uuids)));
184
185        let mut handles = vec![];
186
187        for _ in 0..num_threads {
188            let thread_generator = Arc::clone(&generator);
189            let thread_barrier = Arc::clone(&barrier);
190            let thread_uuids = Arc::clone(&all_uuids);
191
192            let handle = thread::spawn(move || {
193                thread_barrier.wait(); // Wait for all threads to be ready
194
195                let mut local_uuids = Vec::with_capacity(uuids_per_thread);
196                for _ in 0..uuids_per_thread {
197                    local_uuids.push(thread_generator.next());
198                }
199
200                // Add thread's UUIDs to the shared collection
201                let mut all = thread_uuids.lock().unwrap();
202                all.extend(local_uuids);
203            });
204
205            handles.push(handle);
206        }
207
208        // Wait for all threads to complete
209        for handle in handles {
210            handle.join().unwrap();
211        }
212
213        // Check that all UUIDs are unique
214        let all_uuids = all_uuids.lock().unwrap();
215        let unique_uuids: HashSet<_> = all_uuids.iter().collect();
216
217        assert_eq!(
218            unique_uuids.len(),
219            total_uuids,
220            "All generated UUIDs should be unique"
221        );
222
223        // Verify the counter was incremented correctly
224        assert_eq!(
225            generator.counter.load(Ordering::SeqCst),
226            total_uuids as u64,
227            "Counter should match the total number of generated UUIDs"
228        );
229    }
230
231    #[test]
232    fn test_uuid_generator_with_initial_counter() {
233        // Create a generator with a custom initial counter value
234        let namespace = create_test_namespace();
235        let initial_counter = 1000;
236
237        let mut generator = UuidGenerator::new(namespace);
238        generator.counter = AtomicU64::new(initial_counter);
239
240        // Generate a UUID
241        let _ = generator.next();
242
243        // Verify counter was incremented
244        assert_eq!(
245            generator.counter.load(Ordering::SeqCst),
246            initial_counter + 1
247        );
248
249        // Create another generator with initial counter at 1001
250        let mut generator2 = UuidGenerator::new(namespace);
251        generator2.counter = AtomicU64::new(initial_counter + 1);
252
253        // The next UUID from generator2 should match the next from generator1
254        assert_eq!(generator.next(), generator2.next());
255    }
256}