rust_rule_engine/plugins/
string_utils.rs1use crate::engine::plugin::{PluginHealth, PluginMetadata, PluginState, RulePlugin};
2use crate::engine::RustRuleEngine;
3use crate::errors::{Result, RuleEngineError};
4use crate::types::Value;
5
6pub 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 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 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 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 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 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 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 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 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 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
241fn 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}