1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use rand::prelude::*;
use std::error::Error;
use std::time::{SystemTime, UNIX_EPOCH};
use std::vec::Vec;

const ENCODE: &[u8; 36] = &[
    0x30, // input 0 -> "0"
    0x31, // input 1 -> "1"
    0x32, // input 2 -> "2"
    0x33, // input 3 -> "3"
    0x34, // input 4 -> "4"
    0x35, // input 5 -> "5"
    0x36, // input 6 -> "6"
    0x37, // input 7 -> "7"
    0x38, // input 8 -> "8"
    0x39, // input 9 -> "9"
    0x61, // input 10 -> "a"
    0x62, // input 11 -> "b"
    0x63, // input 12 -> "c"
    0x64, // input 13 -> "d"
    0x65, // input 14 -> "e"
    0x66, // input 15 -> "f"
    0x67, // input 16 -> "g"
    0x68, // input 17 -> "h"
    0x69, // input 18 -> "i"
    0x6a, // input 19 -> "j"
    0x6b, // input 20 -> "k"
    0x6c, // input 21 -> "l"
    0x6d, // input 22 -> "m"
    0x6e, // input 23 -> "n"
    0x6f, // input 24 -> "o"
    0x70, // input 25 -> "p"
    0x71, // input 26 -> "q"
    0x72, // input 27 -> "r"
    0x73, // input 28 -> "s"
    0x74, // input 29 -> "t"
    0x75, // input 30 -> "u"
    0x76, // input 31 -> "v"
    0x77, // input 32 -> "w"
    0x78, // input 33 -> "x"
    0x79, // input 34 -> "y"
    0x7a, // input 35 -> "z"
];

pub fn encode_base36(number: usize) -> Result<String, Box<dyn Error>> {
    let mut res = Vec::new();

    let mut number = number;
    while 0 < number {
        res.push(ENCODE[number % 36]);
        number /= 36;
    }
    res.reverse();

    Ok(String::from_utf8(res)?)
}
const AID_EPOCH: u64 = 946684800000;

fn gen_time(time: SystemTime) -> Result<String, Box<dyn Error>> {
    let time = time.duration_since(UNIX_EPOCH)?;

    Ok(format!(
        "{:0>8}",
        encode_base36(
            (time.as_secs() * 1000 + time.subsec_nanos() as u64 / 1_000_000 - AID_EPOCH) as usize,
        )?
    ))
}

fn gen_noise() -> Result<String, Box<dyn Error>> {
    let mut rng = rand::thread_rng();
    let n = format!("{:0>2}", encode_base36(rng.gen::<u16>() as usize)?).as_str()[..2].to_string();

    Ok(n)
}

/// Generate aid.
///
/// The aid consists of 8 bytes of milliseconds elapsed since 2000-01-01 00:00:00 UTC and 2 bytes of a random characters.
///
/// # Arguments
/// * `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.
///
/// # Exmaple
///
/// ```
/// use aid::gen;
/// use std::time::SystemTime;
///
/// let aid = gen(SystemTime::now());
/// ```
pub fn gen(time: SystemTime) -> Result<String, Box<dyn Error>> {
    let t = gen_time(time)?;
    let n = gen_noise()?;

    Ok(format!("{}{}", t, n))
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashSet;
    use std::hash::Hash;
    use std::thread::sleep;
    use std::time::{Duration, SystemTime};

    fn has_unique_elements<T>(iter: T) -> bool
    where
        T: IntoIterator,
        T::Item: Eq + Hash,
    {
        let mut uniq = HashSet::new();
        iter.into_iter().all(move |x| uniq.insert(x))
    }

    #[test]
    fn no_dups() {
        let mut ids: Vec<String> = vec![String::new(); 1000];

        for i in 0..999 {
            let aid = gen(SystemTime::now()).unwrap();
            ids[i] = aid;

            sleep(Duration::from_millis(1));
        }

        assert!(has_unique_elements(ids));
    }
}