1use rand::seq::IndexedRandom;
7use rand::{Rng, RngExt};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
13#[serde(rename_all = "snake_case")]
14pub enum NameCulture {
15 #[default]
17 WesternUs,
18 German,
20 French,
22 Chinese,
24 Japanese,
26 Indian,
28 Hispanic,
30}
31
32impl NameCulture {
33 pub fn all() -> &'static [NameCulture] {
35 &[
36 NameCulture::WesternUs,
37 NameCulture::German,
38 NameCulture::French,
39 NameCulture::Chinese,
40 NameCulture::Japanese,
41 NameCulture::Indian,
42 NameCulture::Hispanic,
43 ]
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct PersonName {
50 pub first_name: String,
52 pub last_name: String,
54 pub display_name: String,
56 pub culture: NameCulture,
58 pub is_male: bool,
60}
61
62impl PersonName {
63 pub fn to_user_id(&self, index: usize) -> String {
65 let first_initial = self.first_name.chars().next().unwrap_or('X');
66 let last_part: String = self
67 .last_name
68 .chars()
69 .filter(char::is_ascii_alphabetic)
70 .take(5)
71 .collect();
72 format!("{}{}{:03}", first_initial, last_part.to_uppercase(), index)
73 }
74
75 pub fn to_email(&self, domain: &str) -> String {
77 let first = self
78 .first_name
79 .chars()
80 .filter(char::is_ascii_alphabetic)
81 .collect::<String>()
82 .to_lowercase();
83 let last = self
84 .last_name
85 .chars()
86 .filter(char::is_ascii_alphabetic)
87 .collect::<String>()
88 .to_lowercase();
89 format!("{first}.{last}@{domain}")
90 }
91}
92
93#[derive(Debug, Clone)]
95pub struct NamePool {
96 pub first_names_male: Vec<String>,
98 pub first_names_female: Vec<String>,
100 pub last_names: Vec<String>,
102 pub culture: NameCulture,
104}
105
106fn sv(items: &[&str]) -> Vec<String> {
108 items.iter().map(|s| (*s).to_string()).collect()
109}
110
111impl NamePool {
112 pub fn western_us() -> Self {
114 Self {
115 culture: NameCulture::WesternUs,
116 first_names_male: sv(&[
117 "James",
118 "John",
119 "Robert",
120 "Michael",
121 "William",
122 "David",
123 "Richard",
124 "Joseph",
125 "Thomas",
126 "Christopher",
127 "Charles",
128 "Daniel",
129 "Matthew",
130 "Anthony",
131 "Mark",
132 "Donald",
133 "Steven",
134 "Paul",
135 "Andrew",
136 "Joshua",
137 "Kenneth",
138 "Kevin",
139 "Brian",
140 "George",
141 "Timothy",
142 "Ronald",
143 "Edward",
144 "Jason",
145 "Jeffrey",
146 "Ryan",
147 "Jacob",
148 "Gary",
149 "Nicholas",
150 "Eric",
151 "Jonathan",
152 "Stephen",
153 "Larry",
154 "Justin",
155 "Scott",
156 "Brandon",
157 "Benjamin",
158 "Samuel",
159 "Raymond",
160 "Gregory",
161 "Frank",
162 "Alexander",
163 "Patrick",
164 "Jack",
165 "Dennis",
166 "Jerry",
167 "Tyler",
168 "Aaron",
169 "Jose",
170 "Adam",
171 "Nathan",
172 ]),
173 first_names_female: sv(&[
174 "Mary",
175 "Patricia",
176 "Jennifer",
177 "Linda",
178 "Barbara",
179 "Elizabeth",
180 "Susan",
181 "Jessica",
182 "Sarah",
183 "Karen",
184 "Lisa",
185 "Nancy",
186 "Betty",
187 "Margaret",
188 "Sandra",
189 "Ashley",
190 "Kimberly",
191 "Emily",
192 "Donna",
193 "Michelle",
194 "Dorothy",
195 "Carol",
196 "Amanda",
197 "Melissa",
198 "Deborah",
199 "Stephanie",
200 "Rebecca",
201 "Sharon",
202 "Laura",
203 "Cynthia",
204 "Kathleen",
205 "Amy",
206 "Angela",
207 "Shirley",
208 "Anna",
209 "Brenda",
210 "Pamela",
211 "Emma",
212 "Nicole",
213 "Helen",
214 "Samantha",
215 "Katherine",
216 "Christine",
217 "Debra",
218 "Rachel",
219 "Carolyn",
220 "Janet",
221 "Catherine",
222 "Maria",
223 "Heather",
224 "Diane",
225 "Ruth",
226 "Julie",
227 ]),
228 last_names: sv(&[
229 "Smith",
230 "Johnson",
231 "Williams",
232 "Brown",
233 "Jones",
234 "Garcia",
235 "Miller",
236 "Davis",
237 "Rodriguez",
238 "Martinez",
239 "Hernandez",
240 "Lopez",
241 "Gonzalez",
242 "Wilson",
243 "Anderson",
244 "Thomas",
245 "Taylor",
246 "Moore",
247 "Jackson",
248 "Martin",
249 "Lee",
250 "Perez",
251 "Thompson",
252 "White",
253 "Harris",
254 "Sanchez",
255 "Clark",
256 "Ramirez",
257 "Lewis",
258 "Robinson",
259 "Walker",
260 "Young",
261 "Allen",
262 "King",
263 "Wright",
264 "Scott",
265 "Torres",
266 "Nguyen",
267 "Hill",
268 "Flores",
269 "Green",
270 "Adams",
271 "Nelson",
272 "Baker",
273 "Hall",
274 "Rivera",
275 "Campbell",
276 "Mitchell",
277 "Carter",
278 "Roberts",
279 "Turner",
280 "Phillips",
281 "Evans",
282 "Parker",
283 "Edwards",
284 "Collins",
285 ]),
286 }
287 }
288
289 pub fn german() -> Self {
291 Self {
292 culture: NameCulture::German,
293 first_names_male: sv(&[
294 "Hans",
295 "Klaus",
296 "Wolfgang",
297 "Dieter",
298 "Jürgen",
299 "Peter",
300 "Michael",
301 "Thomas",
302 "Andreas",
303 "Stefan",
304 "Markus",
305 "Christian",
306 "Martin",
307 "Frank",
308 "Bernd",
309 "Uwe",
310 "Ralf",
311 "Werner",
312 "Heinz",
313 "Helmut",
314 "Gerhard",
315 "Manfred",
316 "Horst",
317 "Karl",
318 "Heinrich",
319 "Friedrich",
320 "Wilhelm",
321 "Otto",
322 "Matthias",
323 "Tobias",
324 "Sebastian",
325 "Florian",
326 "Alexander",
327 "Maximilian",
328 "Felix",
329 "Lukas",
330 "Jonas",
331 "Leon",
332 "Paul",
333 "Philipp",
334 "Tim",
335 "Jan",
336 "Nico",
337 "Erik",
338 "Lars",
339 "Sven",
340 "Kai",
341 "Olaf",
342 "Rainer",
343 ]),
344 first_names_female: sv(&[
345 "Anna",
346 "Maria",
347 "Elisabeth",
348 "Ursula",
349 "Helga",
350 "Monika",
351 "Petra",
352 "Sabine",
353 "Karin",
354 "Renate",
355 "Ingrid",
356 "Brigitte",
357 "Gisela",
358 "Erika",
359 "Christa",
360 "Andrea",
361 "Claudia",
362 "Susanne",
363 "Birgit",
364 "Heike",
365 "Martina",
366 "Nicole",
367 "Stefanie",
368 "Julia",
369 "Katharina",
370 "Christina",
371 "Sandra",
372 "Melanie",
373 "Daniela",
374 "Anja",
375 "Tanja",
376 "Simone",
377 "Silke",
378 "Nadine",
379 "Yvonne",
380 "Manuela",
381 "Sonja",
382 "Michaela",
383 "Angelika",
384 "Barbara",
385 "Gabriele",
386 "Beate",
387 "Doris",
388 "Eva",
389 "Franziska",
390 "Lena",
391 "Hannah",
392 "Sophie",
393 "Leonie",
394 "Laura",
395 ]),
396 last_names: sv(&[
397 "Müller",
398 "Schmidt",
399 "Schneider",
400 "Fischer",
401 "Weber",
402 "Meyer",
403 "Wagner",
404 "Becker",
405 "Schulz",
406 "Hoffmann",
407 "Schäfer",
408 "Koch",
409 "Bauer",
410 "Richter",
411 "Klein",
412 "Wolf",
413 "Schröder",
414 "Neumann",
415 "Schwarz",
416 "Zimmermann",
417 "Braun",
418 "Krüger",
419 "Hofmann",
420 "Hartmann",
421 "Lange",
422 "Schmitt",
423 "Werner",
424 "Schmitz",
425 "Krause",
426 "Meier",
427 "Lehmann",
428 "Schmid",
429 "Schulze",
430 "Maier",
431 "Köhler",
432 "Herrmann",
433 "König",
434 "Walter",
435 "Mayer",
436 "Huber",
437 "Kaiser",
438 "Fuchs",
439 "Peters",
440 "Lang",
441 "Scholz",
442 "Möller",
443 "Weiß",
444 "Jung",
445 "Hahn",
446 "Schubert",
447 ]),
448 }
449 }
450
451 pub fn french() -> Self {
453 Self {
454 culture: NameCulture::French,
455 first_names_male: sv(&[
456 "Jean",
457 "Pierre",
458 "Michel",
459 "André",
460 "Philippe",
461 "Jacques",
462 "Bernard",
463 "Alain",
464 "François",
465 "Robert",
466 "Marcel",
467 "René",
468 "Louis",
469 "Claude",
470 "Daniel",
471 "Yves",
472 "Christian",
473 "Patrick",
474 "Nicolas",
475 "Julien",
476 "Thomas",
477 "Antoine",
478 "Alexandre",
479 "Maxime",
480 "Lucas",
481 "Hugo",
482 "Théo",
483 "Mathieu",
484 "Guillaume",
485 "Laurent",
486 "Olivier",
487 "Christophe",
488 "Sébastien",
489 "Frédéric",
490 "Vincent",
491 "David",
492 "Eric",
493 "Pascal",
494 "Gilles",
495 "Thierry",
496 "Stéphane",
497 "Bruno",
498 "Dominique",
499 "Serge",
500 "Maurice",
501 "Henri",
502 "Paul",
503 "Charles",
504 "Emmanuel",
505 "Raphaël",
506 ]),
507 first_names_female: sv(&[
508 "Marie",
509 "Jeanne",
510 "Françoise",
511 "Monique",
512 "Catherine",
513 "Nathalie",
514 "Isabelle",
515 "Sylvie",
516 "Anne",
517 "Martine",
518 "Nicole",
519 "Christine",
520 "Sophie",
521 "Valérie",
522 "Julie",
523 "Camille",
524 "Léa",
525 "Manon",
526 "Emma",
527 "Chloé",
528 "Inès",
529 "Sarah",
530 "Laura",
531 "Louise",
532 "Jade",
533 "Alice",
534 "Lola",
535 "Margot",
536 "Charlotte",
537 "Clara",
538 "Pauline",
539 "Marine",
540 "Aurélie",
541 "Céline",
542 "Sandrine",
543 "Virginie",
544 "Stéphanie",
545 "Élodie",
546 "Delphine",
547 "Laurence",
548 "Brigitte",
549 "Jacqueline",
550 "Simone",
551 "Denise",
552 "Madeleine",
553 "Thérèse",
554 "Hélène",
555 "Élise",
556 "Juliette",
557 "Marguerite",
558 ]),
559 last_names: sv(&[
560 "Martin",
561 "Bernard",
562 "Dubois",
563 "Thomas",
564 "Robert",
565 "Richard",
566 "Petit",
567 "Durand",
568 "Leroy",
569 "Moreau",
570 "Simon",
571 "Laurent",
572 "Lefebvre",
573 "Michel",
574 "Garcia",
575 "David",
576 "Bertrand",
577 "Roux",
578 "Vincent",
579 "Fournier",
580 "Morel",
581 "Girard",
582 "André",
583 "Lefèvre",
584 "Mercier",
585 "Dupont",
586 "Lambert",
587 "Bonnet",
588 "François",
589 "Martinez",
590 "Legrand",
591 "Garnier",
592 "Faure",
593 "Rousseau",
594 "Blanc",
595 "Guérin",
596 "Muller",
597 "Henry",
598 "Roussel",
599 "Nicolas",
600 "Perrin",
601 "Morin",
602 "Mathieu",
603 "Clément",
604 "Gauthier",
605 "Dumont",
606 "Lopez",
607 "Fontaine",
608 "Chevalier",
609 "Robin",
610 ]),
611 }
612 }
613
614 pub fn chinese() -> Self {
616 Self {
617 culture: NameCulture::Chinese,
618 first_names_male: sv(&[
619 "Wei", "Fang", "Lei", "Jun", "Jian", "Hao", "Chen", "Yang", "Ming", "Tao", "Long",
620 "Feng", "Bin", "Qiang", "Gang", "Hui", "Peng", "Xiang", "Bo", "Chao", "Dong",
621 "Liang", "Ning", "Kai", "Jie", "Yong", "Hai", "Lin", "Wen", "Zheng", "Hong", "Xin",
622 "Da", "Zhi", "Guang", "Cheng", "Yi", "Sheng", "Biao", "Ping", "Yun", "Song",
623 "Chang", "Kang", "Rui", "Nan", "Jia", "Xiao", "Yu", "Hua",
624 ]),
625 first_names_female: sv(&[
626 "Fang", "Min", "Jing", "Li", "Yan", "Hong", "Juan", "Mei", "Ying", "Xia", "Hui",
627 "Lin", "Ling", "Ping", "Dan", "Yun", "Na", "Qian", "Xin", "Ya", "Wei", "Wen",
628 "Jie", "Qing", "Yu", "Hua", "Yue", "Xue", "Lan", "Zhen", "Rong", "Shu", "Fei",
629 "Lei", "Shan", "Ting", "Ni", "Ying", "Chen", "Huan", "Lu", "Ai", "Xiao", "Xiang",
630 "Yao", "Meng", "Qi", "Jun", "Bei", "Zhi",
631 ]),
632 last_names: sv(&[
633 "Wang", "Li", "Zhang", "Liu", "Chen", "Yang", "Huang", "Zhao", "Wu", "Zhou", "Xu",
634 "Sun", "Ma", "Zhu", "Hu", "Guo", "He", "Lin", "Gao", "Luo", "Zheng", "Liang",
635 "Xie", "Tang", "Song", "Xu", "Han", "Deng", "Feng", "Cao", "Peng", "Xiao", "Cheng",
636 "Yuan", "Tian", "Dong", "Pan", "Cai", "Jiang", "Wei", "Yu", "Du", "Ye", "Shi",
637 "Lu", "Shen", "Su", "Jia", "Fan", "Jin",
638 ]),
639 }
640 }
641
642 pub fn japanese() -> Self {
644 Self {
645 culture: NameCulture::Japanese,
646 first_names_male: sv(&[
647 "Hiroshi", "Takeshi", "Kenji", "Yuki", "Kazuki", "Ryota", "Daiki", "Shota", "Yuto",
648 "Kenta", "Haruto", "Sota", "Riku", "Yuma", "Kaito", "Ren", "Hayato", "Takumi",
649 "Kouki", "Ryuu", "Naoki", "Tsubasa", "Yuuki", "Akira", "Satoshi", "Makoto",
650 "Tetsuya", "Masaki", "Shin", "Kei", "Daisuke", "Shunsuke", "Tomoya", "Yusuke",
651 "Tatsuya", "Katsuki", "Shun", "Yamato", "Koji", "Hideo", "Takahiro", "Noboru",
652 "Shinji", "Osamu", "Minoru", "Hideki", "Jun", "Masaru", "Ken", "Ryo",
653 ]),
654 first_names_female: sv(&[
655 "Yuki", "Sakura", "Hana", "Yui", "Mio", "Rin", "Aoi", "Mei", "Saki", "Miku",
656 "Nanami", "Ayaka", "Misaki", "Haruka", "Momoka", "Rina", "Yuna", "Hinata",
657 "Koharu", "Miyu", "Akari", "Hikari", "Kaede", "Natsuki", "Mai", "Ami", "Aya",
658 "Emi", "Kana", "Megumi", "Tomoko", "Yoko", "Keiko", "Naomi", "Mayumi", "Chika",
659 "Nana", "Risa", "Asuka", "Fumiko", "Kyoko", "Reiko", "Noriko", "Sachiko", "Mariko",
660 "Shiori", "Midori", "Kanako", "Minami", "Eriko",
661 ]),
662 last_names: sv(&[
663 "Sato",
664 "Suzuki",
665 "Takahashi",
666 "Tanaka",
667 "Watanabe",
668 "Ito",
669 "Yamamoto",
670 "Nakamura",
671 "Kobayashi",
672 "Kato",
673 "Yoshida",
674 "Yamada",
675 "Sasaki",
676 "Yamaguchi",
677 "Matsumoto",
678 "Inoue",
679 "Kimura",
680 "Hayashi",
681 "Shimizu",
682 "Yamazaki",
683 "Mori",
684 "Abe",
685 "Ikeda",
686 "Hashimoto",
687 "Yamashita",
688 "Ishikawa",
689 "Nakajima",
690 "Maeda",
691 "Fujita",
692 "Ogawa",
693 "Goto",
694 "Okada",
695 "Hasegawa",
696 "Murakami",
697 "Kondo",
698 "Ishii",
699 "Saito",
700 "Sakamoto",
701 "Endo",
702 "Aoki",
703 "Fujii",
704 "Nishimura",
705 "Fukuda",
706 "Ota",
707 "Miura",
708 "Okamoto",
709 "Matsuda",
710 "Nakagawa",
711 "Fujiwara",
712 "Kawamura",
713 ]),
714 }
715 }
716
717 pub fn indian() -> Self {
719 Self {
720 culture: NameCulture::Indian,
721 first_names_male: sv(&[
722 "Raj", "Amit", "Vikram", "Rahul", "Sanjay", "Arun", "Suresh", "Rajesh", "Deepak",
723 "Vijay", "Prakash", "Manoj", "Sunil", "Anil", "Ravi", "Ashok", "Ramesh", "Mukesh",
724 "Sandeep", "Ajay", "Naveen", "Pradeep", "Sachin", "Nitin", "Vinod", "Rakesh",
725 "Srinivas", "Ganesh", "Krishna", "Mohan", "Kiran", "Venkat", "Hari", "Shankar",
726 "Dinesh", "Mahesh", "Satish", "Girish", "Naresh", "Harish", "Pavan", "Arjun",
727 "Anand", "Vivek", "Rohit", "Gaurav", "Kunal", "Vishal", "Akhil", "Dev",
728 ]),
729 first_names_female: sv(&[
730 "Priya",
731 "Anita",
732 "Sunita",
733 "Kavita",
734 "Neha",
735 "Pooja",
736 "Anjali",
737 "Deepa",
738 "Meena",
739 "Rekha",
740 "Lakshmi",
741 "Padma",
742 "Gita",
743 "Sita",
744 "Rani",
745 "Devi",
746 "Shalini",
747 "Nisha",
748 "Swati",
749 "Preeti",
750 "Divya",
751 "Shreya",
752 "Aishwarya",
753 "Ritu",
754 "Seema",
755 "Jyoti",
756 "Shweta",
757 "Pallavi",
758 "Rashmi",
759 "Smita",
760 "Varsha",
761 "Archana",
762 "Asha",
763 "Manisha",
764 "Usha",
765 "Vandana",
766 "Geeta",
767 "Rita",
768 "Maya",
769 "Radha",
770 "Sapna",
771 "Megha",
772 "Nikita",
773 "Tanvi",
774 "Aditi",
775 "Bhavna",
776 "Chitra",
777 "Komal",
778 "Madhuri",
779 "Parul",
780 ]),
781 last_names: sv(&[
782 "Sharma",
783 "Patel",
784 "Singh",
785 "Kumar",
786 "Gupta",
787 "Joshi",
788 "Verma",
789 "Shah",
790 "Mehta",
791 "Reddy",
792 "Rao",
793 "Iyer",
794 "Nair",
795 "Menon",
796 "Pillai",
797 "Das",
798 "Bose",
799 "Sen",
800 "Chatterjee",
801 "Banerjee",
802 "Mukherjee",
803 "Ghosh",
804 "Roy",
805 "Dutta",
806 "Kapoor",
807 "Malhotra",
808 "Agarwal",
809 "Sinha",
810 "Thakur",
811 "Saxena",
812 "Mishra",
813 "Pandey",
814 "Trivedi",
815 "Desai",
816 "Modi",
817 "Kulkarni",
818 "Patil",
819 "Kaur",
820 "Chopra",
821 "Khanna",
822 "Bhatia",
823 "Choudhury",
824 "Rajan",
825 "Subramaniam",
826 "Venkatesh",
827 "Naidu",
828 "Hegde",
829 "Shukla",
830 "Prasad",
831 "Murthy",
832 ]),
833 }
834 }
835
836 pub fn hispanic() -> Self {
838 Self {
839 culture: NameCulture::Hispanic,
840 first_names_male: sv(&[
841 "José",
842 "Juan",
843 "Carlos",
844 "Luis",
845 "Miguel",
846 "Francisco",
847 "Antonio",
848 "Manuel",
849 "Pedro",
850 "Javier",
851 "Fernando",
852 "Rafael",
853 "Alejandro",
854 "Ricardo",
855 "Eduardo",
856 "Roberto",
857 "Daniel",
858 "Pablo",
859 "Sergio",
860 "Jorge",
861 "Andrés",
862 "Raúl",
863 "Diego",
864 "Enrique",
865 "Óscar",
866 "Adrián",
867 "Víctor",
868 "Martín",
869 "Gabriel",
870 "Álvaro",
871 "Iván",
872 "Mario",
873 "César",
874 "Héctor",
875 "Alberto",
876 "Gustavo",
877 "Arturo",
878 "Ramón",
879 "Hugo",
880 "Salvador",
881 "Ernesto",
882 "Guillermo",
883 "Ignacio",
884 "Jaime",
885 "Felipe",
886 "Tomás",
887 "Santiago",
888 "Mateo",
889 "Sebastián",
890 "Nicolás",
891 ]),
892 first_names_female: sv(&[
893 "María",
894 "Carmen",
895 "Ana",
896 "Laura",
897 "Rosa",
898 "Isabel",
899 "Elena",
900 "Patricia",
901 "Teresa",
902 "Lucia",
903 "Pilar",
904 "Dolores",
905 "Paula",
906 "Sara",
907 "Marta",
908 "Julia",
909 "Cristina",
910 "Claudia",
911 "Andrea",
912 "Mónica",
913 "Gloria",
914 "Beatriz",
915 "Alicia",
916 "Rocío",
917 "Victoria",
918 "Silvia",
919 "Eva",
920 "Raquel",
921 "Adriana",
922 "Lorena",
923 "Gabriela",
924 "Marina",
925 "Sandra",
926 "Verónica",
927 "Natalia",
928 "Carolina",
929 "Diana",
930 "Alejandra",
931 "Cecilia",
932 "Daniela",
933 "Sofía",
934 "Valentina",
935 "Camila",
936 "Isabella",
937 "Mariana",
938 "Fernanda",
939 "Paola",
940 "Liliana",
941 "Angela",
942 "Inés",
943 ]),
944 last_names: sv(&[
945 "García",
946 "Rodríguez",
947 "Martínez",
948 "López",
949 "González",
950 "Hernández",
951 "Pérez",
952 "Sánchez",
953 "Ramírez",
954 "Torres",
955 "Flores",
956 "Rivera",
957 "Gómez",
958 "Díaz",
959 "Reyes",
960 "Cruz",
961 "Morales",
962 "Ortiz",
963 "Gutiérrez",
964 "Chávez",
965 "Ramos",
966 "Vargas",
967 "Castillo",
968 "Jiménez",
969 "Moreno",
970 "Romero",
971 "Herrera",
972 "Medina",
973 "Aguilar",
974 "Vega",
975 "Castro",
976 "Mendoza",
977 "Ruiz",
978 "Fernández",
979 "Álvarez",
980 "Muñoz",
981 "Rojas",
982 "Silva",
983 "Suárez",
984 "Delgado",
985 "Navarro",
986 "Santos",
987 "Molina",
988 "Espinoza",
989 "Guerrero",
990 "Cabrera",
991 "Campos",
992 "Cortés",
993 "Salazar",
994 "Luna",
995 ]),
996 }
997 }
998
999 pub fn for_culture(culture: NameCulture) -> Self {
1001 match culture {
1002 NameCulture::WesternUs => Self::western_us(),
1003 NameCulture::German => Self::german(),
1004 NameCulture::French => Self::french(),
1005 NameCulture::Chinese => Self::chinese(),
1006 NameCulture::Japanese => Self::japanese(),
1007 NameCulture::Indian => Self::indian(),
1008 NameCulture::Hispanic => Self::hispanic(),
1009 }
1010 }
1011
1012 pub fn validate(&self) -> Result<(), String> {
1016 if self.first_names_male.is_empty() {
1017 return Err(format!(
1018 "NamePool for {:?} has empty male first names list",
1019 self.culture
1020 ));
1021 }
1022 if self.first_names_female.is_empty() {
1023 return Err(format!(
1024 "NamePool for {:?} has empty female first names list",
1025 self.culture
1026 ));
1027 }
1028 if self.last_names.is_empty() {
1029 return Err(format!(
1030 "NamePool for {:?} has empty last names list",
1031 self.culture
1032 ));
1033 }
1034 Ok(())
1035 }
1036
1037 pub fn generate_name(&self, rng: &mut impl Rng) -> PersonName {
1039 let is_male = rng.random_bool(0.5);
1040
1041 let first_name = if is_male {
1042 self.first_names_male.choose(rng).expect(
1043 "NamePool male first names must not be empty; call validate() at construction",
1044 )
1045 } else {
1046 self.first_names_female.choose(rng).expect(
1047 "NamePool female first names must not be empty; call validate() at construction",
1048 )
1049 };
1050
1051 let last_name = self
1052 .last_names
1053 .choose(rng)
1054 .expect("NamePool last names must not be empty; call validate() at construction");
1055
1056 PersonName {
1057 first_name: first_name.clone(),
1058 last_name: last_name.clone(),
1059 display_name: format!("{first_name} {last_name}"),
1060 culture: self.culture,
1061 is_male,
1062 }
1063 }
1064
1065 pub fn from_culture_config(
1067 config: &crate::country::schema::CultureConfig,
1068 culture: NameCulture,
1069 ) -> Self {
1070 Self {
1071 culture,
1072 first_names_male: config.male_first_names.clone(),
1073 first_names_female: config.female_first_names.clone(),
1074 last_names: config.last_names.clone(),
1075 }
1076 }
1077}
1078
1079#[derive(Debug, Clone)]
1081pub struct MultiCultureNameGenerator {
1082 pools: HashMap<NameCulture, NamePool>,
1083 distribution: Vec<(NameCulture, f64)>,
1084 email_domain: String,
1085}
1086
1087impl Default for MultiCultureNameGenerator {
1088 fn default() -> Self {
1089 Self::new()
1090 }
1091}
1092
1093impl MultiCultureNameGenerator {
1094 pub fn new() -> Self {
1096 let mut pools = HashMap::new();
1097 for culture in NameCulture::all() {
1098 pools.insert(*culture, NamePool::for_culture(*culture));
1099 }
1100
1101 Self {
1102 pools,
1103 distribution: vec![
1104 (NameCulture::WesternUs, 0.40),
1105 (NameCulture::Hispanic, 0.20),
1106 (NameCulture::German, 0.10),
1107 (NameCulture::French, 0.05),
1108 (NameCulture::Chinese, 0.10),
1109 (NameCulture::Japanese, 0.05),
1110 (NameCulture::Indian, 0.10),
1111 ],
1112 email_domain: "company.com".to_string(),
1113 }
1114 }
1115
1116 pub fn with_distribution(distribution: Vec<(NameCulture, f64)>) -> Self {
1118 let mut gen = Self::new();
1119 gen.distribution = distribution;
1120 gen
1121 }
1122
1123 pub fn with_email_domain(mut self, domain: &str) -> Self {
1125 self.email_domain = domain.to_string();
1126 self
1127 }
1128
1129 pub fn set_email_domain(&mut self, domain: &str) {
1131 self.email_domain = domain.to_string();
1132 }
1133
1134 pub fn email_domain(&self) -> &str {
1136 &self.email_domain
1137 }
1138
1139 fn select_culture(&self, rng: &mut impl Rng) -> NameCulture {
1141 let roll: f64 = rng.random();
1142 let mut cumulative = 0.0;
1143
1144 for (culture, weight) in &self.distribution {
1145 cumulative += weight;
1146 if roll < cumulative {
1147 return *culture;
1148 }
1149 }
1150
1151 NameCulture::WesternUs
1153 }
1154
1155 pub fn generate_name(&self, rng: &mut impl Rng) -> PersonName {
1157 let culture = self.select_culture(rng);
1158 self.pools
1159 .get(&culture)
1160 .map(|pool| pool.generate_name(rng))
1161 .unwrap_or_else(|| NamePool::western_us().generate_name(rng))
1162 }
1163
1164 pub fn generate_name_for_culture(
1166 &self,
1167 culture: NameCulture,
1168 rng: &mut impl Rng,
1169 ) -> PersonName {
1170 self.pools
1171 .get(&culture)
1172 .map(|pool| pool.generate_name(rng))
1173 .unwrap_or_else(|| NamePool::western_us().generate_name(rng))
1174 }
1175
1176 pub fn generate_user_id(&self, name: &PersonName, index: usize) -> String {
1178 name.to_user_id(index)
1179 }
1180
1181 pub fn generate_email(&self, name: &PersonName) -> String {
1183 name.to_email(&self.email_domain)
1184 }
1185
1186 pub fn from_country_pack(pack: &crate::country::schema::CountryPack) -> Self {
1188 let mut pools = HashMap::new();
1189 let mut distribution = Vec::new();
1190
1191 for culture_config in &pack.names.cultures {
1192 let culture = match culture_config.culture_id.as_str() {
1194 "western_us" | "western" => NameCulture::WesternUs,
1195 "german" | "deutsch" => NameCulture::German,
1196 "french" | "français" => NameCulture::French,
1197 "chinese" | "中文" => NameCulture::Chinese,
1198 "japanese" | "日本語" => NameCulture::Japanese,
1199 "indian" | "hindi" => NameCulture::Indian,
1200 "hispanic" | "latino" | "español" => NameCulture::Hispanic,
1201 _ => NameCulture::WesternUs,
1202 };
1203
1204 let pool = NamePool::from_culture_config(culture_config, culture);
1205 pools.insert(culture, pool);
1206 distribution.push((culture, culture_config.weight));
1207 }
1208
1209 if pools.is_empty() {
1211 return Self::new();
1212 }
1213
1214 let email_domain = pack
1215 .names
1216 .email_domains
1217 .first()
1218 .cloned()
1219 .unwrap_or_else(|| "company.com".to_string());
1220
1221 Self {
1222 pools,
1223 distribution,
1224 email_domain,
1225 }
1226 }
1227}
1228
1229#[cfg(test)]
1230mod tests {
1231 use super::*;
1232 use rand::SeedableRng;
1233 use rand_chacha::ChaCha8Rng;
1234
1235 #[test]
1236 fn test_name_pool_generation() {
1237 let mut rng = ChaCha8Rng::seed_from_u64(42);
1238 let pool = NamePool::western_us();
1239 let name = pool.generate_name(&mut rng);
1240
1241 assert!(!name.first_name.is_empty());
1242 assert!(!name.last_name.is_empty());
1243 assert_eq!(name.culture, NameCulture::WesternUs);
1244 }
1245
1246 #[test]
1247 fn test_multi_culture_generator() {
1248 let mut rng = ChaCha8Rng::seed_from_u64(42);
1249 let generator = MultiCultureNameGenerator::new();
1250
1251 let mut culture_counts: HashMap<NameCulture, usize> = HashMap::new();
1253 for _ in 0..100 {
1254 let name = generator.generate_name(&mut rng);
1255 *culture_counts.entry(name.culture).or_insert(0) += 1;
1256 }
1257
1258 assert!(culture_counts.len() > 1);
1260 }
1261
1262 #[test]
1263 fn test_user_id_generation() {
1264 let name = PersonName {
1265 first_name: "John".to_string(),
1266 last_name: "Smith".to_string(),
1267 display_name: "John Smith".to_string(),
1268 culture: NameCulture::WesternUs,
1269 is_male: true,
1270 };
1271
1272 let user_id = name.to_user_id(42);
1273 assert_eq!(user_id, "JSMITH042");
1274 }
1275
1276 #[test]
1277 fn test_email_generation() {
1278 let name = PersonName {
1279 first_name: "María".to_string(),
1280 last_name: "García".to_string(),
1281 display_name: "María García".to_string(),
1282 culture: NameCulture::Hispanic,
1283 is_male: false,
1284 };
1285
1286 let email = name.to_email("acme.com");
1287 assert_eq!(email, "mara.garca@acme.com"); }
1289
1290 #[test]
1291 fn test_all_culture_pools() {
1292 let mut rng = ChaCha8Rng::seed_from_u64(42);
1293
1294 for culture in NameCulture::all() {
1295 let pool = NamePool::for_culture(*culture);
1296 let name = pool.generate_name(&mut rng);
1297 assert!(!name.first_name.is_empty());
1298 assert!(!name.last_name.is_empty());
1299 assert_eq!(name.culture, *culture);
1300 }
1301 }
1302}