aid 0.1.1

Rust port of the id generation algorithm use in Misskey
Documentation
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));
    }
}