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