1use core::fmt;
2
3pub const ULID_LEN: usize = 26;
5
6const ALPHABET: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
7
8const NO_VALUE: u8 = 255;
9const LOOKUP: [u8; 256] = [
10 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
11 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
12 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255,
13 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 255, 18, 19, 255, 20, 21, 255, 22, 23, 24,
14 25, 26, 255, 27, 28, 29, 30, 31, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17,
15 255, 18, 19, 255, 20, 21, 255, 22, 23, 24, 25, 26, 255, 27, 28, 29, 30, 31, 255, 255, 255, 255,
16 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
17 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
18 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
19 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
20 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
21 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
22 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
23];
24
25#[cfg(test)]
27#[test]
28fn test_lookup_table() {
29 let mut lookup = [NO_VALUE; 256];
30 for (i, &c) in ALPHABET.iter().enumerate() {
31 lookup[c as usize] = i as u8;
32 if !(c as char).is_numeric() {
33 lookup[(c + 32) as usize] = i as u8;
35 }
36 }
37 assert_eq!(LOOKUP, lookup);
38}
39
40#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
42pub enum EncodeError {
43 BufferTooSmall,
45}
46
47#[cfg(feature = "std")]
48impl std::error::Error for EncodeError {}
49
50impl fmt::Display for EncodeError {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
52 let text = match *self {
53 EncodeError::BufferTooSmall => "buffer too small",
54 };
55 write!(f, "{}", text)
56 }
57}
58
59#[deprecated(
61 since = "1.2.0",
62 note = "Use the infallible `encode_to_array` instead."
63)]
64pub fn encode_to(mut value: u128, buffer: &mut [u8]) -> Result<usize, EncodeError> {
65 if buffer.len() < ULID_LEN {
68 return Err(EncodeError::BufferTooSmall);
69 }
70
71 for i in 0..ULID_LEN {
72 buffer[ULID_LEN - 1 - i] = ALPHABET[(value & 0x1f) as usize];
73 value >>= 5;
74 }
75
76 Ok(ULID_LEN)
77}
78
79pub fn encode_to_array(mut value: u128, buffer: &mut [u8; ULID_LEN]) {
81 for i in 0..ULID_LEN {
84 buffer[ULID_LEN - 1 - i] = ALPHABET[(value & 0x1f) as usize];
85 value >>= 5;
86 }
87}
88
89#[cfg(feature = "std")]
90pub fn encode(value: u128) -> String {
91 let mut buffer: [u8; ULID_LEN] = [0; ULID_LEN];
92
93 encode_to_array(value, &mut buffer);
94
95 String::from_utf8(buffer.to_vec()).expect("unexpected failure in base32 encode for ulid")
96}
97
98#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
100pub enum DecodeError {
101 InvalidLength,
103 InvalidChar,
105}
106
107#[cfg(feature = "std")]
108impl std::error::Error for DecodeError {}
109
110impl fmt::Display for DecodeError {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
112 let text = match *self {
113 DecodeError::InvalidLength => "invalid length",
114 DecodeError::InvalidChar => "invalid character",
115 };
116 write!(f, "{}", text)
117 }
118}
119
120pub const fn decode(encoded: &str) -> Result<u128, DecodeError> {
121 if encoded.len() != ULID_LEN {
122 return Err(DecodeError::InvalidLength);
123 }
124
125 let mut value: u128 = 0;
126
127 let bytes = encoded.as_bytes();
128
129 let mut i = 0;
131 while i < ULID_LEN {
132 let val = LOOKUP[bytes[i] as usize];
133 if val != NO_VALUE {
134 value = (value << 5) | val as u128;
135 } else {
136 return Err(DecodeError::InvalidChar);
137 }
138 i += 1;
139 }
140
141 Ok(value)
142}
143
144#[cfg(all(test, feature = "std"))]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_valid() {
150 let val = 0x41414141414141414141414141414141;
151 assert_eq!(decode("21850M2GA1850M2GA1850M2GA1").unwrap(), val);
152 assert_eq!(encode(val), "21850M2GA1850M2GA1850M2GA1");
153
154 let val = 0x4d4e385051444a59454234335a413756;
155 let enc = "2D9RW50MA499CMAGHM6DD42DTP";
156 let lower = enc.to_lowercase();
157 assert_eq!(encode(val), enc);
158 assert_eq!(decode(enc).unwrap(), val);
159 assert_eq!(decode(&lower).unwrap(), val);
160 }
161
162 #[test]
163 fn test_length() {
164 assert_eq!(encode(0xffffffffffffffffffffffffffffffff).len(), ULID_LEN);
165 assert_eq!(encode(0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f).len(), ULID_LEN);
166 assert_eq!(encode(0x00000000000000000000000000000000).len(), ULID_LEN);
167
168 assert_eq!(decode(""), Err(DecodeError::InvalidLength));
169 assert_eq!(
170 decode("2D9RW50MA499CMAGHM6DD42DT"),
171 Err(DecodeError::InvalidLength)
172 );
173 assert_eq!(
174 decode("2D9RW50MA499CMAGHM6DD42DTPP"),
175 Err(DecodeError::InvalidLength)
176 );
177 }
178
179 #[test]
180 fn test_chars() {
181 for ref c in encode(0xffffffffffffffffffffffffffffffff).bytes() {
182 assert!(ALPHABET.contains(c));
183 }
184 for ref c in encode(0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f).bytes() {
185 assert!(ALPHABET.contains(c));
186 }
187 for ref c in encode(0x00000000000000000000000000000000).bytes() {
188 assert!(ALPHABET.contains(c));
189 }
190
191 assert_eq!(
192 decode("2D9RW50[A499CMAGHM6DD42DTP"),
193 Err(DecodeError::InvalidChar)
194 );
195 assert_eq!(
196 decode("2D9RW50LA499CMAGHM6DD42DTP"),
197 Err(DecodeError::InvalidChar)
198 );
199 assert_eq!(
200 decode("2D9RW50IA499CMAGHM6DD42DTP"),
201 Err(DecodeError::InvalidChar)
202 );
203 }
204}