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}