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 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}