Skip to main content

vld_fake/
lib.rs

1//! # vld-fake
2//!
3//! Generate fake / test data that satisfies a [`vld`](https://docs.rs/vld) JSON Schema.
4//!
5//! ## Quick start — typed API
6//!
7//! ```rust
8//! use vld::prelude::*;
9//! use vld_fake::FakeData;
10//!
11//! vld::schema! {
12//!     #[derive(Debug)]
13//!     pub struct User {
14//!         pub name:  String      => vld::string().min(2).max(50),
15//!         pub email: String      => vld::string().email(),
16//!         pub age:   i64         => vld::number().int().min(18).max(99),
17//!     }
18//! }
19//!
20//! vld_fake::impl_fake!(User);
21//!
22//! let user = User::fake();
23//! assert!(user.name.len() >= 2);
24//! assert!(user.email.contains('@'));
25//!
26//! // Multiple
27//! let users = User::fake_many(5);
28//!
29//! // Reproducible
30//! let u1 = User::fake_seeded(42);
31//! let u2 = User::fake_seeded(42);
32//! assert_eq!(u1.name, u2.name);
33//! ```
34//!
35//! ## Low-level (untyped) API
36//!
37//! ```rust
38//! use vld::prelude::*;
39//!
40//! vld::schema! {
41//!     #[derive(Debug)]
42//!     pub struct User {
43//!         pub name:  String      => vld::string().min(2).max(50),
44//!         pub email: String      => vld::string().email(),
45//!         pub age:   i64         => vld::number().int().min(18).max(99),
46//!     }
47//! }
48//!
49//! let schema = User::json_schema();
50//! let value  = vld_fake::fake_value(&schema);
51//! // value is a random serde_json::Value
52//! ```
53
54mod dict;
55
56use dict::*;
57use rand::Rng;
58use serde_json::{json, Map, Value};
59use vld::prelude::VldParse;
60
61// ───────────────────────── public convenience API ──────────────────────────
62
63/// Generate a single random [`Value`] conforming to the given JSON Schema.
64pub fn fake_value(schema: &Value) -> Value {
65    FakeGen::new().value(schema)
66}
67
68/// Generate a JSON string conforming to the given JSON Schema.
69pub fn fake_json(schema: &Value) -> String {
70    serde_json::to_string_pretty(&fake_value(schema)).expect("serialisation cannot fail")
71}
72
73/// Generate `count` random values from the same schema.
74pub fn fake_many(schema: &Value, count: usize) -> Vec<Value> {
75    let mut gen = FakeGen::new();
76    (0..count).map(|_| gen.value(schema)).collect()
77}
78
79/// Generate a random value **and** parse it through `T::vld_parse_value`,
80/// returning a fully validated typed instance.
81///
82/// Panics if the generated value does not pass validation (should not happen
83/// unless the schema is ambiguous).
84pub fn fake_parsed<T: VldParse>(schema: &Value) -> T {
85    let val = fake_value(schema);
86    T::vld_parse_value(&val).unwrap_or_else(|e| {
87        panic!(
88            "vld_fake::fake_parsed: generated value failed validation.\nValue: {}\nError: {:?}",
89            serde_json::to_string_pretty(&val).unwrap_or_default(),
90            e,
91        )
92    })
93}
94
95/// Same as [`fake_parsed`], but returns a `Result` instead of panicking.
96pub fn try_fake_parsed<T: VldParse>(schema: &Value) -> Result<T, vld::error::VldError> {
97    let val = fake_value(schema);
98    T::vld_parse_value(&val)
99}
100
101/// Generate with a specific seed for reproducible output.
102pub fn fake_value_seeded(schema: &Value, seed: u64) -> Value {
103    use rand::SeedableRng;
104    let rng = rand::rngs::StdRng::seed_from_u64(seed);
105    FakeGen::with_rng(rng).value(schema)
106}
107
108// ──────────────────────── FakeGen (configurable) ───────────────────────────
109
110/// Stateful fake-data generator backed by any [`rand::Rng`].
111pub struct FakeGen<R: Rng> {
112    rng: R,
113    /// Recursion depth guard.
114    depth: usize,
115    /// Auto-incrementing counter for unique values.
116    counter: u64,
117}
118
119const MAX_DEPTH: usize = 12;
120
121impl FakeGen<rand::rngs::ThreadRng> {
122    /// Create a generator using [`rand::thread_rng()`].
123    pub fn new() -> Self {
124        Self {
125            rng: rand::thread_rng(),
126            depth: 0,
127            counter: 0,
128        }
129    }
130}
131
132impl Default for FakeGen<rand::rngs::ThreadRng> {
133    fn default() -> Self {
134        Self::new()
135    }
136}
137
138impl<R: Rng> FakeGen<R> {
139    /// Create a generator from any [`Rng`] implementation (e.g. a seeded `StdRng`).
140    pub fn with_rng(rng: R) -> Self {
141        Self {
142            rng,
143            depth: 0,
144            counter: 0,
145        }
146    }
147
148    fn next_id(&mut self) -> u64 {
149        self.counter += 1;
150        self.counter
151    }
152
153    fn pick<'a, T>(&mut self, items: &'a [T]) -> &'a T {
154        &items[self.rng.gen_range(0..items.len())]
155    }
156
157    // ────────────────────── main dispatcher ─────────────────────────────
158
159    /// Generate one random [`Value`] conforming to `schema`.
160    pub fn value(&mut self, schema: &Value) -> Value {
161        self.value_with_hint(schema, None)
162    }
163
164    /// Generate one random [`Value`], optionally using a field-name hint for
165    /// smarter generation.
166    pub fn value_with_hint(&mut self, schema: &Value, hint: Option<&str>) -> Value {
167        if self.depth > MAX_DEPTH {
168            return Value::Null;
169        }
170        self.depth += 1;
171        let result = self.dispatch(schema, hint);
172        self.depth -= 1;
173        result
174    }
175
176    fn dispatch(&mut self, schema: &Value, hint: Option<&str>) -> Value {
177        let obj = match schema.as_object() {
178            Some(o) => o,
179            None => return Value::Null,
180        };
181
182        // ---- const / enum ----
183        if let Some(c) = obj.get("const") {
184            return c.clone();
185        }
186        if let Some(en) = obj.get("enum").and_then(Value::as_array) {
187            if en.is_empty() {
188                return Value::Null;
189            }
190            let idx = self.rng.gen_range(0..en.len());
191            return en[idx].clone();
192        }
193
194        // ---- oneOf / anyOf ----
195        if let Some(variants) = obj
196            .get("oneOf")
197            .or_else(|| obj.get("anyOf"))
198            .and_then(Value::as_array)
199        {
200            let concrete: Vec<&Value> = variants
201                .iter()
202                .filter(|v| v.get("type").and_then(Value::as_str) != Some("null"))
203                .collect();
204            if !concrete.is_empty() {
205                let idx = self.rng.gen_range(0..concrete.len());
206                return self.value_with_hint(concrete[idx], hint);
207            }
208            if !variants.is_empty() {
209                let idx = self.rng.gen_range(0..variants.len());
210                return self.value_with_hint(&variants[idx], hint);
211            }
212            return Value::Null;
213        }
214
215        // ---- allOf ----
216        if let Some(all) = obj.get("allOf").and_then(Value::as_array) {
217            return self.merge_all_of(all);
218        }
219
220        // ---- type-based dispatch ----
221        let ty = obj.get("type").and_then(Value::as_str).unwrap_or("string");
222
223        match ty {
224            "string" => self.gen_string(obj, hint),
225            "integer" => self.gen_integer(obj, hint),
226            "number" => self.gen_number(obj, hint),
227            "boolean" => self.gen_boolean(),
228            "array" => self.gen_array(obj, hint),
229            "object" => self.gen_object(obj, hint),
230            "null" => Value::Null,
231            _ => Value::Null,
232        }
233    }
234
235    // ═══════════════════════════════════════════════════════════════════════
236    //  STRING
237    // ═══════════════════════════════════════════════════════════════════════
238
239    fn gen_string(&mut self, obj: &Map<String, Value>, hint: Option<&str>) -> Value {
240        let format = obj.get("format").and_then(Value::as_str);
241
242        // 1) Explicit JSON Schema format
243        if let Some(fmt) = format {
244            return Value::String(self.gen_string_format(fmt, obj));
245        }
246
247        // 2) Heuristic: infer content from field name
248        if let Some(h) = hint {
249            if let Some(v) = self.gen_string_by_hint(h, obj) {
250                return v;
251            }
252        }
253
254        // 3) Fallback: readable word-based text
255        self.gen_readable_string(obj)
256    }
257
258    /// Try to generate a realistic value based on the JSON property name.
259    fn gen_string_by_hint(&mut self, hint: &str, obj: &Map<String, Value>) -> Option<Value> {
260        let h = hint.to_ascii_lowercase();
261        let h = h.as_str();
262
263        // Exact or suffix/contains matches
264        let result: Option<String> = match h {
265            // ── names ─────────────────────────────────────────
266            "first_name" | "firstname" | "given_name" | "givenname" => {
267                Some(self.pick(FIRST_NAMES).to_string())
268            }
269            "last_name" | "lastname" | "surname" | "family_name" | "familyname" => {
270                Some(self.pick(LAST_NAMES).to_string())
271            }
272            "name" | "full_name" | "fullname" | "display_name" | "displayname" | "user_name"
273            | "author" | "author_name" => {
274                let first = self.pick(FIRST_NAMES);
275                let last = self.pick(LAST_NAMES);
276                Some(format!("{first} {last}"))
277            }
278            "username" | "login" | "handle" | "nick" | "nickname" => {
279                let first = self.pick(FIRST_NAMES).to_lowercase();
280                let n: u16 = self.rng.gen_range(1..999);
281                Some(format!("{first}{n}"))
282            }
283
284            // ── contact ───────────────────────────────────────
285            "email" | "email_address" | "emailaddress" | "mail" => Some(self.gen_email()),
286            "phone" | "phone_number" | "phonenumber" | "tel" | "telephone" | "mobile" | "cell" => {
287                Some(self.gen_phone())
288            }
289
290            // ── address ───────────────────────────────────────
291            "city" | "town" => Some(self.pick(CITIES).to_string()),
292            "country" => Some(self.pick(COUNTRIES).to_string()),
293            "state" | "province" | "region" => Some(self.pick(STATES).to_string()),
294            "street" | "street_address" | "address_line" | "address_line1" | "address"
295            | "address1" | "line1" => Some(self.gen_street_address()),
296            "zip" | "zipcode" | "zip_code" | "postal" | "postal_code" | "postalcode" => {
297                Some(self.gen_zip())
298            }
299            "latitude" | "lat" => {
300                let v: f64 = self.rng.gen_range(-90.0..=90.0);
301                Some(format!("{:.6}", v))
302            }
303            "longitude" | "lng" | "lon" => {
304                let v: f64 = self.rng.gen_range(-180.0..=180.0);
305                Some(format!("{:.6}", v))
306            }
307
308            // ── company / org ─────────────────────────────────
309            "company" | "company_name" | "companyname" | "organization" | "organisation"
310            | "org" | "employer" => Some(self.pick(COMPANIES).to_string()),
311            "department" | "team" | "division" => Some(self.pick(DEPARTMENTS).to_string()),
312            "job_title" | "jobtitle" | "position" | "role" | "title" | "occupation" => {
313                Some(self.pick(JOB_TITLES).to_string())
314            }
315
316            // ── internet ──────────────────────────────────────
317            "url" | "website" | "homepage" | "link" | "site" | "uri" | "href" => {
318                Some(self.gen_url())
319            }
320            "domain" | "domain_name" | "domainname" | "host" | "hostname" => {
321                Some(self.gen_hostname())
322            }
323            "ip" | "ip_address" | "ipaddress" | "ipv4" => Some(self.gen_ipv4()),
324            "ipv6" => Some(self.gen_ipv6()),
325            "user_agent" | "useragent" | "ua" => Some(self.gen_user_agent()),
326            "mac" | "mac_address" | "macaddress" => Some(self.gen_mac_address()),
327
328            // ── identifiers ───────────────────────────────────
329            "id" | "uid" | "uuid" | "guid" => Some(self.gen_uuid()),
330            "slug" => Some(self.gen_slug()),
331            "token" | "api_key" | "apikey" | "secret" | "access_token" | "refresh_token" => {
332                Some(self.gen_token(32))
333            }
334            "password" | "pass" | "pwd" | "secret_key" => Some(self.gen_password()),
335
336            // ── text ──────────────────────────────────────────
337            "description" | "desc" | "bio" | "about" | "summary" | "overview" => {
338                Some(self.gen_sentence_range(8, 20))
339            }
340            "comment" | "note" | "notes" | "message" | "body" | "content" | "text" => {
341                Some(self.gen_sentence_range(5, 15))
342            }
343
344            // ── product ───────────────────────────────────────
345            "product" | "product_name" | "productname" | "item" | "item_name" => {
346                let adj = self.pick(ADJECTIVES);
347                let noun = self.pick(PRODUCT_NOUNS);
348                Some(format!("{adj} {noun}"))
349            }
350            "brand" => Some(self.pick(COMPANIES).to_string()),
351            "sku" | "product_code" | "productcode" | "code" | "barcode" => {
352                let prefix: String = (0..3)
353                    .map(|_| self.rng.gen_range(b'A'..=b'Z') as char)
354                    .collect();
355                let num: u32 = self.rng.gen_range(10000..99999);
356                Some(format!("{prefix}-{num}"))
357            }
358            "category" | "genre" | "type" | "kind" | "group" => {
359                Some(self.pick(CATEGORIES).to_string())
360            }
361            "tag" | "label" => Some(self.pick(TAGS).to_string()),
362
363            // ── color ─────────────────────────────────────────
364            "color" | "colour" => Some(self.pick(COLORS).to_string()),
365            "hex_color" | "hexcolor" | "color_hex" => Some(format!(
366                "#{:02x}{:02x}{:02x}",
367                self.rng.gen_range(0u8..=255),
368                self.rng.gen_range(0u8..=255),
369                self.rng.gen_range(0u8..=255),
370            )),
371
372            // ── misc ──────────────────────────────────────────
373            "currency" | "currency_code" => Some(self.pick(CURRENCIES).to_string()),
374            "locale" | "lang" | "language" => Some(self.pick(LOCALES).to_string()),
375            "timezone" | "tz" | "time_zone" => Some(self.pick(TIMEZONES).to_string()),
376            "mime" | "mime_type" | "mimetype" | "content_type" | "contenttype" => {
377                Some(self.pick(MIME_TYPES).to_string())
378            }
379            "file_name" | "filename" => {
380                let word = self.pick(WORDS).to_lowercase();
381                let ext = self.pick(FILE_EXTENSIONS);
382                Some(format!("{word}.{ext}"))
383            }
384            "extension" | "ext" | "file_ext" | "file_extension" => {
385                Some(self.pick(FILE_EXTENSIONS).to_string())
386            }
387            "version" | "semver" => {
388                let major = self.rng.gen_range(0u8..10);
389                let minor = self.rng.gen_range(0u8..30);
390                let patch = self.rng.gen_range(0u16..100);
391                Some(format!("{major}.{minor}.{patch}"))
392            }
393            "credit_card" | "creditcard" | "card_number" | "cardnumber" | "cc" => {
394                Some(self.gen_credit_card())
395            }
396            "isbn" => Some(self.gen_isbn()),
397            "ssn" => {
398                let a: u16 = self.rng.gen_range(100..999);
399                let b: u8 = self.rng.gen_range(10..99);
400                let c: u16 = self.rng.gen_range(1000..9999);
401                Some(format!("{a}-{b}-{c}"))
402            }
403
404            _ => None,
405        };
406
407        // Check length constraints
408        if let Some(mut s) = result {
409            let min = obj.get("minLength").and_then(Value::as_u64).unwrap_or(0) as usize;
410            let max = obj
411                .get("maxLength")
412                .and_then(Value::as_u64)
413                .map(|v| v as usize);
414
415            // Pad if too short
416            while s.len() < min {
417                s.push('x');
418            }
419            // Truncate if too long
420            if let Some(mx) = max {
421                if s.len() > mx {
422                    s.truncate(mx);
423                }
424            }
425            Some(Value::String(s))
426        } else {
427            None
428        }
429    }
430
431    /// Generate a readable, word-based string that respects `minLength`/`maxLength`.
432    fn gen_readable_string(&mut self, obj: &Map<String, Value>) -> Value {
433        let min_len = obj.get("minLength").and_then(Value::as_u64).unwrap_or(1) as usize;
434        let max_len = obj
435            .get("maxLength")
436            .and_then(Value::as_u64)
437            .unwrap_or(min_len.max(1) as u64 + 30) as usize;
438        let max_len = max_len.max(min_len);
439
440        // Build word-by-word up to the target range
441        let target = if min_len == max_len {
442            min_len
443        } else {
444            self.rng.gen_range(min_len..=max_len)
445        };
446
447        let mut s = String::new();
448        let capitalize_first = true;
449
450        loop {
451            if s.len() >= target {
452                break;
453            }
454            let word = self.pick(WORDS);
455            if !s.is_empty() {
456                // Check if adding " word" would exceed max
457                if s.len() + 1 + word.len() > max_len {
458                    // Try to fill remaining with a short word or chars
459                    let remaining = max_len - s.len();
460                    if remaining > 1 {
461                        s.push(' ');
462                        let filler: String = WORDS
463                            .iter()
464                            .filter(|w| w.len() < remaining)
465                            .take(1)
466                            .map(|w| w.to_string())
467                            .next()
468                            .unwrap_or_else(|| {
469                                (0..remaining - 1)
470                                    .map(|_| {
471                                        LOWER_ALPHA[self.rng.gen_range(0..LOWER_ALPHA.len())]
472                                            as char
473                                    })
474                                    .collect()
475                            });
476                        s.push_str(&filler);
477                    }
478                    break;
479                }
480                s.push(' ');
481            }
482            s.push_str(word);
483        }
484
485        // Capitalize first letter
486        if capitalize_first && !s.is_empty() {
487            let mut chars = s.chars();
488            s = chars.next().unwrap().to_uppercase().chain(chars).collect();
489        }
490
491        // Pad if too short
492        while s.len() < min_len {
493            s.push('a');
494        }
495
496        // Truncate if too long
497        if s.len() > max_len {
498            s.truncate(max_len);
499        }
500
501        Value::String(s)
502    }
503
504    // ── format-specific generators ──────────────────────────────────────
505
506    fn gen_string_format(&mut self, fmt: &str, obj: &Map<String, Value>) -> String {
507        match fmt {
508            "email" => self.gen_email(),
509            "uuid" => self.gen_uuid(),
510            "uri" | "url" => self.gen_url(),
511            "ipv4" => self.gen_ipv4(),
512            "ipv6" => self.gen_ipv6(),
513            "hostname" => self.gen_hostname(),
514            "date" | "iso-date" => self.gen_date(),
515            "time" | "iso-time" => self.gen_time(),
516            "date-time" | "iso-datetime" => self.gen_datetime(),
517            "base64" => self.gen_base64(),
518            "cuid2" => self.gen_cuid2(),
519            "ulid" => self.gen_ulid(),
520            "nanoid" => self.gen_nanoid(),
521            "emoji" => self.gen_emoji(),
522            "phone" => self.gen_phone(),
523            "credit-card" => self.gen_credit_card(),
524            "mac-address" | "mac" => self.gen_mac_address(),
525            "color" | "hex-color" => self.gen_hex_color(),
526            "semver" => {
527                let major = self.rng.gen_range(0u8..10);
528                let minor = self.rng.gen_range(0u8..30);
529                let patch = self.rng.gen_range(0u16..100);
530                format!("{major}.{minor}.{patch}")
531            }
532            "slug" => self.gen_slug(),
533            _ => {
534                // Unknown — readable string
535                let min = obj.get("minLength").and_then(Value::as_u64).unwrap_or(1) as usize;
536                let max = obj
537                    .get("maxLength")
538                    .and_then(Value::as_u64)
539                    .unwrap_or(min as u64 + 20) as usize;
540                let target = self.rng.gen_range(min..=max.max(min));
541                let mut s = String::new();
542                while s.len() < target {
543                    if !s.is_empty() {
544                        s.push(' ');
545                    }
546                    let idx = self.rng.gen_range(0..WORDS.len());
547                    s.push_str(WORDS[idx]);
548                }
549                s.truncate(max.max(min));
550                while s.len() < min {
551                    s.push('a');
552                }
553                s
554            }
555        }
556    }
557
558    fn gen_email(&mut self) -> String {
559        let first = self.pick(FIRST_NAMES).to_lowercase();
560        let last = self.pick(LAST_NAMES).to_lowercase();
561        let n: u16 = self.rng.gen_range(1..99);
562        let domain = self.pick(EMAIL_DOMAINS);
563        // Vary the pattern
564        match self.rng.gen_range(0..4) {
565            0 => format!("{first}.{last}@{domain}"),
566            1 => format!("{first}{last}{n}@{domain}"),
567            2 => format!("{first}_{last}@{domain}"),
568            _ => format!("{}_{n}@{domain}", &first[..1.min(first.len())]),
569        }
570    }
571
572    fn gen_uuid(&mut self) -> String {
573        let hex = |n: usize, rng: &mut R| -> String {
574            (0..n)
575                .map(|_| HEX[rng.gen_range(0..HEX.len())] as char)
576                .collect()
577        };
578        format!(
579            "{}-{}-4{}-{}{}-{}",
580            hex(8, &mut self.rng),
581            hex(4, &mut self.rng),
582            hex(3, &mut self.rng),
583            HEX_89AB[self.rng.gen_range(0..4)] as char,
584            hex(3, &mut self.rng),
585            hex(12, &mut self.rng),
586        )
587    }
588
589    fn gen_url(&mut self) -> String {
590        let tld = self.pick(TLDS);
591        let word = self.pick(WORDS).to_lowercase();
592        let path_word = self.pick(WORDS).to_lowercase();
593        let scheme = if self.rng.gen_bool(0.9) {
594            "https"
595        } else {
596            "http"
597        };
598        match self.rng.gen_range(0..4) {
599            0 => format!("{scheme}://www.{word}.{tld}/{path_word}"),
600            1 => format!("{scheme}://{word}.{tld}"),
601            2 => {
602                let slug = self.gen_slug();
603                format!("{scheme}://{word}.{tld}/blog/{slug}")
604            }
605            _ => {
606                let id: u32 = self.rng.gen_range(1..99999);
607                format!("{scheme}://{word}.{tld}/item/{id}")
608            }
609        }
610    }
611
612    fn gen_ipv4(&mut self) -> String {
613        let a = self.rng.gen_range(1u8..=254);
614        let b = self.rng.gen_range(0u8..=255);
615        let c = self.rng.gen_range(0u8..=255);
616        let d = self.rng.gen_range(1u8..=254);
617        format!("{a}.{b}.{c}.{d}")
618    }
619
620    fn gen_ipv6(&mut self) -> String {
621        let groups: Vec<String> = (0..8)
622            .map(|_| format!("{:04x}", self.rng.gen_range(0u16..=0xffff)))
623            .collect();
624        groups.join(":")
625    }
626
627    fn gen_hostname(&mut self) -> String {
628        let sub = self.pick(WORDS).to_lowercase();
629        let tld = self.pick(TLDS);
630        let word = self.pick(WORDS).to_lowercase();
631        format!("{sub}.{word}.{tld}")
632    }
633
634    fn gen_date(&mut self) -> String {
635        let y = self.rng.gen_range(2000..=2030);
636        let m = self.rng.gen_range(1..=12);
637        let d = self.rng.gen_range(1..=days_in_month(m));
638        format!("{y:04}-{m:02}-{d:02}")
639    }
640
641    fn gen_time(&mut self) -> String {
642        let h = self.rng.gen_range(0..24);
643        let m = self.rng.gen_range(0..60);
644        let s = self.rng.gen_range(0..60);
645        format!("{h:02}:{m:02}:{s:02}")
646    }
647
648    fn gen_datetime(&mut self) -> String {
649        let date = self.gen_date();
650        let time = self.gen_time();
651        format!("{date}T{time}Z")
652    }
653
654    fn gen_base64(&mut self) -> String {
655        let byte_len = self.rng.gen_range(8..32);
656        let bytes: Vec<u8> = (0..byte_len).map(|_| self.rng.gen()).collect();
657        base64_encode(&bytes)
658    }
659
660    fn gen_cuid2(&mut self) -> String {
661        let first = LOWER_ALPHA[self.rng.gen_range(0..LOWER_ALPHA.len())] as char;
662        let rest: String = (0..23)
663            .map(|_| LOWER_DIGIT[self.rng.gen_range(0..LOWER_DIGIT.len())] as char)
664            .collect();
665        format!("{first}{rest}")
666    }
667
668    fn gen_ulid(&mut self) -> String {
669        (0..26)
670            .map(|_| CROCKFORD[self.rng.gen_range(0..CROCKFORD.len())] as char)
671            .collect()
672    }
673
674    fn gen_nanoid(&mut self) -> String {
675        let len = self.rng.gen_range(10..22);
676        (0..len)
677            .map(|_| NANOID_ALPHA[self.rng.gen_range(0..NANOID_ALPHA.len())] as char)
678            .collect()
679    }
680
681    fn gen_emoji(&mut self) -> String {
682        self.pick(EMOJIS).to_string()
683    }
684
685    fn gen_phone(&mut self) -> String {
686        let area: u16 = self.rng.gen_range(200..999);
687        let prefix: u16 = self.rng.gen_range(200..999);
688        let line: u16 = self.rng.gen_range(1000..9999);
689        match self.rng.gen_range(0..3) {
690            0 => format!("+1 ({area}) {prefix}-{line}"),
691            1 => format!("+44 {area} {prefix} {line}"),
692            _ => format!("+7 {area} {prefix}-{line}"),
693        }
694    }
695
696    fn gen_credit_card(&mut self) -> String {
697        // Luhn-valid 16-digit number (Visa-like starting with 4)
698        let mut digits = vec![4u8];
699        for _ in 1..15 {
700            digits.push(self.rng.gen_range(0..10));
701        }
702        // compute Luhn check digit
703        let check = luhn_check_digit(&digits);
704        digits.push(check);
705        let s: String = digits.iter().map(|d| (b'0' + d) as char).collect();
706        format!("{}-{}-{}-{}", &s[0..4], &s[4..8], &s[8..12], &s[12..16])
707    }
708
709    fn gen_isbn(&mut self) -> String {
710        let mut digits: Vec<u8> = (0..12).map(|_| self.rng.gen_range(0..10)).collect();
711        digits[0] = 9;
712        digits[1] = 7;
713        digits[2] = if self.rng.gen_bool(0.5) { 8 } else { 9 };
714        let sum: u8 = digits
715            .iter()
716            .enumerate()
717            .map(|(i, &d)| if i % 2 == 0 { d } else { d * 3 })
718            .sum();
719        let check = (10 - (sum % 10)) % 10;
720        digits.push(check);
721        let s: String = digits.iter().map(|d| (b'0' + d) as char).collect();
722        format!(
723            "{}-{}-{}-{}-{}",
724            &s[0..3],
725            &s[3..4],
726            &s[4..8],
727            &s[8..12],
728            &s[12..13]
729        )
730    }
731
732    fn gen_mac_address(&mut self) -> String {
733        let octets: Vec<String> = (0..6)
734            .map(|_| format!("{:02X}", self.rng.gen_range(0u8..=255)))
735            .collect();
736        octets.join(":")
737    }
738
739    fn gen_hex_color(&mut self) -> String {
740        format!(
741            "#{:02x}{:02x}{:02x}",
742            self.rng.gen_range(0u8..=255),
743            self.rng.gen_range(0u8..=255),
744            self.rng.gen_range(0u8..=255),
745        )
746    }
747
748    fn gen_user_agent(&mut self) -> String {
749        let browsers = [
750            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
751            "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15",
752            "Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0",
753            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0",
754        ];
755        self.pick(&browsers).to_string()
756    }
757
758    fn gen_slug(&mut self) -> String {
759        let count = self.rng.gen_range(2..5);
760        let words: Vec<String> = (0..count)
761            .map(|_| self.pick(WORDS).to_lowercase())
762            .collect();
763        words.join("-")
764    }
765
766    fn gen_token(&mut self, len: usize) -> String {
767        (0..len)
768            .map(|_| TOKEN_ALPHA[self.rng.gen_range(0..TOKEN_ALPHA.len())] as char)
769            .collect()
770    }
771
772    fn gen_password(&mut self) -> String {
773        let len = self.rng.gen_range(10..18);
774        let mut s: String = (0..len)
775            .map(|_| PASSWORD_ALPHA[self.rng.gen_range(0..PASSWORD_ALPHA.len())] as char)
776            .collect();
777        // Guarantee at least one of each class
778        let positions: Vec<usize> = (0..s.len()).collect();
779        if s.len() >= 4 {
780            let bytes = unsafe { s.as_bytes_mut() };
781            bytes[positions[0]] = b'A' + self.rng.gen_range(0..26);
782            bytes[positions[1]] = b'a' + self.rng.gen_range(0..26);
783            bytes[positions[2]] = b'0' + self.rng.gen_range(0..10);
784            let specials = b"!@#$%&*";
785            bytes[positions[3]] = specials[self.rng.gen_range(0..specials.len())];
786        }
787        s
788    }
789
790    fn gen_street_address(&mut self) -> String {
791        let num: u16 = self.rng.gen_range(1..9999);
792        let street = self.pick(STREET_NAMES);
793        let suffix = self.pick(STREET_SUFFIXES);
794        format!("{num} {street} {suffix}")
795    }
796
797    fn gen_zip(&mut self) -> String {
798        let n: u32 = self.rng.gen_range(10000..99999);
799        format!("{n}")
800    }
801
802    fn gen_sentence_range(&mut self, min_words: usize, max_words: usize) -> String {
803        let count = self.rng.gen_range(min_words..=max_words);
804        let mut words: Vec<String> = (0..count)
805            .map(|_| self.pick(LOREM_WORDS).to_string())
806            .collect();
807        if let Some(first) = words.first_mut() {
808            let mut chars = first.chars();
809            *first = chars.next().unwrap().to_uppercase().chain(chars).collect();
810        }
811        let mut sentence = words.join(" ");
812        sentence.push('.');
813        sentence
814    }
815
816    // ═══════════════════════════════════════════════════════════════════════
817    //  INTEGER
818    // ═══════════════════════════════════════════════════════════════════════
819
820    fn gen_integer(&mut self, obj: &Map<String, Value>, hint: Option<&str>) -> Value {
821        let min = self.num_as_i64(obj, "minimum", "exclusiveMinimum", 1);
822        let max = self.num_as_i64_max(obj, "maximum", "exclusiveMaximum", 1);
823
824        let (min, max) = if min > max { (max, min) } else { (min, max) };
825
826        // Heuristic defaults based on field name
827        let (min, max) = if let Some(h) = hint {
828            self.adjust_int_range_by_hint(h, min, max, obj)
829        } else {
830            (min, max)
831        };
832
833        let mut val = self.rng.gen_range(min..=max);
834
835        // multipleOf
836        if let Some(m) = obj
837            .get("multipleOf")
838            .and_then(|v| v.as_i64().or_else(|| v.as_f64().map(|f| f as i64)))
839        {
840            if m > 0 {
841                val = (val / m) * m;
842                if val < min {
843                    val += m;
844                }
845            }
846        }
847
848        json!(val)
849    }
850
851    fn adjust_int_range_by_hint(
852        &self,
853        hint: &str,
854        schema_min: i64,
855        schema_max: i64,
856        obj: &Map<String, Value>,
857    ) -> (i64, i64) {
858        let h = hint.to_ascii_lowercase();
859        let has_min = obj.contains_key("minimum") || obj.contains_key("exclusiveMinimum");
860        let has_max = obj.contains_key("maximum") || obj.contains_key("exclusiveMaximum");
861
862        if has_min && has_max {
863            return (schema_min, schema_max);
864        }
865
866        match h.as_str() {
867            "age" if !has_max => (schema_min, schema_max.min(99)),
868            "port" if !has_max => (schema_min.max(1024), schema_max.min(65535)),
869            "year" if !has_min => (2000.max(schema_min), schema_max.min(2030)),
870            "quantity" | "qty" | "count" if !has_max => (schema_min, schema_max.min(100)),
871            "rating" | "score" if !has_max => (schema_min, schema_max.min(10)),
872            _ => (schema_min, schema_max),
873        }
874    }
875
876    fn num_as_i64(&self, obj: &Map<String, Value>, key: &str, ex_key: &str, offset: i64) -> i64 {
877        if let Some(v) = obj
878            .get(key)
879            .and_then(|v| v.as_i64().or_else(|| v.as_f64().map(|f| f as i64)))
880        {
881            return v;
882        }
883        if let Some(v) = obj
884            .get(ex_key)
885            .and_then(|v| v.as_i64().or_else(|| v.as_f64().map(|f| f as i64)))
886        {
887            return v + offset;
888        }
889        0
890    }
891
892    fn num_as_i64_max(
893        &self,
894        obj: &Map<String, Value>,
895        key: &str,
896        ex_key: &str,
897        offset: i64,
898    ) -> i64 {
899        if let Some(v) = obj
900            .get(key)
901            .and_then(|v| v.as_i64().or_else(|| v.as_f64().map(|f| f as i64)))
902        {
903            return v;
904        }
905        if let Some(v) = obj
906            .get(ex_key)
907            .and_then(|v| v.as_i64().or_else(|| v.as_f64().map(|f| f as i64)))
908        {
909            return v - offset;
910        }
911        1000
912    }
913
914    // ═══════════════════════════════════════════════════════════════════════
915    //  NUMBER (float)
916    // ═══════════════════════════════════════════════════════════════════════
917
918    fn gen_number(&mut self, obj: &Map<String, Value>, hint: Option<&str>) -> Value {
919        if obj.get("type").and_then(Value::as_str) == Some("integer") {
920            return self.gen_integer(obj, hint);
921        }
922
923        // Heuristic by hint — override range for known fields
924        if let Some(h) = hint {
925            let has_min = obj.contains_key("minimum") || obj.contains_key("exclusiveMinimum");
926            let has_max = obj.contains_key("maximum") || obj.contains_key("exclusiveMaximum");
927            if !has_min && !has_max {
928                if let Some(val) = self.gen_number_by_hint(h) {
929                    return json!(val);
930                }
931            }
932        }
933
934        let min = obj
935            .get("minimum")
936            .and_then(Value::as_f64)
937            .or_else(|| {
938                obj.get("exclusiveMinimum")
939                    .and_then(Value::as_f64)
940                    .map(|v| v + 0.01)
941            })
942            .unwrap_or(0.0);
943        let max = obj
944            .get("maximum")
945            .and_then(Value::as_f64)
946            .or_else(|| {
947                obj.get("exclusiveMaximum")
948                    .and_then(Value::as_f64)
949                    .map(|v| v - 0.01)
950            })
951            .unwrap_or(min + 1000.0);
952
953        let max = if max < min { min + 1.0 } else { max };
954
955        let val = min + self.rng.gen::<f64>() * (max - min);
956
957        // Round to 2 decimal places for nicer output
958        let val = (val * 100.0).round() / 100.0;
959
960        json!(val)
961    }
962
963    /// Generate a realistic float based on field name when no constraints given.
964    fn gen_number_by_hint(&mut self, hint: &str) -> Option<f64> {
965        let h = hint.to_ascii_lowercase();
966        match h.as_str() {
967            "latitude" | "lat" => {
968                let v: f64 = self.rng.gen_range(-90.0..=90.0);
969                Some((v * 1_000_000.0).round() / 1_000_000.0)
970            }
971            "longitude" | "lng" | "lon" => {
972                let v: f64 = self.rng.gen_range(-180.0..=180.0);
973                Some((v * 1_000_000.0).round() / 1_000_000.0)
974            }
975            "altitude" | "elevation" | "alt" => {
976                let v: f64 = self.rng.gen_range(0.0..=8848.0);
977                Some((v * 100.0).round() / 100.0)
978            }
979            "price" | "cost" | "amount" | "total" | "subtotal" | "fee" | "balance" | "payment" => {
980                let v: f64 = self.rng.gen_range(0.01..=9999.99);
981                Some((v * 100.0).round() / 100.0)
982            }
983            "tax" | "discount" | "vat" => {
984                let v: f64 = self.rng.gen_range(0.0..=30.0);
985                Some((v * 100.0).round() / 100.0)
986            }
987            "rating" | "score" => {
988                let v: f64 = self.rng.gen_range(1.0..=5.0);
989                Some((v * 10.0).round() / 10.0)
990            }
991            "weight" | "mass" => {
992                let v: f64 = self.rng.gen_range(0.1..=1000.0);
993                Some((v * 100.0).round() / 100.0)
994            }
995            "temperature" | "temp" => {
996                let v: f64 = self.rng.gen_range(-40.0..=50.0);
997                Some((v * 10.0).round() / 10.0)
998            }
999            "percentage" | "percent" | "progress" => {
1000                let v: f64 = self.rng.gen_range(0.0..=100.0);
1001                Some((v * 100.0).round() / 100.0)
1002            }
1003            "speed" | "velocity" => {
1004                let v: f64 = self.rng.gen_range(0.0..=300.0);
1005                Some((v * 100.0).round() / 100.0)
1006            }
1007            "distance" | "radius" => {
1008                let v: f64 = self.rng.gen_range(0.1..=10000.0);
1009                Some((v * 100.0).round() / 100.0)
1010            }
1011            "area" => {
1012                let v: f64 = self.rng.gen_range(1.0..=100000.0);
1013                Some((v * 100.0).round() / 100.0)
1014            }
1015            _ => None,
1016        }
1017    }
1018
1019    // ═══════════════════════════════════════════════════════════════════════
1020    //  BOOLEAN
1021    // ═══════════════════════════════════════════════════════════════════════
1022
1023    fn gen_boolean(&mut self) -> Value {
1024        Value::Bool(self.rng.gen())
1025    }
1026
1027    // ═══════════════════════════════════════════════════════════════════════
1028    //  ARRAY
1029    // ═══════════════════════════════════════════════════════════════════════
1030
1031    fn gen_array(&mut self, obj: &Map<String, Value>, _hint: Option<&str>) -> Value {
1032        let min_items = obj.get("minItems").and_then(Value::as_u64).unwrap_or(0) as usize;
1033        let max_items = obj
1034            .get("maxItems")
1035            .and_then(Value::as_u64)
1036            .unwrap_or(min_items.max(1) as u64 + 3) as usize;
1037        let max_items = max_items.max(min_items);
1038
1039        let count = if min_items == max_items {
1040            min_items
1041        } else {
1042            self.rng.gen_range(min_items..=max_items)
1043        };
1044
1045        // Tuple-style (prefixItems)
1046        if let Some(prefix) = obj.get("prefixItems").and_then(Value::as_array) {
1047            let mut arr: Vec<Value> = prefix.iter().map(|s| self.value(s)).collect();
1048            if count > arr.len() {
1049                if let Some(items) = obj.get("items") {
1050                    for _ in arr.len()..count {
1051                        arr.push(self.value(items));
1052                    }
1053                }
1054            }
1055            return Value::Array(arr);
1056        }
1057
1058        // uniqueItems
1059        let unique = obj
1060            .get("uniqueItems")
1061            .and_then(Value::as_bool)
1062            .unwrap_or(false);
1063
1064        let items_schema = obj
1065            .get("items")
1066            .cloned()
1067            .unwrap_or(json!({"type": "string"}));
1068
1069        if unique {
1070            let mut arr = Vec::new();
1071            let mut attempts = 0;
1072            while arr.len() < count && attempts < count * 10 {
1073                let v = self.value(&items_schema);
1074                if !arr.contains(&v) {
1075                    arr.push(v);
1076                }
1077                attempts += 1;
1078            }
1079            Value::Array(arr)
1080        } else {
1081            let arr: Vec<Value> = (0..count).map(|_| self.value(&items_schema)).collect();
1082            Value::Array(arr)
1083        }
1084    }
1085
1086    // ═══════════════════════════════════════════════════════════════════════
1087    //  OBJECT
1088    // ═══════════════════════════════════════════════════════════════════════
1089
1090    fn gen_object(&mut self, obj: &Map<String, Value>, hint: Option<&str>) -> Value {
1091        let has_properties = obj
1092            .get("properties")
1093            .and_then(Value::as_object)
1094            .is_some_and(|p| !p.is_empty());
1095        let has_required = obj
1096            .get("required")
1097            .and_then(Value::as_array)
1098            .is_some_and(|r| !r.is_empty());
1099
1100        // If the object schema has no properties/required, try to generate a
1101        // template based on the field-name hint.
1102        if !has_properties && !has_required {
1103            if let Some(h) = hint {
1104                if let Some(tmpl) = self.gen_object_template(h) {
1105                    return tmpl;
1106                }
1107            }
1108        }
1109
1110        let mut result = Map::new();
1111
1112        let required: Vec<String> = obj
1113            .get("required")
1114            .and_then(Value::as_array)
1115            .map(|arr| {
1116                arr.iter()
1117                    .filter_map(Value::as_str)
1118                    .map(String::from)
1119                    .collect()
1120            })
1121            .unwrap_or_default();
1122
1123        if let Some(props) = obj.get("properties").and_then(Value::as_object) {
1124            for (key, prop_schema) in props {
1125                if required.contains(key) || self.rng.gen_bool(0.7) {
1126                    result.insert(key.clone(), self.value_with_hint(prop_schema, Some(key)));
1127                }
1128            }
1129        }
1130
1131        // additionalProperties with a schema
1132        if let Some(additional) = obj.get("additionalProperties") {
1133            if additional.is_object() {
1134                let extra_count = self.rng.gen_range(0..3usize);
1135                for _ in 0..extra_count {
1136                    let key = format!("{}_{}", self.pick(WORDS), self.next_id());
1137                    result.insert(key, self.value(additional));
1138                }
1139            }
1140        }
1141
1142        Value::Object(result)
1143    }
1144
1145    /// Generate a pre-built object based on a field-name hint when the schema
1146    /// declares `{"type":"object"}` without any `properties`.
1147    fn gen_object_template(&mut self, hint: &str) -> Option<Value> {
1148        let h = hint.to_ascii_lowercase();
1149        match h.as_str() {
1150            // ── address ───────────────────────────────────────────────
1151            "address" | "billing_address" | "billingaddress" | "shipping_address"
1152            | "shippingaddress" | "home_address" | "work_address" | "mailing_address" => {
1153                Some(self.template_address())
1154            }
1155
1156            // ── location / geo ────────────────────────────────────────
1157            "location" | "geo" | "geolocation" | "coordinates" | "coords" | "position" | "gps" => {
1158                Some(self.template_geo())
1159            }
1160
1161            "place" | "venue" | "point_of_interest" | "poi" => Some(self.template_place()),
1162
1163            // ── person ────────────────────────────────────────────────
1164            "person" | "user" | "author" | "owner" | "creator" | "contact" | "sender"
1165            | "recipient" | "assignee" | "reviewer" | "member" | "employee" | "customer"
1166            | "client" | "patient" => Some(self.template_person()),
1167            "profile" | "user_profile" | "account" => Some(self.template_profile()),
1168
1169            // ── company / org ─────────────────────────────────────────
1170            "company" | "organization" | "organisation" | "employer" | "business" => {
1171                Some(self.template_company())
1172            }
1173
1174            // ── product ───────────────────────────────────────────────
1175            "product" | "item" | "goods" | "merchandise" => Some(self.template_product()),
1176
1177            // ── money / payment ───────────────────────────────────────
1178            "price" | "money" | "amount" | "cost" | "payment" | "transaction" => {
1179                Some(self.template_money())
1180            }
1181
1182            // ── date range / period ───────────────────────────────────
1183            "period" | "date_range" | "daterange" | "time_range" | "timerange" | "duration"
1184            | "schedule" | "availability" => Some(self.template_date_range()),
1185
1186            // ── config / settings / metadata ──────────────────────────
1187            "config" | "configuration" | "settings" | "options" | "preferences" => {
1188                Some(self.template_config())
1189            }
1190            "metadata" | "meta" | "extra" | "attributes" | "props" | "info" => {
1191                Some(self.template_metadata())
1192            }
1193
1194            // ── image / media ─────────────────────────────────────────
1195            "image" | "photo" | "picture" | "avatar" | "thumbnail" | "media" => {
1196                Some(self.template_image())
1197            }
1198
1199            // ── dimensions / size ─────────────────────────────────────
1200            "dimensions" | "size" | "resolution" => Some(self.template_dimensions()),
1201
1202            _ => None,
1203        }
1204    }
1205
1206    // ── template builders ─────────────────────────────────────────────────
1207
1208    fn template_address(&mut self) -> Value {
1209        let num: u16 = self.rng.gen_range(1..9999);
1210        let street = self.pick(STREET_NAMES);
1211        let suffix = self.pick(STREET_SUFFIXES);
1212        let city = self.pick(CITIES);
1213        let state = self.pick(STATES);
1214        let country = self.pick(COUNTRIES);
1215        let zip: u32 = self.rng.gen_range(10000..99999);
1216        let lat: f64 = self.rng.gen_range(-90.0..=90.0);
1217        let lng: f64 = self.rng.gen_range(-180.0..=180.0);
1218
1219        json!({
1220            "street": format!("{num} {street} {suffix}"),
1221            "city": city,
1222            "state": state,
1223            "country": country,
1224            "zip": format!("{zip}"),
1225            "latitude": (lat * 1_000_000.0).round() / 1_000_000.0,
1226            "longitude": (lng * 1_000_000.0).round() / 1_000_000.0,
1227        })
1228    }
1229
1230    fn template_geo(&mut self) -> Value {
1231        let lat: f64 = self.rng.gen_range(-90.0..=90.0);
1232        let lng: f64 = self.rng.gen_range(-180.0..=180.0);
1233        let alt: f64 = self.rng.gen_range(0.0..=8848.0);
1234        let accuracy: f64 = self.rng.gen_range(1.0..=100.0);
1235
1236        json!({
1237            "latitude": (lat * 1_000_000.0).round() / 1_000_000.0,
1238            "longitude": (lng * 1_000_000.0).round() / 1_000_000.0,
1239            "altitude": (alt * 100.0).round() / 100.0,
1240            "accuracy": (accuracy * 100.0).round() / 100.0,
1241        })
1242    }
1243
1244    fn template_place(&mut self) -> Value {
1245        let city = self.pick(CITIES);
1246        let country = self.pick(COUNTRIES);
1247        let lat: f64 = self.rng.gen_range(-90.0..=90.0);
1248        let lng: f64 = self.rng.gen_range(-180.0..=180.0);
1249        let category = self.pick(PLACE_CATEGORIES);
1250        let adj = self.pick(ADJECTIVES);
1251        let noun = self.pick(PLACE_NOUNS);
1252
1253        json!({
1254            "name": format!("{adj} {noun}"),
1255            "category": category,
1256            "city": city,
1257            "country": country,
1258            "latitude": (lat * 1_000_000.0).round() / 1_000_000.0,
1259            "longitude": (lng * 1_000_000.0).round() / 1_000_000.0,
1260        })
1261    }
1262
1263    fn template_person(&mut self) -> Value {
1264        let first = self.pick(FIRST_NAMES);
1265        let last = self.pick(LAST_NAMES);
1266        let email = self.gen_email();
1267        let phone = self.gen_phone();
1268
1269        json!({
1270            "first_name": first,
1271            "last_name": last,
1272            "email": email,
1273            "phone": phone,
1274        })
1275    }
1276
1277    fn template_profile(&mut self) -> Value {
1278        let first = self.pick(FIRST_NAMES);
1279        let last = self.pick(LAST_NAMES);
1280        let email = self.gen_email();
1281        let phone = self.gen_phone();
1282        let username = format!("{}{}", first.to_lowercase(), self.rng.gen_range(1u16..999));
1283        let job = self.pick(JOB_TITLES);
1284        let company = self.pick(COMPANIES);
1285        let city = self.pick(CITIES);
1286        let bio = self.gen_sentence_range(8, 16);
1287        let avatar_id: u32 = self.rng.gen_range(1..999);
1288
1289        json!({
1290            "username": username,
1291            "first_name": first,
1292            "last_name": last,
1293            "email": email,
1294            "phone": phone,
1295            "job_title": job,
1296            "company": company,
1297            "city": city,
1298            "bio": bio,
1299            "avatar_url": format!("https://i.pravatar.cc/300?u={avatar_id}"),
1300        })
1301    }
1302
1303    fn template_company(&mut self) -> Value {
1304        let name = self.pick(COMPANIES);
1305        let department = self.pick(DEPARTMENTS);
1306        let industry = self.pick(INDUSTRIES);
1307        let employees: u32 = self.rng.gen_range(10..50000);
1308        let founded: u16 = self.rng.gen_range(1950..2024);
1309        let city = self.pick(CITIES);
1310        let country = self.pick(COUNTRIES);
1311        let website = self.gen_url();
1312
1313        json!({
1314            "name": name,
1315            "industry": industry,
1316            "department": department,
1317            "employees": employees,
1318            "founded": founded,
1319            "city": city,
1320            "country": country,
1321            "website": website,
1322        })
1323    }
1324
1325    fn template_product(&mut self) -> Value {
1326        let adj = self.pick(ADJECTIVES);
1327        let noun = self.pick(PRODUCT_NOUNS);
1328        let price: f64 = self.rng.gen_range(0.99..9999.99);
1329        let category = self.pick(CATEGORIES);
1330        let sku_prefix: String = (0..3)
1331            .map(|_| self.rng.gen_range(b'A'..=b'Z') as char)
1332            .collect();
1333        let sku_num: u32 = self.rng.gen_range(10000..99999);
1334        let rating: f64 = self.rng.gen_range(1.0..5.0);
1335
1336        json!({
1337            "name": format!("{adj} {noun}"),
1338            "sku": format!("{sku_prefix}-{sku_num}"),
1339            "price": (price * 100.0).round() / 100.0,
1340            "currency": self.pick(CURRENCIES),
1341            "category": category,
1342            "rating": (rating * 10.0).round() / 10.0,
1343            "in_stock": self.rng.gen_bool(0.8),
1344        })
1345    }
1346
1347    fn template_money(&mut self) -> Value {
1348        let amount: f64 = self.rng.gen_range(0.01..99999.99);
1349        let currency = self.pick(CURRENCIES);
1350
1351        json!({
1352            "amount": (amount * 100.0).round() / 100.0,
1353            "currency": currency,
1354        })
1355    }
1356
1357    fn template_date_range(&mut self) -> Value {
1358        let start = self.gen_datetime();
1359        let end = self.gen_datetime();
1360
1361        json!({
1362            "start": start,
1363            "end": end,
1364        })
1365    }
1366
1367    fn template_config(&mut self) -> Value {
1368        let env = self.pick(&["development", "staging", "production", "test"]);
1369        let port: u16 = self.rng.gen_range(3000..9999);
1370        let host = self.gen_hostname();
1371
1372        json!({
1373            "environment": env,
1374            "host": host,
1375            "port": port,
1376            "debug": self.rng.gen_bool(0.3),
1377            "log_level": self.pick(&["debug", "info", "warn", "error"]),
1378        })
1379    }
1380
1381    fn template_metadata(&mut self) -> Value {
1382        let created = self.gen_datetime();
1383        let updated = self.gen_datetime();
1384        let version = format!(
1385            "{}.{}.{}",
1386            self.rng.gen_range(0u8..10),
1387            self.rng.gen_range(0u8..30),
1388            self.rng.gen_range(0u16..100)
1389        );
1390
1391        json!({
1392            "created_at": created,
1393            "updated_at": updated,
1394            "version": version,
1395            "source": self.pick(&["api", "web", "mobile", "import", "sync"]),
1396        })
1397    }
1398
1399    fn template_image(&mut self) -> Value {
1400        let w: u16 = self.rng.gen_range(100..4000);
1401        let h: u16 = self.rng.gen_range(100..4000);
1402        let ext = self.pick(&["jpg", "png", "webp", "gif"]);
1403        let id: u32 = self.rng.gen_range(1..99999);
1404
1405        json!({
1406            "url": format!("https://picsum.photos/{w}/{h}?random={id}"),
1407            "width": w,
1408            "height": h,
1409            "format": ext,
1410            "size_bytes": self.rng.gen_range(10_000u32..10_000_000),
1411        })
1412    }
1413
1414    fn template_dimensions(&mut self) -> Value {
1415        let w: f64 = self.rng.gen_range(1.0..1000.0);
1416        let h: f64 = self.rng.gen_range(1.0..1000.0);
1417        let d: f64 = self.rng.gen_range(1.0..500.0);
1418        let unit = self.pick(&["mm", "cm", "in", "px", "m"]);
1419
1420        json!({
1421            "width": (w * 100.0).round() / 100.0,
1422            "height": (h * 100.0).round() / 100.0,
1423            "depth": (d * 100.0).round() / 100.0,
1424            "unit": unit,
1425        })
1426    }
1427
1428    // ═══════════════════════════════════════════════════════════════════════
1429    //  allOf merge
1430    // ═══════════════════════════════════════════════════════════════════════
1431
1432    fn merge_all_of(&mut self, schemas: &[Value]) -> Value {
1433        let mut merged = Map::new();
1434        let mut required: Vec<String> = Vec::new();
1435
1436        for s in schemas {
1437            if let Some(obj) = s.as_object() {
1438                if let Some(props) = obj.get("properties").and_then(Value::as_object) {
1439                    for (k, v) in props {
1440                        merged
1441                            .entry("properties")
1442                            .or_insert_with(|| json!({}))
1443                            .as_object_mut()
1444                            .unwrap()
1445                            .insert(k.clone(), v.clone());
1446                    }
1447                }
1448                if let Some(req) = obj.get("required").and_then(Value::as_array) {
1449                    for r in req {
1450                        if let Some(s) = r.as_str() {
1451                            if !required.contains(&s.to_string()) {
1452                                required.push(s.to_string());
1453                            }
1454                        }
1455                    }
1456                }
1457                if let Some(ty) = obj.get("type") {
1458                    merged.insert("type".into(), ty.clone());
1459                }
1460            }
1461        }
1462
1463        if !required.is_empty() {
1464            merged.insert(
1465                "required".into(),
1466                Value::Array(required.into_iter().map(Value::String).collect()),
1467            );
1468        }
1469
1470        self.value(&Value::Object(merged))
1471    }
1472}
1473
1474// ═══════════════════════════════════════════════════════════════════════════
1475//  Helpers
1476// ═══════════════════════════════════════════════════════════════════════════
1477
1478fn days_in_month(m: u32) -> u32 {
1479    match m {
1480        2 => 28,
1481        4 | 6 | 9 | 11 => 30,
1482        _ => 31,
1483    }
1484}
1485
1486fn luhn_check_digit(digits: &[u8]) -> u8 {
1487    let mut sum: u16 = 0;
1488    for (i, &d) in digits.iter().rev().enumerate() {
1489        let mut v = d as u16;
1490        if i % 2 == 0 {
1491            v *= 2;
1492            if v > 9 {
1493                v -= 9;
1494            }
1495        }
1496        sum += v;
1497    }
1498    ((10 - (sum % 10)) % 10) as u8
1499}
1500
1501const LOWER_ALPHA: &[u8] = b"abcdefghijklmnopqrstuvwxyz";
1502const LOWER_DIGIT: &[u8] = b"abcdefghijklmnopqrstuvwxyz0123456789";
1503const HEX: &[u8] = b"0123456789abcdef";
1504const HEX_89AB: &[u8] = b"89ab";
1505const CROCKFORD: &[u8] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
1506const NANOID_ALPHA: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-";
1507const TOKEN_ALPHA: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
1508const PASSWORD_ALPHA: &[u8] =
1509    b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&*";
1510
1511fn base64_encode(data: &[u8]) -> String {
1512    const TABLE: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1513    let mut out = String::with_capacity((data.len() + 2) / 3 * 4);
1514    for chunk in data.chunks(3) {
1515        let b0 = chunk[0] as u32;
1516        let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
1517        let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
1518        let triple = (b0 << 16) | (b1 << 8) | b2;
1519        out.push(TABLE[((triple >> 18) & 0x3F) as usize] as char);
1520        out.push(TABLE[((triple >> 12) & 0x3F) as usize] as char);
1521        if chunk.len() > 1 {
1522            out.push(TABLE[((triple >> 6) & 0x3F) as usize] as char);
1523        } else {
1524            out.push('=');
1525        }
1526        if chunk.len() > 2 {
1527            out.push(TABLE[(triple & 0x3F) as usize] as char);
1528        } else {
1529            out.push('=');
1530        }
1531    }
1532    out
1533}
1534
1535// ═══════════════════════════════════════════════════════════════════════════
1536//  FakeData trait — typed API
1537// ═══════════════════════════════════════════════════════════════════════════
1538
1539/// Trait for types that can generate fake instances of themselves.
1540///
1541/// Implement it with the [`impl_fake!`] macro on any `vld::schema!` struct:
1542///
1543/// ```rust,ignore
1544/// vld_fake::impl_fake!(User);
1545///
1546/// let user = User::fake();
1547/// println!("{}", user.name);
1548/// ```
1549pub trait FakeData: Sized {
1550    /// Generate one random, fully validated instance.
1551    fn fake() -> Self;
1552
1553    /// Generate `count` random instances.
1554    fn fake_many(count: usize) -> Vec<Self>;
1555
1556    /// Generate a reproducible instance from a seed.
1557    fn fake_seeded(seed: u64) -> Self;
1558
1559    /// Try to generate — returns `Err` if the generated value somehow fails
1560    /// validation (should not happen for well-defined schemas).
1561    fn try_fake() -> Result<Self, vld::error::VldError>;
1562}
1563
1564/// Implement [`FakeData`] for a `vld::schema!` struct.
1565///
1566/// The struct must have `json_schema()` (requires `openapi` feature on `vld`)
1567/// and implement `VldParse` (which `schema!` provides automatically).
1568///
1569/// # Example
1570///
1571/// ```rust,ignore
1572/// vld::schema! {
1573///     #[derive(Debug)]
1574///     pub struct User {
1575///         pub name:  String => vld::string().min(2).max(50),
1576///         pub email: String => vld::string().email(),
1577///     }
1578/// }
1579///
1580/// vld_fake::impl_fake!(User);
1581///
1582/// let user = User::fake();
1583/// println!("{}", user.name);   // typed access!
1584///
1585/// let users = User::fake_many(10);
1586/// let same  = User::fake_seeded(42);
1587/// ```
1588#[macro_export]
1589macro_rules! impl_fake {
1590    ($ty:ty) => {
1591        impl $crate::FakeData for $ty {
1592            fn fake() -> Self {
1593                let schema = <$ty>::json_schema();
1594                $crate::fake_parsed::<$ty>(&schema)
1595            }
1596
1597            fn fake_many(count: usize) -> Vec<Self> {
1598                let schema = <$ty>::json_schema();
1599                let mut gen = $crate::FakeGen::new();
1600                (0..count)
1601                    .map(|_| {
1602                        let val = gen.value(&schema);
1603                        <$ty as ::vld::prelude::VldParse>::vld_parse_value(&val)
1604                            .expect("vld_fake: generated value failed validation")
1605                    })
1606                    .collect()
1607            }
1608
1609            fn fake_seeded(seed: u64) -> Self {
1610                let schema = <$ty>::json_schema();
1611                let val = $crate::fake_value_seeded(&schema, seed);
1612                <$ty as ::vld::prelude::VldParse>::vld_parse_value(&val)
1613                    .expect("vld_fake: generated value failed validation")
1614            }
1615
1616            fn try_fake() -> Result<Self, ::vld::error::VldError> {
1617                let schema = <$ty>::json_schema();
1618                $crate::try_fake_parsed::<$ty>(&schema)
1619            }
1620        }
1621    };
1622}
1623
1624// ──────────────────── prelude ────────────────────────────────────────────
1625
1626pub mod prelude {
1627    pub use crate::{
1628        fake_json, fake_many, fake_parsed, fake_value, fake_value_seeded, impl_fake,
1629        try_fake_parsed, FakeData, FakeGen,
1630    };
1631}