Skip to main content

faker_rust/
base.rs

1//! Base utilities for Faker generators
2
3use crate::config::FakerConfig;
4
5/// Uppercase letters
6pub const U_LETTERS: [char; 26] = [
7    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
8    'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
9];
10
11/// Lowercase letters
12pub const L_LETTERS: [char; 26] = [
13    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
14    't', 'u', 'v', 'w', 'x', 'y', 'z',
15];
16
17/// All letters
18pub const LETTERS: [char; 52] = {
19    let mut letters = [' '; 52];
20    let mut i = 0;
21    while i < 26 {
22        letters[i] = U_LETTERS[i];
23        letters[i + 26] = L_LETTERS[i];
24        i += 1;
25    }
26    letters
27};
28
29/// Digits
30pub const DIGITS: [char; 10] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
31
32/// Hex digits uppercase
33pub const HEX_UPPER: [char; 16] = [
34    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
35];
36
37/// Hex digits lowercase
38pub const HEX_LOWER: [char; 16] = [
39    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
40];
41
42/// Alphanumeric characters
43pub const ALPHANUMERIC: [char; 62] = {
44    let mut chars = [' '; 62];
45    let mut i = 0;
46    while i < 26 {
47        chars[i] = U_LETTERS[i];
48        chars[i + 26] = L_LETTERS[i];
49        i += 1;
50    }
51    // Add digits after letters
52    let mut j = 0;
53    while j < 10 {
54        chars[52 + j] = DIGITS[j];
55        j += 1;
56    }
57    chars
58};
59
60/// Replace `#` with random digit, `?` with random letter
61/// By default, the first `#` is replaced with 1-9 (no leading zero)
62pub fn numerify(template: &str) -> String {
63    let config = FakerConfig::current();
64    let mut result = String::new();
65    let mut chars = template.chars().peekable();
66
67    while let Some(c) = chars.next() {
68        if c == '#' {
69            // Check if this is the first # (to avoid leading zeros)
70            let prev_is_hash = result.ends_with('#');
71            if !prev_is_hash {
72                // Check if the previous char in template was # (we're in a sequence)
73                let is_first_in_sequence = result.is_empty() || !result.ends_with('#');
74
75                if is_first_in_sequence && chars.peek().is_some() {
76                    // Use 1-9 for first digit in sequence to avoid leading zero
77                    result.push(config.rand_char(&['1', '2', '3', '4', '5', '6', '7', '8', '9']));
78                } else {
79                    result.push(config.rand_char(&DIGITS));
80                }
81            } else {
82                result.push(config.rand_char(&DIGITS));
83            }
84        } else {
85            result.push(c);
86        }
87    }
88
89    // Clean up consecutive # markers
90    while result.contains("##") {
91        result = result.replace("##", "#");
92    }
93
94    result
95}
96
97/// Replace `?` with random letter
98pub fn letterify(template: &str) -> String {
99    let config = FakerConfig::current();
100    template.replace('?', &config.rand_char(&LETTERS).to_string())
101}
102
103/// Replace both `#` and `?` in the template
104pub fn bothify(template: &str) -> String {
105    letterify(&numerify(template))
106}
107
108/// Sample a random element from a slice
109pub fn sample<T: Clone>(items: &[T]) -> T {
110    FakerConfig::current().sample(items)
111}
112
113/// Sample multiple random elements from a slice (with replacement)
114pub fn sample_many<T: Clone>(items: &[T], count: usize) -> Vec<T> {
115    let config = FakerConfig::current();
116    (0..count).map(|_| config.sample(items)).collect()
117}
118
119/// Generate a random number in range [min, max]
120pub fn rand_in_range(min: u32, max: u32) -> u32 {
121    if max < min {
122        return min;
123    }
124    FakerConfig::current().rand_range(min, max)
125}
126
127/// Parse a template with interpolations like "#{first_name} #{last_name}"
128pub fn parse(template: &str) -> String {
129    let mut result = template.to_string();
130
131    // Simple parsing - in the real implementation this would be more sophisticated
132    // For now, just handle basic patterns
133    while result.contains("#{") {
134        if let Some(start) = result.find("#{") {
135            if let Some(end) = result[start..].find('}') {
136                let placeholder = &result[start + 2..start + end];
137                let replacement = fetch_value(placeholder);
138                result = format!(
139                    "{}{}{}",
140                    &result[..start],
141                    replacement,
142                    &result[start + end + 1..]
143                );
144            } else {
145                break;
146            }
147        } else {
148            break;
149        }
150    }
151
152    result
153}
154
155fn fetch_value(key: &str) -> String {
156    // Map placeholder keys to actual generator calls
157    // For now, return key as-is - the t! macro from rust-i18n will handle the rest
158    key.to_string()
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164
165    #[test]
166    fn test_numerify() {
167        let result = numerify("###");
168        assert_eq!(result.len(), 3);
169        assert!(result.chars().all(|c| c.is_ascii_digit()));
170    }
171
172    #[test]
173    fn test_letterify() {
174        let result = letterify("???");
175        assert_eq!(result.len(), 3);
176        assert!(result.chars().all(|c| c.is_alphabetic()));
177    }
178
179    #[test]
180    fn test_bothify() {
181        let result = bothify("???###");
182        assert_eq!(result.len(), 6);
183    }
184
185    #[test]
186    fn test_sample() {
187        let items = vec![1, 2, 3, 4, 5];
188        let result = sample(&items);
189        assert!(items.contains(&result));
190    }
191
192    #[test]
193    fn test_rand_in_range() {
194        let result = rand_in_range(1, 10);
195        assert!((1..=10).contains(&result));
196    }
197}