rust_rule_engine/engine/
template.rs

1use crate::engine::rule::Rule;
2use crate::errors::{Result, RuleEngineError};
3use crate::parser::grl::GRLParser;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// Parameter types for rule templates
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub enum ParameterType {
10    /// String parameter type
11    String,
12    /// Numeric parameter type
13    Number,
14    /// Boolean parameter type
15    Boolean,
16    /// Array parameter type
17    Array,
18}
19
20/// A parameter definition for a rule template
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct ParameterDef {
23    /// Parameter name
24    pub name: String,
25    /// Parameter type
26    pub param_type: ParameterType,
27    /// Default value for the parameter
28    pub default_value: Option<String>,
29    /// Human-readable description
30    pub description: Option<String>,
31}
32
33/// Rule template that can generate multiple rules with different parameters
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct RuleTemplate {
36    /// Template name
37    pub name: String,
38    /// Template description
39    pub description: Option<String>,
40    /// List of parameters this template accepts
41    pub parameters: Vec<ParameterDef>,
42    /// Condition template with parameter placeholders
43    pub condition_template: String,
44    /// Action template with parameter placeholders
45    pub action_template: String,
46    /// Rule salience/priority
47    pub salience: Option<i32>,
48}
49
50/// Builder for creating rule template instances
51pub struct TemplateInstance {
52    template: RuleTemplate,
53    rule_name: String,
54    parameter_values: HashMap<String, String>,
55}
56
57/// Manager for rule templates
58pub struct TemplateManager {
59    templates: HashMap<String, RuleTemplate>,
60}
61
62impl RuleTemplate {
63    /// Create a new rule template
64    pub fn new(name: &str) -> Self {
65        Self {
66            name: name.to_string(),
67            description: None,
68            parameters: Vec::new(),
69            condition_template: String::new(),
70            action_template: String::new(),
71            salience: None,
72        }
73    }
74
75    /// Add a parameter to the template
76    pub fn with_parameter(mut self, name: &str, param_type: ParameterType) -> Self {
77        self.parameters.push(ParameterDef {
78            name: name.to_string(),
79            param_type,
80            default_value: None,
81            description: None,
82        });
83        self
84    }
85
86    /// Set the condition template
87    pub fn with_condition(mut self, condition: &str) -> Self {
88        self.condition_template = condition.to_string();
89        self
90    }
91
92    /// Set the action template
93    pub fn with_action(mut self, action: &str) -> Self {
94        self.action_template = action.to_string();
95        self
96    }
97
98    /// Set the salience for generated rules
99    pub fn with_salience(mut self, salience: i32) -> Self {
100        self.salience = Some(salience);
101        self
102    }
103
104    /// Set description
105    pub fn with_description(mut self, description: &str) -> Self {
106        self.description = Some(description.to_string());
107        self
108    }
109
110    /// Create a template instance for generating a specific rule
111    pub fn instantiate(&self, rule_name: &str) -> TemplateInstance {
112        TemplateInstance {
113            template: self.clone(),
114            rule_name: rule_name.to_string(),
115            parameter_values: HashMap::new(),
116        }
117    }
118
119    /// Validate that all required parameters are provided
120    pub fn validate_parameters(&self, params: &HashMap<String, String>) -> Result<()> {
121        for param_def in &self.parameters {
122            if !params.contains_key(&param_def.name) && param_def.default_value.is_none() {
123                return Err(RuleEngineError::ParseError {
124                    message: format!("Missing required parameter: {}", param_def.name),
125                });
126            }
127        }
128        Ok(())
129    }
130
131    /// Replace template placeholders with actual values (public for demo)
132    pub fn substitute_placeholders(&self, text: &str, params: &HashMap<String, String>) -> String {
133        let mut result = text.to_string();
134
135        for (key, value) in params {
136            let placeholder = format!("{{{{{}}}}}", key);
137            result = result.replace(&placeholder, value);
138        }
139
140        // Apply default values for missing parameters
141        for param_def in &self.parameters {
142            if !params.contains_key(&param_def.name) {
143                if let Some(default_value) = &param_def.default_value {
144                    let placeholder = format!("{{{{{}}}}}", param_def.name);
145                    result = result.replace(&placeholder, default_value);
146                }
147            }
148        }
149
150        result
151    }
152}
153
154impl TemplateInstance {
155    /// Set a parameter value
156    pub fn with_param(mut self, name: &str, value: impl ToString) -> Self {
157        self.parameter_values
158            .insert(name.to_string(), value.to_string());
159        self
160    }
161
162    /// Build the actual rule from the template
163    pub fn build(self) -> Result<Rule> {
164        // Validate parameters
165        self.template.validate_parameters(&self.parameter_values)?;
166
167        // Substitute placeholders
168        let condition = self
169            .template
170            .substitute_placeholders(&self.template.condition_template, &self.parameter_values);
171        let action = self
172            .template
173            .substitute_placeholders(&self.template.action_template, &self.parameter_values);
174
175        // Generate GRL rule text
176        let grl_rule = if let Some(salience) = self.template.salience {
177            format!(
178                r#"rule "{}" salience {} {{
179when
180{}
181then
182{};
183}}"#,
184                self.rule_name, salience, condition, action
185            )
186        } else {
187            format!(
188                r#"rule "{}" {{
189when
190{}
191then
192{};
193}}"#,
194                self.rule_name, condition, action
195            )
196        };
197
198        // Parse the generated GRL
199        let rules = GRLParser::parse_rules(&grl_rule)?;
200
201        if rules.is_empty() {
202            return Err(RuleEngineError::ParseError {
203                message: "Failed to generate rule from template".to_string(),
204            });
205        }
206
207        Ok(rules.into_iter().next().unwrap())
208    }
209}
210
211impl TemplateManager {
212    /// Create a new template manager
213    pub fn new() -> Self {
214        Self {
215            templates: HashMap::new(),
216        }
217    }
218
219    /// Register a template
220    pub fn register_template(&mut self, template: RuleTemplate) {
221        self.templates.insert(template.name.clone(), template);
222    }
223
224    /// Get a template by name
225    pub fn get_template(&self, name: &str) -> Option<&RuleTemplate> {
226        self.templates.get(name)
227    }
228
229    /// Generate multiple rules from a template with different parameter sets
230    pub fn generate_rules(
231        &self,
232        template_name: &str,
233        rule_configs: Vec<(String, HashMap<String, String>)>,
234    ) -> Result<Vec<Rule>> {
235        let template =
236            self.get_template(template_name)
237                .ok_or_else(|| RuleEngineError::ParseError {
238                    message: format!("Template not found: {}", template_name),
239                })?;
240
241        let mut rules = Vec::new();
242
243        for (rule_name, params) in rule_configs {
244            let mut instance = template.instantiate(&rule_name);
245            instance.parameter_values = params;
246
247            rules.push(instance.build()?);
248        }
249
250        Ok(rules)
251    }
252
253    /// Load templates from JSON file
254    pub fn load_from_json(&mut self, json_content: &str) -> Result<()> {
255        let templates: Vec<RuleTemplate> =
256            serde_json::from_str(json_content).map_err(|e| RuleEngineError::ParseError {
257                message: format!("Failed to parse template JSON: {}", e),
258            })?;
259
260        for template in templates {
261            self.register_template(template);
262        }
263
264        Ok(())
265    }
266
267    /// Save templates to JSON
268    pub fn save_to_json(&self) -> Result<String> {
269        let templates: Vec<&RuleTemplate> = self.templates.values().collect();
270        serde_json::to_string_pretty(&templates).map_err(|e| RuleEngineError::ParseError {
271            message: format!("Failed to serialize templates: {}", e),
272        })
273    }
274
275    /// List all available templates
276    pub fn list_templates(&self) -> Vec<&str> {
277        self.templates.keys().map(|s| s.as_str()).collect()
278    }
279}
280
281impl Default for TemplateManager {
282    fn default() -> Self {
283        Self::new()
284    }
285}
286
287#[cfg(test)]
288mod tests {
289    use super::*;
290
291    #[test]
292    fn test_template_creation() {
293        let template = RuleTemplate::new("VIPCheck")
294            .with_parameter("country", ParameterType::String)
295            .with_parameter("threshold", ParameterType::Number)
296            .with_condition(
297                "User.Country == \"{{country}}\" && User.SpendingTotal >= {{threshold}}",
298            )
299            .with_action("User.setIsVIP(true)")
300            .with_salience(10);
301
302        assert_eq!(template.name, "VIPCheck");
303        assert_eq!(template.parameters.len(), 2);
304        assert_eq!(template.salience, Some(10));
305    }
306
307    #[test]
308    fn test_template_instantiation() {
309        let template = RuleTemplate::new("VIPCheck")
310            .with_parameter("country", ParameterType::String)
311            .with_parameter("threshold", ParameterType::Number)
312            .with_condition(
313                "User.Country == \"{{country}}\" && User.SpendingTotal >= {{threshold}}",
314            )
315            .with_action("User.setIsVIP(true)");
316
317        let rule = template
318            .instantiate("VIPCheck_US")
319            .with_param("country", "US")
320            .with_param("threshold", "1000")
321            .build()
322            .unwrap();
323
324        assert_eq!(rule.name, "VIPCheck_US");
325    }
326
327    #[test]
328    fn test_template_manager() {
329        let mut manager = TemplateManager::new();
330
331        let template = RuleTemplate::new("TestTemplate")
332            .with_parameter("value", ParameterType::String)
333            .with_condition("User.Field == \"{{value}}\"")
334            .with_action("User.setResult(true)");
335
336        manager.register_template(template);
337
338        assert!(manager.get_template("TestTemplate").is_some());
339        assert_eq!(manager.list_templates().len(), 1);
340    }
341}