rust_rule_engine/plugins/
string_utils.rs

1use crate::engine::plugin::{PluginHealth, PluginMetadata, PluginState, RulePlugin};
2use crate::engine::RustRuleEngine;
3use crate::errors::{Result, RuleEngineError};
4use crate::types::Value;
5
6/// Built-in plugin for string manipulation operations
7pub struct StringUtilsPlugin {
8    metadata: PluginMetadata,
9}
10
11impl StringUtilsPlugin {
12    pub fn new() -> Self {
13        Self {
14            metadata: PluginMetadata {
15                name: "string-utils".to_string(),
16                version: "1.0.0".to_string(),
17                description: "String manipulation utilities".to_string(),
18                author: "Rust Rule Engine Team".to_string(),
19                state: PluginState::Loaded,
20                health: PluginHealth::Healthy,
21                actions: vec![
22                    "ToUpperCase".to_string(),
23                    "ToLowerCase".to_string(),
24                    "StringLength".to_string(),
25                    "StringContains".to_string(),
26                    "StringTrim".to_string(),
27                    "StringReplace".to_string(),
28                    "StringSplit".to_string(),
29                    "StringJoin".to_string(),
30                ],
31                functions: vec![
32                    "concat".to_string(),
33                    "repeat".to_string(),
34                    "substring".to_string(),
35                    "padLeft".to_string(),
36                    "padRight".to_string(),
37                ],
38                dependencies: vec![],
39            },
40        }
41    }
42}
43
44impl RulePlugin for StringUtilsPlugin {
45    fn get_metadata(&self) -> &PluginMetadata {
46        &self.metadata
47    }
48
49    fn register_actions(&self, engine: &mut RustRuleEngine) -> Result<()> {
50        // ToUpperCase - Convert string to uppercase
51        engine.register_action_handler("ToUpperCase", |params, facts| {
52            let input = get_string_param(params, "input", "0")?;
53            let output = get_string_param(params, "output", "1")?;
54
55            if let Some(value) = facts.get(&input) {
56                let text = value_to_string(&value)?;
57                facts.set_nested(&output, Value::String(text.to_uppercase()))?;
58            }
59            Ok(())
60        });
61
62        // ToLowerCase - Convert string to lowercase
63        engine.register_action_handler("ToLowerCase", |params, facts| {
64            let input = get_string_param(params, "input", "0")?;
65            let output = get_string_param(params, "output", "1")?;
66
67            if let Some(value) = facts.get(&input) {
68                let text = value_to_string(&value)?;
69                facts.set_nested(&output, Value::String(text.to_lowercase()))?;
70            }
71            Ok(())
72        });
73
74        // StringLength - Get string length
75        engine.register_action_handler("StringLength", |params, facts| {
76            let input = get_string_param(params, "input", "0")?;
77            let output = get_string_param(params, "output", "1")?;
78
79            if let Some(value) = facts.get(&input) {
80                let text = value_to_string(&value)?;
81                facts.set_nested(&output, Value::Integer(text.len() as i64))?;
82            }
83            Ok(())
84        });
85
86        // StringContains - Check if string contains substring
87        engine.register_action_handler("StringContains", |params, facts| {
88            let input = get_string_param(params, "input", "0")?;
89            let search = get_string_param(params, "search", "1")?;
90            let output = get_string_param(params, "output", "2")?;
91
92            if let Some(value) = facts.get(&input) {
93                let text = value_to_string(&value)?;
94                let search_text = if search.contains('.') {
95                    if let Some(search_value) = facts.get(&search) {
96                        value_to_string(&search_value)?
97                    } else {
98                        search
99                    }
100                } else {
101                    search
102                };
103
104                let contains = text.contains(&search_text);
105                facts.set_nested(&output, Value::Boolean(contains))?;
106            }
107            Ok(())
108        });
109
110        // StringTrim - Trim whitespace
111        engine.register_action_handler("StringTrim", |params, facts| {
112            let input = get_string_param(params, "input", "0")?;
113            let output = get_string_param(params, "output", "1")?;
114
115            if let Some(value) = facts.get(&input) {
116                let text = value_to_string(&value)?;
117                facts.set_nested(&output, Value::String(text.trim().to_string()))?;
118            }
119            Ok(())
120        });
121
122        // StringReplace - Replace substring
123        engine.register_action_handler("StringReplace", |params, facts| {
124            let input = get_string_param(params, "input", "0")?;
125            let from = get_string_param(params, "from", "1")?;
126            let to = get_string_param(params, "to", "2")?;
127            let output = get_string_param(params, "output", "3")?;
128
129            if let Some(value) = facts.get(&input) {
130                let text = value_to_string(&value)?;
131                let result = text.replace(&from, &to);
132                facts.set_nested(&output, Value::String(result))?;
133            }
134            Ok(())
135        });
136
137        Ok(())
138    }
139
140    fn register_functions(&self, engine: &mut RustRuleEngine) -> Result<()> {
141        // concat - Concatenate strings
142        engine.register_function("concat", |args, _facts| {
143            if args.len() < 2 {
144                return Err(RuleEngineError::EvaluationError {
145                    message: "concat requires at least 2 arguments".to_string(),
146                });
147            }
148
149            let mut result = String::new();
150            for arg in args {
151                result.push_str(&value_to_string(arg)?);
152            }
153            Ok(Value::String(result))
154        });
155
156        // repeat - Repeat string n times
157        engine.register_function("repeat", |args, _facts| {
158            if args.len() != 2 {
159                return Err(RuleEngineError::EvaluationError {
160                    message: "repeat requires exactly 2 arguments: text, count".to_string(),
161                });
162            }
163
164            let text = value_to_string(&args[0])?;
165            let count = match &args[1] {
166                Value::Integer(i) => *i as usize,
167                _ => {
168                    return Err(RuleEngineError::EvaluationError {
169                        message: "Second argument must be integer".to_string(),
170                    })
171                }
172            };
173
174            if count > 1000 {
175                return Err(RuleEngineError::EvaluationError {
176                    message: "Repeat count too large (max 1000)".to_string(),
177                });
178            }
179
180            Ok(Value::String(text.repeat(count)))
181        });
182
183        // substring - Get substring
184        engine.register_function("substring", |args, _facts| {
185            if args.len() < 2 || args.len() > 3 {
186                return Err(RuleEngineError::EvaluationError {
187                    message: "substring requires 2-3 arguments: text, start, [length]".to_string(),
188                });
189            }
190
191            let text = value_to_string(&args[0])?;
192            let start = match &args[1] {
193                Value::Integer(i) => *i as usize,
194                _ => {
195                    return Err(RuleEngineError::EvaluationError {
196                        message: "Start position must be integer".to_string(),
197                    })
198                }
199            };
200
201            if start >= text.len() {
202                return Ok(Value::String(String::new()));
203            }
204
205            let result = if args.len() == 3 {
206                let length = match &args[2] {
207                    Value::Integer(i) => *i as usize,
208                    _ => {
209                        return Err(RuleEngineError::EvaluationError {
210                            message: "Length must be integer".to_string(),
211                        })
212                    }
213                };
214                let end = std::cmp::min(start + length, text.len());
215                text[start..end].to_string()
216            } else {
217                text[start..].to_string()
218            };
219
220            Ok(Value::String(result))
221        });
222
223        Ok(())
224    }
225
226    fn unload(&mut self) -> Result<()> {
227        self.metadata.state = PluginState::Unloaded;
228        Ok(())
229    }
230
231    fn health_check(&mut self) -> PluginHealth {
232        match self.metadata.state {
233            PluginState::Loaded => PluginHealth::Healthy,
234            PluginState::Loading => PluginHealth::Warning("Plugin is loading".to_string()),
235            PluginState::Error => PluginHealth::Error("Plugin is in error state".to_string()),
236            PluginState::Unloaded => PluginHealth::Warning("Plugin is unloaded".to_string()),
237        }
238    }
239}
240
241// Helper functions
242fn get_string_param(
243    params: &std::collections::HashMap<String, Value>,
244    name: &str,
245    pos: &str,
246) -> Result<String> {
247    let value = params
248        .get(name)
249        .or_else(|| params.get(pos))
250        .ok_or_else(|| RuleEngineError::ActionError {
251            message: format!("Missing parameter: {}", name),
252        })?;
253
254    match value {
255        Value::String(s) => Ok(s.clone()),
256        _ => Err(RuleEngineError::ActionError {
257            message: format!("Parameter {} must be string", name),
258        }),
259    }
260}
261
262fn value_to_string(value: &Value) -> Result<String> {
263    match value {
264        Value::String(s) => Ok(s.clone()),
265        Value::Integer(i) => Ok(i.to_string()),
266        Value::Number(f) => Ok(f.to_string()),
267        Value::Boolean(b) => Ok(b.to_string()),
268        _ => Err(RuleEngineError::ActionError {
269            message: "Value cannot be converted to string".to_string(),
270        }),
271    }
272}