aid/
lib.rs

1use rand::prelude::*;
2use std::error::Error;
3use std::time::{SystemTime, UNIX_EPOCH};
4use std::vec::Vec;
5
6const ENCODE: &[u8; 36] = &[
7    0x30, // input 0 -> "0"
8    0x31, // input 1 -> "1"
9    0x32, // input 2 -> "2"
10    0x33, // input 3 -> "3"
11    0x34, // input 4 -> "4"
12    0x35, // input 5 -> "5"
13    0x36, // input 6 -> "6"
14    0x37, // input 7 -> "7"
15    0x38, // input 8 -> "8"
16    0x39, // input 9 -> "9"
17    0x61, // input 10 -> "a"
18    0x62, // input 11 -> "b"
19    0x63, // input 12 -> "c"
20    0x64, // input 13 -> "d"
21    0x65, // input 14 -> "e"
22    0x66, // input 15 -> "f"
23    0x67, // input 16 -> "g"
24    0x68, // input 17 -> "h"
25    0x69, // input 18 -> "i"
26    0x6a, // input 19 -> "j"
27    0x6b, // input 20 -> "k"
28    0x6c, // input 21 -> "l"
29    0x6d, // input 22 -> "m"
30    0x6e, // input 23 -> "n"
31    0x6f, // input 24 -> "o"
32    0x70, // input 25 -> "p"
33    0x71, // input 26 -> "q"
34    0x72, // input 27 -> "r"
35    0x73, // input 28 -> "s"
36    0x74, // input 29 -> "t"
37    0x75, // input 30 -> "u"
38    0x76, // input 31 -> "v"
39    0x77, // input 32 -> "w"
40    0x78, // input 33 -> "x"
41    0x79, // input 34 -> "y"
42    0x7a, // input 35 -> "z"
43];
44
45pub fn encode_base36(number: usize) -> Result<String, Box<dyn Error>> {
46    let mut res = Vec::new();
47
48    let mut number = number;
49    while 0 < number {
50        res.push(ENCODE[number % 36]);
51        number /= 36;
52    }
53    res.reverse();
54
55    Ok(String::from_utf8(res)?)
56}
57const AID_EPOCH: u64 = 946684800000;
58
59fn gen_time(time: SystemTime) -> Result<String, Box<dyn Error>> {
60    let time = time.duration_since(UNIX_EPOCH)?;
61
62    Ok(format!(
63        "{:0>8}",
64        encode_base36(
65            (time.as_secs() * 1000 + time.subsec_nanos() as u64 / 1_000_000 - AID_EPOCH) as usize,
66        )?
67    ))
68}
69
70fn gen_noise() -> Result<String, Box<dyn Error>> {
71    let mut rng = rand::thread_rng();
72    let n = format!("{:0>2}", encode_base36(rng.gen::<u16>() as usize)?).as_str()[..2].to_string();
73
74    Ok(n)
75}
76
77/// Generate aid.
78///
79/// The aid consists of 8 bytes of milliseconds elapsed since 2000-01-01 00:00:00 UTC and 2 bytes of a random characters.
80///
81/// # Arguments
82/// * `time` - Indicates the current time. It will probably work if you put in a date in the past, but it's probably best not to.
83///
84/// # Exmaple
85///
86/// ```
87/// use aid::gen;
88/// use std::time::SystemTime;
89///
90/// let aid = gen(SystemTime::now());
91/// ```
92pub fn gen(time: SystemTime) -> Result<String, Box<dyn Error>> {
93    let t = gen_time(time)?;
94    let n = gen_noise()?;
95
96    Ok(format!("{}{}", t, n))
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use std::collections::HashSet;
103    use std::hash::Hash;
104    use std::thread::sleep;
105    use std::time::{Duration, SystemTime};
106
107    fn has_unique_elements<T>(iter: T) -> bool
108    where
109        T: IntoIterator,
110        T::Item: Eq + Hash,
111    {
112        let mut uniq = HashSet::new();
113        iter.into_iter().all(move |x| uniq.insert(x))
114    }
115
116    #[test]
117    fn no_dups() {
118        let mut ids: Vec<String> = vec![String::new(); 1000];
119
120        for i in 0..999 {
121            let aid = gen(SystemTime::now()).unwrap();
122            ids[i] = aid;
123
124            sleep(Duration::from_millis(1));
125        }
126
127        assert!(has_unique_elements(ids));
128    }
129}