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