reval/ruleset/
builder.rs

1use crate::{
2    error::{Error, Result},
3    function::{UserFunction, UserFunctions},
4    ruleset::{rule::Rule, RuleSet},
5    symbol::Symbols,
6    value::Value,
7};
8
9/// Start building a ruleset
10pub fn ruleset() -> Builder {
11    Builder {
12        rules: Vec::new(),
13        functions: Default::default(),
14        symbols: Default::default(),
15    }
16}
17
18/// Ruleset builder
19pub struct Builder {
20    rules: Vec<Rule>,
21    functions: UserFunctions,
22    symbols: Symbols,
23}
24
25impl Builder {
26    /// Add a rule to the ruleset
27    pub fn with_rule(mut self, rule: Rule) -> Result<Self> {
28        let name = rule.name();
29
30        if self.rules.iter().any(|r| r.name() == name) {
31            return Err(Error::DuplicateRuleName(rule.name));
32        }
33
34        self.rules.push(rule);
35        Ok(self)
36    }
37
38    /// Add multiple rules to the RuleSet
39    pub fn with_rules(mut self, rules: impl IntoIterator<Item = Rule>) -> Result<Self> {
40        for rule in rules {
41            self = self.with_rule(rule)?;
42        }
43        Ok(self)
44    }
45
46    /// Add a user-function to the ruleset
47    pub fn with_function(
48        mut self,
49        function: impl UserFunction + Send + Sync + 'static,
50    ) -> Result<Self> {
51        self.functions.add_function(function)?;
52        Ok(self)
53    }
54
55    /// Add multiple boxed user-functions to the ruleset
56    pub fn with_functions(
57        mut self,
58        functions: impl IntoIterator<Item = Box<dyn UserFunction + Send + Sync + 'static>>,
59    ) -> Result<Self> {
60        for function in functions {
61            self.functions.add_boxed_function(function)?;
62        }
63        Ok(self)
64    }
65
66    pub fn with_symbol(mut self, symbol: impl ToString, value: Value) -> Self {
67        self.symbols.insert(symbol, value);
68        self
69    }
70
71    pub fn with_symbols(mut self, symbols: Symbols) -> Result<Self> {
72        self.symbols.append(symbols.0);
73        Ok(self)
74    }
75
76    /// Finalize the builder and create the RuleSet
77    pub fn build(self) -> RuleSet {
78        RuleSet {
79            rules: self.rules,
80            functions: self.functions,
81            symbols: self.symbols,
82        }
83    }
84}
85
86#[cfg(test)]
87pub mod when_building_ruleset {
88    use std::collections::BTreeMap;
89
90    use super::*;
91    use crate::value::Value;
92
93    /// Test helper that creates an empty rule
94    fn rule(name: &str) -> Rule {
95        Rule::new(name, BTreeMap::new(), Value::None.into())
96    }
97
98    #[test]
99    fn should_add_rule() {
100        ruleset().with_rule(rule("test rule 1")).unwrap();
101    }
102
103    #[test]
104    fn should_add_multiple_rules() {
105        let builder = ruleset().with_rule(rule("test rule 1")).unwrap();
106
107        builder.with_rule(rule("test rule 2")).unwrap();
108    }
109
110    #[test]
111    fn should_not_add_duplicate_rule_name() {
112        // Start a ruleset builder and add one rule
113        let builder = ruleset().with_rule(rule("test rule 1")).unwrap();
114
115        assert!(matches!(
116            builder.with_rule(rule("test rule 1")),
117            Err(Error::DuplicateRuleName(name)) if name == "test rule 1".to_string()
118        ));
119    }
120}