1#![cfg_attr(docsrs, feature(doc_cfg))]
19use uuid::Uuid;
21
22pub struct HyperId {
38 uuid: Uuid,
39 c: u8,
40}
41
42impl HyperId {
43 pub fn new() -> Self {
49 let uuid = Uuid::new_v4();
50 let c: u8 = 0;
51
52 Self { uuid, c }
53 }
54
55 pub fn from_id(id: Id) -> Self {
65 let uuid = id.uuid_as_128;
66 let uuid = Uuid::from_u128(uuid);
67 let c = id.c;
68
69 Self { uuid, c }
70 }
71
72 pub fn get(&self) -> Id {
81 Id {
82 uuid_as_128: self.uuid.as_u128(),
83 c: self.c,
84 }
85 }
86
87 pub fn generate(&mut self) -> Id {
96 self.c = self.c.checked_add(1).unwrap_or(0);
97 if self.c == 0 {
98 self.uuid = Uuid::new_v4();
99 }
100
101 Id {
102 uuid_as_128: self.uuid.as_u128(),
103 c: self.c,
104 }
105 }
106}
107
108impl Default for HyperId {
109 fn default() -> Self {
110 Self::new()
111 }
112}
113#[derive(PartialEq, Debug, Clone, Copy, Eq, Hash)]
115pub struct Id {
116 uuid_as_128: u128,
117 c: u8,
118}
119
120impl Id {
121 pub fn into_bytes(self) -> Vec<u8> {
129 let uuid_as_128 = self.uuid_as_128;
130 let mut bytes = uuid_as_128.to_be_bytes().to_vec();
131 bytes.push(self.c);
132 bytes
133 }
134
135 pub fn from_bytes(mut bytes: Vec<u8>) -> Result<Self, ParseIdError> {
143 if bytes.len() != 17 {
144 return Err(ParseIdError::WrongByteSize);
145 }
146 let c = bytes.pop().unwrap();
147 let mut arr = [0u8; 16];
148 bytes.swap_with_slice(&mut arr);
149 Ok(Self {
150 uuid_as_128: u128::from_be_bytes(arr),
151 c,
152 })
153 }
154
155 #[cfg_attr(docsrs, doc(cfg(feature = "url_safe")))]
163 #[cfg(feature = "url_safe")]
164 pub fn to_url_safe(&self) -> String {
165 let uuid_as_bytes = self.uuid_as_128.to_le_bytes();
166 let str = base64::encode_config(uuid_as_bytes, base64::URL_SAFE_NO_PAD);
167 format!("{}.{}", str, self.c)
168 }
169
170 #[cfg_attr(docsrs, doc(cfg(feature = "url_safe")))]
180 #[cfg(feature = "url_safe")]
181 pub fn from_url_safe(s: String) -> Result<Id, ParseIdError> {
182 let mut split = s.split('.');
183 let uuid_as_128 = split
184 .next()
185 .ok_or(ParseIdError::NoBaseFound)
186 .and_then(|uuid_as_128| {
187 base64::decode_config(uuid_as_128, base64::URL_SAFE_NO_PAD)
188 .or(Err(ParseIdError::NoBaseFound))
189 .map(|mut v| {
190 let mut arr = [0u8; 16];
191 v.swap_with_slice(&mut arr);
192 arr
193 })
194 .map(u128::from_le_bytes)
195 });
196 let c = split
197 .next()
198 .ok_or(ParseIdError::NoCounterFound)
199 .and_then(|c| c.parse::<u8>().map_err(|_| ParseIdError::NoCounterFound));
200
201 match (uuid_as_128, c) {
202 (Ok(uuid_as_128), Ok(c)) => Ok(Id { uuid_as_128, c }),
203 (Err(err), _) | (_, Err(err)) => Err(err),
204 }
205 }
206}
207
208#[derive(Debug)]
209pub enum ParseIdError {
210 NoBaseFound,
211 NoCounterFound,
212 WrongByteSize,
213}
214
215#[cfg(test)]
216mod tests {
217 use std::collections::HashMap;
218
219 use super::*;
220
221 #[test]
222 fn starts_from_zero() {
223 let hyperid = HyperId::default();
224 assert_eq!(hyperid.c, 0);
225 }
226
227 #[test]
228 fn get_return_equal_id() {
229 let hyperid = HyperId::default();
230 assert_eq!(hyperid.get(), hyperid.get());
231 }
232
233 #[test]
234 fn generate_change_internal_state() {
235 let mut hyperid = HyperId::default();
236
237 let c = hyperid.c;
238 hyperid.generate();
239 assert_ne!(hyperid.c, c);
240 }
241
242 #[test]
243 fn generate_returns_different_id() {
244 let mut hyperid = HyperId::default();
245
246 let previous_id = hyperid.get();
247 let next_id = hyperid.generate();
248 assert_ne!(previous_id, next_id);
249 }
250
251 #[test]
252 fn on_255_generate_a_new_base() {
253 let mut hyperid = HyperId::default();
254
255 let base = hyperid.uuid;
256
257 for _ in 0..255 {
258 hyperid.generate();
259 }
260 let new_base = hyperid.uuid;
261 assert_eq!(base, new_base);
262
263 hyperid.generate();
264
265 let new_base = hyperid.uuid;
266 assert_ne!(base, new_base);
267 }
268
269 #[test]
270 fn different_instances_have_different_base() {
271 let hyperid1 = HyperId::default();
272 let hyperid2 = HyperId::default();
273 assert_ne!(hyperid1.uuid, hyperid2.uuid);
274 }
275
276 #[test]
277 fn into_bytes() {
278 let hyperid = HyperId::default();
279
280 let id = hyperid.get();
281
282 let id_bytes = id.into_bytes();
283
284 let id_from_decode = Id::from_bytes(id_bytes).unwrap();
285
286 assert_eq!(hyperid.get(), id_from_decode);
287 }
288
289 #[test]
290 fn id_could_be_the_key_of_hashmap() {
291 let hyperid = HyperId::default();
292
293 let id = hyperid.get();
294
295 let mut map = HashMap::new();
296 map.insert(id, true);
297 }
298
299 #[cfg(feature = "url_safe")]
300 #[test]
301 fn url_safe_encode_decode() {
302 let hyperid = HyperId::default();
303
304 let id = hyperid.get();
305
306 let id_str = id.to_url_safe();
307
308 let id_from_decode = Id::from_url_safe(id_str).unwrap();
309
310 assert_eq!(id, id_from_decode);
311 }
312}