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 = "posei_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 fn validate_v4(uuid: &Uuid) {
93 assert_eq!(
95 uuid.get_version(),
96 Some(uuid::Version::Random),
97 "UUID is not version 4"
98 );
99
100 assert_eq!(
102 uuid.get_variant(),
103 uuid::Variant::RFC4122,
104 "UUID is not RFC 4122 variant"
105 );
106 }
107
108 fn from_validated_uuid(uuid: &Uuid) -> Self {
109 let mut value = [0; UUID4_LEN];
110 let uuid_str = uuid.to_string();
111 value[..uuid_str.len()].copy_from_slice(uuid_str.as_bytes());
112 value[uuid_str.len()] = 0; Self { value }
114 }
115}
116
117impl FromStr for UUID4 {
118 type Err = uuid::Error;
119
120 fn from_str(value: &str) -> Result<Self, Self::Err> {
128 let uuid = Uuid::try_parse(value)?;
129 Self::validate_v4(&uuid);
130 Ok(Self::from_validated_uuid(&uuid))
131 }
132}
133
134impl From<&str> for UUID4 {
135 fn from(value: &str) -> Self {
141 value
142 .parse()
143 .expect("`value` should be a valid UUID version 4 (RFC 4122)")
144 }
145}
146
147impl From<String> for UUID4 {
148 fn from(value: String) -> Self {
154 Self::from(value.as_str())
155 }
156}
157
158impl From<uuid::Uuid> for UUID4 {
159 fn from(value: uuid::Uuid) -> Self {
165 Self::validate_v4(&value);
166 Self::from_validated_uuid(&value)
167 }
168}
169
170impl Default for UUID4 {
171 fn default() -> Self {
175 Self::new()
176 }
177}
178
179impl Debug for UUID4 {
180 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
181 write!(f, "{}('{}')", stringify!(UUID4), self)
182 }
183}
184
185impl Display for UUID4 {
186 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
187 write!(f, "{}", self.to_cstr().to_string_lossy())
188 }
189}
190
191impl Serialize for UUID4 {
192 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
193 where
194 S: Serializer,
195 {
196 self.to_string().serialize(serializer)
197 }
198}
199
200impl<'de> Deserialize<'de> for UUID4 {
201 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
202 where
203 D: Deserializer<'de>,
204 {
205 let uuid4_str: &str = Deserialize::deserialize(deserializer)?;
206 let uuid4: Self = uuid4_str.into();
207 Ok(uuid4)
208 }
209}
210
211#[cfg(test)]
215mod tests {
216 use std::{
217 collections::hash_map::DefaultHasher,
218 hash::{Hash, Hasher},
219 };
220
221 use rstest::*;
222 use uuid;
223
224 use super::*;
225
226 #[rstest]
227 fn test_new() {
228 let uuid = UUID4::new();
229 let uuid_string = uuid.to_string();
230 let uuid_parsed = Uuid::parse_str(&uuid_string).unwrap();
231 assert_eq!(uuid_parsed.get_version().unwrap(), uuid::Version::Random);
232 assert_eq!(uuid_parsed.to_string().len(), 36);
233
234 assert_eq!(&uuid_string[14..15], "4");
236 let variant_char = &uuid_string[19..20];
238 assert!(matches!(variant_char, "8" | "9" | "a" | "b" | "A" | "B"));
239 }
240
241 #[rstest]
242 fn test_uuid_format() {
243 let uuid = UUID4::new();
244 let bytes = uuid.value;
245
246 assert_eq!(bytes[36], 0);
248
249 assert_eq!(bytes[8] as char, '-');
251 assert_eq!(bytes[13] as char, '-');
252 assert_eq!(bytes[18] as char, '-');
253 assert_eq!(bytes[23] as char, '-');
254
255 let s = uuid.to_string();
256 assert_eq!(s.chars().nth(14).unwrap(), '4');
257 }
258
259 #[rstest]
260 #[should_panic(expected = "UUID is not version 4")]
261 fn test_from_str_with_non_version_4_uuid_panics() {
262 let uuid_string = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"; let _ = UUID4::from(uuid_string);
264 }
265
266 #[rstest]
267 fn test_case_insensitive_parsing() {
268 let upper = "2D89666B-1A1E-4A75-B193-4EB3B454C757";
269 let lower = "2d89666b-1a1e-4a75-b193-4eb3b454c757";
270 let uuid_upper = UUID4::from(upper);
271 let uuid_lower = UUID4::from(lower);
272
273 assert_eq!(uuid_upper, uuid_lower);
274 assert_eq!(uuid_upper.to_string(), lower);
275 }
276
277 #[rstest]
278 #[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")]
283 fn test_invalid_version(#[case] uuid_string: &str) {
284 let _ = UUID4::from(uuid_string);
285 }
286
287 #[rstest]
288 #[should_panic(expected = "UUID is not RFC 4122 variant")]
289 fn test_non_rfc4122_variant() {
290 let uuid = "550e8400-e29b-41d4-0000-446655440000";
292 let _ = UUID4::from(uuid);
293 }
294
295 #[rstest]
296 #[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) {
305 assert!(UUID4::from_str(invalid_uuid).is_err());
306 }
307
308 #[rstest]
309 fn test_default() {
310 let uuid: UUID4 = UUID4::default();
311 let uuid_string = uuid.to_string();
312 let uuid_parsed = Uuid::parse_str(&uuid_string).unwrap();
313 assert_eq!(uuid_parsed.get_version().unwrap(), uuid::Version::Random);
314 }
315
316 #[rstest]
317 fn test_from_str() {
318 let uuid_string = "2d89666b-1a1e-4a75-b193-4eb3b454c757";
319 let uuid = UUID4::from(uuid_string);
320 let result_string = uuid.to_string();
321 let result_parsed = Uuid::parse_str(&result_string).unwrap();
322 let expected_parsed = Uuid::parse_str(uuid_string).unwrap();
323 assert_eq!(result_parsed, expected_parsed);
324 }
325
326 #[rstest]
327 fn test_from_uuid() {
328 let original = uuid::Uuid::new_v4();
329 let uuid4 = UUID4::from(original);
330 assert_eq!(uuid4.to_string(), original.to_string());
331 }
332
333 #[rstest]
334 fn test_equality() {
335 let uuid1 = UUID4::from("2d89666b-1a1e-4a75-b193-4eb3b454c757");
336 let uuid2 = UUID4::from("46922ecb-4324-4e40-a56c-841e0d774cef");
337 assert_eq!(uuid1, uuid1);
338 assert_ne!(uuid1, uuid2);
339 }
340
341 #[rstest]
342 fn test_debug() {
343 let uuid_string = "2d89666b-1a1e-4a75-b193-4eb3b454c757";
344 let uuid = UUID4::from(uuid_string);
345 assert_eq!(format!("{uuid:?}"), format!("UUID4('{uuid_string}')"));
346 }
347
348 #[rstest]
349 fn test_display() {
350 let uuid_string = "2d89666b-1a1e-4a75-b193-4eb3b454c757";
351 let uuid = UUID4::from(uuid_string);
352 assert_eq!(format!("{uuid}"), uuid_string);
353 }
354
355 #[rstest]
356 fn test_to_cstr() {
357 let uuid = UUID4::new();
358 let cstr = uuid.to_cstr();
359
360 assert_eq!(cstr.to_str().unwrap(), uuid.to_string());
361 assert_eq!(cstr.to_bytes_with_nul()[36], 0);
362 }
363
364 #[rstest]
365 fn test_hash_consistency() {
366 let uuid = UUID4::new();
367
368 let mut hasher1 = DefaultHasher::new();
369 let mut hasher2 = DefaultHasher::new();
370
371 uuid.hash(&mut hasher1);
372 uuid.hash(&mut hasher2);
373
374 assert_eq!(hasher1.finish(), hasher2.finish());
375 }
376
377 #[rstest]
378 fn test_serialize_json() {
379 let uuid_string = "2d89666b-1a1e-4a75-b193-4eb3b454c757";
380 let uuid = UUID4::from(uuid_string);
381
382 let serialized = serde_json::to_string(&uuid).unwrap();
383 let expected_json = format!("\"{uuid_string}\"");
384 assert_eq!(serialized, expected_json);
385 }
386
387 #[rstest]
388 fn test_deserialize_json() {
389 let uuid_string = "2d89666b-1a1e-4a75-b193-4eb3b454c757";
390 let serialized = format!("\"{uuid_string}\"");
391
392 let deserialized: UUID4 = serde_json::from_str(&serialized).unwrap();
393 assert_eq!(deserialized.to_string(), uuid_string);
394 }
395
396 #[rstest]
397 fn test_serialize_deserialize_round_trip() {
398 let uuid = UUID4::new();
399
400 let serialized = serde_json::to_string(&uuid).unwrap();
401 let deserialized: UUID4 = serde_json::from_str(&serialized).unwrap();
402
403 assert_eq!(uuid, deserialized);
404 }
405}