Skip to main content

wpl/generator/
rule.rs

1use std::collections::HashMap;
2use std::fmt::{Debug, Display, Formatter};
3use std::net::Ipv4Addr;
4
5use orion_error::{ContextRecord, ErrorOwe, ErrorWith, WithContext};
6use wp_model_core::model::FNameStr;
7
8use crate::parser::error::WplCodeResult;
9
10#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
11pub struct FieldsGenRule {
12    pub items: NamedFieldGF,
13}
14
15impl FieldsGenRule {
16    pub fn load(path: &str) -> WplCodeResult<Self> {
17        let mut ctx = WithContext::want("load gen rule");
18        ctx.record("path", path);
19
20        let content = std::fs::read_to_string(path).owe_conf().with(&ctx)?;
21        let res: Self = toml::from_str(&content).owe_conf().with(&ctx)?;
22        Ok(res)
23    }
24    pub fn new() -> Self {
25        Self {
26            items: NamedFieldGF::default(),
27        }
28    }
29    pub fn add(&mut self, key: &str, value: FieldGenConf) {
30        self.items.insert(key.into(), value);
31    }
32}
33
34impl Default for FieldsGenRule {
35    fn default() -> Self {
36        Self::new()
37    }
38}
39
40#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
41pub struct FieldGenConf {
42    pub scope: Option<GenScopeEnum>,
43    pub gen_type: String,
44    pub gen_fmt: Option<String>,
45}
46
47impl Default for FieldGenConf {
48    fn default() -> Self {
49        Self {
50            scope: Some(GenScopeEnum::Digit(GenScope { beg: 0, end: 100 })),
51            gen_type: "digit".to_string(),
52            gen_fmt: Some("SN-{val}".to_string()),
53        }
54    }
55}
56
57impl FieldGenConf {
58    /// Create a digit generator with a half-open range [beg, end).
59    /// Example: FieldGenConf::digit_of(100, 4096)
60    pub fn digit_of(beg: i64, end: i64) -> Self {
61        Self {
62            scope: Some(GenScopeEnum::Digit(GenScope { beg, end })),
63            gen_type: "digit".to_string(),
64            gen_fmt: None,
65        }
66    }
67
68    /// Create a float generator with a half-open range [beg, end).
69    pub fn float_of(beg: f64, end: f64) -> Self {
70        Self {
71            scope: Some(GenScopeEnum::Float(GenScope { beg, end })),
72            gen_type: "float".to_string(),
73            gen_fmt: None,
74        }
75    }
76
77    /// Create an IPv4 range generator.
78    pub fn ip_of(beg: Ipv4Addr, end: Ipv4Addr) -> Self {
79        Self {
80            scope: Some(GenScopeEnum::Ip(GenScope { beg, end })),
81            gen_type: "ip".to_string(),
82            gen_fmt: None,
83        }
84    }
85
86    /// Create a chars generator that randomly picks from a fixed set of values.
87    pub fn chars_from_list(values: Vec<String>) -> Self {
88        Self {
89            scope: Some(GenScopeEnum::Chars(values)),
90            gen_type: "chars".to_string(),
91            gen_fmt: None,
92        }
93    }
94
95    /// Convenience helper for chars_from_list from &str slices.
96    pub fn chars_from(values: &[&str]) -> Self {
97        Self::chars_from_list(values.iter().map(|s| s.to_string()).collect())
98    }
99
100    /// Attach a render format (e.g., "SN-{val}") to the current generator.
101    pub fn with_fmt(mut self, fmt: impl Into<String>) -> Self {
102        self.gen_fmt = Some(fmt.into());
103        self
104    }
105    pub fn example_1() -> Self {
106        Self {
107            scope: Some(GenScopeEnum::Digit(GenScope { beg: 100, end: 200 })),
108            gen_type: "digit".to_string(),
109            gen_fmt: None,
110        }
111    }
112    pub fn example_2() -> Self {
113        Self {
114            scope: Some(GenScopeEnum::Digit(GenScope { beg: 0, end: 100 })),
115            gen_type: "chars".to_string(),
116            gen_fmt: Some("SN-{val}".to_string()),
117        }
118    }
119    pub fn example_3() -> Self {
120        Self {
121            scope: Some(GenScopeEnum::Ip(GenScope {
122                beg: Ipv4Addr::new(10, 0, 10, 0),
123                end: Ipv4Addr::new(10, 0, 100, 255),
124            })),
125            gen_type: "ip".to_string(),
126            gen_fmt: None,
127        }
128    }
129}
130
131impl Display for FieldGenConf {
132    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
133        write!(f, "{}", self.gen_type)?;
134        if let Some(scope) = &self.scope {
135            write!(f, ":rand{}", scope)?;
136        }
137        if let Some(fmt) = &self.gen_fmt {
138            write!(f, " > {}", fmt)?;
139        }
140        Ok(())
141    }
142}
143
144#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
145pub struct GenScope<T>
146where
147    T: Clone + Debug + PartialEq + Display,
148{
149    pub beg: T,
150    pub end: T,
151}
152
153impl<T> Display for GenScope<T>
154where
155    T: Clone + Debug + PartialEq + Display,
156{
157    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
158        write!(f, "({},{})", self.beg, self.end)
159    }
160}
161
162impl Display for GenScopeEnum {
163    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
164        match self {
165            GenScopeEnum::Ip(scope) => write!(f, "{}", scope),
166            GenScopeEnum::Digit(scope) => write!(f, "{}", scope),
167            GenScopeEnum::Float(scope) => write!(f, "{}", scope),
168            GenScopeEnum::Chars(scope) => write!(f, "{:?}", scope),
169        }
170    }
171}
172
173pub type NamedFieldGF = HashMap<FNameStr, FieldGenConf>;
174
175#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
176pub enum GenScopeEnum {
177    #[serde(rename = "ip")]
178    Ip(GenScope<Ipv4Addr>),
179    #[serde(rename = "digit")]
180    Digit(GenScope<i64>),
181    #[serde(rename = "float")]
182    Float(GenScope<f64>),
183    #[serde(rename = "chars")]
184    Chars(Vec<String>),
185}
186
187/// Builder for NamedFieldGF: provides a friendly, chainable API to assemble field generators.
188#[derive(Default, Debug, Clone)]
189pub struct FieldGenBuilder {
190    items: NamedFieldGF,
191}
192
193impl FieldGenBuilder {
194    /// Create an empty builder.
195    pub fn new() -> Self {
196        Self {
197            items: NamedFieldGF::default(),
198        }
199    }
200
201    /// Insert a raw config for `name`.
202    pub fn insert_conf(mut self, name: impl Into<FNameStr>, conf: FieldGenConf) -> Self {
203        self.items.insert(name.into(), conf);
204        self
205    }
206
207    /// Add a digit field in range [beg, end).
208    pub fn digit(self, name: impl Into<FNameStr>, beg: i64, end: i64) -> Self {
209        self.insert_conf(name, FieldGenConf::digit_of(beg, end))
210    }
211
212    /// Add a float field in range [beg, end).
213    pub fn float(self, name: impl Into<FNameStr>, beg: f64, end: f64) -> Self {
214        self.insert_conf(name, FieldGenConf::float_of(beg, end))
215    }
216
217    /// Add an IPv4 field with [beg, end) range.
218    pub fn ip(self, name: impl Into<FNameStr>, beg: Ipv4Addr, end: Ipv4Addr) -> Self {
219        self.insert_conf(name, FieldGenConf::ip_of(beg, end))
220    }
221
222    /// Add a chars field with no predefined scope (free text generator).
223    pub fn chars(self, name: impl Into<FNameStr>) -> Self {
224        self.insert_conf(
225            name,
226            FieldGenConf {
227                scope: None,
228                gen_type: "chars".to_string(),
229                gen_fmt: None,
230            },
231        )
232    }
233
234    /// Add a chars field that randomly picks from the provided set.
235    pub fn chars_from(self, name: impl Into<FNameStr>, values: &[&str]) -> Self {
236        self.insert_conf(name, FieldGenConf::chars_from(values))
237    }
238
239    /// Attach a format string for an existing field (no-op if the field does not exist).
240    pub fn with_fmt(mut self, name: &str, fmt: impl Into<String>) -> Self {
241        if let Some(conf) = self.items.get_mut(name) {
242            conf.gen_fmt = Some(fmt.into());
243        }
244        self
245    }
246
247    /// Finalize and get the underlying map.
248    pub fn build(self) -> NamedFieldGF {
249        self.items
250    }
251}
252
253impl From<FieldGenBuilder> for NamedFieldGF {
254    fn from(b: FieldGenBuilder) -> Self {
255        b.items
256    }
257}