hightower_naming/
lib.rs

1mod adjectives;
2mod nouns;
3
4use rand::Rng;
5
6/// Generate a random name with optional random suffix.
7///
8/// # Examples
9///
10/// ```
11/// // No random suffix - just adjective-noun
12/// let name = hightower_naming::generate_random_name(None);
13/// // Example: "brave-tiger"
14///
15/// // With random suffix of default length (5 characters)
16/// let name = hightower_naming::generate_random_name(Some(5));
17/// // Example: "brave-tiger-a1b2c"
18///
19/// // With custom random suffix length (10 characters)
20/// let name = hightower_naming::generate_random_name(Some(10));
21/// // Example: "brave-tiger-x9y8z7w6v5"
22/// ```
23pub fn generate_random_name(random_suffix_length: Option<usize>) -> String {
24    generate_random_name_with_prefix(None, random_suffix_length)
25}
26
27/// Generate a random name with custom prefix and optional random suffix.
28///
29/// # Examples
30///
31/// ```
32/// // With custom prefix
33/// let name = hightower_naming::generate_random_name_with_prefix(Some("app"), None);
34/// // Example: "app-brave-tiger"
35///
36/// // With custom prefix and random suffix
37/// let name = hightower_naming::generate_random_name_with_prefix(Some("app"), Some(5));
38/// // Example: "app-brave-tiger-a1b2c"
39///
40/// // No prefix (same as generate_random_name)
41/// let name = hightower_naming::generate_random_name_with_prefix(None, Some(5));
42/// // Example: "brave-tiger-a1b2c"
43/// ```
44pub fn generate_random_name_with_prefix(prefix: Option<&str>, random_suffix_length: Option<usize>) -> String {
45    let mut rng = rand::thread_rng();
46    let adjective = adjectives::ADJECTIVES[rng.gen_range(0..adjectives::ADJECTIVES.len())];
47    let noun = nouns::NOUNS[rng.gen_range(0..nouns::NOUNS.len())];
48
49    let base = match prefix {
50        Some(p) if !p.is_empty() => format!("{}-{}-{}", p, adjective, noun),
51        _ => format!("{}-{}", adjective, noun),
52    };
53
54    match random_suffix_length {
55        Some(length) if length > 0 => {
56            let random_chars: String = (0..length)
57                .map(|_| {
58                    let chars = b"0123456789";
59                    chars[rng.gen_range(0..chars.len())] as char
60                })
61                .collect();
62            format!("{}-{}", base, random_chars)
63        }
64        _ => base,
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_generates_valid_name() {
74        let name = generate_random_name(Some(5));
75        assert_eq!(name.matches('-').count(), 2);
76    }
77
78    #[test]
79    fn test_name_format() {
80        let name = generate_random_name(Some(5));
81        let parts: Vec<&str> = name.split('-').collect();
82        assert_eq!(parts.len(), 3);
83        assert!(!parts[0].is_empty()); // adjective
84        assert!(!parts[1].is_empty()); // noun
85        assert_eq!(parts[2].len(), 5); // random chars
86    }
87
88    #[test]
89    fn test_random_suffix_numeric() {
90        let name = generate_random_name(Some(5));
91        let parts: Vec<&str> = name.split('-').collect();
92        let suffix = parts[2];
93        assert!(suffix.chars().all(|c| c.is_ascii_digit()));
94    }
95
96    #[test]
97    fn test_adjective_from_list() {
98        let name = generate_random_name(Some(5));
99        let parts: Vec<&str> = name.split('-').collect();
100        let adjective = parts[0];
101        assert!(adjectives::ADJECTIVES.contains(&adjective));
102    }
103
104    #[test]
105    fn test_noun_from_list() {
106        let name = generate_random_name(Some(5));
107        let parts: Vec<&str> = name.split('-').collect();
108        let noun = parts[1];
109        assert!(nouns::NOUNS.contains(&noun));
110    }
111
112    #[test]
113    fn test_generates_different_names() {
114        let name1 = generate_random_name(Some(5));
115        let name2 = generate_random_name(Some(5));
116        // Very unlikely to be the same
117        assert_ne!(name1, name2);
118    }
119
120    #[test]
121    fn test_no_suffix() {
122        let name = generate_random_name(None);
123        assert_eq!(name.matches('-').count(), 1);
124        let parts: Vec<&str> = name.split('-').collect();
125        assert_eq!(parts.len(), 2);
126    }
127
128    #[test]
129    fn test_custom_suffix_length() {
130        let name = generate_random_name(Some(10));
131        let parts: Vec<&str> = name.split('-').collect();
132        assert_eq!(parts.len(), 3);
133        assert_eq!(parts[2].len(), 10);
134    }
135
136    #[test]
137    fn test_zero_suffix_length() {
138        let name = generate_random_name(Some(0));
139        assert_eq!(name.matches('-').count(), 1);
140    }
141
142    #[test]
143    fn test_no_trailing_dash() {
144        let name_no_suffix = generate_random_name(None);
145        assert!(!name_no_suffix.ends_with('-'));
146
147        let name_zero_suffix = generate_random_name(Some(0));
148        assert!(!name_zero_suffix.ends_with('-'));
149    }
150
151    #[test]
152    fn test_custom_prefix() {
153        let name = generate_random_name_with_prefix(Some("custom"), Some(5));
154        assert!(name.starts_with("custom-"));
155        let parts: Vec<&str> = name.split('-').collect();
156        assert_eq!(parts.len(), 4);
157        assert_eq!(parts[0], "custom");
158    }
159
160    #[test]
161    fn test_no_prefix() {
162        let name = generate_random_name_with_prefix(None, Some(5));
163        let parts: Vec<&str> = name.split('-').collect();
164        assert_eq!(parts.len(), 3);
165        assert!(adjectives::ADJECTIVES.contains(&parts[0]));
166        assert!(nouns::NOUNS.contains(&parts[1]));
167        assert_eq!(parts[2].len(), 5);
168    }
169
170    #[test]
171    fn test_empty_prefix() {
172        let name = generate_random_name_with_prefix(Some(""), None);
173        let parts: Vec<&str> = name.split('-').collect();
174        assert_eq!(parts.len(), 2);
175        assert!(adjectives::ADJECTIVES.contains(&parts[0]));
176        assert!(nouns::NOUNS.contains(&parts[1]));
177    }
178}