#![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/nanoid"
)]
#[cfg(feature = "smartstring")]
use smartstring::alias::String;
pub mod alphabet;
pub mod rngs;
pub fn format<F: FnMut(usize) -> Vec<u8>>(random: F, alphabet: &[char], size: usize) -> String {
assert!(
alphabet.len() <= u8::MAX as usize,
"The alphabet cannot be longer than a `u8` (to comply with the `random` function)"
);
#[cfg(not(feature = "smartstring"))]
let mut id = String::with_capacity(size);
#[cfg(feature = "smartstring")]
let mut id = String::new();
if alphabet.len().is_power_of_two() {
fast_impl(&mut id, random, alphabet, size);
} else {
generic_impl(&mut id, random, alphabet, size);
}
id
}
fn generic_impl<F: FnMut(usize) -> Vec<u8>>(
id: &mut String,
mut random: F,
alphabet: &[char],
size: usize,
) {
let mask = alphabet.len().next_power_of_two() - 1;
let step: usize = 8 * size / 5;
debug_assert!(alphabet.len() <= mask + 1);
loop {
let bytes = random(step);
for &byte in &bytes {
let byte = byte as usize & mask;
if alphabet.len() > byte {
id.push(alphabet[byte]);
if id.len() == size {
return;
}
}
}
}
}
fn fast_impl<F: FnMut(usize) -> Vec<u8>>(
id: &mut String,
mut random: F,
alphabet: &[char],
size: usize,
) {
debug_assert!(alphabet.len().is_power_of_two());
let mask = alphabet.len() - 1;
let bytes = random(size);
for &byte in &bytes {
let byte = byte as usize & mask;
id.push(alphabet[byte]);
}
}
#[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);
}
#[test]
fn power_of_two_uses_fast_path() {
fn random(size: usize) -> Vec<u8> {
(0..size as u8).collect()
}
let alphabet: [char; 4] = ['a', 'b', 'c', 'd'];
assert_eq!(format(random, &alphabet, 8), "abcdabcd");
}
}
#[macro_export]
macro_rules! nanoid {
() => {
$crate::format($crate::rngs::default, &$crate::alphabet::SAFE, 21)
};
($size:expr) => {
$crate::format($crate::rngs::default, &$crate::alphabet::SAFE, $size)
};
($size:expr, $alphabet:expr) => {
$crate::format($crate::rngs::default, $alphabet, $size)
};
($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 closure() {
let uuid = "8936ad0c-9443-4007-9430-e223c64d4629";
let id1 = nanoid!(20, &alphabet::SAFE, |_| uuid.as_bytes().to_vec());
let id2 = nanoid!(20, &alphabet::SAFE, |_| uuid.as_bytes().to_vec());
assert_eq!(id1, id2);
}
#[test]
fn simple_expression() {
let id: String = nanoid!(42 / 2);
assert_eq!(id.len(), 21);
}
#[test]
fn fnmut_closure() {
let mut counter = 0u8;
let id = nanoid!(10, &alphabet::SAFE, |size| {
let mut bytes = vec![0u8; size];
for byte in &mut bytes {
*byte = counter;
counter = counter.wrapping_add(1);
}
bytes
});
assert_eq!(id.len(), 10);
assert!(counter > 0); }
}
#[cfg(doctest)]
doc_comment::doctest!("../README.md");