rust_rule_engine/rete/
template.rs

1//! Template System (inspired by CLIPS deftemplate)
2//!
3//! Provides type-safe structured facts with schema validation.
4//! Similar to CLIPS deftemplate and Drools declared types.
5
6use crate::errors::{Result, RuleEngineError};
7use crate::rete::facts::{FactValue, TypedFacts};
8use std::collections::HashMap;
9
10/// Field definition in a template
11#[derive(Debug, Clone, PartialEq)]
12pub struct FieldDef {
13    pub name: String,
14    pub field_type: FieldType,
15    pub default_value: Option<FactValue>,
16    pub required: bool,
17}
18
19/// Supported field types
20#[derive(Debug, Clone, PartialEq)]
21pub enum FieldType {
22    String,
23    Integer,
24    Float,
25    Boolean,
26    Array(Box<FieldType>),
27    Any,
28}
29
30impl FieldType {
31    /// Check if a value matches this field type
32    pub fn matches(&self, value: &FactValue) -> bool {
33        match (self, value) {
34            (FieldType::String, FactValue::String(_)) => true,
35            (FieldType::Integer, FactValue::Integer(_)) => true,
36            (FieldType::Float, FactValue::Float(_)) => true,
37            (FieldType::Boolean, FactValue::Boolean(_)) => true,
38            (FieldType::Array(inner), FactValue::Array(arr)) => {
39                // Check if all elements match the inner type
40                arr.iter().all(|v| inner.matches(v))
41            }
42            (FieldType::Any, _) => true,
43            _ => false,
44        }
45    }
46
47    /// Get default value for this type
48    pub fn default_value(&self) -> FactValue {
49        match self {
50            FieldType::String => FactValue::String(String::new()),
51            FieldType::Integer => FactValue::Integer(0),
52            FieldType::Float => FactValue::Float(0.0),
53            FieldType::Boolean => FactValue::Boolean(false),
54            FieldType::Array(_) => FactValue::Array(Vec::new()),
55            FieldType::Any => FactValue::Null,
56        }
57    }
58}
59
60/// Template definition (like CLIPS deftemplate)
61#[derive(Debug, Clone)]
62pub struct Template {
63    pub name: String,
64    pub fields: Vec<FieldDef>,
65    field_map: HashMap<String, usize>,
66}
67
68impl Template {
69    /// Create a new template
70    pub fn new(name: impl Into<String>) -> Self {
71        Self {
72            name: name.into(),
73            fields: Vec::new(),
74            field_map: HashMap::new(),
75        }
76    }
77
78    /// Add a field to the template
79    pub fn add_field(&mut self, field: FieldDef) -> &mut Self {
80        let idx = self.fields.len();
81        self.field_map.insert(field.name.clone(), idx);
82        self.fields.push(field);
83        self
84    }
85
86    /// Validate that facts conform to this template
87    pub fn validate(&self, facts: &TypedFacts) -> Result<()> {
88        // Check required fields
89        for field in &self.fields {
90            let value = facts.get(&field.name);
91
92            if field.required && value.is_none() {
93                return Err(RuleEngineError::EvaluationError {
94                    message: format!(
95                        "Required field '{}' missing in template '{}'",
96                        field.name, self.name
97                    ),
98                });
99            }
100
101            // Check type if value exists
102            if let Some(val) = value {
103                if !field.field_type.matches(val) {
104                    return Err(RuleEngineError::EvaluationError {
105                        message: format!(
106                            "Field '{}' has wrong type. Expected {:?}, got {:?}",
107                            field.name, field.field_type, val
108                        ),
109                    });
110                }
111            }
112        }
113
114        Ok(())
115    }
116
117    /// Create facts from template with default values
118    pub fn create_instance(&self) -> TypedFacts {
119        let mut facts = TypedFacts::new();
120
121        for field in &self.fields {
122            let value = field
123                .default_value
124                .clone()
125                .unwrap_or_else(|| field.field_type.default_value());
126            facts.set(&field.name, value);
127        }
128
129        facts
130    }
131
132    /// Get field definition by name
133    pub fn get_field(&self, name: &str) -> Option<&FieldDef> {
134        self.field_map
135            .get(name)
136            .and_then(|idx| self.fields.get(*idx))
137    }
138}
139
140/// Template builder for fluent API
141pub struct TemplateBuilder {
142    template: Template,
143}
144
145impl TemplateBuilder {
146    /// Start building a template
147    pub fn new(name: impl Into<String>) -> Self {
148        Self {
149            template: Template::new(name),
150        }
151    }
152
153    /// Add a string field
154    pub fn string_field(mut self, name: impl Into<String>) -> Self {
155        self.template.add_field(FieldDef {
156            name: name.into(),
157            field_type: FieldType::String,
158            default_value: None,
159            required: false,
160        });
161        self
162    }
163
164    /// Add a required string field
165    pub fn required_string(mut self, name: impl Into<String>) -> Self {
166        self.template.add_field(FieldDef {
167            name: name.into(),
168            field_type: FieldType::String,
169            default_value: None,
170            required: true,
171        });
172        self
173    }
174
175    /// Add an integer field
176    pub fn integer_field(mut self, name: impl Into<String>) -> Self {
177        self.template.add_field(FieldDef {
178            name: name.into(),
179            field_type: FieldType::Integer,
180            default_value: None,
181            required: false,
182        });
183        self
184    }
185
186    /// Add a float field
187    pub fn float_field(mut self, name: impl Into<String>) -> Self {
188        self.template.add_field(FieldDef {
189            name: name.into(),
190            field_type: FieldType::Float,
191            default_value: None,
192            required: false,
193        });
194        self
195    }
196
197    /// Add a boolean field
198    pub fn boolean_field(mut self, name: impl Into<String>) -> Self {
199        self.template.add_field(FieldDef {
200            name: name.into(),
201            field_type: FieldType::Boolean,
202            default_value: None,
203            required: false,
204        });
205        self
206    }
207
208    /// Add a field with custom default
209    pub fn field_with_default(
210        mut self,
211        name: impl Into<String>,
212        field_type: FieldType,
213        default: FactValue,
214    ) -> Self {
215        self.template.add_field(FieldDef {
216            name: name.into(),
217            field_type,
218            default_value: Some(default),
219            required: false,
220        });
221        self
222    }
223
224    /// Add an array field
225    pub fn array_field(mut self, name: impl Into<String>, element_type: FieldType) -> Self {
226        self.template.add_field(FieldDef {
227            name: name.into(),
228            field_type: FieldType::Array(Box::new(element_type)),
229            default_value: None,
230            required: false,
231        });
232        self
233    }
234
235    /// Add a multislot field (CLIPS-style naming for arrays)
236    ///
237    /// This is an alias for `array_field` that uses CLIPS terminology.
238    /// In CLIPS, multislot fields can hold multiple values of the same type.
239    ///
240    /// # Example
241    ///
242    /// ```rust,ignore
243    /// let template = TemplateBuilder::new("Order")
244    ///     .required_string("order_id")
245    ///     .multislot_field("items", FieldType::String)  // CLIPS style
246    ///     .build();
247    /// ```
248    ///
249    /// Equivalent to:
250    /// ```clips
251    /// (deftemplate order
252    ///   (slot order-id (type STRING))
253    ///   (multislot items (type STRING)))
254    /// ```
255    pub fn multislot_field(self, name: impl Into<String>, element_type: FieldType) -> Self {
256        self.array_field(name, element_type)
257    }
258
259    /// Add a required array field
260    pub fn required_array_field(
261        mut self,
262        name: impl Into<String>,
263        element_type: FieldType,
264    ) -> Self {
265        self.template.add_field(FieldDef {
266            name: name.into(),
267            field_type: FieldType::Array(Box::new(element_type)),
268            default_value: None,
269            required: true,
270        });
271        self
272    }
273
274    /// Add a required multislot field (CLIPS-style)
275    pub fn required_multislot_field(
276        self,
277        name: impl Into<String>,
278        element_type: FieldType,
279    ) -> Self {
280        self.required_array_field(name, element_type)
281    }
282
283    /// Build the template
284    pub fn build(self) -> Template {
285        self.template
286    }
287}
288
289/// Template registry for managing templates
290pub struct TemplateRegistry {
291    templates: HashMap<String, Template>,
292}
293
294impl TemplateRegistry {
295    /// Create a new template registry
296    pub fn new() -> Self {
297        Self {
298            templates: HashMap::new(),
299        }
300    }
301
302    /// Register a template
303    pub fn register(&mut self, template: Template) {
304        self.templates.insert(template.name.clone(), template);
305    }
306
307    /// Get a template by name
308    pub fn get(&self, name: &str) -> Option<&Template> {
309        self.templates.get(name)
310    }
311
312    /// Create an instance from a template
313    pub fn create_instance(&self, template_name: &str) -> Result<TypedFacts> {
314        let template = self
315            .get(template_name)
316            .ok_or_else(|| RuleEngineError::EvaluationError {
317                message: format!("Template '{}' not found", template_name),
318            })?;
319
320        Ok(template.create_instance())
321    }
322
323    /// Validate facts against a template
324    pub fn validate(&self, template_name: &str, facts: &TypedFacts) -> Result<()> {
325        let template = self
326            .get(template_name)
327            .ok_or_else(|| RuleEngineError::EvaluationError {
328                message: format!("Template '{}' not found", template_name),
329            })?;
330
331        template.validate(facts)
332    }
333
334    /// List all registered templates
335    pub fn list_templates(&self) -> Vec<&str> {
336        self.templates.keys().map(|s| s.as_str()).collect()
337    }
338}
339
340impl Default for TemplateRegistry {
341    fn default() -> Self {
342        Self::new()
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349
350    #[test]
351    fn test_template_builder() {
352        let template = TemplateBuilder::new("Person")
353            .required_string("name")
354            .integer_field("age")
355            .boolean_field("is_adult")
356            .build();
357
358        assert_eq!(template.name, "Person");
359        assert_eq!(template.fields.len(), 3);
360        assert!(template.get_field("name").unwrap().required);
361    }
362
363    #[test]
364    fn test_create_instance() {
365        let template = TemplateBuilder::new("Person")
366            .string_field("name")
367            .integer_field("age")
368            .build();
369
370        let instance = template.create_instance();
371        assert_eq!(
372            instance.get("name"),
373            Some(&FactValue::String(String::new()))
374        );
375        assert_eq!(instance.get("age"), Some(&FactValue::Integer(0)));
376    }
377
378    #[test]
379    fn test_validation_success() {
380        let template = TemplateBuilder::new("Person")
381            .required_string("name")
382            .integer_field("age")
383            .build();
384
385        let mut facts = TypedFacts::new();
386        facts.set("name", FactValue::String("Alice".to_string()));
387        facts.set("age", FactValue::Integer(30));
388
389        assert!(template.validate(&facts).is_ok());
390    }
391
392    #[test]
393    fn test_validation_missing_required() {
394        let template = TemplateBuilder::new("Person")
395            .required_string("name")
396            .integer_field("age")
397            .build();
398
399        let mut facts = TypedFacts::new();
400        facts.set("age", FactValue::Integer(30));
401
402        assert!(template.validate(&facts).is_err());
403    }
404
405    #[test]
406    fn test_validation_wrong_type() {
407        let template = TemplateBuilder::new("Person")
408            .string_field("name")
409            .integer_field("age")
410            .build();
411
412        let mut facts = TypedFacts::new();
413        facts.set("name", FactValue::String("Alice".to_string()));
414        facts.set("age", FactValue::String("thirty".to_string())); // Wrong type!
415
416        assert!(template.validate(&facts).is_err());
417    }
418
419    #[test]
420    fn test_template_registry() {
421        let mut registry = TemplateRegistry::new();
422
423        let template = TemplateBuilder::new("Order")
424            .required_string("order_id")
425            .float_field("amount")
426            .build();
427
428        registry.register(template);
429
430        assert!(registry.get("Order").is_some());
431        assert!(registry.create_instance("Order").is_ok());
432        assert_eq!(registry.list_templates(), vec!["Order"]);
433    }
434
435    #[test]
436    fn test_array_field() {
437        let template = TemplateBuilder::new("ShoppingCart")
438            .array_field("items", FieldType::String)
439            .build();
440
441        let mut facts = TypedFacts::new();
442        facts.set(
443            "items",
444            FactValue::Array(vec![
445                FactValue::String("item1".to_string()),
446                FactValue::String("item2".to_string()),
447            ]),
448        );
449
450        assert!(template.validate(&facts).is_ok());
451    }
452
453    #[test]
454    fn test_field_with_default() {
455        let template = TemplateBuilder::new("Config")
456            .field_with_default("timeout", FieldType::Integer, FactValue::Integer(30))
457            .build();
458
459        let instance = template.create_instance();
460        assert_eq!(instance.get("timeout"), Some(&FactValue::Integer(30)));
461    }
462}