Skip to main content

linkrs_core/value/
uuid.rs

1//! UUID Type Module - Graph Database UUID Support
2//!
3//! This module provides UUID (Universally Unique Identifier) type support
4//! following RFC 4122 standard.
5//!
6//! ## Features
7//! - Standard UUID format (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
8//! - Binary storage (16 bytes)
9//! - Fast comparison and hashing
10//! - PostgreSQL compatible
11
12use serde::{Deserialize, Serialize};
13use std::fmt;
14use std::hash::Hash;
15use std::str::FromStr;
16
17/// UUID Value Type (16 bytes)
18///
19/// Stores UUID in binary format for efficient storage and comparison.
20/// Supports all UUID versions (1, 3, 4, 5, 6, 7, 8).
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
22pub struct UuidValue(pub [u8; 16]);
23
24impl UuidValue {
25    /// Create UUID from raw bytes
26    pub const fn from_bytes(bytes: [u8; 16]) -> Self {
27        Self(bytes)
28    }
29
30    /// Create UUID from slice (returns error if slice length != 16)
31    pub fn from_slice(slice: &[u8]) -> Result<Self, UuidError> {
32        if slice.len() != 16 {
33            return Err(UuidError::InvalidLength(slice.len()));
34        }
35        let mut bytes = [0u8; 16];
36        bytes.copy_from_slice(slice);
37        Ok(Self(bytes))
38    }
39
40    /// Parse UUID from string (standard format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
41    pub fn parse_str(s: &str) -> Result<Self, UuidError> {
42        // Remove hyphens and validate length
43        let hex_str: String = s.chars().filter(|&c| c != '-').collect();
44
45        if hex_str.len() != 32 {
46            return Err(UuidError::InvalidFormat(s.to_string()));
47        }
48
49        // Parse hex string to bytes
50        let mut bytes = [0u8; 16];
51        for (i, chunk) in hex_str.as_bytes().chunks(2).enumerate() {
52            let hex_chunk =
53                std::str::from_utf8(chunk).map_err(|_| UuidError::InvalidFormat(s.to_string()))?;
54            bytes[i] = u8::from_str_radix(hex_chunk, 16)
55                .map_err(|_| UuidError::InvalidFormat(s.to_string()))?;
56        }
57
58        Ok(Self(bytes))
59    }
60
61    /// Generate a new UUID v4 (random)]
62    pub fn new_v4() -> Self {
63        use rand::Rng;
64        let mut bytes = [0u8; 16];
65        rand::thread_rng().fill(&mut bytes);
66
67        // Set version (4) and variant (RFC 4122)
68        bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4
69        bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10
70
71        Self(bytes)
72    }
73
74    /// Get raw bytes
75    pub const fn as_bytes(&self) -> &[u8; 16] {
76        &self.0
77    }
78
79    /// Convert to bytes array
80    pub const fn to_bytes(self) -> [u8; 16] {
81        self.0
82    }
83
84    /// Get UUID version (bits 12-15 of time_hi_and_version field)
85    pub fn version(&self) -> u8 {
86        (self.0[6] >> 4) & 0x0f
87    }
88
89    /// Get UUID variant
90    pub fn variant(&self) -> UuidVariant {
91        match self.0[8] >> 6 {
92            0b00 => UuidVariant::NCS,
93            0b10 => UuidVariant::RFC4122,
94            0b11 => UuidVariant::Microsoft,
95            _ => UuidVariant::Future,
96        }
97    }
98
99    /// Format as hyphenated string (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
100    pub fn to_hyphenated_string(&self) -> String {
101        format!(
102            "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
103            self.0[0], self.0[1], self.0[2], self.0[3],
104            self.0[4], self.0[5],
105            self.0[6], self.0[7],
106            self.0[8], self.0[9],
107            self.0[10], self.0[11], self.0[12], self.0[13], self.0[14], self.0[15]
108        )
109    }
110
111    /// Format as simple string (no hyphens)
112    pub fn to_simple_string(&self) -> String {
113        format!(
114            "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
115            self.0[0], self.0[1], self.0[2], self.0[3],
116            self.0[4], self.0[5], self.0[6], self.0[7],
117            self.0[8], self.0[9], self.0[10], self.0[11],
118            self.0[12], self.0[13], self.0[14], self.0[15]
119        )
120    }
121
122    /// Format as URN string (urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
123    pub fn to_urn_string(&self) -> String {
124        format!("urn:uuid:{}", self.to_hyphenated_string())
125    }
126
127    /// Estimate memory usage (always 16 bytes)
128    pub const fn estimated_size(&self) -> usize {
129        std::mem::size_of::<Self>()
130    }
131
132    /// Nil UUID (all zeros)
133    pub const fn nil() -> Self {
134        Self([0u8; 16])
135    }
136
137    /// Max UUID (all ones)
138    pub const fn max() -> Self {
139        Self([0xffu8; 16])
140    }
141}
142
143impl Default for UuidValue {
144    fn default() -> Self {
145        Self::nil()
146    }
147}
148
149impl fmt::Display for UuidValue {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        write!(f, "{}", self.to_hyphenated_string())
152    }
153}
154
155impl FromStr for UuidValue {
156    type Err = UuidError;
157
158    fn from_str(s: &str) -> Result<Self, Self::Err> {
159        Self::parse_str(s)
160    }
161}
162
163impl AsRef<[u8]> for UuidValue {
164    fn as_ref(&self) -> &[u8] {
165        &self.0
166    }
167}
168
169impl From<[u8; 16]> for UuidValue {
170    fn from(bytes: [u8; 16]) -> Self {
171        Self(bytes)
172    }
173}
174
175impl From<UuidValue> for [u8; 16] {
176    fn from(uuid: UuidValue) -> Self {
177        uuid.0
178    }
179}
180
181/// UUID Variant
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
183pub enum UuidVariant {
184    /// NCS compatibility variant (0b0xx)
185    NCS,
186    /// RFC 4122 variant (0b10x)
187    RFC4122,
188    /// Microsoft variant (0b110)
189    Microsoft,
190    /// Future reserved variant (0b111)
191    Future,
192}
193
194/// UUID Error Type
195#[derive(Debug, Clone, PartialEq, Eq)]
196pub enum UuidError {
197    InvalidLength(usize),
198    InvalidFormat(String),
199}
200
201impl fmt::Display for UuidError {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        match self {
204            UuidError::InvalidLength(len) => {
205                write!(f, "Invalid UUID length: expected 16, got {}", len)
206            }
207            UuidError::InvalidFormat(s) => write!(f, "Invalid UUID format: {}", s),
208        }
209    }
210}
211
212impl std::error::Error for UuidError {}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217
218    #[test]
219    fn test_uuid_parse() {
220        let uuid_str = "550e8400-e29b-41d4-a716-446655440000";
221        let uuid = UuidValue::parse_str(uuid_str).unwrap();
222        assert_eq!(uuid.to_hyphenated_string(), uuid_str);
223    }
224
225    #[test]
226    fn test_uuid_parse_simple() {
227        let uuid_str = "550e8400e29b41d4a716446655440000";
228        let uuid = UuidValue::parse_str(uuid_str).unwrap();
229        assert_eq!(uuid.to_simple_string(), uuid_str);
230    }
231
232    #[test]
233    fn test_uuid_version() {
234        // Version 4 UUID: 550e8400-e29b-41d4-a716-446655440000
235        let uuid = UuidValue::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
236        assert_eq!(uuid.version(), 4);
237    }
238
239    #[test]
240    fn test_uuid_variant() {
241        let uuid = UuidValue::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
242        assert_eq!(uuid.variant(), UuidVariant::RFC4122);
243    }
244
245    #[test]
246    fn test_uuid_nil() {
247        let nil = UuidValue::nil();
248        assert_eq!(nil.to_simple_string(), "00000000000000000000000000000000");
249    }
250
251    #[test]
252    fn test_uuid_max() {
253        let max = UuidValue::max();
254        assert_eq!(max.to_simple_string(), "ffffffffffffffffffffffffffffffff");
255    }
256
257    #[test]
258    fn test_uuid_from_bytes() {
259        let bytes = [
260            0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44,
261            0x00, 0x00,
262        ];
263        let uuid = UuidValue::from_bytes(bytes);
264        assert_eq!(uuid.as_bytes(), &bytes);
265    }
266
267    #[test]
268    fn test_uuid_invalid_format() {
269        let result = UuidValue::parse_str("invalid");
270        assert!(result.is_err());
271    }
272
273    #[test]
274    fn test_uuid_invalid_length() {
275        let result = UuidValue::from_slice(&[1, 2, 3]);
276        assert!(result.is_err());
277    }
278
279    #[test]
280    fn test_uuid_display() {
281        let uuid = UuidValue::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
282        assert_eq!(format!("{}", uuid), "550e8400-e29b-41d4-a716-446655440000");
283    }
284
285    #[test]
286    fn test_uuid_urn() {
287        let uuid = UuidValue::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
288        assert_eq!(
289            uuid.to_urn_string(),
290            "urn:uuid:550e8400-e29b-41d4-a716-446655440000"
291        );
292    }
293
294    #[test]
295    fn test_uuid_equality() {
296        let uuid1 = UuidValue::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
297        let uuid2 = UuidValue::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
298        let uuid3 = UuidValue::parse_str("660e8400-e29b-41d4-a716-446655440000").unwrap();
299        assert_eq!(uuid1, uuid2);
300        assert_ne!(uuid1, uuid3);
301    }
302
303    #[test]
304    fn test_uuid_from_str_trait() {
305        let uuid: UuidValue = "550e8400-e29b-41d4-a716-446655440000".parse().unwrap();
306        assert_eq!(
307            uuid.to_hyphenated_string(),
308            "550e8400-e29b-41d4-a716-446655440000"
309        );
310    }
311}