1#[macro_export]
26macro_rules! uuid {
27 ($s:literal) => {{
28 const BYTES: [u8; 16] = $crate::UUID::parse_const($s);
29 $crate::UUID::from_bytes(BYTES)
30 }};
31}
32
33use crate::UUID;
34
35impl UUID {
36 #[must_use]
45 pub const fn parse_const(s: &str) -> [u8; 16] {
46 let s = s.as_bytes();
47 let (start, end) = find_uuid_bounds(s);
48 let len = end - start;
49
50 let expect_hyphens = match len {
51 32 => false,
52 36 => true,
53 _ => panic!("UUID string must be 32 or 36 characters"),
54 };
55
56 let mut bytes = [0u8; 16];
57 let mut byte_idx = 0;
58 let mut i = start;
59 let mut pos = 0; while i < end {
62 let c = s[i];
63
64 if c == b'-' {
65 assert!(expect_hyphens, "unexpected hyphen in UUID");
66 assert!(is_hyphen_position(pos), "hyphen at invalid position");
67 i += 1;
68 pos += 1;
69 continue;
70 }
71
72 let high = hex_digit(c);
73 let low = hex_digit(s[i + 1]);
74 bytes[byte_idx] = (high << 4) | low;
75 byte_idx += 1;
76 i += 2;
77 pos += 2;
78 }
79
80 assert!(byte_idx == 16, "UUID must be exactly 16 bytes");
81
82 bytes
83 }
84}
85
86const fn find_uuid_bounds(s: &[u8]) -> (usize, usize) {
88 let mut start = 0;
89 let mut end = s.len();
90
91 if s.len() >= 9
93 && (s[0] == b'u' || s[0] == b'U')
94 && (s[1] == b'r' || s[1] == b'R')
95 && (s[2] == b'n' || s[2] == b'N')
96 && s[3] == b':'
97 && (s[4] == b'u' || s[4] == b'U')
98 && (s[5] == b'u' || s[5] == b'U')
99 && (s[6] == b'i' || s[6] == b'I')
100 && (s[7] == b'd' || s[7] == b'D')
101 && s[8] == b':'
102 {
103 start = 9;
104 }
105
106 if end > start + 1 && s[start] == b'{' {
108 assert!(s[end - 1] == b'}', "mismatched braces");
109 start += 1;
110 end -= 1;
111 }
112
113 (start, end)
114}
115
116const fn is_hyphen_position(i: usize) -> bool {
117 i == 8 || i == 13 || i == 18 || i == 23
118}
119
120const fn hex_digit(c: u8) -> u8 {
121 match c {
122 b'0'..=b'9' => c - b'0',
123 b'a'..=b'f' => c - b'a' + 10,
124 b'A'..=b'F' => c - b'A' + 10,
125 _ => panic!("invalid hex digit"),
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use crate::UUID;
132
133 const EXPECTED: &str = "6ba7b810-9dad-11d1-80b4-00c04fd430c8";
134
135 #[test]
136 fn parse_hyphenated() {
137 const UUID1: UUID = uuid!("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
138 assert_eq!(UUID1.to_string(), "6ba7b810-9dad-11d1-80b4-00c04fd430c8");
139 }
140
141 #[test]
142 fn parse_simple() {
143 const UUID1: UUID = uuid!("6ba7b8109dad11d180b400c04fd430c8");
144 assert_eq!(UUID1.to_string(), "6ba7b810-9dad-11d1-80b4-00c04fd430c8");
145 }
146
147 #[test]
148 fn parse_braced() {
149 const UUID1: UUID = uuid!("{6ba7b810-9dad-11d1-80b4-00c04fd430c8}");
150 assert_eq!(UUID1.to_string(), "6ba7b810-9dad-11d1-80b4-00c04fd430c8");
151 }
152
153 #[test]
154 fn parse_braced_simple() {
155 const UUID1: UUID = uuid!("{6ba7b8109dad11d180b400c04fd430c8}");
156 assert_eq!(UUID1.to_string(), "6ba7b810-9dad-11d1-80b4-00c04fd430c8");
157 }
158
159 #[test]
160 fn parse_urn() {
161 const UUID1: UUID = uuid!("urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8");
162 assert_eq!(UUID1.to_string(), "6ba7b810-9dad-11d1-80b4-00c04fd430c8");
163 }
164
165 #[test]
166 fn parse_urn_uppercase() {
167 const UUID1: UUID = uuid!("URN:UUID:6BA7B810-9DAD-11D1-80B4-00C04FD430C8");
168 assert_eq!(UUID1.to_string(), "6ba7b810-9dad-11d1-80b4-00c04fd430c8");
169 }
170
171 #[test]
172 fn parse_max() {
173 const MAX: UUID = uuid!("ffffffff-ffff-ffff-ffff-ffffffffffff");
174 assert_eq!(MAX, UUID::max());
175 }
176
177 #[test]
179 fn hyphenated_lower() {
180 const U: UUID = uuid!("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
181 assert_eq!(U.to_string(), EXPECTED);
182 }
183
184 #[test]
186 fn hyphenated_upper() {
187 const U: UUID = uuid!("6BA7B810-9DAD-11D1-80B4-00C04FD430C8");
188 assert_eq!(U.to_string(), EXPECTED);
189 }
190
191 #[test]
193 fn hyphenated_mixed() {
194 const U: UUID = uuid!("6Ba7b810-9DaD-11d1-80B4-00c04FD430c8");
195 assert_eq!(U.to_string(), EXPECTED);
196 }
197
198 #[test]
200 fn simple_lower() {
201 const U: UUID = uuid!("6ba7b8109dad11d180b400c04fd430c8");
202 assert_eq!(U.to_string(), EXPECTED);
203 }
204
205 #[test]
207 fn simple_upper() {
208 const U: UUID = uuid!("6BA7B8109DAD11D180B400C04FD430C8");
209 assert_eq!(U.to_string(), EXPECTED);
210 }
211
212 #[test]
214 fn simple_mixed() {
215 const U: UUID = uuid!("6Ba7b8109DaD11d180B400c04FD430c8");
216 assert_eq!(U.to_string(), EXPECTED);
217 }
218
219 #[test]
221 fn braced_hyphenated_lower() {
222 const U: UUID = uuid!("{6ba7b810-9dad-11d1-80b4-00c04fd430c8}");
223 assert_eq!(U.to_string(), EXPECTED);
224 }
225
226 #[test]
228 fn braced_hyphenated_upper() {
229 const U: UUID = uuid!("{6BA7B810-9DAD-11D1-80B4-00C04FD430C8}");
230 assert_eq!(U.to_string(), EXPECTED);
231 }
232
233 #[test]
235 fn braced_hyphenated_mixed() {
236 const U: UUID = uuid!("{6Ba7b810-9DaD-11d1-80B4-00c04FD430c8}");
237 assert_eq!(U.to_string(), EXPECTED);
238 }
239
240 #[test]
242 fn braced_simple_lower() {
243 const U: UUID = uuid!("{6ba7b8109dad11d180b400c04fd430c8}");
244 assert_eq!(U.to_string(), EXPECTED);
245 }
246
247 #[test]
249 fn braced_simple_upper() {
250 const U: UUID = uuid!("{6BA7B8109DAD11D180B400C04FD430C8}");
251 assert_eq!(U.to_string(), EXPECTED);
252 }
253
254 #[test]
256 fn braced_simple_mixed() {
257 const U: UUID = uuid!("{6Ba7b8109DaD11d180B400c04FD430c8}");
258 assert_eq!(U.to_string(), EXPECTED);
259 }
260
261 #[test]
263 fn urn_hyphenated_lower() {
264 const U: UUID = uuid!("urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8");
265 assert_eq!(U.to_string(), EXPECTED);
266 }
267
268 #[test]
270 fn urn_hyphenated_upper() {
271 const U: UUID = uuid!("URN:UUID:6BA7B810-9DAD-11D1-80B4-00C04FD430C8");
272 assert_eq!(U.to_string(), EXPECTED);
273 }
274
275 #[test]
277 fn urn_hyphenated_mixed() {
278 const U: UUID = uuid!("Urn:UuId:6Ba7b810-9DaD-11d1-80B4-00c04FD430c8");
279 assert_eq!(U.to_string(), EXPECTED);
280 }
281
282 #[test]
284 fn urn_simple_lower() {
285 const U: UUID = uuid!("urn:uuid:6ba7b8109dad11d180b400c04fd430c8");
286 assert_eq!(U.to_string(), EXPECTED);
287 }
288
289 #[test]
291 fn urn_simple_upper() {
292 const U: UUID = uuid!("URN:UUID:6BA7B8109DAD11D180B400C04FD430C8");
293 assert_eq!(U.to_string(), EXPECTED);
294 }
295
296 #[test]
298 fn urn_simple_mixed() {
299 const U: UUID = uuid!("Urn:UuId:6Ba7b8109DaD11d180B400c04FD430c8");
300 assert_eq!(U.to_string(), EXPECTED);
301 }
302
303 #[test]
305 fn parse_nil() {
306 const NIL: UUID = uuid!("00000000-0000-0000-0000-000000000000");
307 assert_eq!(NIL, UUID::nil());
308 }
309
310 #[test]
311 fn parse_max_lower() {
312 const MAX: UUID = uuid!("ffffffff-ffff-ffff-ffff-ffffffffffff");
313 assert_eq!(MAX, UUID::max());
314 }
315
316 #[test]
317 fn parse_max_upper() {
318 const MAX: UUID = uuid!("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF");
319 assert_eq!(MAX, UUID::max());
320 }
321
322 #[test]
323 fn usable_in_const_context() {
324 const DNS_NS: UUID = uuid!("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
325 static STATIC_UUID: UUID = uuid!("6ba7b811-9dad-11d1-80b4-00c04fd430c8");
326
327 assert!(DNS_NS.is_v1());
328 assert!(STATIC_UUID.is_v1());
329 }
330}