1use std::cell::RefCell;
9
10use rand::prelude::IteratorRandom;
11use rand::{Rng, RngCore};
12
13use crate::Randomizer;
14
15const SYMBOLS: &str = r##"!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"##;
16
17#[derive(Clone)]
19#[allow(clippy::struct_excessive_bools)]
20pub struct StringDef {
21 pub length: u32,
23 pub include_unicode: bool,
25 pub include_symbol: bool,
27 pub include_capital_letters: bool,
29 pub include_numbers: bool,
31}
32
33impl Default for StringDef {
35 fn default() -> Self {
36 Self {
37 length: 6,
38 include_unicode: false,
39 include_symbol: false,
40 include_capital_letters: false,
41 include_numbers: false,
42 }
43 }
44}
45
46pub struct StringDefBuilder<'a> {
48 pub string_def: StringDef,
49 pub rng: &'a RefCell<dyn RngCore + Send>,
50}
51
52impl StringDefBuilder<'_> {
53 #[must_use]
55 pub const fn length(mut self, length: u32) -> Self {
56 self.string_def.length = length;
57 self
58 }
59
60 #[must_use]
62 pub const fn include_unicode(mut self, yes: bool) -> Self {
63 self.string_def.include_unicode = yes;
64 self
65 }
66
67 #[must_use]
69 pub const fn include_symbol(mut self, yes: bool) -> Self {
70 self.string_def.include_symbol = yes;
71 self
72 }
73
74 #[must_use]
76 pub const fn include_capital_letters(mut self, yes: bool) -> Self {
77 self.string_def.include_capital_letters = yes;
78 self
79 }
80
81 #[must_use]
83 pub const fn include_numbers(mut self, yes: bool) -> Self {
84 self.string_def.include_numbers = yes;
85 self
86 }
87}
88
89impl std::fmt::Display for StringDefBuilder<'_> {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 let mut rng = self.rng.borrow_mut();
93 let result = self.string_def.generate(&mut *rng);
94 write!(f, "{result}")
95 }
96}
97
98impl StringDef {
99 pub fn from_randomizer(randomizer: &Randomizer) -> Self {
101 Self {
102 length: randomizer.number_between(1, 50),
103 include_unicode: randomizer.bool(),
104 include_symbol: randomizer.bool(),
105 include_capital_letters: randomizer.bool(),
106 include_numbers: randomizer.bool(),
107 }
108 }
109
110 pub fn generate(&self, rng: &mut dyn RngCore) -> String {
124 let mut result = String::new();
125 let length: usize = self.length as usize;
126
127 while result.len() < length {
128 let choice: u8 = rng.gen_range(0..100);
129
130 if self.include_unicode && choice < 20 {
131 if let Some(unicode_char) = std::char::from_u32(rng.gen_range(0x1F600..0x1F64F)) {
132 result.push(unicode_char);
133 } else {
134 result.push('?');
135 }
136 } else if self.include_symbol && choice < 40 {
137 if let Some(symbol) = SYMBOLS.chars().choose(rng) {
138 result.push(symbol);
139 } else {
140 result.push('#');
141 }
142 } else if self.include_capital_letters && choice < 60 {
143 let capital_letter = rng.gen_range(b'A'..=b'Z') as char;
144 result.push(capital_letter);
145 } else if self.include_numbers && choice < 80 {
146 let number = rng.gen_range(b'0'..=b'9') as char;
147 result.push(number);
148 } else {
149 let lowercase_letter = rng.gen_range(b'a'..=b'z') as char;
150 result.push(lowercase_letter);
151 }
152 }
153
154 result
155 }
156
157 #[must_use]
170 pub fn contains_only_lowercase(s: &str) -> bool {
171 !Self::contains_capital_letters(s)
172 && !Self::contains_numbers(s)
173 && !Self::contains_symbols(s)
174 && !Self::contains_unicode(s)
175 }
176
177 #[must_use]
187 pub fn contains_unicode(s: &str) -> bool {
188 s.chars().any(|ch| !ch.is_ascii())
189 }
190
191 #[must_use]
201 pub fn contains_symbols(s: &str) -> bool {
202 s.chars().any(|ch| SYMBOLS.contains(ch))
203 }
204
205 #[must_use]
215 pub fn contains_numbers(s: &str) -> bool {
216 s.chars().any(char::is_numeric)
217 }
218
219 #[must_use]
228 pub fn contains_capital_letters(s: &str) -> bool {
229 s.chars().any(char::is_uppercase)
230 }
231}
232
233#[cfg(test)]
234mod tests {
235
236 use super::*;
237 use rand::{rngs::StdRng, SeedableRng};
238
239 #[test]
240 fn has_unicode() {
241 assert!(!StringDef::contains_unicode("test"));
242 assert!(StringDef::contains_unicode("🙆test"));
243 }
244
245 #[test]
246 fn has_symbols() {
247 assert!(!StringDef::contains_symbols("test"));
248 assert!(StringDef::contains_symbols("test#"));
249 }
250
251 #[test]
252 fn has_numbers() {
253 assert!(!StringDef::contains_numbers("test"));
254 assert!(StringDef::contains_numbers("test1"));
255 }
256
257 #[test]
258 fn has_capital_letters() {
259 assert!(!StringDef::contains_capital_letters("test"));
260 assert!(StringDef::contains_capital_letters("Test1"));
261 }
262
263 #[test]
264 fn string_def_default() {
265 let string_def = StringDef::default();
266 let randomizer = Randomizer::with_seed(42);
267 let mut rng = randomizer.rng.borrow_mut();
268 assert_eq!(string_def.generate(&mut *rng), "noqkak");
269 assert_eq!(string_def.generate(&mut *rng), "twdayn");
270 assert_eq!(string_def.generate(&mut *rng), "kdnfan");
271 }
272
273 #[test]
274 fn string_def_with_length() {
275 let string_def = StringDef {
276 length: 10,
277 include_unicode: false,
278 include_symbol: false,
279 include_capital_letters: false,
280 include_numbers: false,
281 };
282 let mut rand = Box::new(StdRng::seed_from_u64(42));
283 assert_eq!(string_def.generate(&mut rand), "noqkaktwda");
284 assert_eq!(string_def.generate(&mut rand), "ynkdnfanbq");
285 assert_eq!(string_def.generate(&mut rand), "vmnbjlufkr");
286 }
287
288 #[test]
289 fn string_def_include_unicode() {
290 let string_def = StringDef {
291 length: 6,
292 include_unicode: true,
293 include_symbol: false,
294 include_capital_letters: false,
295 include_numbers: false,
296 };
297 let mut rand = Box::new(StdRng::seed_from_u64(42));
298 assert_eq!(string_def.generate(&mut rand), "😩oq");
299 assert_eq!(string_def.generate(&mut rand), "kakt🙃");
300 assert_eq!(string_def.generate(&mut rand), "daynkd");
301 }
302
303 #[test]
304 fn string_def_include_symbol() {
305 let string_def = StringDef {
306 length: 6,
307 include_unicode: false,
308 include_symbol: true,
309 include_capital_letters: false,
310 include_numbers: false,
311 };
312 let mut rand = Box::new(StdRng::seed_from_u64(42));
313 assert_eq!(string_def.generate(&mut rand), "\"eq)a)");
314 assert_eq!(string_def.generate(&mut rand), "=wqf`g");
315 assert_eq!(string_def.generate(&mut rand), "/uzw=d");
316 }
317
318 #[test]
319 fn string_def_include_capital_letters() {
320 let string_def = StringDef {
321 length: 6,
322 include_unicode: false,
323 include_symbol: false,
324 include_capital_letters: true,
325 include_numbers: false,
326 };
327 let mut rand = Box::new(StdRng::seed_from_u64(42));
328 assert_eq!(string_def.generate(&mut rand), "NOqkak");
329 assert_eq!(string_def.generate(&mut rand), "TWdAyN");
330 assert_eq!(string_def.generate(&mut rand), "kdnfaN");
331 }
332
333 #[test]
334 fn string_def_include_numbers() {
335 let string_def = StringDef {
336 length: 6,
337 include_unicode: false,
338 include_symbol: false,
339 include_capital_letters: false,
340 include_numbers: true,
341 };
342 let mut rand = Box::new(StdRng::seed_from_u64(42));
343 assert_eq!(string_def.generate(&mut rand), "55qka4");
344 assert_eq!(string_def.generate(&mut rand), "7810y5");
345 assert_eq!(string_def.generate(&mut rand), "k1nf05");
346 }
347}