t_rust_less_lib/service/pw_generator/
chars.rs1use crate::api::PasswordGeneratorCharsParam;
2use rand::seq::SliceRandom;
3use rand::{thread_rng, Rng};
4
5const LOWERS: &[u8] = b"abcdefghijklmnopqrstuvwxyz";
6const UPPERS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
7const NUMBERS: &[u8] = b"0123456789";
8const SYMBOLS: &[u8] = b"!-+*#_$%&/()=?{}[]()/\\'\"`-,;:.<>";
9const AMBIGOUS_CHARS: &[u8] = b"{}[]()/\\'\"`-,;:.<>";
10const SIMILAR_CHARS: &[u8] = b"QO01lIB8S5G62ZUV";
11
12pub fn generate_chars(params: &PasswordGeneratorCharsParam) -> String {
13 let mut rng = thread_rng();
14 let mut pool = Vec::with_capacity(params.num_chars as usize);
15
16 if params.require_upper {
17 pool.push(pick_char_from(&mut rng, UPPERS, params));
18 }
19 if params.require_number {
20 pool.push(pick_char_from(&mut rng, NUMBERS, params));
21 }
22 if params.require_symbol {
23 pool.push(pick_char_from(&mut rng, SYMBOLS, params));
24 }
25 let candidates = create_base_set(params);
26 while pool.len() < params.num_chars as usize {
27 pool.push(*candidates.choose(&mut rng).unwrap());
28 }
29
30 pool.shuffle(&mut rng);
31
32 String::from_utf8(pool).unwrap()
33}
34
35fn create_base_set(params: &PasswordGeneratorCharsParam) -> Vec<u8> {
36 let mut candidates = Vec::with_capacity(LOWERS.len() + UPPERS.len() + NUMBERS.len() + SYMBOLS.len());
37
38 filter_set(&mut candidates, LOWERS, params);
39 if params.include_uppers {
40 filter_set(&mut candidates, UPPERS, params);
41 }
42 if params.include_numbers {
43 filter_set(&mut candidates, NUMBERS, params);
44 }
45 if params.include_symbols {
46 filter_set(&mut candidates, SYMBOLS, params);
47 }
48
49 candidates
50}
51
52fn filter_set(candidates: &mut Vec<u8>, set: &[u8], params: &PasswordGeneratorCharsParam) {
53 for ch in set {
54 if params.exclude_similar && SIMILAR_CHARS.contains(ch) {
55 continue;
56 }
57 if params.exclude_ambiguous && AMBIGOUS_CHARS.contains(ch) {
58 continue;
59 }
60 candidates.push(*ch);
61 }
62}
63
64fn pick_char_from<R: Rng>(rng: &mut R, set: &[u8], params: &PasswordGeneratorCharsParam) -> u8 {
65 let mut candidates = Vec::with_capacity(set.len());
66 filter_set(&mut candidates, set, params);
67
68 *candidates.choose(rng).unwrap()
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use spectral::prelude::*;
75
76 #[test]
77 fn test_generate_chars() {
78 let pw1 = generate_chars(&PasswordGeneratorCharsParam {
79 num_chars: 14,
80 include_uppers: false,
81 include_numbers: false,
82 include_symbols: false,
83 require_number: false,
84 require_upper: false,
85 require_symbol: false,
86 exclude_similar: false,
87 exclude_ambiguous: false,
88 });
89
90 assert_that(&pw1.len()).is_equal_to(14);
91 assert_that(&pw1.chars().all(|ch| ch.is_lowercase())).is_true();
92
93 let pw2: String = generate_chars(&PasswordGeneratorCharsParam {
94 num_chars: 20,
95 include_uppers: true,
96 include_numbers: false,
97 include_symbols: false,
98 require_number: false,
99 require_upper: true,
100 require_symbol: false,
101 exclude_similar: false,
102 exclude_ambiguous: false,
103 });
104
105 assert_that(&pw2.len()).is_equal_to(20);
106 assert_that(&pw2.chars().any(|ch| ch.is_uppercase())).is_true();
107 }
108}