1use rand::seq::IndexedRandom;
7use rand::Rng;
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(|c| c.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(|c| c.is_ascii_alphabetic())
81 .collect::<String>()
82 .to_lowercase();
83 let last = self
84 .last_name
85 .chars()
86 .filter(|c| c.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)]
1230#[allow(clippy::unwrap_used)]
1231mod tests {
1232 use super::*;
1233 use rand::SeedableRng;
1234 use rand_chacha::ChaCha8Rng;
1235
1236 #[test]
1237 fn test_name_pool_generation() {
1238 let mut rng = ChaCha8Rng::seed_from_u64(42);
1239 let pool = NamePool::western_us();
1240 let name = pool.generate_name(&mut rng);
1241
1242 assert!(!name.first_name.is_empty());
1243 assert!(!name.last_name.is_empty());
1244 assert_eq!(name.culture, NameCulture::WesternUs);
1245 }
1246
1247 #[test]
1248 fn test_multi_culture_generator() {
1249 let mut rng = ChaCha8Rng::seed_from_u64(42);
1250 let generator = MultiCultureNameGenerator::new();
1251
1252 let mut culture_counts: HashMap<NameCulture, usize> = HashMap::new();
1254 for _ in 0..100 {
1255 let name = generator.generate_name(&mut rng);
1256 *culture_counts.entry(name.culture).or_insert(0) += 1;
1257 }
1258
1259 assert!(culture_counts.len() > 1);
1261 }
1262
1263 #[test]
1264 fn test_user_id_generation() {
1265 let name = PersonName {
1266 first_name: "John".to_string(),
1267 last_name: "Smith".to_string(),
1268 display_name: "John Smith".to_string(),
1269 culture: NameCulture::WesternUs,
1270 is_male: true,
1271 };
1272
1273 let user_id = name.to_user_id(42);
1274 assert_eq!(user_id, "JSMITH042");
1275 }
1276
1277 #[test]
1278 fn test_email_generation() {
1279 let name = PersonName {
1280 first_name: "María".to_string(),
1281 last_name: "García".to_string(),
1282 display_name: "María García".to_string(),
1283 culture: NameCulture::Hispanic,
1284 is_male: false,
1285 };
1286
1287 let email = name.to_email("acme.com");
1288 assert_eq!(email, "mara.garca@acme.com"); }
1290
1291 #[test]
1292 fn test_all_culture_pools() {
1293 let mut rng = ChaCha8Rng::seed_from_u64(42);
1294
1295 for culture in NameCulture::all() {
1296 let pool = NamePool::for_culture(*culture);
1297 let name = pool.generate_name(&mut rng);
1298 assert!(!name.first_name.is_empty());
1299 assert!(!name.last_name.is_empty());
1300 assert_eq!(name.culture, *culture);
1301 }
1302 }
1303}