vortex_otp_lib/
lib.rs

1use rand::{rngs::ThreadRng, seq::SliceRandom, Rng};
2
3pub enum OtpCharSet {
4    Numeric,
5    Alphanumeric,
6    AlphanumericSpecialChars,
7    Custom(String),
8}
9
10pub fn generate_otp(length: usize, char_set_type: OtpCharSet) -> Result<String, &'static str> {
11    let mut rng: ThreadRng = rand::thread_rng();
12
13    let chars: Vec<char> = match char_set_type {
14        OtpCharSet::Numeric => "0123456789".chars().collect(),
15        OtpCharSet::Alphanumeric => "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect(),
16        OtpCharSet::AlphanumericSpecialChars => "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-=[]{}|;:,.<>?".chars().collect(),
17        OtpCharSet::Custom(s) => {
18            if s.is_empty() {
19                return Err("Custom character set cannot be empty");
20            }
21            s.chars().collect()
22        }
23    };
24
25    if chars.is_empty() {
26        return Err("Effective character set is empty");
27    }
28
29    let otp: String = (0..length)
30        .map(|_| {
31            *chars
32                .choose(&mut rng)
33                .expect("Character set should not be empty at this point")
34        })
35        .collect();
36
37    Ok(otp)
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    #[test]
45    fn generate_numeric_otp() {
46        let otp = generate_otp(6, OtpCharSet::Numeric).unwrap();
47        assert_eq!(otp.len(), 6);
48        assert!(otp.chars().all(|c| c.is_digit(10)));
49    }
50
51    #[test]
52    fn generate_alphanumeric_otp() {
53        let otp = generate_otp(8, OtpCharSet::Alphanumeric).unwrap();
54        assert_eq!(otp.len(), 8);
55        assert!(otp.chars().all(|c| c.is_alphanumeric()));
56    }
57
58    #[test]
59    fn generate_alphanumeric_special_otp() {
60        let otp = generate_otp(10, OtpCharSet::AlphanumericSpecialChars).unwrap();
61        assert_eq!(otp.len(), 10);
62        assert!(otp
63            .chars()
64            .any(|c| !c.is_alphanumeric() && !c.is_whitespace()));
65    }
66
67    #[test]
68    fn generate_custom_otp_valid() {
69        let custom_set = "ABC123xyz!@#".to_string();
70        let otp = generate_otp(7, OtpCharSet::Custom(custom_set.clone())).unwrap();
71        assert_eq!(otp.len(), 7);
72        assert!(otp.chars().all(|c| custom_set.contains(c)));
73    }
74
75    #[test]
76    fn generate_custom_otp_empty_set() {
77        let result = generate_otp(5, OtpCharSet::Custom("".to_string()));
78        assert!(result.is_err());
79        assert_eq!(result.err(), Some("Custom character set cannot be empty"));
80    }
81
82    #[test]
83    fn generate_zero_length_otp() {
84        let otp = generate_otp(0, OtpCharSet::Numeric).unwrap();
85        assert_eq!(otp.len(), 0);
86        assert_eq!(otp, "");
87    }
88
89    #[test]
90    fn generate_custom_otp_only_one_char() {
91        let custom_set = "A".to_string();
92        let otp = generate_otp(5, OtpCharSet::Custom(custom_set.clone())).unwrap();
93        assert_eq!(otp.len(), 5);
94        assert_eq!(otp, "AAAAA");
95    }
96
97    fn add(left: u64, right: u64) -> u64 {
98        left + right
99    }
100
101    #[test]
102    fn test_add_function() {
103        assert_eq!(add(2, 2), 4);
104    }
105}