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::rete::facts::{FactValue, TypedFacts};
7use crate::errors::{Result, RuleEngineError};
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.default_value.clone()
123                .unwrap_or_else(|| field.field_type.default_value());
124            facts.set(&field.name, value);
125        }
126
127        facts
128    }
129
130    /// Get field definition by name
131    pub fn get_field(&self, name: &str) -> Option<&FieldDef> {
132        self.field_map.get(name).and_then(|idx| self.fields.get(*idx))
133    }
134}
135
136/// Template builder for fluent API
137pub struct TemplateBuilder {
138    template: Template,
139}
140
141impl TemplateBuilder {
142    /// Start building a template
143    pub fn new(name: impl Into<String>) -> Self {
144        Self {
145            template: Template::new(name),
146        }
147    }
148
149    /// Add a string field
150    pub fn string_field(mut self, name: impl Into<String>) -> Self {
151        self.template.add_field(FieldDef {
152            name: name.into(),
153            field_type: FieldType::String,
154            default_value: None,
155            required: false,
156        });
157        self
158    }
159
160    /// Add a required string field
161    pub fn required_string(mut self, name: impl Into<String>) -> Self {
162        self.template.add_field(FieldDef {
163            name: name.into(),
164            field_type: FieldType::String,
165            default_value: None,
166            required: true,
167        });
168        self
169    }
170
171    /// Add an integer field
172    pub fn integer_field(mut self, name: impl Into<String>) -> Self {
173        self.template.add_field(FieldDef {
174            name: name.into(),
175            field_type: FieldType::Integer,
176            default_value: None,
177            required: false,
178        });
179        self
180    }
181
182    /// Add a float field
183    pub fn float_field(mut self, name: impl Into<String>) -> Self {
184        self.template.add_field(FieldDef {
185            name: name.into(),
186            field_type: FieldType::Float,
187            default_value: None,
188            required: false,
189        });
190        self
191    }
192
193    /// Add a boolean field
194    pub fn boolean_field(mut self, name: impl Into<String>) -> Self {
195        self.template.add_field(FieldDef {
196            name: name.into(),
197            field_type: FieldType::Boolean,
198            default_value: None,
199            required: false,
200        });
201        self
202    }
203
204    /// Add a field with custom default
205    pub fn field_with_default(
206        mut self,
207        name: impl Into<String>,
208        field_type: FieldType,
209        default: FactValue,
210    ) -> Self {
211        self.template.add_field(FieldDef {
212            name: name.into(),
213            field_type,
214            default_value: Some(default),
215            required: false,
216        });
217        self
218    }
219
220    /// Add an array field
221    pub fn array_field(mut self, name: impl Into<String>, element_type: FieldType) -> Self {
222        self.template.add_field(FieldDef {
223            name: name.into(),
224            field_type: FieldType::Array(Box::new(element_type)),
225            default_value: None,
226            required: false,
227        });
228        self
229    }
230
231    /// Add a multislot field (CLIPS-style naming for arrays)
232    ///
233    /// This is an alias for `array_field` that uses CLIPS terminology.
234    /// In CLIPS, multislot fields can hold multiple values of the same type.
235    ///
236    /// # Example
237    ///
238    /// ```rust,ignore
239    /// let template = TemplateBuilder::new("Order")
240    ///     .required_string("order_id")
241    ///     .multislot_field("items", FieldType::String)  // CLIPS style
242    ///     .build();
243    /// ```
244    ///
245    /// Equivalent to:
246    /// ```clips
247    /// (deftemplate order
248    ///   (slot order-id (type STRING))
249    ///   (multislot items (type STRING)))
250    /// ```
251    pub fn multislot_field(self, name: impl Into<String>, element_type: FieldType) -> Self {
252        self.array_field(name, element_type)
253    }
254
255    /// Add a required array field
256    pub fn required_array_field(mut self, name: impl Into<String>, element_type: FieldType) -> Self {
257        self.template.add_field(FieldDef {
258            name: name.into(),
259            field_type: FieldType::Array(Box::new(element_type)),
260            default_value: None,
261            required: true,
262        });
263        self
264    }
265
266    /// Add a required multislot field (CLIPS-style)
267    pub fn required_multislot_field(self, name: impl Into<String>, element_type: FieldType) -> Self {
268        self.required_array_field(name, element_type)
269    }
270
271    /// Build the template
272    pub fn build(self) -> Template {
273        self.template
274    }
275}
276
277/// Template registry for managing templates
278pub struct TemplateRegistry {
279    templates: HashMap<String, Template>,
280}
281
282impl TemplateRegistry {
283    /// Create a new template registry
284    pub fn new() -> Self {
285        Self {
286            templates: HashMap::new(),
287        }
288    }
289
290    /// Register a template
291    pub fn register(&mut self, template: Template) {
292        self.templates.insert(template.name.clone(), template);
293    }
294
295    /// Get a template by name
296    pub fn get(&self, name: &str) -> Option<&Template> {
297        self.templates.get(name)
298    }
299
300    /// Create an instance from a template
301    pub fn create_instance(&self, template_name: &str) -> Result<TypedFacts> {
302        let template = self.get(template_name).ok_or_else(|| {
303            RuleEngineError::EvaluationError {
304                message: format!("Template '{}' not found", template_name),
305            }
306        })?;
307
308        Ok(template.create_instance())
309    }
310
311    /// Validate facts against a template
312    pub fn validate(&self, template_name: &str, facts: &TypedFacts) -> Result<()> {
313        let template = self.get(template_name).ok_or_else(|| {
314            RuleEngineError::EvaluationError {
315                message: format!("Template '{}' not found", template_name),
316            }
317        })?;
318
319        template.validate(facts)
320    }
321
322    /// List all registered templates
323    pub fn list_templates(&self) -> Vec<&str> {
324        self.templates.keys().map(|s| s.as_str()).collect()
325    }
326}
327
328impl Default for TemplateRegistry {
329    fn default() -> Self {
330        Self::new()
331    }
332}
333
334#[cfg(test)]
335mod tests {
336    use super::*;
337
338    #[test]
339    fn test_template_builder() {
340        let template = TemplateBuilder::new("Person")
341            .required_string("name")
342            .integer_field("age")
343            .boolean_field("is_adult")
344            .build();
345
346        assert_eq!(template.name, "Person");
347        assert_eq!(template.fields.len(), 3);
348        assert!(template.get_field("name").unwrap().required);
349    }
350
351    #[test]
352    fn test_create_instance() {
353        let template = TemplateBuilder::new("Person")
354            .string_field("name")
355            .integer_field("age")
356            .build();
357
358        let instance = template.create_instance();
359        assert_eq!(instance.get("name"), Some(&FactValue::String(String::new())));
360        assert_eq!(instance.get("age"), Some(&FactValue::Integer(0)));
361    }
362
363    #[test]
364    fn test_validation_success() {
365        let template = TemplateBuilder::new("Person")
366            .required_string("name")
367            .integer_field("age")
368            .build();
369
370        let mut facts = TypedFacts::new();
371        facts.set("name", FactValue::String("Alice".to_string()));
372        facts.set("age", FactValue::Integer(30));
373
374        assert!(template.validate(&facts).is_ok());
375    }
376
377    #[test]
378    fn test_validation_missing_required() {
379        let template = TemplateBuilder::new("Person")
380            .required_string("name")
381            .integer_field("age")
382            .build();
383
384        let mut facts = TypedFacts::new();
385        facts.set("age", FactValue::Integer(30));
386
387        assert!(template.validate(&facts).is_err());
388    }
389
390    #[test]
391    fn test_validation_wrong_type() {
392        let template = TemplateBuilder::new("Person")
393            .string_field("name")
394            .integer_field("age")
395            .build();
396
397        let mut facts = TypedFacts::new();
398        facts.set("name", FactValue::String("Alice".to_string()));
399        facts.set("age", FactValue::String("thirty".to_string())); // Wrong type!
400
401        assert!(template.validate(&facts).is_err());
402    }
403
404    #[test]
405    fn test_template_registry() {
406        let mut registry = TemplateRegistry::new();
407
408        let template = TemplateBuilder::new("Order")
409            .required_string("order_id")
410            .float_field("amount")
411            .build();
412
413        registry.register(template);
414
415        assert!(registry.get("Order").is_some());
416        assert!(registry.create_instance("Order").is_ok());
417        assert_eq!(registry.list_templates(), vec!["Order"]);
418    }
419
420    #[test]
421    fn test_array_field() {
422        let template = TemplateBuilder::new("ShoppingCart")
423            .array_field("items", FieldType::String)
424            .build();
425
426        let mut facts = TypedFacts::new();
427        facts.set("items", FactValue::Array(vec![
428            FactValue::String("item1".to_string()),
429            FactValue::String("item2".to_string()),
430        ]));
431
432        assert!(template.validate(&facts).is_ok());
433    }
434
435    #[test]
436    fn test_field_with_default() {
437        let template = TemplateBuilder::new("Config")
438            .field_with_default(
439                "timeout",
440                FieldType::Integer,
441                FactValue::Integer(30),
442            )
443            .build();
444
445        let instance = template.create_instance();
446        assert_eq!(instance.get("timeout"), Some(&FactValue::Integer(30)));
447    }
448}