password_maker/
lib.rs

1use indexmap::IndexSet;
2use rand::prelude::*;
3
4#[cfg(test)]
5// Use a fixed seed random number generator during tests to ensure reproducibility
6use rand_chacha::ChaCha20Rng;
7
8#[derive(Debug, Clone)]
9/// Settings for characters used in the password
10pub struct Classifier {
11    /// Candidate characters
12    pub candidates: Vec<String>,
13    /// Minimum number of characters to include
14    pub minimum_count: u32,
15}
16
17#[derive(Debug, Clone)]
18/// Password generator
19///
20/// You can specify the following for the generated password:
21/// - Length
22/// - Whether to include similar characters
23/// - Whether to include whitespace
24/// - Candidates for uppercase, lowercase, numbers, symbols, and other characters
25/// - Minimum number of characters for each type
26pub struct PasswordMaker {
27    /// Length of the password
28    pub length: u32,
29    /// Exclude similar characters ('i', 'l', '1', 'o', '0', 'O') from the password
30    pub exclude_similar: bool,
31    /// Include whitespace in the candidate characters for the password
32    pub include_whitespace_in_candidate: bool,
33    /// Settings for lowercases
34    pub lowercase: Classifier,
35    /// Settings for uppercases
36    pub uppercase: Classifier,
37    /// Settings for numbers
38    pub number: Classifier,
39    /// Settings for symbols
40    pub symbol: Classifier,
41    /// Settings for other characters
42    pub others: Vec<Classifier>,
43}
44
45impl PasswordMaker {
46    /// Generate a password
47    ///
48    /// Generates a password according to the settings of the password generator.
49    /// Returns an error if there is an issue with the settings.
50    ///
51    /// Issues include:
52    /// - No candidates for a character type, but the minimum number of characters is set to 1 or more
53    /// - The total minimum number of characters for all types exceeds the password length
54    ///
55    /// # Returns
56    ///
57    /// * Ok: Password
58    /// * Err: Error message
59    ///
60    /// # Errors
61    ///
62    /// * No candidates for a character type, but the minimum number of characters is set to 1 or more
63    /// * The total minimum number of characters for all types exceeds the password length
64    /// * No candidates for the password
65    /// * The password length is 0
66    ///
67    /// # Examples
68    ///
69    /// ```
70    /// use password_maker::PasswordMaker;
71    ///
72    /// let mut password_maker = PasswordMaker::default();
73    /// let password = password_maker.generate().unwrap();
74    /// println!("{}", password);
75    /// ```
76    ///
77    pub fn generate(&mut self) -> Result<String, String> {
78        // Return an error if validation fails
79        self.validate()?;
80
81        let candidates = self.candidates();
82
83        let mut rng = Self::create_rng();
84
85        // 上書き処理があるので、String ではなく Vec<String> を使う
86        let mut password: Vec<String> = (0..self.length)
87            .map(|_| candidates.choose(&mut rng).unwrap().to_string())
88            .collect();
89
90        // Ensure the minimum number of characters is met
91        // To maintain randomness, overwrite random positions with characters that meet the minimum count
92        self.overwrite_to_meet_minimum_count(&mut password);
93
94        Ok(password.concat())
95    }
96
97    /// Return a list of candidate characters for the password according to the settings of the password generator
98    ///
99    /// # Returns
100    ///
101    /// * List of candidate characters for the password
102    ///
103    /// # Examples
104    ///
105    /// ```
106    /// use password_maker::PasswordMaker;
107    ///
108    /// let password_maker = PasswordMaker::default();
109    /// let candidates = password_maker.candidates();
110    /// println!("{:?}", candidates);
111    /// ```
112    pub fn candidates(&self) -> Vec<String> {
113        let mut candidates = Vec::new();
114        candidates.extend(self.lowercase.candidates.clone());
115        candidates.extend(self.uppercase.candidates.clone());
116        candidates.extend(self.number.candidates.clone());
117        candidates.extend(self.symbol.candidates.clone());
118        for classifier in &self.others {
119            candidates.extend(classifier.candidates.clone());
120        }
121
122        if self.include_whitespace_in_candidate {
123            candidates.push(" ".to_string());
124        }
125
126        if self.exclude_similar {
127            candidates.retain(|c| !matches!(c.as_str(), "i" | "l" | "1" | "o" | "0" | "O"));
128        }
129
130        candidates
131    }
132
133    /// Create a random number generator
134    ///
135    /// During unit tests, return a fixed seed random number generator to ensure reproducibility
136    ///
137    /// Outside of unit tests, return a random number generator with a different seed for each thread
138    ///
139    /// # Returns
140    ///
141    /// * Random number generator
142    fn create_rng() -> Box<dyn RngCore> {
143        #[cfg(test)]
144        {
145            // Use a fixed seed during unit tests to ensure reproducibility
146            // StdRng may change with version upgrades, so use ChaCha20Rng during tests to ensure future reproducibility
147            Box::new(ChaCha20Rng::seed_from_u64(0))
148        }
149        #[cfg(not(test))]
150        {
151            // Use random numbers outside of unit tests
152            Box::new(rand::thread_rng())
153        }
154    }
155
156    /// Validate the settings of the password generator
157    ///
158    /// Checks:
159    /// - No candidates for a character type, but the minimum number of characters is set to 1 or more
160    /// - The total minimum number of characters for all types exceeds the password length
161    /// - No candidates for the password
162    /// - The password length is 0
163    fn validate(&self) -> Result<(), String> {
164        // Check if the minimum number of characters for each parameter is not violated
165        let classifier = [
166            // Capitalize the first letter for error messages
167            (&self.uppercase, "Uppercases"),
168            (&self.lowercase, "Lowercases"),
169            (&self.number, "Numbers"),
170            (&self.symbol, "Symbols"),
171        ];
172
173        for (index, classify) in self.others.iter().enumerate() {
174            if classify.candidates.is_empty() && 0 < classify.minimum_count {
175                return Err(format!(
176                    "Other characters at index {} is empty, but the minimum number of characters is set to {}. Please set the minimum number of characters to 0.",
177                    index, classify.minimum_count
178                ));
179            }
180        }
181
182        for (classify, name) in classifier.iter() {
183            if classify.candidates.is_empty() && 0 < classify.minimum_count {
184                return Err(format!(
185                    "{} is empty, but the minimum number of characters is set to {}. Please set the minimum number of characters to 0.",
186                    name, classify.minimum_count
187                ));
188            }
189        }
190
191        // Check if the total minimum number of characters is not violated
192        let total_min = self.lowercase.minimum_count
193            + self.uppercase.minimum_count
194            + self.number.minimum_count
195            + self.symbol.minimum_count
196            + self.others.iter().map(|c| c.minimum_count).sum::<u32>();
197
198        if self.length < total_min {
199            return Err(format!("The total minimum number of characters is greater than the password length. The total minimum number of characters is {}, but the password length is {}", total_min, self.length));
200        }
201
202        // Check if there are candidates for the password
203        if self.candidates().is_empty() {
204            return Err(
205                "No candidates for the password. Please set the candidates for the password."
206                    .to_string(),
207            );
208        }
209
210        // Check if the password length is 0
211        if self.length == 0 {
212            return Err(
213                "The password length is 0. Please set the password length to 1 or more."
214                    .to_string(),
215            );
216        }
217
218        Ok(())
219    }
220
221    /// Update the password string to meet the minimum number of characters for each type
222    ///
223    /// To maintain randomness, overwrite random positions with characters that meet the minimum count
224    ///
225    /// # Arguments
226    ///
227    /// * `password` - Password
228    fn overwrite_to_meet_minimum_count(&self, password: &mut [String]) {
229        // Number of characters to overwrite
230        let overwrite_count = std::cmp::min(
231            self.length,
232            self.lowercase.minimum_count
233                + self.uppercase.minimum_count
234                + self.number.minimum_count
235                + self.symbol.minimum_count
236                + self.others.iter().map(|c| c.minimum_count).sum::<u32>(),
237        );
238
239        // Randomly select characters to overwrite
240        let mut overwrite_chars =
241            self.unique_random_numbers(overwrite_count as usize, 0..password.len() as u32);
242
243        // Update each character type in order (the order can be changed without affecting functionality)
244        let mut classifier = vec![&self.uppercase, &self.lowercase, &self.number, &self.symbol];
245        for classify in &self.others {
246            classifier.push(classify);
247        }
248
249        for classify in classifier.iter() {
250            self.replace_characters(
251                password,
252                classify,
253                overwrite_chars
254                    .drain(0..classify.minimum_count as usize)
255                    .map(|x| x as usize)
256                    .collect(),
257            );
258        }
259    }
260
261    /// Overwrite characters in the password string
262    ///
263    /// For example, if the password is "abcde" and overwrite_indexes is \[3, 1, 4\], it becomes "aXcXXe"
264    /// (X is a character randomly chosen from the classifier candidates)
265    ///
266    /// # Arguments
267    ///
268    /// * `password` - Password
269    /// * `classifier` - Character type to replace
270    /// * `overwrite_indexes` - Indexes of characters to replace
271    ///
272    /// # Panics
273    ///
274    /// * If the index of an element in overwrite_indexes is greater than the number of characters in the password
275    fn replace_characters(
276        &self,
277        password: &mut [String],
278        classifier: &Classifier,
279        overwrite_indexes: Vec<usize>,
280    ) {
281        let mut rng = Self::create_rng();
282        for index in overwrite_indexes {
283            // ここはユーザーの入力ミスなどで index が password.len() 以上になることはなく、
284            // なった場合はプログラムのバグなので panic しても問題ない
285            if password.len() <= index {
286                panic!(
287                    "Index out of range: index {} is greater than or equal to password length {}",
288                    index,
289                    password.len()
290                );
291            }
292
293            let overwrite_char = classifier.candidates.choose(&mut rng).unwrap().clone();
294            password[index] = overwrite_char;
295        }
296    }
297
298    /// Generate unique random numbers
299    /// The generated values are between 0 and max (exclusive)
300    ///
301    /// # Arguments
302    ///
303    /// * count: Number of random numbers to generate
304    /// * max: Maximum value of the generated random numbers
305    fn unique_random_numbers(&self, count: usize, range: std::ops::Range<u32>) -> Vec<u32> {
306        let mut rng = Self::create_rng();
307        let mut numbers = IndexSet::new();
308
309        while numbers.len() < count {
310            let num = rng.gen_range(range.clone());
311            numbers.insert(num);
312        }
313
314        numbers.into_iter().collect()
315    }
316}
317
318impl Default for PasswordMaker {
319    /// Create a password generator with default settings
320    ///
321    /// The default settings are as follows:
322    /// - length: 16
323    /// - exclude_similar: false
324    /// - include_whitespace_in_candidate: false
325    /// - lowercase_letters
326    ///   - candidates: a-z
327    ///   - min: 1
328    /// - uppercase_letters
329    ///   - candidates: A-Z
330    ///   - min: 1
331    /// - numbers:
332    ///   - candidates: 0-9
333    ///   - min: 1
334    /// - symbols:
335    ///   - candidates: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ \[ \ \] ^ _ \` { | } ~
336    ///   - min: 1
337    /// - other_characters:
338    ///   - candidates: None
339    ///   - min: 0
340    fn default() -> Self {
341        PasswordMaker {
342            length: 16,
343            exclude_similar: false,
344            // Whitespace is less commonly used in passwords compared to other symbols,
345            // and leading or trailing whitespace can cause input errors, so it is disabled by default.
346            include_whitespace_in_candidate: false,
347            lowercase: Classifier {
348                candidates: ('a'..='z').map(|c| c.to_string()).collect(),
349                minimum_count: 1,
350            },
351            uppercase: Classifier {
352                candidates: ('A'..='Z').map(|c| c.to_string()).collect(),
353                minimum_count: 1,
354            },
355            number: Classifier {
356                candidates: (0..=9).map(|c| c.to_string()).collect(),
357                minimum_count: 1,
358            },
359            // Symbols are sorted in ascending order of ASCII values
360            symbol: Classifier {
361                candidates: "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
362                    .chars()
363                    .map(|c| c.to_string())
364                    .collect(),
365                minimum_count: 1,
366            },
367            others: vec![],
368        }
369    }
370}
371
372#[cfg(test)]
373mod tests {
374    use super::*;
375
376    // Test if a password that meets the conditions can be generated
377    // If the number of characters is small, it may not be possible to generate a password that meets the conditions,
378    // so set a large number of characters (1000) for tests other than length tests
379    const PASSWORD_LENGTH: u32 = 1000;
380
381    #[test]
382    fn default() {
383        let mut password_maker = PasswordMaker::default();
384        let password = password_maker.generate().unwrap();
385        assert_eq!(password.chars().count(), 16);
386    }
387
388    #[test]
389    /// Test if a password with a length of 0 can be generated
390    /// By default, the minimum number of characters is set to 1, so an error occurs
391    /// Set the minimum number of characters to 0 for the test
392    fn empty() {
393        let mut password_maker = PasswordMaker {
394            length: 0,
395            ..PasswordMaker::default()
396        };
397
398        // By default, the minimum number of characters for uppercase, lowercase, numbers, and symbols is set to 1, so an error occurs
399        // Therefore, set the minimum number of characters to 0 for the test
400        password_maker.uppercase.minimum_count = 0;
401        password_maker.lowercase.minimum_count = 0;
402        password_maker.number.minimum_count = 0;
403        password_maker.symbol.minimum_count = 0;
404
405        let result = password_maker.generate();
406        assert!(result.is_err());
407    }
408
409    #[test]
410    fn length() {
411        // Set the password length to 8 characters
412        let mut password_maker = PasswordMaker {
413            length: 8,
414            ..PasswordMaker::default()
415        };
416        let password = password_maker.generate().unwrap();
417        assert_eq!(password.chars().count(), 8);
418
419        // Set the password length to 32 characters
420        password_maker.length = 32;
421        let password = password_maker.generate().unwrap();
422        assert_eq!(password.chars().count(), 32);
423    }
424
425    #[test]
426    fn uppercases() {
427        // By default, include uppercases
428        let mut password_maker = PasswordMaker {
429            length: PASSWORD_LENGTH,
430            ..PasswordMaker::default()
431        };
432        let password = password_maker.generate().unwrap();
433        assert!(password.chars().any(|c| c.is_ascii_uppercase()));
434
435        // Do not include uppercases
436        password_maker.uppercase = Classifier {
437            candidates: vec![],
438            minimum_count: 0,
439        };
440        let password = password_maker.generate().unwrap();
441        assert!(password.chars().all(|c| !c.is_ascii_uppercase()));
442
443        // Specify the types of uppercases
444        password_maker.uppercase = Classifier {
445            candidates: vec![
446                'A'.to_string(),
447                'M'.to_string(),
448                'N'.to_string(),
449                'Z'.to_string(),
450            ],
451            minimum_count: 1,
452        };
453        let password = password_maker.generate().unwrap();
454        // Check if the types of uppercases are only those specified
455        // Specify the first, middle, and last letters of the alphabet
456        assert!(password.contains('A'));
457        assert!(password.contains('M'));
458        assert!(password.contains('N'));
459        assert!(password.contains('Z'));
460        // Check if other uppercases are not included
461        assert!(password.chars().all(|c| !matches!(
462            c,
463            'B' | 'C'
464                | 'D'
465                | 'E'
466                | 'F'
467                | 'G'
468                | 'H'
469                | 'I'
470                | 'J'
471                | 'K'
472                | 'L'
473                | 'O'
474                | 'P'
475                | 'Q'
476                | 'R'
477                | 'S'
478                | 'T'
479                | 'U'
480                | 'V'
481                | 'W'
482                | 'X'
483                | 'Y'
484        )));
485
486        // Ensure the minimum number of characters is met
487        // No candidates for uppercases, but the minimum number of characters is set to 1
488        password_maker.length = 4;
489        password_maker.uppercase.minimum_count = 1;
490        let password = password_maker.generate().unwrap();
491        assert!(password.chars().any(|c| c.is_ascii_uppercase()));
492    }
493    #[test]
494    fn lowercases() {
495        // Include lowercases by default
496        let mut password_maker = PasswordMaker {
497            length: PASSWORD_LENGTH,
498            ..PasswordMaker::default()
499        };
500        let password = password_maker.generate().unwrap();
501        assert!(password.chars().any(|c| c.is_ascii_lowercase()));
502
503        // Do not include lowercases
504        password_maker.lowercase = Classifier {
505            candidates: vec![],
506            minimum_count: 0,
507        };
508        let password = password_maker.generate().unwrap();
509        assert!(password.chars().all(|c| !c.is_ascii_lowercase()));
510
511        // Specify the types of lowercases
512        password_maker.lowercase = Classifier {
513            candidates: ['a', 'm', 'n', 'z']
514                .iter()
515                .map(|&c| c.to_string())
516                .collect(),
517            minimum_count: 1,
518        };
519        let password = password_maker.generate().unwrap();
520        // Check if the types of lowercases are only those specified
521        // Specify the first, middle, and last letters of the alphabet
522        assert!(password.contains('a'));
523        assert!(password.contains('m'));
524        assert!(password.contains('n'));
525        assert!(password.contains('z'));
526        // Check if other lowercases are not included
527        assert!(password.chars().all(|c| !matches!(
528            c,
529            'b' | 'c'
530                | 'd'
531                | 'e'
532                | 'f'
533                | 'g'
534                | 'h'
535                | 'i'
536                | 'j'
537                | 'k'
538                | 'l'
539                | 'o'
540                | 'p'
541                | 'q'
542                | 'r'
543                | 's'
544                | 't'
545                | 'u'
546                | 'v'
547                | 'w'
548                | 'x'
549                | 'y'
550        )));
551    }
552
553    #[test]
554    fn numbers() {
555        // Include numbers by default
556        let mut password_maker = PasswordMaker {
557            length: PASSWORD_LENGTH,
558            ..PasswordMaker::default()
559        };
560        let password = password_maker.generate().unwrap();
561        assert!(password.chars().any(|c| c.is_ascii_digit()));
562
563        // Do not include numbers
564        password_maker.number = Classifier {
565            candidates: vec![],
566            minimum_count: 0,
567        };
568        let password = password_maker.generate().unwrap();
569        assert!(password.chars().all(|c| !c.is_ascii_digit()));
570
571        // Specify the types of numbers
572        password_maker.number = Classifier {
573            candidates: ['0', '5', '9'].iter().map(|&c| c.to_string()).collect(),
574            minimum_count: 1,
575        };
576        let password = password_maker.generate().unwrap();
577        // Check if the types of numbers are only those specified
578        assert!(password.contains('0'));
579        assert!(password.contains('5'));
580        assert!(password.contains('9'));
581        // Check if other numbers are not included
582        assert!(password
583            .chars()
584            .all(|c| !matches!(c, '1' | '2' | '3' | '4' | '6' | '7' | '8')));
585
586        // Ensure the minimum number of characters is met
587
588        // Error case
589        // No candidates for numbers, but the minimum number of characters is set to 1
590        password_maker.number = Classifier {
591            candidates: vec![],
592            minimum_count: 1,
593        };
594        let password = password_maker.generate();
595        assert!(password.is_err());
596    }
597
598    #[test]
599    fn symbols() {
600        // Include symbols by default
601        let mut password_maker = PasswordMaker {
602            length: PASSWORD_LENGTH,
603            ..PasswordMaker::default()
604        };
605        let password = password_maker.generate().unwrap();
606        assert!(password.chars().any(|c| c.is_ascii_punctuation()));
607
608        // Do not include symbols
609        password_maker.symbol = Classifier {
610            candidates: vec![],
611            minimum_count: 0,
612        };
613        let password = password_maker.generate().unwrap();
614        assert!(password.chars().all(|c| !c.is_ascii_punctuation()));
615
616        // Specify the types of symbols
617        password_maker.symbol = Classifier {
618            candidates: ['!', '@', '~'].iter().map(|&c| c.to_string()).collect(),
619            minimum_count: 1,
620        };
621        let password = password_maker.generate().unwrap();
622        // Check if the types of symbols are only those specified
623        assert!(password.contains('!'));
624        assert!(password.contains('@'));
625        assert!(password.contains('~'));
626        // Check if other symbols are not included
627        assert!(password.chars().all(|c| !matches!(
628            c,
629            '"' | '#'
630                | '$'
631                | '%'
632                | '&'
633                | '\''
634                | '('
635                | ')'
636                | '*'
637                | '+'
638                | ','
639                | '-'
640                | '.'
641                | '/'
642                | ':'
643                | ';'
644                | '<'
645                | '='
646                | '>'
647                | '?'
648                | '['
649                | '\\'
650                | ']'
651                | '^'
652                | '_'
653                | '`'
654                | '{'
655                | '|'
656                | '}'
657        )));
658    }
659
660    #[test]
661    fn similar() {
662        // Do not include similar characters
663        let mut password_maker = PasswordMaker {
664            length: PASSWORD_LENGTH,
665            exclude_similar: true,
666            ..PasswordMaker::default()
667        };
668        let password = password_maker.generate().unwrap();
669        assert!(password
670            .chars()
671            .all(|c| !matches!(c, 'i' | 'l' | '1' | 'o' | '0' | 'O')));
672
673        // Include similar characters
674        password_maker.exclude_similar = false;
675        let password = password_maker.generate().unwrap();
676        assert!(password
677            .chars()
678            .any(|c| matches!(c, 'i' | 'l' | '1' | 'o' | '0' | 'O')));
679
680        // Include similar characters by default
681        let mut password_maker = PasswordMaker::default();
682        let password = password_maker.generate().unwrap();
683        assert!(password
684            .chars()
685            .any(|c| matches!(c, 'i' | 'l' | '1' | 'o' | '0' | 'O')));
686    }
687
688    #[test]
689    fn whitespace() {
690        // Do not include whitespace
691        let mut password_maker = PasswordMaker {
692            length: PASSWORD_LENGTH,
693            include_whitespace_in_candidate: false,
694            ..PasswordMaker::default()
695        };
696        let password = password_maker.generate().unwrap();
697        assert!(!password.contains(' '));
698
699        // Include whitespace
700        password_maker.include_whitespace_in_candidate = true;
701        let password = password_maker.generate().unwrap();
702        assert!(password.contains(' '));
703    }
704
705    #[test]
706    fn other_chars() {
707        // Do not include other characters
708        // For testing other characters, include only numbers, excluding alphabets and symbols
709        let mut password_maker = PasswordMaker {
710            length: PASSWORD_LENGTH,
711            uppercase: Classifier {
712                candidates: vec![],
713                minimum_count: 0,
714            },
715            lowercase: Classifier {
716                candidates: vec![],
717                minimum_count: 0,
718            },
719            symbol: Classifier {
720                candidates: vec![],
721                minimum_count: 0,
722            },
723            ..PasswordMaker::default()
724        };
725        let password = password_maker.generate().unwrap();
726        assert!(password.chars().all(|c| c.is_ascii_digit()));
727
728        // Include other characters
729        // Include Variable-width characters (characters that are treated as one character in char type, such as ⌨️, are not included)
730        password_maker.others = vec![Classifier {
731            candidates: ['あ', '🍣', '!'].iter().map(|&c| c.to_string()).collect(),
732            minimum_count: 1,
733        }];
734        let password = password_maker.generate().unwrap();
735        assert!(password.contains('あ'));
736        assert!(password.contains('🍣'));
737        assert!(password.contains('!'));
738        assert!(password.chars().any(|c| c.is_ascii_digit()));
739    }
740
741    #[test]
742    fn candidates() {
743        // Include uppercase, lowercase, numbers, and symbols by default
744        let password_maker = PasswordMaker {
745            length: PASSWORD_LENGTH,
746            ..PasswordMaker::default()
747        };
748        let candidates = password_maker.candidates();
749        assert!(candidates
750            .iter()
751            .any(|c| c.chars().all(|ch| ch.is_ascii_uppercase())));
752        assert!(candidates
753            .iter()
754            .any(|c| c.chars().all(|ch| ch.is_ascii_lowercase())));
755        assert!(candidates
756            .iter()
757            .any(|c| c.chars().all(|ch| ch.is_ascii_digit())));
758        assert!(candidates
759            .iter()
760            .any(|c| c.chars().all(|ch| ch.is_ascii_punctuation())));
761
762        // Do not include something
763        // This time, do not include uppercases
764        let password_maker = PasswordMaker {
765            length: PASSWORD_LENGTH,
766            uppercase: Classifier {
767                candidates: vec![],
768                minimum_count: 0,
769            },
770            ..PasswordMaker::default()
771        };
772        let candidates = password_maker.candidates();
773        assert!(!candidates
774            .iter()
775            .any(|c| c.chars().all(|ch| ch.is_ascii_uppercase())));
776
777        // Include characters other than uppercase, lowercase, numbers, and symbols
778        let password_maker = PasswordMaker {
779            others: vec![Classifier {
780                candidates: ['あ', '🍣', '!'].iter().map(|&c| c.to_string()).collect(),
781                minimum_count: 1,
782            }],
783            ..PasswordMaker::default()
784        };
785        let candidates = password_maker.candidates();
786        assert!(candidates
787            .iter()
788            .any(|c| c.chars().all(|ch| ch.is_ascii_uppercase())));
789        assert!(candidates
790            .iter()
791            .any(|c| c.chars().all(|ch| ch.is_ascii_lowercase())));
792        assert!(candidates
793            .iter()
794            .any(|c| c.chars().all(|ch| ch.is_ascii_digit())));
795        assert!(candidates
796            .iter()
797            .any(|c| c.chars().all(|ch| ch.is_ascii_punctuation())));
798        assert!(candidates.contains(&"あ".to_string()));
799        assert!(candidates.contains(&"🍣".to_string()));
800        assert!(candidates.contains(&"!".to_string()));
801    }
802
803    #[test]
804    fn validate_uppercase_letter() {
805        // Normal case
806        {
807            // There are candidates for uppercases, and the minimum number of characters is set to 1
808            {
809                let password_maker = PasswordMaker {
810                    ..PasswordMaker::default()
811                };
812                let result = password_maker.validate();
813                assert!(result.is_ok());
814            }
815
816            // There are no candidates for uppercases, but the minimum number of characters is set to 0
817            {
818                let password_maker = PasswordMaker {
819                    uppercase: Classifier {
820                        candidates: vec![],
821                        minimum_count: 0,
822                    },
823                    ..PasswordMaker::default()
824                };
825                let result = password_maker.validate();
826                assert!(result.is_ok());
827            }
828        }
829
830        // Error case
831        {
832            // There are no candidates for uppercases, but the minimum number of characters is set to 1
833            {
834                let password_maker = PasswordMaker {
835                    uppercase: Classifier {
836                        candidates: vec![],
837                        minimum_count: 1,
838                    },
839                    ..PasswordMaker::default()
840                };
841                let result = password_maker.validate();
842                assert!(result.is_err());
843            }
844
845            // There are no candidates for uppercases, but the minimum number of characters is set to 2
846            {
847                let password_maker = PasswordMaker {
848                    uppercase: Classifier {
849                        candidates: vec![],
850                        minimum_count: 2,
851                    },
852                    ..PasswordMaker::default()
853                };
854                let result = password_maker.validate();
855                assert!(result.is_err());
856            }
857        }
858    }
859
860    #[test]
861    fn validate_lowercase_letter() {
862        // Normal case
863        {
864            // There are candidates for lowercases, and the minimum number of characters is set to 1
865            {
866                let password_maker = PasswordMaker {
867                    ..PasswordMaker::default()
868                };
869                let result = password_maker.validate();
870                assert!(result.is_ok());
871            }
872
873            // There are no candidates for lowercases, but the minimum number of characters is set to 0
874            {
875                let password_maker = PasswordMaker {
876                    lowercase: Classifier {
877                        candidates: vec![],
878                        minimum_count: 0,
879                    },
880                    ..PasswordMaker::default()
881                };
882                let result = password_maker.validate();
883                assert!(result.is_ok());
884            }
885        }
886
887        // Error case
888        {
889            // There are no candidates for lowercases, but the minimum number of characters is set to 1
890            {
891                let password_maker = PasswordMaker {
892                    lowercase: Classifier {
893                        candidates: vec![],
894                        minimum_count: 1,
895                    },
896                    ..PasswordMaker::default()
897                };
898                let result = password_maker.validate();
899                assert!(result.is_err());
900            }
901
902            // There are no candidates for lowercases, but the minimum number of characters is set to 2
903            {
904                let password_maker = PasswordMaker {
905                    lowercase: Classifier {
906                        candidates: vec![],
907                        minimum_count: 2,
908                    },
909                    ..PasswordMaker::default()
910                };
911                let result = password_maker.validate();
912                assert!(result.is_err());
913            }
914        }
915    }
916
917    #[test]
918    fn validate_number() {
919        // Normal case
920        {
921            // There are candidates for numbers, and the minimum number of characters is set to 1
922            {
923                let password_maker = PasswordMaker {
924                    ..PasswordMaker::default()
925                };
926                let result = password_maker.validate();
927                assert!(result.is_ok());
928            }
929
930            // There are no candidates for numbers, but the minimum number of characters is set to 0
931            {
932                let password_maker = PasswordMaker {
933                    number: Classifier {
934                        candidates: vec![],
935                        minimum_count: 0,
936                    },
937                    ..PasswordMaker::default()
938                };
939                let result = password_maker.validate();
940                assert!(result.is_ok());
941            }
942        }
943
944        // Error case
945        {
946            // There are no candidates for numbers, but the minimum number of characters is set to 1
947            {
948                let password_maker = PasswordMaker {
949                    number: Classifier {
950                        candidates: vec![],
951                        minimum_count: 1,
952                    },
953                    ..PasswordMaker::default()
954                };
955                let result = password_maker.validate();
956                assert!(result.is_err());
957            }
958
959            // There are no candidates for numbers, but the minimum number of characters is set to 2
960            {
961                let password_maker = PasswordMaker {
962                    number: Classifier {
963                        candidates: vec![],
964                        minimum_count: 2,
965                    },
966                    ..PasswordMaker::default()
967                };
968                let result = password_maker.validate();
969                assert!(result.is_err());
970            }
971        }
972    }
973
974    #[test]
975    fn validate_symbol() {
976        // Normal case
977        {
978            // There are candidates for symbols, and the minimum number of characters is set to 1
979            {
980                let password_maker = PasswordMaker {
981                    ..PasswordMaker::default()
982                };
983                let result = password_maker.validate();
984                assert!(result.is_ok());
985            }
986
987            // There are no candidates for symbols, but the minimum number of characters is set to 0
988            {
989                let password_maker = PasswordMaker {
990                    symbol: Classifier {
991                        candidates: vec![],
992                        minimum_count: 0,
993                    },
994                    ..PasswordMaker::default()
995                };
996                let result = password_maker.validate();
997                assert!(result.is_ok());
998            }
999        }
1000
1001        // Error case
1002        {
1003            // There are no candidates for symbols, but the minimum number of characters is set to 1
1004            {
1005                let password_maker = PasswordMaker {
1006                    symbol: Classifier {
1007                        candidates: vec![],
1008                        minimum_count: 1,
1009                    },
1010                    ..PasswordMaker::default()
1011                };
1012                let result = password_maker.validate();
1013                assert!(result.is_err());
1014            }
1015
1016            // There are no candidates for symbols, but the minimum number of characters is set to 2
1017            {
1018                let password_maker = PasswordMaker {
1019                    symbol: Classifier {
1020                        candidates: vec![],
1021                        minimum_count: 2,
1022                    },
1023                    ..PasswordMaker::default()
1024                };
1025                let result = password_maker.validate();
1026                assert!(result.is_err());
1027            }
1028        }
1029    }
1030
1031    #[test]
1032    fn validate_other_characters() {
1033        // Normal case
1034        {
1035            // There are candidates for other characters, and the minimum number of characters is set to 1
1036            {
1037                let password_maker = PasswordMaker {
1038                    others: vec![Classifier {
1039                        candidates: ['あ', '🍣', '!'].iter().map(|&c| c.to_string()).collect(),
1040                        minimum_count: 1,
1041                    }],
1042                    ..PasswordMaker::default()
1043                };
1044                let result = password_maker.validate();
1045                assert!(result.is_ok());
1046            }
1047
1048            // There are no candidates for other characters, but the minimum number of characters is set to 0
1049            {
1050                let password_maker = PasswordMaker {
1051                    others: vec![Classifier {
1052                        candidates: vec![],
1053                        minimum_count: 0,
1054                    }],
1055                    ..PasswordMaker::default()
1056                };
1057                let result = password_maker.validate();
1058                assert!(result.is_ok());
1059            }
1060        }
1061
1062        // Error case
1063        {
1064            // There are no candidates for other characters, but the minimum number of characters is set to 1
1065            {
1066                let password_maker = PasswordMaker {
1067                    others: vec![Classifier {
1068                        candidates: vec![],
1069                        minimum_count: 1,
1070                    }],
1071                    ..PasswordMaker::default()
1072                };
1073                let result = password_maker.validate();
1074                assert!(result.is_err());
1075            }
1076
1077            // There are no candidates for other characters, but the minimum number of characters is set to 2
1078            {
1079                let password_maker = PasswordMaker {
1080                    others: vec![Classifier {
1081                        candidates: vec![],
1082                        minimum_count: 2,
1083                    }],
1084                    ..PasswordMaker::default()
1085                };
1086                let result = password_maker.validate();
1087                assert!(result.is_err());
1088            }
1089        }
1090    }
1091
1092    #[test]
1093    fn validate_total() {
1094        // Test the total minimum number of characters for each type
1095        {
1096            let mut password_maker = PasswordMaker {
1097                others: vec![Classifier {
1098                    candidates: ['あ', '🍣', '!'].iter().map(|&c| c.to_string()).collect(),
1099                    minimum_count: 1,
1100                }],
1101                ..PasswordMaker::default()
1102            };
1103
1104            // The total minimum number of characters is less than the password length
1105            {
1106                password_maker.length = password_maker.uppercase.minimum_count
1107                    + password_maker.lowercase.minimum_count
1108                    + password_maker.number.minimum_count
1109                    + password_maker.symbol.minimum_count
1110                    + password_maker
1111                        .others
1112                        .iter()
1113                        .map(|c| c.minimum_count)
1114                        .sum::<u32>()
1115                    - 1;
1116                let result = password_maker.validate();
1117                assert!(result.is_err());
1118            }
1119
1120            // The total minimum number of characters is equal to the password length
1121            {
1122                password_maker.length = password_maker.uppercase.minimum_count
1123                    + password_maker.lowercase.minimum_count
1124                    + password_maker.number.minimum_count
1125                    + password_maker.symbol.minimum_count
1126                    + password_maker
1127                        .others
1128                        .iter()
1129                        .map(|c| c.minimum_count)
1130                        .sum::<u32>();
1131                let result = password_maker.validate();
1132                assert!(result.is_ok());
1133            }
1134
1135            // The total minimum number of characters is greater than the password length
1136            {
1137                password_maker.length = password_maker.uppercase.minimum_count
1138                    + password_maker.lowercase.minimum_count
1139                    + password_maker.number.minimum_count
1140                    + password_maker.symbol.minimum_count
1141                    + password_maker
1142                        .others
1143                        .iter()
1144                        .map(|c| c.minimum_count)
1145                        .sum::<u32>()
1146                    + 1;
1147                let result = password_maker.validate();
1148                assert!(result.is_ok());
1149            }
1150        }
1151    }
1152
1153    #[test]
1154    fn validate_candidates() {
1155        // Test if there are candidates for the password
1156        {
1157            let mut password_maker = PasswordMaker::default();
1158
1159            // There are candidates
1160            {
1161                let result = password_maker.validate();
1162                assert!(result.is_ok());
1163            }
1164
1165            // There are no candidates
1166            {
1167                password_maker.uppercase = Classifier {
1168                    candidates: vec![],
1169                    minimum_count: 0,
1170                };
1171                password_maker.lowercase = Classifier {
1172                    candidates: vec![],
1173                    minimum_count: 0,
1174                };
1175                password_maker.number = Classifier {
1176                    candidates: vec![],
1177                    minimum_count: 0,
1178                };
1179                password_maker.symbol = Classifier {
1180                    candidates: vec![],
1181                    minimum_count: 0,
1182                };
1183                password_maker.others = vec![Classifier {
1184                    candidates: vec![],
1185                    minimum_count: 0,
1186                }];
1187                let result = password_maker.validate();
1188                assert!(result.is_err());
1189            }
1190        }
1191    }
1192
1193    #[test]
1194    fn overwrite_to_meet_minimum_count() {
1195        // Confirm that it is overwritten by making everything blank
1196
1197        // By default, uppercase, lowercase, numbers, symbols, and the minimum number of characters are set to 1,
1198        // and there are no candidates for other characters, so check if each type of character is included
1199        {
1200            let mut password = vec![" ".to_string(); 5];
1201
1202            let password_maker = PasswordMaker::default();
1203
1204            password_maker.overwrite_to_meet_minimum_count(&mut password);
1205
1206            assert!(password
1207                .iter()
1208                .any(|s| s.chars().any(|c| c.is_ascii_uppercase())));
1209            assert!(password
1210                .iter()
1211                .any(|s| s.chars().any(|c| c.is_ascii_lowercase())));
1212            assert!(password
1213                .iter()
1214                .any(|s| s.chars().any(|c| c.is_ascii_digit())));
1215            assert!(password
1216                .iter()
1217                .any(|s| s.chars().any(|c| c.is_ascii_punctuation())));
1218            assert!(!password
1219                .iter()
1220                .any(|s| s.chars().any(|c| c.eq(&'あ') || c.eq(&'🍣') || c.eq(&'!'))));
1221        }
1222
1223        // If the minimum number of characters is 0, that type of character is not included
1224        // Test by setting 0 and 1 alternately
1225        // Since the test for other variable-width strings is done above, make the string blank before overwriting
1226        {
1227            let mut password = vec![" ".to_string(); 5];
1228
1229            let mut password_maker = PasswordMaker {
1230                others: vec![Classifier {
1231                    candidates: ['あ', '🍣', '!'].iter().map(|&c| c.to_string()).collect(),
1232                    minimum_count: 1,
1233                }],
1234                ..PasswordMaker::default()
1235            };
1236
1237            password_maker.uppercase.minimum_count = 0;
1238            password_maker.lowercase.minimum_count = 1;
1239            password_maker.number.minimum_count = 0;
1240            password_maker.symbol.minimum_count = 1;
1241            for classifier in &mut password_maker.others {
1242                classifier.minimum_count = 0;
1243            }
1244            password_maker.overwrite_to_meet_minimum_count(&mut password);
1245
1246            assert!(!password
1247                .iter()
1248                .any(|s| s.chars().any(|c| c.is_ascii_uppercase())));
1249            assert!(password
1250                .iter()
1251                .any(|s| s.chars().any(|c| c.is_ascii_lowercase())));
1252            assert!(!password
1253                .iter()
1254                .any(|s| s.chars().any(|c| c.is_ascii_digit())));
1255            assert!(password
1256                .iter()
1257                .any(|s| s.chars().any(|c| c.is_ascii_punctuation())));
1258            assert!(!password
1259                .iter()
1260                .any(|s| s.chars().any(|c| c.eq(&'あ') || c.eq(&'🍣') || c.eq(&'!'))));
1261
1262            let mut password = vec![" ".to_string(); 5];
1263            password_maker.uppercase.minimum_count = 1;
1264            password_maker.lowercase.minimum_count = 0;
1265            password_maker.number.minimum_count = 1;
1266            password_maker.symbol.minimum_count = 0;
1267            for classifier in &mut password_maker.others {
1268                classifier.minimum_count = 1;
1269            }
1270            password_maker.overwrite_to_meet_minimum_count(&mut password);
1271
1272            assert!(password
1273                .iter()
1274                .any(|s| s.chars().any(|c| c.is_ascii_uppercase())));
1275            assert!(!password
1276                .iter()
1277                .any(|s| s.chars().any(|c| c.is_ascii_lowercase())));
1278            assert!(password
1279                .iter()
1280                .any(|s| s.chars().any(|c| c.is_ascii_digit())));
1281            assert!(!password
1282                .iter()
1283                .any(|s| s.chars().any(|c| c.is_ascii_punctuation())));
1284            assert!(password
1285                .iter()
1286                .any(|s| s.chars().any(|c| c.eq(&'あ') || c.eq(&'🍣') || c.eq(&'!'))));
1287        }
1288    }
1289
1290    #[test]
1291    fn replace_characters() {
1292        // Confirm that it is overwritten by making all characters not in the candidates
1293        // Include characters that consist of 1 to 4 bytes to check if character boundaries are properly recognized
1294        let mut password = vec![
1295            "μ".to_string(),
1296            "日".to_string(),
1297            "🦀".to_string(),
1298            "1".to_string(),
1299            "°".to_string(),
1300        ];
1301        let old_password_length = password.len();
1302
1303        let password_maker = PasswordMaker {
1304            others: vec![Classifier {
1305                candidates: ['あ', '🍣', '!'].iter().map(|&c| c.to_string()).collect(),
1306                minimum_count: 1, // 引数で上書き数を指定するため、値はなんでもよい
1307            }],
1308            ..PasswordMaker::default()
1309        };
1310        for classifier in &password_maker.others {
1311            password_maker.replace_characters(&mut password, classifier, vec![0, 4, 2]);
1312        }
1313
1314        // The number of characters does not change
1315        assert_eq!(
1316            password.iter().map(|s| s.chars().count()).sum::<usize>(),
1317            old_password_length
1318        );
1319
1320        let mut iter = password.iter();
1321        // The 0th character becomes one of あ, 🍣, !
1322        assert!(matches!(iter.next(), Some(s) if s == "あ" || s == "🍣" || s == "!"));
1323
1324        // The 1st character is the same as the 1st character of the original string
1325        assert_eq!(iter.next().map(String::as_str), Some("日"));
1326
1327        // The 2nd character becomes one of あ, 🍣, !
1328        assert!(matches!(iter.next(), Some(s) if s == "あ" || s == "🍣" || s == "!"));
1329
1330        // The 3rd character is the same as the 3rd character of the original string
1331        assert_eq!(iter.next().map(String::as_str), Some("1"));
1332
1333        // The 4th character becomes one of あ, 🍣, !
1334        assert!(matches!(iter.next(), Some(s) if s == "あ" || s == "🍣" || s == "!"));
1335    }
1336
1337    #[test]
1338    #[should_panic]
1339    fn replace_characters_panic() {
1340        // Test for panic when the index is out of range
1341        let mut password = vec![
1342            "μ".to_string(),
1343            "日".to_string(),
1344            "🦀".to_string(),
1345            "1".to_string(),
1346            "°".to_string(),
1347        ];
1348
1349        let password_maker = PasswordMaker {
1350            others: vec![Classifier {
1351                candidates: ['あ', '🍣', '!'].iter().map(|&c| c.to_string()).collect(),
1352                minimum_count: 1, // 引数で上書き数を指定するため、値はなんでもよい
1353            }],
1354            ..PasswordMaker::default()
1355        };
1356        password_maker.replace_characters(&mut password, &password_maker.others[0], vec![5]);
1357    }
1358
1359    #[test]
1360    fn unique_random_numbers() {
1361        let password_maker = PasswordMaker::default();
1362
1363        // Generate 0 random numbers
1364        {
1365            let numbers = password_maker.unique_random_numbers(0, 0..100);
1366            assert_eq!(numbers.len(), 0);
1367        }
1368
1369        // Generate 1 random number
1370        {
1371            let numbers = password_maker.unique_random_numbers(1, 0..100);
1372            assert_eq!(numbers.len(), 1);
1373            // Check if the value is within the range
1374            assert!(numbers[0] < 100);
1375        }
1376
1377        // Generate 10 random numbers
1378        {
1379            let numbers = password_maker.unique_random_numbers(10, 0..100);
1380            assert_eq!(numbers.len(), 10);
1381            // Check for duplicates
1382            assert_eq!(
1383                numbers
1384                    .iter()
1385                    .collect::<std::collections::HashSet<_>>()
1386                    .len(),
1387                10
1388            );
1389            // Check if all values are within the range
1390            assert!(numbers.iter().all(|&x| x < 100));
1391        }
1392    }
1393}