pricelevel/utils/
uuid.rs

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