Skip to main content

seedfaker_core/gen/
enum_.rs

1use crate::ctx::GenContext;
2
3/// Generate a random value from a user-supplied comma-separated list.
4///
5/// Syntax:
6///   `enum:a,b,c`       — uniform random pick
7///   `enum:a=3,b=1`     — weighted: `a` appears 3x more often than `b`
8///
9/// Values must match `[a-zA-Z0-9_-]+`. Weights must be positive integers.
10pub fn gen(ctx: &mut GenContext<'_>, buf: &mut String) {
11    if ctx.modifier.is_empty() {
12        return;
13    }
14    let entries: Vec<&str> = ctx.modifier.split(',').collect();
15    let has_weights = entries.iter().any(|e| e.contains('='));
16
17    if has_weights {
18        let mut values = Vec::with_capacity(entries.len());
19        let mut weights = Vec::with_capacity(entries.len());
20        for entry in &entries {
21            if let Some((val, w_str)) = entry.split_once('=') {
22                let w: u32 = w_str.parse().unwrap_or(1);
23                values.push(val);
24                weights.push(w.max(1));
25            } else {
26                // No weight specified — default to 1
27                values.push(entry);
28                weights.push(1);
29            }
30        }
31        let total: u32 = weights.iter().sum();
32        let roll = ctx.rng.urange(0, total as usize - 1) as u32;
33        let mut acc = 0;
34        for (i, &w) in weights.iter().enumerate() {
35            acc += w;
36            if roll < acc {
37                buf.push_str(values[i]);
38                return;
39            }
40        }
41        if let Some(last) = values.last() {
42            buf.push_str(last);
43        }
44    } else {
45        buf.push_str(entries[ctx.rng.urange(0, entries.len() - 1)]);
46    }
47}
48
49/// Validate enum modifier at parse time. Returns `Err` on invalid syntax.
50pub fn validate_enum(modifier: &str) -> Result<(), String> {
51    if modifier.is_empty() {
52        return Err("enum requires values: enum:a,b,c or enum:yes=3,no=1".into());
53    }
54    let entries: Vec<&str> = modifier.split(',').collect();
55    for entry in &entries {
56        let (val, weight) =
57            if let Some((v, w)) = entry.split_once('=') { (v, Some(w)) } else { (*entry, None) };
58        // Validate value: [a-zA-Z0-9_-]+
59        if val.is_empty() {
60            return Err(format!("empty value in enum: '{modifier}'"));
61        }
62        if !val.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-' || b == b'.') {
63            return Err(format!(
64                "enum value '{val}' contains invalid characters; allowed: a-z, A-Z, 0-9, _, -, ."
65            ));
66        }
67        // Validate weight if present
68        if let Some(w) = weight {
69            if w.parse::<u32>().is_err() {
70                return Err(format!(
71                    "invalid weight '{w}' in enum entry '{entry}'; weight must be a positive integer"
72                ));
73            }
74            if w == "0" {
75                return Err(format!("weight cannot be 0 in enum entry '{entry}'"));
76            }
77        }
78    }
79    Ok(())
80}