rustq-nanoid 0.0.1-dev3

A tiny, secure, URL-friendly, unique string ID generator for Rust.
Documentation
#![doc(
    html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png",
    html_favicon_url = "https://www.rust-lang.org/favicon.ico",
    html_root_url = "https://docs.rs/rustq-nanoid"
)]

#[cfg(feature = "smartstring")]
use smartstring::alias::String;

pub mod alphabet;
pub mod rngs;

use std::sync::Mutex;

const POOL_SIZE_MULTIPLIER: usize = 128;
const DEFAULT_SIZE: usize = 21;
const POOL_SIZE: usize = DEFAULT_SIZE * POOL_SIZE_MULTIPLIER;

lazy_static! {
    static ref POOL: Mutex<[u8; POOL_SIZE]> = Mutex::new([0; POOL_SIZE]);
    static ref POOL_OFFSET: Mutex<usize> = Mutex::new(POOL_SIZE);
}

pub fn format(random: fn(usize) -> Vec<u8>, alphabet: &[char], size: usize) -> String {
    assert!(
        alphabet.len() <= u8::max_value() as usize,
        "The alphabet cannot be longer than a `u8` (to comply with the `random` function)"
    );

    assert!(size <= POOL_SIZE, "The size should smaller than pool size");

    let bytes = &mut POOL.lock().unwrap();
    let mask = alphabet.len().next_power_of_two() - 1;
    // Assert that the masking does not truncate the alphabet. (See #9)
    debug_assert!(alphabet.len() <= mask + 1);

    let mut pointer = *POOL_OFFSET.lock().unwrap();

    let mut id = String::with_capacity(size);

    while id.len() < size {
        if pointer == POOL_SIZE {
            let buf = random(POOL_SIZE);
            for i in 0..POOL_SIZE {
                bytes[i] = buf[i];
            }
            pointer = 0;
        }
        let byte = bytes[pointer] as usize & mask;
        if alphabet.len() > byte {
            id.push(alphabet[byte]);
        }
        pointer += 1;
    }

    *POOL_OFFSET.lock().unwrap() = pointer;

    id
}

#[cfg(test)]
mod test_format {
    use super::*;

    // #[test]
    // fn generates_random_string() {
    //     fn random(size: usize) -> Vec<u8> {
    //         [2, 255, 0, 1].iter().cloned().cycle().take(size).collect()
    //     }

    //     assert_eq!(format(random, &['a', 'b', 'c'], 4), "cabc");
    // }

    #[test]
    #[should_panic]
    fn bad_alphabet() {
        let alphabet: Vec<char> = (0..32_u8).cycle().map(|i| i as char).take(1000).collect();
        nanoid!(21, &alphabet);
    }

    #[test]
    fn non_power_2() {
        let id: String = nanoid!(42, &alphabet::SAFE[0..62]);

        assert_eq!(id.len(), 42);
    }
}

#[macro_export]
macro_rules! nanoid {
    // simple
    () => {
        $crate::format($crate::rngs::default, &$crate::alphabet::SAFE, 21)
    };

    // generate
    ($size:expr) => {
        $crate::format($crate::rngs::default, &$crate::alphabet::SAFE, $size)
    };

    // custom
    ($size:expr, $alphabet:expr) => {
        $crate::format($crate::rngs::default, $alphabet, $size)
    };

    // complex
    ($size:expr, $alphabet:expr, $random:expr) => {
        $crate::format($random, $alphabet, $size)
    };
}

#[cfg(test)]
mod test_macros {
    use super::*;

    #[test]
    fn simple() {
        let id: String = nanoid!();

        assert_eq!(id.len(), 21);
    }

    #[test]
    fn generate() {
        let id: String = nanoid!(42);

        assert_eq!(id.len(), 42);
    }

    #[test]
    fn custom() {
        let id: String = nanoid!(42, &alphabet::SAFE);

        assert_eq!(id.len(), 42);
    }

    #[test]
    fn complex() {
        let id: String = nanoid!(4, &alphabet::SAFE, rngs::default);

        assert_eq!(id.len(), 4);
    }

    #[test]
    fn simple_expression() {
        let id: String = nanoid!(42 / 2);

        assert_eq!(id.len(), 21);
    }
}

#[cfg(doctest)]
doc_comment::doctest!("../README.md");

#[macro_use]
extern crate lazy_static;
// pub mod prefill;