rust-auth-utils 1.0.0

A rust port of @better-auth/utils.
Documentation
// based on https://github.com/better-auth/utils/blob/main/src/random.ts

use rand::{thread_rng, RngCore};

#[derive(Debug, Clone, Copy)]
pub enum Alphabet {
    Lowercase, // a-z
    Uppercase, // A-Z
    Digits,    // 0-9
    Special,   // -_
}

impl Alphabet {
    fn expand(&self) -> &'static str {
        match self {
            Alphabet::Lowercase => "abcdefghijklmnopqrstuvwxyz",
            Alphabet::Uppercase => "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
            Alphabet::Digits => "0123456789",
            Alphabet::Special => "-_",
        }
    }
}

#[derive(Debug)]
pub struct Error(String);

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl std::error::Error for Error {}

pub struct RandomStringGenerator {
    base_character_set: String,
}

impl RandomStringGenerator {
    pub fn new(alphabets: &[Alphabet]) -> Result<Self, Error> {
        if alphabets.is_empty() {
            return Err(Error(
                "No valid characters provided for random string generation.".to_string(),
            ));
        }

        let base_character_set = alphabets
            .iter()
            .map(|a| a.expand())
            .collect::<Vec<&str>>()
            .join("");

        Ok(Self { base_character_set })
    }

    pub fn generate(&self, length: usize, alphabet: Option<Alphabet>) -> Result<String, Error> {
        if length == 0 {
            return Err(Error("Length must be a positive integer.".to_string()));
        }

        let (character_set, char_set_length) = if let Some(alphabet) = alphabet {
            let set = alphabet.expand();
            (set, set.len())
        } else {
            (&self.base_character_set[..], self.base_character_set.len())
        };

        let mut rng = thread_rng();
        let mut char_array = vec![0u8; length];
        rng.fill_bytes(&mut char_array);

        let result = char_array
            .iter()
            .map(|&byte| {
                let index = byte as usize % char_set_length;
                character_set.chars().nth(index).unwrap()
            })
            .collect();

        Ok(result)
    }
}