rust_auth_utils/
random.rs

1// based on https://github.com/better-auth/utils/blob/main/src/random.ts
2
3use rand::{thread_rng, RngCore};
4
5#[derive(Debug, Clone, Copy)]
6pub enum Alphabet {
7    Lowercase, // a-z
8    Uppercase, // A-Z
9    Digits,    // 0-9
10    Special,   // -_
11}
12
13impl Alphabet {
14    fn expand(&self) -> &'static str {
15        match self {
16            Alphabet::Lowercase => "abcdefghijklmnopqrstuvwxyz",
17            Alphabet::Uppercase => "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
18            Alphabet::Digits => "0123456789",
19            Alphabet::Special => "-_",
20        }
21    }
22}
23
24#[derive(Debug)]
25pub struct Error(String);
26
27impl std::fmt::Display for Error {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        write!(f, "{}", self.0)
30    }
31}
32
33impl std::error::Error for Error {}
34
35pub struct RandomStringGenerator {
36    base_character_set: String,
37}
38
39impl RandomStringGenerator {
40    pub fn new(alphabets: &[Alphabet]) -> Result<Self, Error> {
41        if alphabets.is_empty() {
42            return Err(Error(
43                "No valid characters provided for random string generation.".to_string(),
44            ));
45        }
46
47        let base_character_set = alphabets
48            .iter()
49            .map(|a| a.expand())
50            .collect::<Vec<&str>>()
51            .join("");
52
53        Ok(Self { base_character_set })
54    }
55
56    pub fn generate(&self, length: usize, alphabet: Option<Alphabet>) -> Result<String, Error> {
57        if length == 0 {
58            return Err(Error("Length must be a positive integer.".to_string()));
59        }
60
61        let (character_set, char_set_length) = if let Some(alphabet) = alphabet {
62            let set = alphabet.expand();
63            (set, set.len())
64        } else {
65            (&self.base_character_set[..], self.base_character_set.len())
66        };
67
68        let mut rng = thread_rng();
69        let mut char_array = vec![0u8; length];
70        rng.fill_bytes(&mut char_array);
71
72        let result = char_array
73            .iter()
74            .map(|&byte| {
75                let index = byte as usize % char_set_length;
76                character_set.chars().nth(index).unwrap()
77            })
78            .collect();
79
80        Ok(result)
81    }
82}