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 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 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 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 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 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 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 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 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 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 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
247fn 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}