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