1const ALPHABET: &[u8; 64] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$";
17
18const fn reverse_table() -> [i8; 256] {
20 let mut table = [-1i8; 256];
21 let mut i = 0;
22 while i < 64 {
23 table[ALPHABET[i] as usize] = i as i8;
24 i += 1;
25 }
26 table
27}
28
29const REVERSE: [i8; 256] = reverse_table();
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
33pub struct Guid([u8; 22]);
34
35impl Guid {
36 pub fn parse(text: &str) -> Option<Self> {
38 let bytes = text.as_bytes();
39 if bytes.len() != 22 {
40 return None;
41 }
42 if bytes.iter().any(|&b| REVERSE[b as usize] < 0) {
43 return None;
44 }
45 let mut buf = [0u8; 22];
46 buf.copy_from_slice(bytes);
47 Some(Self(buf))
48 }
49
50 pub fn as_str(&self) -> &str {
52 std::str::from_utf8(&self.0).expect("alphabet is ASCII by construction")
55 }
56
57 pub fn from_uuid(uuid: [u8; 16]) -> Self {
59 let mut num = 0u128;
60 for b in uuid {
61 num = (num << 8) | b as u128;
62 }
63 let mut out = [b'0'; 22];
66 let mut n = num;
67 for slot in out.iter_mut().rev() {
68 *slot = ALPHABET[(n & 0x3f) as usize];
69 n >>= 6;
70 }
71 Self(out)
72 }
73
74 pub fn to_uuid(self) -> [u8; 16] {
76 let mut num = 0u128;
77 for &b in &self.0 {
78 num = (num << 6) | (REVERSE[b as usize] as u128);
79 }
80 let mut out = [0u8; 16];
81 for (i, slot) in out.iter_mut().enumerate() {
82 *slot = ((num >> (8 * (15 - i))) & 0xff) as u8;
83 }
84 out
85 }
86}
87
88impl std::fmt::Display for Guid {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 f.write_str(self.as_str())
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn rejects_wrong_length_and_foreign_characters() {
100 assert!(Guid::parse("tooshort").is_none());
101 assert!(Guid::parse("0123456789ABCDEFGHIJ+/").is_none());
103 }
104
105 #[test]
106 fn accepts_a_real_globalid_from_the_fixture_corpus() {
107 assert!(Guid::parse("2O2Fr$t4X7Zf8NOew3FLOH").is_some());
108 }
109
110 #[test]
113 fn uuid_roundtrip_is_lossless() {
114 let uuid: [u8; 16] = [
115 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54,
116 0x32, 0x10,
117 ];
118 assert_eq!(Guid::from_uuid(uuid).to_uuid(), uuid);
119 }
120
121 #[test]
122 fn text_roundtrip_is_lossless() {
123 let g = Guid::parse("2O2Fr$t4X7Zf8NOew3FLOH").unwrap();
124 assert_eq!(Guid::from_uuid(g.to_uuid()), g);
125 }
126}