1use indexmap::IndexSet;
2use rand::prelude::*;
3
4#[cfg(test)]
5use rand_chacha::ChaCha20Rng;
7
8#[derive(Debug, Clone)]
9pub struct Classifier {
11 pub candidates: Vec<String>,
13 pub minimum_count: u32,
15}
16
17#[derive(Debug, Clone)]
18pub struct PasswordMaker {
27 pub length: u32,
29 pub exclude_similar: bool,
31 pub include_whitespace_in_candidate: bool,
33 pub lowercase: Classifier,
35 pub uppercase: Classifier,
37 pub number: Classifier,
39 pub symbol: Classifier,
41 pub others: Vec<Classifier>,
43}
44
45impl PasswordMaker {
46 pub fn generate(&mut self) -> Result<String, String> {
78 self.validate()?;
80
81 let candidates = self.candidates();
82
83 let mut rng = Self::create_rng();
84
85 let mut password: Vec<String> = (0..self.length)
87 .map(|_| candidates.choose(&mut rng).unwrap().to_string())
88 .collect();
89
90 self.overwrite_to_meet_minimum_count(&mut password);
93
94 Ok(password.concat())
95 }
96
97 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 fn create_rng() -> Box<dyn RngCore> {
143 #[cfg(test)]
144 {
145 Box::new(ChaCha20Rng::seed_from_u64(0))
148 }
149 #[cfg(not(test))]
150 {
151 Box::new(rand::thread_rng())
153 }
154 }
155
156 fn validate(&self) -> Result<(), String> {
164 let classifier = [
166 (&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 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 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 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 fn overwrite_to_meet_minimum_count(&self, password: &mut [String]) {
229 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 let mut overwrite_chars =
241 self.unique_random_numbers(overwrite_count as usize, 0..password.len() as u32);
242
243 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 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 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 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 fn default() -> Self {
341 PasswordMaker {
342 length: 16,
343 exclude_similar: false,
344 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 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 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 fn empty() {
393 let mut password_maker = PasswordMaker {
394 length: 0,
395 ..PasswordMaker::default()
396 };
397
398 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 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 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 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 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 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 assert!(password.contains('A'));
457 assert!(password.contains('M'));
458 assert!(password.contains('N'));
459 assert!(password.contains('Z'));
460 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 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 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 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 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 assert!(password.contains('a'));
523 assert!(password.contains('m'));
524 assert!(password.contains('n'));
525 assert!(password.contains('z'));
526 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 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 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 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 assert!(password.contains('0'));
579 assert!(password.contains('5'));
580 assert!(password.contains('9'));
581 assert!(password
583 .chars()
584 .all(|c| !matches!(c, '1' | '2' | '3' | '4' | '6' | '7' | '8')));
585
586 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 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 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 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 assert!(password.contains('!'));
624 assert!(password.contains('@'));
625 assert!(password.contains('~'));
626 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 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 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 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 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 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 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 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 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 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 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 {
807 {
809 let password_maker = PasswordMaker {
810 ..PasswordMaker::default()
811 };
812 let result = password_maker.validate();
813 assert!(result.is_ok());
814 }
815
816 {
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 {
832 {
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 {
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 {
864 {
866 let password_maker = PasswordMaker {
867 ..PasswordMaker::default()
868 };
869 let result = password_maker.validate();
870 assert!(result.is_ok());
871 }
872
873 {
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 {
889 {
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 {
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 {
921 {
923 let password_maker = PasswordMaker {
924 ..PasswordMaker::default()
925 };
926 let result = password_maker.validate();
927 assert!(result.is_ok());
928 }
929
930 {
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 {
946 {
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 {
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 {
978 {
980 let password_maker = PasswordMaker {
981 ..PasswordMaker::default()
982 };
983 let result = password_maker.validate();
984 assert!(result.is_ok());
985 }
986
987 {
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 {
1003 {
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 {
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 {
1035 {
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 {
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 {
1064 {
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 {
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 {
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 {
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 {
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 {
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 {
1157 let mut password_maker = PasswordMaker::default();
1158
1159 {
1161 let result = password_maker.validate();
1162 assert!(result.is_ok());
1163 }
1164
1165 {
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 {
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 {
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 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, }],
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 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 assert!(matches!(iter.next(), Some(s) if s == "あ" || s == "🍣" || s == "!"));
1323
1324 assert_eq!(iter.next().map(String::as_str), Some("日"));
1326
1327 assert!(matches!(iter.next(), Some(s) if s == "あ" || s == "🍣" || s == "!"));
1329
1330 assert_eq!(iter.next().map(String::as_str), Some("1"));
1332
1333 assert!(matches!(iter.next(), Some(s) if s == "あ" || s == "🍣" || s == "!"));
1335 }
1336
1337 #[test]
1338 #[should_panic]
1339 fn replace_characters_panic() {
1340 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, }],
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 {
1365 let numbers = password_maker.unique_random_numbers(0, 0..100);
1366 assert_eq!(numbers.len(), 0);
1367 }
1368
1369 {
1371 let numbers = password_maker.unique_random_numbers(1, 0..100);
1372 assert_eq!(numbers.len(), 1);
1373 assert!(numbers[0] < 100);
1375 }
1376
1377 {
1379 let numbers = password_maker.unique_random_numbers(10, 0..100);
1380 assert_eq!(numbers.len(), 10);
1381 assert_eq!(
1383 numbers
1384 .iter()
1385 .collect::<std::collections::HashSet<_>>()
1386 .len(),
1387 10
1388 );
1389 assert!(numbers.iter().all(|&x| x < 100));
1391 }
1392 }
1393}