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