use std::sync::Arc;
use thiserror::Error;
use super::{Id, IdGenerator, IdType};
use std::future::Future;
use std::pin::Pin;
#[derive(Debug, Error)]
pub enum NanoIdError {
#[error("Failed to generate random bytes: {0}")]
RandomError(String),
}
pub const DEFAULT_ALPHABET: &[u8] =
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
#[derive(Debug, Clone)]
pub struct NanoIdConfig {
pub length: usize,
pub alphabet: Arc<[u8]>,
}
impl Default for NanoIdConfig {
fn default() -> Self {
Self {
length: 21,
alphabet: Arc::from(DEFAULT_ALPHABET),
}
}
}
impl NanoIdConfig {
pub fn new() -> Self {
Self::default()
}
pub fn length(mut self, length: usize) -> Self {
self.length = length;
self
}
pub fn alphabet(mut self, alphabet: &[u8]) -> Self {
self.alphabet = Arc::from(alphabet.to_vec().into_boxed_slice());
self
}
}
#[derive(Debug, Clone)]
pub struct NanoId {
config: NanoIdConfig,
mask: u8,
step: usize,
}
impl NanoId {
pub fn new(config: NanoIdConfig) -> Self {
let alphabet_len = config.alphabet.len();
let mask = {
let mut m = 1u8;
while m < alphabet_len as u8 - 1 {
m = m * 2 + 1;
}
m
};
let step = (config.length * 8)
.div_ceil(mask.count_ones() as usize)
.max(1);
Self { config, mask, step }
}
pub fn with_default() -> Self {
Self::new(NanoIdConfig::default())
}
pub fn generate_string(&self) -> Result<String, NanoIdError> {
let mut result = String::with_capacity(self.config.length);
let alphabet = &self.config.alphabet;
let mut bytes = vec![0u8; self.step * 2];
while result.len() < self.config.length {
getrandom::fill(&mut bytes).map_err(|e| NanoIdError::RandomError(e.to_string()))?;
for &byte in &bytes {
let index = byte & self.mask;
if (index as usize) < alphabet.len() {
result.push(alphabet[index as usize] as char);
if result.len() >= self.config.length {
break;
}
}
}
}
Ok(result)
}
}
impl IdGenerator for NanoId {
type Error = NanoIdError;
fn generate(&self) -> Pin<Box<dyn Future<Output = Result<Id, Self::Error>> + Send + '_>> {
Box::pin(async move {
let s = self.generate_string()?;
Ok(Id::String(s))
})
}
fn id_type(&self) -> IdType {
IdType::ShortString
}
}