1#![allow(clippy::manual_range_contains)]
9
10use core::fmt;
11use core::str::FromStr;
12
13#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
14pub struct Id128([u8; 16]);
15
16impl Id128 {
17 pub const NIL: Self = Self([0; 16]);
18
19 #[inline]
20 pub const fn from_bytes(bytes: [u8; 16]) -> Self {
21 Self(bytes)
22 }
23
24 #[inline]
25 pub const fn as_bytes(&self) -> &[u8; 16] {
26 &self.0
27 }
28
29 #[inline]
30 pub const fn is_nil(&self) -> bool {
31 let b = &self.0;
32 let mut i = 0;
33 while i < 16 {
34 if b[i] != 0 {
35 return false;
36 }
37 i += 1;
38 }
39 true
40 }
41
42 #[inline]
43 pub const fn from_u128(v: u128) -> Self {
44 Self(v.to_be_bytes())
45 }
46
47 #[inline]
48 pub const fn to_u128(&self) -> u128 {
49 u128::from_be_bytes(self.0)
50 }
51}
52
53const HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
54
55impl fmt::Display for Id128 {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 let b = &self.0;
58 let mut buf = [0u8; 36];
59 let mut pos = 0;
60
61 let groups: &[(usize, usize)] = &[(0, 4), (4, 6), (6, 8), (8, 10), (10, 16)];
63 for (gi, &(start, end)) in groups.iter().enumerate() {
64 if gi > 0 {
65 buf[pos] = b'-';
66 pos += 1;
67 }
68 for i in start..end {
69 buf[pos] = HEX_CHARS[(b[i] >> 4) as usize];
70 buf[pos + 1] = HEX_CHARS[(b[i] & 0x0f) as usize];
71 pos += 2;
72 }
73 }
74 f.write_str(core::str::from_utf8(&buf[..pos]).expect("hex chars are valid utf8"))
75 }
76}
77
78impl fmt::Debug for Id128 {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 write!(f, "Id128({self})")
81 }
82}
83
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85pub enum ParseIdError {
86 InvalidLength,
87 InvalidHex,
88}
89
90impl fmt::Display for ParseIdError {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 match self {
93 Self::InvalidLength => f.write_str("expected UUID: 32 hex chars or 36 with hyphens"),
94 Self::InvalidHex => f.write_str("invalid hex character in UUID"),
95 }
96 }
97}
98
99#[cfg(feature = "std")]
100impl std::error::Error for ParseIdError {}
101
102fn hex_val(c: u8) -> Option<u8> {
103 match c {
104 b'0'..=b'9' => Some(c - b'0'),
105 b'a'..=b'f' => Some(c - b'a' + 10),
106 b'A'..=b'F' => Some(c - b'A' + 10),
107 _ => None,
108 }
109}
110
111fn parse_hex_bytes(hex: &[u8]) -> Result<[u8; 16], ParseIdError> {
112 if hex.len() != 32 {
113 return Err(ParseIdError::InvalidLength);
114 }
115 let mut bytes = [0u8; 16];
116 for i in 0..16 {
117 let hi = hex_val(hex[i * 2]).ok_or(ParseIdError::InvalidHex)?;
118 let lo = hex_val(hex[i * 2 + 1]).ok_or(ParseIdError::InvalidHex)?;
119 bytes[i] = (hi << 4) | lo;
120 }
121 Ok(bytes)
122}
123
124impl FromStr for Id128 {
125 type Err = ParseIdError;
126
127 fn from_str(s: &str) -> Result<Self, Self::Err> {
128 let b = s.as_bytes();
129 match b.len() {
130 32 => Ok(Self(parse_hex_bytes(b)?)),
131 36 => {
132 if b[8] != b'-' || b[13] != b'-' || b[18] != b'-' || b[23] != b'-' {
134 return Err(ParseIdError::InvalidHex);
135 }
136 let mut hex = [0u8; 32];
137 hex[..8].copy_from_slice(&b[..8]);
138 hex[8..12].copy_from_slice(&b[9..13]);
139 hex[12..16].copy_from_slice(&b[14..18]);
140 hex[16..20].copy_from_slice(&b[19..23]);
141 hex[20..32].copy_from_slice(&b[24..36]);
142 Ok(Self(parse_hex_bytes(&hex)?))
143 }
144 _ => Err(ParseIdError::InvalidLength),
145 }
146 }
147}
148
149impl Default for Id128 {
150 #[inline]
151 fn default() -> Self {
152 Self::NIL
153 }
154}
155
156#[cfg(feature = "serde")]
157impl serde::Serialize for Id128 {
158 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
159 use alloc::string::ToString;
160 serializer.serialize_str(&self.to_string())
161 }
162}
163
164#[cfg(feature = "serde")]
165impl<'de> serde::Deserialize<'de> for Id128 {
166 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
167 let s = <&str>::deserialize(deserializer)?;
168 s.parse().map_err(serde::de::Error::custom)
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use alloc::format;
176
177 #[test]
178 fn nil() {
179 assert!(Id128::NIL.is_nil());
180 assert!(!Id128::from_u128(1).is_nil());
181 }
182
183 #[test]
184 fn roundtrip_u128() {
185 let v: u128 = 0xdeadbeef_12345678_9abcdef0_11223344;
186 let id = Id128::from_u128(v);
187 assert_eq!(id.to_u128(), v);
188 }
189
190 #[test]
191 fn display_is_hyphenated_uuid() {
192 let id = Id128::from_u128(0xabcdef0123456789abcdef0123456789);
193 let s = format!("{id}");
194 assert_eq!(s.len(), 36);
195 assert_eq!(s, "abcdef01-2345-6789-abcd-ef0123456789");
196 }
197
198 #[test]
199 fn parse_hyphenated() {
200 let id: Id128 = "abcdef01-2345-6789-abcd-ef0123456789".parse().unwrap();
201 assert_eq!(id.to_u128(), 0xabcdef0123456789abcdef0123456789);
202 }
203
204 #[test]
205 fn parse_simple() {
206 let id: Id128 = "abcdef0123456789abcdef0123456789".parse().unwrap();
207 assert_eq!(id.to_u128(), 0xabcdef0123456789abcdef0123456789);
208 }
209
210 #[test]
211 fn display_parse_roundtrip() {
212 let id = Id128::from_u128(0xabcdef0123456789abcdef0123456789);
213 let s = format!("{id}");
214 let parsed: Id128 = s.parse().unwrap();
215 assert_eq!(parsed, id);
216 }
217
218 #[test]
219 fn parse_errors() {
220 assert_eq!("abc".parse::<Id128>(), Err(ParseIdError::InvalidLength));
221 assert_eq!(
222 "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz".parse::<Id128>(),
223 Err(ParseIdError::InvalidHex)
224 );
225 assert_eq!(
227 "abcdef01-2345-6789-abcd-ef012345678".parse::<Id128>(),
228 Err(ParseIdError::InvalidLength)
229 );
230 }
231
232 #[test]
233 fn ordering() {
234 let a = Id128::from_u128(1);
235 let b = Id128::from_u128(2);
236 assert!(a < b);
237 }
238}