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