Skip to main content

robinpath_modules/modules/
faker_mod.rs

1use robinpath::{RobinPath, Value};
2use std::sync::atomic::{AtomicU64, Ordering};
3
4static SEED: AtomicU64 = AtomicU64::new(12345);
5
6fn next_random() -> u64 {
7    let mut s = SEED.load(Ordering::Relaxed);
8    s ^= s << 13;
9    s ^= s >> 7;
10    s ^= s << 17;
11    // Mix in some entropy from time
12    s = s.wrapping_add(
13        std::time::SystemTime::now()
14            .duration_since(std::time::UNIX_EPOCH)
15            .unwrap_or_default()
16            .as_nanos() as u64,
17    );
18    SEED.store(s, Ordering::Relaxed);
19    s
20}
21
22fn random_range(min: u64, max: u64) -> u64 {
23    if max <= min {
24        return min;
25    }
26    min + next_random() % (max - min)
27}
28
29fn pick_from(items: &[&str]) -> String {
30    items[random_range(0, items.len() as u64) as usize].to_string()
31}
32
33const FIRST_NAMES: &[&str] = &[
34    "James", "Mary", "Robert", "Patricia", "John", "Jennifer", "Michael", "Linda",
35    "David", "Elizabeth", "William", "Barbara", "Richard", "Susan", "Joseph", "Jessica",
36    "Thomas", "Sarah", "Christopher", "Karen", "Daniel", "Lisa", "Matthew", "Nancy",
37    "Anthony", "Betty", "Mark", "Margaret", "Donald", "Sandra", "Steven", "Ashley",
38    "Paul", "Kimberly", "Andrew", "Emily", "Joshua", "Donna", "Kenneth", "Michelle",
39    "Kevin", "Carol", "Brian", "Amanda", "George", "Dorothy", "Timothy", "Melissa",
40];
41
42const LAST_NAMES: &[&str] = &[
43    "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis",
44    "Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzalez", "Wilson", "Anderson",
45    "Thomas", "Taylor", "Moore", "Jackson", "Martin", "Lee", "Perez", "Thompson",
46    "White", "Harris", "Sanchez", "Clark", "Ramirez", "Lewis", "Robinson", "Walker",
47    "Young", "Allen", "King", "Wright", "Scott", "Torres", "Nguyen", "Hill", "Flores",
48];
49
50const CITIES: &[&str] = &[
51    "New York", "Los Angeles", "Chicago", "Houston", "Phoenix", "Philadelphia",
52    "San Antonio", "San Diego", "Dallas", "San Jose", "Austin", "Jacksonville",
53    "Fort Worth", "Columbus", "Charlotte", "San Francisco", "Indianapolis", "Seattle",
54    "Denver", "Washington", "Nashville", "Oklahoma City", "Portland", "Las Vegas",
55];
56
57const COUNTRIES: &[&str] = &[
58    "United States", "Canada", "United Kingdom", "Australia", "Germany", "France",
59    "Japan", "Brazil", "India", "Mexico", "South Korea", "Italy", "Spain", "Netherlands",
60    "Sweden", "Norway", "Denmark", "Finland", "Switzerland", "Argentina", "Poland",
61];
62
63const STREETS: &[&str] = &[
64    "Main St", "Oak Ave", "Elm St", "Park Blvd", "Cedar Ln", "Maple Dr",
65    "Pine St", "Washington Ave", "Lake Rd", "Hill St", "River Rd", "Forest Ave",
66];
67
68const COMPANIES: &[&str] = &[
69    "Acme Corp", "Globex", "Initech", "Umbrella Corp", "Stark Industries",
70    "Wayne Enterprises", "Cyberdyne", "Tyrell Corp", "Soylent Corp", "Weyland-Yutani",
71    "Oscorp", "LexCorp", "Massive Dynamic", "InGen", "Aperture Science",
72];
73
74const LOREM_WORDS: &[&str] = &[
75    "lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit",
76    "sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore",
77    "magna", "aliqua", "enim", "ad", "minim", "veniam", "quis", "nostrud",
78    "exercitation", "ullamco", "laboris", "nisi", "aliquip", "ex", "ea", "commodo",
79    "consequat", "duis", "aute", "irure", "in", "reprehenderit", "voluptate",
80    "velit", "esse", "cillum", "fugiat", "nulla", "pariatur", "excepteur",
81    "sint", "occaecat", "cupidatat", "non", "proident", "sunt", "culpa",
82];
83
84const WORDS: &[&str] = &[
85    "ability", "accept", "across", "action", "activity", "agree", "almost", "among",
86    "animal", "answer", "appear", "area", "arrive", "article", "attack", "author",
87    "basket", "battle", "beauty", "become", "before", "begin", "behind", "believe",
88    "camera", "career", "center", "change", "choice", "circle", "class", "clear",
89    "danger", "debate", "decide", "demand", "design", "detail", "dinner", "direct",
90    "effect", "effort", "energy", "engine", "enough", "entire", "escape", "expect",
91];
92
93pub fn register(rp: &mut RobinPath) {
94    rp.register_builtin("faker.seed", |args, _| {
95        let s = args.first().map(|v| v.to_number() as u64).unwrap_or(12345);
96        SEED.store(s, Ordering::Relaxed);
97        Ok(Value::Bool(true))
98    });
99
100    rp.register_builtin("faker.name", |_args, _| {
101        Ok(Value::String(format!("{} {}", pick_from(FIRST_NAMES), pick_from(LAST_NAMES))))
102    });
103
104    rp.register_builtin("faker.firstName", |_args, _| {
105        Ok(Value::String(pick_from(FIRST_NAMES)))
106    });
107
108    rp.register_builtin("faker.lastName", |_args, _| {
109        Ok(Value::String(pick_from(LAST_NAMES)))
110    });
111
112    rp.register_builtin("faker.email", |_args, _| {
113        let first = pick_from(FIRST_NAMES).to_lowercase();
114        let last = pick_from(LAST_NAMES).to_lowercase();
115        let domains = ["gmail.com", "yahoo.com", "outlook.com", "example.com", "mail.com"];
116        let domain = pick_from(&domains);
117        Ok(Value::String(format!("{}.{}@{}", first, last, domain)))
118    });
119
120    rp.register_builtin("faker.phone", |_args, _| {
121        let area = random_range(200, 999);
122        let mid = random_range(100, 999);
123        let end = random_range(1000, 9999);
124        Ok(Value::String(format!("({}) {}-{}", area, mid, end)))
125    });
126
127    rp.register_builtin("faker.address", |_args, _| {
128        let num = random_range(1, 9999);
129        let street = pick_from(STREETS);
130        Ok(Value::String(format!("{} {}", num, street)))
131    });
132
133    rp.register_builtin("faker.city", |_args, _| {
134        Ok(Value::String(pick_from(CITIES)))
135    });
136
137    rp.register_builtin("faker.country", |_args, _| {
138        Ok(Value::String(pick_from(COUNTRIES)))
139    });
140
141    rp.register_builtin("faker.zipCode", |_args, _| {
142        let zip = random_range(10000, 99999);
143        Ok(Value::String(format!("{}", zip)))
144    });
145
146    rp.register_builtin("faker.company", |_args, _| {
147        Ok(Value::String(pick_from(COMPANIES)))
148    });
149
150    rp.register_builtin("faker.lorem", |args, _| {
151        let count = args.first().map(|v| v.to_number() as usize).unwrap_or(10);
152        let words: Vec<String> = (0..count).map(|_| pick_from(LOREM_WORDS)).collect();
153        Ok(Value::String(words.join(" ")))
154    });
155
156    rp.register_builtin("faker.number", |args, _| {
157        let min = args.first().map(|v| v.to_number() as i64).unwrap_or(0);
158        let max = args.get(1).map(|v| v.to_number() as i64).unwrap_or(100);
159        let val = if max > min {
160            min + (next_random() as i64).abs() % (max - min)
161        } else {
162            min
163        };
164        Ok(Value::Number(val as f64))
165    });
166
167    rp.register_builtin("faker.float", |args, _| {
168        let min = args.first().map(|v| v.to_number()).unwrap_or(0.0);
169        let max = args.get(1).map(|v| v.to_number()).unwrap_or(1.0);
170        let frac = (next_random() % 10000) as f64 / 10000.0;
171        Ok(Value::Number(min + frac * (max - min)))
172    });
173
174    rp.register_builtin("faker.boolean", |_args, _| {
175        Ok(Value::Bool(next_random() % 2 == 0))
176    });
177
178    rp.register_builtin("faker.date", |_args, _| {
179        let year = random_range(2000, 2030);
180        let month = random_range(1, 13);
181        let day = random_range(1, 29);
182        Ok(Value::String(format!("{:04}-{:02}-{:02}", year, month, day)))
183    });
184
185    rp.register_builtin("faker.uuid", |_args, _| {
186        let hex_chars = "0123456789abcdef";
187        let hex: Vec<u8> = hex_chars.as_bytes().to_vec();
188        let mut uuid = String::with_capacity(36);
189        let pattern = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
190        for c in pattern.chars() {
191            match c {
192                'x' => uuid.push(hex[random_range(0, 16) as usize] as char),
193                'y' => uuid.push(hex[(random_range(0, 4) + 8) as usize] as char),
194                '-' | '4' => uuid.push(c),
195                _ => uuid.push(c),
196            }
197        }
198        Ok(Value::String(uuid))
199    });
200
201    rp.register_builtin("faker.pick", |args, _| {
202        let arr = args.first().cloned().unwrap_or(Value::Null);
203        if let Value::Array(items) = &arr {
204            if items.is_empty() {
205                return Ok(Value::Null);
206            }
207            let idx = random_range(0, items.len() as u64) as usize;
208            Ok(items[idx].clone())
209        } else {
210            Ok(Value::Null)
211        }
212    });
213
214    rp.register_builtin("faker.shuffle", |args, _| {
215        let arr = args.first().cloned().unwrap_or(Value::Null);
216        if let Value::Array(mut items) = arr {
217            let n = items.len();
218            for i in (1..n).rev() {
219                let j = random_range(0, (i + 1) as u64) as usize;
220                items.swap(i, j);
221            }
222            Ok(Value::Array(items))
223        } else {
224            Ok(Value::Array(vec![]))
225        }
226    });
227
228    rp.register_builtin("faker.paragraph", |args, _| {
229        let sentences = args.first().map(|v| v.to_number() as usize).unwrap_or(3);
230        let mut parts = Vec::new();
231        for _ in 0..sentences {
232            let word_count = random_range(5, 15) as usize;
233            let words: Vec<String> = (0..word_count).map(|_| pick_from(WORDS)).collect();
234            let mut sentence = words.join(" ");
235            if let Some(first) = sentence.get_mut(0..1) {
236                first.make_ascii_uppercase();
237            }
238            sentence.push('.');
239            parts.push(sentence);
240        }
241        Ok(Value::String(parts.join(" ")))
242    });
243
244    rp.register_builtin("faker.sentence", |_args, _| {
245        let word_count = random_range(5, 12) as usize;
246        let words: Vec<String> = (0..word_count).map(|_| pick_from(WORDS)).collect();
247        let mut sentence = words.join(" ");
248        if let Some(first) = sentence.get_mut(0..1) {
249            first.make_ascii_uppercase();
250        }
251        sentence.push('.');
252        Ok(Value::String(sentence))
253    });
254
255    rp.register_builtin("faker.word", |_args, _| {
256        Ok(Value::String(pick_from(WORDS)))
257    });
258
259    rp.register_builtin("faker.color", |_args, _| {
260        let r = random_range(0, 256);
261        let g = random_range(0, 256);
262        let b = random_range(0, 256);
263        Ok(Value::String(format!("#{:02x}{:02x}{:02x}", r, g, b)))
264    });
265
266    rp.register_builtin("faker.ip", |_args, _| {
267        Ok(Value::String(format!(
268            "{}.{}.{}.{}",
269            random_range(1, 255),
270            random_range(0, 255),
271            random_range(0, 255),
272            random_range(1, 255)
273        )))
274    });
275
276    rp.register_builtin("faker.url", |_args, _| {
277        let domains = ["example.com", "test.org", "sample.net", "demo.io", "fake.dev"];
278        let paths = ["page", "post", "article", "item", "resource", "data", "api"];
279        let domain = pick_from(&domains);
280        let path = pick_from(&paths);
281        let id = random_range(1, 1000);
282        Ok(Value::String(format!("https://{}/{}/{}", domain, path, id)))
283    });
284
285    rp.register_builtin("faker.avatar", |_args, _| {
286        let id = random_range(1, 100);
287        Ok(Value::String(format!(
288            "https://i.pravatar.cc/150?img={}",
289            id
290        )))
291    });
292}