1use std::{
19 ffi::CStr,
20 fmt::{Debug, Display, Formatter},
21 hash::Hash,
22 io::{Cursor, Write},
23 str::FromStr,
24};
25
26use rand::RngCore;
27use serde::{Deserialize, Deserializer, Serialize, Serializer};
28use uuid::Uuid;
29
30pub(crate) const UUID4_LEN: usize = 37;
32
33#[repr(C)]
36#[derive(Copy, Clone, Hash, PartialEq, Eq)]
37#[cfg_attr(
38 feature = "python",
39 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.core")
40)]
41pub struct UUID4 {
42 pub(crate) value: [u8; 37], }
45
46impl UUID4 {
47 #[must_use]
51 pub fn new() -> Self {
52 let mut rng = rand::rng();
53 let mut bytes = [0u8; 16];
54 rng.fill_bytes(&mut bytes);
55
56 bytes[6] = (bytes[6] & 0x0F) | 0x40; bytes[8] = (bytes[8] & 0x3F) | 0x80; let mut value = [0u8; UUID4_LEN];
60 let mut cursor = Cursor::new(&mut value[..36]);
61
62 write!(
63 cursor,
64 "{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
65 u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
66 u16::from_be_bytes([bytes[4], bytes[5]]),
67 u16::from_be_bytes([bytes[6], bytes[7]]),
68 u16::from_be_bytes([bytes[8], bytes[9]]),
69 u64::from_be_bytes([
70 bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], 0, 0
71 ]) >> 16
72 )
73 .expect("Error writing UUID string to buffer");
74
75 value[36] = 0; Self { value }
78 }
79
80 #[must_use]
86 pub fn to_cstr(&self) -> &CStr {
87 CStr::from_bytes_with_nul(&self.value)
89 .expect("UUID byte representation should be a valid C string")
90 }
91
92 #[must_use]
94 pub fn as_str(&self) -> &str {
95 self.to_cstr().to_str().expect("UUID should be valid UTF-8")
97 }
98
99 #[must_use]
104 pub fn as_bytes(&self) -> [u8; 16] {
105 let uuid_str = self.to_cstr().to_str().expect("Valid UTF-8");
108 let uuid = Uuid::parse_str(uuid_str).expect("Valid UUID4");
109 *uuid.as_bytes()
110 }
111
112 fn validate_v4(uuid: &Uuid) {
113 assert_eq!(
115 uuid.get_version(),
116 Some(uuid::Version::Random),
117 "UUID is not version 4"
118 );
119
120 assert_eq!(
122 uuid.get_variant(),
123 uuid::Variant::RFC4122,
124 "UUID is not RFC 4122 variant"
125 );
126 }
127
128 fn from_validated_uuid(uuid: &Uuid) -> Self {
129 let mut value = [0; UUID4_LEN];
130 let uuid_str = uuid.to_string();
131 value[..uuid_str.len()].copy_from_slice(uuid_str.as_bytes());
132 value[uuid_str.len()] = 0; Self { value }
134 }
135}
136
137impl FromStr for UUID4 {
138 type Err = uuid::Error;
139
140 fn from_str(value: &str) -> Result<Self, Self::Err> {
148 let uuid = Uuid::try_parse(value)?;
149 Self::validate_v4(&uuid);
150 Ok(Self::from_validated_uuid(&uuid))
151 }
152}
153
154impl From<&str> for UUID4 {
155 fn from(value: &str) -> Self {
161 value
162 .parse()
163 .expect("`value` should be a valid UUID version 4 (RFC 4122)")
164 }
165}
166
167impl From<String> for UUID4 {
168 fn from(value: String) -> Self {
174 Self::from(value.as_str())
175 }
176}
177
178impl From<uuid::Uuid> for UUID4 {
179 fn from(value: uuid::Uuid) -> Self {
185 Self::validate_v4(&value);
186 Self::from_validated_uuid(&value)
187 }
188}
189
190impl From<UUID4> for uuid::Uuid {
191 fn from(value: UUID4) -> Self {
193 Self::from_bytes(value.as_bytes())
194 }
195}
196
197impl Default for UUID4 {
198 fn default() -> Self {
202 Self::new()
203 }
204}
205
206impl Debug for UUID4 {
207 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
208 write!(f, "{}({})", stringify!(UUID4), self)
209 }
210}
211
212impl Display for UUID4 {
213 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
214 write!(f, "{}", self.to_cstr().to_string_lossy())
215 }
216}
217
218impl Serialize for UUID4 {
219 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
220 where
221 S: Serializer,
222 {
223 self.to_string().serialize(serializer)
224 }
225}
226
227impl<'de> Deserialize<'de> for UUID4 {
228 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
229 where
230 D: Deserializer<'de>,
231 {
232 let uuid4_str: &str = Deserialize::deserialize(deserializer)?;
233 let uuid4: Self = uuid4_str.into();
234 Ok(uuid4)
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use std::{
241 collections::hash_map::DefaultHasher,
242 hash::{Hash, Hasher},
243 };
244
245 use rstest::*;
246 use uuid;
247
248 use super::*;
249
250 #[rstest]
251 fn test_new() {
252 let uuid = UUID4::new();
253 let uuid_string = uuid.to_string();
254 let uuid_parsed = Uuid::parse_str(&uuid_string).unwrap();
255 assert_eq!(uuid_parsed.get_version().unwrap(), uuid::Version::Random);
256 assert_eq!(uuid_parsed.to_string().len(), 36);
257
258 assert_eq!(&uuid_string[14..15], "4");
260 let variant_char = &uuid_string[19..20];
262 assert!(matches!(variant_char, "8" | "9" | "a" | "b" | "A" | "B"));
263 }
264
265 #[rstest]
266 fn test_uuid_format() {
267 let uuid = UUID4::new();
268 let bytes = uuid.value;
269
270 assert_eq!(bytes[36], 0);
272
273 assert_eq!(bytes[8] as char, '-');
275 assert_eq!(bytes[13] as char, '-');
276 assert_eq!(bytes[18] as char, '-');
277 assert_eq!(bytes[23] as char, '-');
278
279 let s = uuid.to_string();
280 assert_eq!(s.chars().nth(14).unwrap(), '4');
281 }
282
283 #[rstest]
284 #[should_panic(expected = "UUID is not version 4")]
285 fn test_from_str_with_non_version_4_uuid_panics() {
286 let uuid_string = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"; let _ = UUID4::from(uuid_string);
288 }
289
290 #[rstest]
291 fn test_case_insensitive_parsing() {
292 let upper = "2D89666B-1A1E-4A75-B193-4EB3B454C757";
293 let lower = "2d89666b-1a1e-4a75-b193-4eb3b454c757";
294 let uuid_upper = UUID4::from(upper);
295 let uuid_lower = UUID4::from(lower);
296
297 assert_eq!(uuid_upper, uuid_lower);
298 assert_eq!(uuid_upper.to_string(), lower);
299 }
300
301 #[rstest]
302 #[case("6ba7b810-9dad-11d1-80b4-00c04fd430c8")] #[case("000001f5-8fa9-21d1-9df3-00e098032b8c")] #[case("3d813cbb-47fb-32ba-91df-831e1593ac29")] #[case("fb4f37c1-4ba3-5173-9812-2b90e76a06f7")] #[should_panic(expected = "UUID is not version 4")]
307 fn test_invalid_version(#[case] uuid_string: &str) {
308 let _ = UUID4::from(uuid_string);
309 }
310
311 #[rstest]
312 #[should_panic(expected = "UUID is not RFC 4122 variant")]
313 fn test_non_rfc4122_variant() {
314 let uuid = "550e8400-e29b-41d4-0000-446655440000";
316 let _ = UUID4::from(uuid);
317 }
318
319 #[rstest]
320 #[case("")] #[case("not-a-uuid-at-all")] #[case("6ba7b810-9dad-11d1-80b4")] #[case("6ba7b810-9dad-11d1-80b4-00c04fd430c8-extra")] #[case("6ba7b810-9dad-11d1-80b4=00c04fd430c8")] #[case("6ba7b81019dad111d180b400c04fd430c8")] #[case("6ba7b810-9dad-11d1-80b4-00c04fd430")] #[case("6ba7b810-9dad-11d1-80b4-00c04fd430cg")] fn test_invalid_uuid_cases(#[case] invalid_uuid: &str) {
329 assert!(UUID4::from_str(invalid_uuid).is_err());
330 }
331
332 #[rstest]
333 fn test_default() {
334 let uuid: UUID4 = UUID4::default();
335 let uuid_string = uuid.to_string();
336 let uuid_parsed = Uuid::parse_str(&uuid_string).unwrap();
337 assert_eq!(uuid_parsed.get_version().unwrap(), uuid::Version::Random);
338 }
339
340 #[rstest]
341 fn test_from_str() {
342 let uuid_string = "2d89666b-1a1e-4a75-b193-4eb3b454c757";
343 let uuid = UUID4::from(uuid_string);
344 let result_string = uuid.to_string();
345 let result_parsed = Uuid::parse_str(&result_string).unwrap();
346 let expected_parsed = Uuid::parse_str(uuid_string).unwrap();
347 assert_eq!(result_parsed, expected_parsed);
348 }
349
350 #[rstest]
351 fn test_from_uuid() {
352 let original = uuid::Uuid::new_v4();
353 let uuid4 = UUID4::from(original);
354 assert_eq!(uuid4.to_string(), original.to_string());
355 }
356
357 #[rstest]
358 fn test_equality() {
359 let uuid1 = UUID4::from("2d89666b-1a1e-4a75-b193-4eb3b454c757");
360 let uuid2 = UUID4::from("46922ecb-4324-4e40-a56c-841e0d774cef");
361 assert_eq!(uuid1, uuid1);
362 assert_ne!(uuid1, uuid2);
363 }
364
365 #[rstest]
366 fn test_debug() {
367 let uuid_string = "2d89666b-1a1e-4a75-b193-4eb3b454c757";
368 let uuid = UUID4::from(uuid_string);
369 assert_eq!(format!("{uuid:?}"), format!("UUID4({uuid_string})"));
370 }
371
372 #[rstest]
373 fn test_display() {
374 let uuid_string = "2d89666b-1a1e-4a75-b193-4eb3b454c757";
375 let uuid = UUID4::from(uuid_string);
376 assert_eq!(format!("{uuid}"), uuid_string);
377 }
378
379 #[rstest]
380 fn test_to_cstr() {
381 let uuid = UUID4::new();
382 let cstr = uuid.to_cstr();
383
384 assert_eq!(cstr.to_str().unwrap(), uuid.to_string());
385 assert_eq!(cstr.to_bytes_with_nul()[36], 0);
386 }
387
388 #[rstest]
389 fn test_as_str() {
390 let uuid = UUID4::new();
391 let s = uuid.as_str();
392
393 assert_eq!(s, uuid.to_string());
394 assert_eq!(s.len(), 36);
395 }
396
397 #[rstest]
398 fn test_hash_consistency() {
399 let uuid = UUID4::new();
400
401 let mut hasher1 = DefaultHasher::new();
402 let mut hasher2 = DefaultHasher::new();
403
404 uuid.hash(&mut hasher1);
405 uuid.hash(&mut hasher2);
406
407 assert_eq!(hasher1.finish(), hasher2.finish());
408 }
409
410 #[rstest]
411 fn test_serialize_json() {
412 let uuid_string = "2d89666b-1a1e-4a75-b193-4eb3b454c757";
413 let uuid = UUID4::from(uuid_string);
414
415 let serialized = serde_json::to_string(&uuid).unwrap();
416 let expected_json = format!("\"{uuid_string}\"");
417 assert_eq!(serialized, expected_json);
418 }
419
420 #[rstest]
421 fn test_deserialize_json() {
422 let uuid_string = "2d89666b-1a1e-4a75-b193-4eb3b454c757";
423 let serialized = format!("\"{uuid_string}\"");
424
425 let deserialized: UUID4 = serde_json::from_str(&serialized).unwrap();
426 assert_eq!(deserialized.to_string(), uuid_string);
427 }
428
429 #[rstest]
430 fn test_serialize_deserialize_round_trip() {
431 let uuid = UUID4::new();
432
433 let serialized = serde_json::to_string(&uuid).unwrap();
434 let deserialized: UUID4 = serde_json::from_str(&serialized).unwrap();
435
436 assert_eq!(uuid, deserialized);
437 }
438
439 #[rstest]
440 fn test_as_bytes() {
441 let uuid_string = "2d89666b-1a1e-4a75-b193-4eb3b454c757";
442 let uuid = UUID4::from(uuid_string);
443
444 let bytes = uuid.as_bytes();
445 assert_eq!(bytes.len(), 16);
446
447 let reconstructed = Uuid::from_bytes(bytes);
449 assert_eq!(reconstructed.to_string(), uuid_string);
450
451 assert_eq!(reconstructed.get_version().unwrap(), uuid::Version::Random);
453 }
454
455 #[rstest]
456 fn test_as_bytes_round_trip() {
457 let uuid1 = UUID4::new();
458 let bytes = uuid1.as_bytes();
459 let uuid2 = UUID4::from(Uuid::from_bytes(bytes));
460
461 assert_eq!(uuid1, uuid2);
462 }
463}