rust_rule_engine/plugins/
validation.rs1use crate::engine::plugin::{PluginHealth, PluginMetadata, PluginState, RulePlugin};
2use crate::engine::RustRuleEngine;
3use crate::errors::{Result, RuleEngineError};
4use crate::parser::literal_search::{
5 is_valid_email_literal, is_valid_phone_literal, is_valid_url_literal,
6};
7use crate::types::Value;
8
9pub struct ValidationPlugin {
11 metadata: PluginMetadata,
12}
13
14impl Default for ValidationPlugin {
15 fn default() -> Self {
16 Self::new()
17 }
18}
19
20impl ValidationPlugin {
21 pub fn new() -> Self {
22 Self {
23 metadata: PluginMetadata {
24 name: "validation".to_string(),
25 version: "1.0.0".to_string(),
26 description: "Data validation utilities".to_string(),
27 author: "Rust Rule Engine Team".to_string(),
28 state: PluginState::Loaded,
29 health: PluginHealth::Healthy,
30 actions: vec![
31 "ValidateEmail".to_string(),
32 "ValidatePhone".to_string(),
33 "ValidateUrl".to_string(),
34 "ValidateRegex".to_string(),
35 "ValidateRange".to_string(),
36 "ValidateLength".to_string(),
37 "ValidateNotEmpty".to_string(),
38 "ValidateNumeric".to_string(),
39 ],
40 functions: vec![
41 "isEmail".to_string(),
42 "isPhone".to_string(),
43 "isUrl".to_string(),
44 "isNumeric".to_string(),
45 "isEmpty".to_string(),
46 "inRange".to_string(),
47 ],
48 dependencies: vec![],
49 },
50 }
51 }
52}
53
54impl RulePlugin for ValidationPlugin {
55 fn get_metadata(&self) -> &PluginMetadata {
56 &self.metadata
57 }
58
59 fn register_actions(&self, engine: &mut RustRuleEngine) -> Result<()> {
60 engine.register_action_handler("ValidateEmail", |params, facts| {
62 let input = get_string_param(params, "input", "0")?;
63 let output = get_string_param(params, "output", "1")?;
64
65 if let Some(value) = facts.get(&input) {
66 let email = value_to_string(&value)?;
67 let is_valid = is_valid_email(&email);
68 facts.set_nested(&output, Value::Boolean(is_valid))?;
69 }
70 Ok(())
71 });
72
73 engine.register_action_handler("ValidatePhone", |params, facts| {
75 let input = get_string_param(params, "input", "0")?;
76 let output = get_string_param(params, "output", "1")?;
77
78 if let Some(value) = facts.get(&input) {
79 let phone = value_to_string(&value)?;
80 let is_valid = is_valid_phone(&phone);
81 facts.set_nested(&output, Value::Boolean(is_valid))?;
82 }
83 Ok(())
84 });
85
86 engine.register_action_handler("ValidateUrl", |params, facts| {
88 let input = get_string_param(params, "input", "0")?;
89 let output = get_string_param(params, "output", "1")?;
90
91 if let Some(value) = facts.get(&input) {
92 let url = value_to_string(&value)?;
93 let is_valid = is_valid_url(&url);
94 facts.set_nested(&output, Value::Boolean(is_valid))?;
95 }
96 Ok(())
97 });
98
99 engine.register_action_handler("ValidateRegex", |params, facts| {
102 let input = get_string_param(params, "input", "0")?;
103 let pattern = get_string_param(params, "pattern", "1")?;
104 let output = get_string_param(params, "output", "2")?;
105
106 if let Some(value) = facts.get(&input) {
107 let text = value_to_string(&value)?;
108 let is_valid = text.contains(&pattern);
111 facts.set_nested(&output, Value::Boolean(is_valid))?;
112 }
113 Ok(())
114 });
115
116 engine.register_action_handler("ValidateRange", |params, facts| {
118 let input = get_string_param(params, "input", "0")?;
119 let min = get_number_param(params, facts, "min", "1")?;
120 let max = get_number_param(params, facts, "max", "2")?;
121 let output = get_string_param(params, "output", "3")?;
122
123 if let Some(value) = facts.get(&input) {
124 let num = value_to_number(&value)?;
125 let is_valid = num >= min && num <= max;
126 facts.set_nested(&output, Value::Boolean(is_valid))?;
127 }
128 Ok(())
129 });
130
131 engine.register_action_handler("ValidateLength", |params, facts| {
133 let input = get_string_param(params, "input", "0")?;
134 let min_len = get_number_param(params, facts, "minLength", "1")? as usize;
135 let max_len = get_number_param(params, facts, "maxLength", "2")? as usize;
136 let output = get_string_param(params, "output", "3")?;
137
138 if let Some(value) = facts.get(&input) {
139 let text = value_to_string(&value)?;
140 let len = text.len();
141 let is_valid = len >= min_len && len <= max_len;
142 facts.set_nested(&output, Value::Boolean(is_valid))?;
143 }
144 Ok(())
145 });
146
147 engine.register_action_handler("ValidateNotEmpty", |params, facts| {
149 let input = get_string_param(params, "input", "0")?;
150 let output = get_string_param(params, "output", "1")?;
151
152 if let Some(value) = facts.get(&input) {
153 let is_not_empty = match value {
154 Value::String(s) => !s.trim().is_empty(),
155 Value::Array(arr) => !arr.is_empty(),
156 Value::Object(obj) => !obj.is_empty(),
157 Value::Null => false,
158 _ => true,
159 };
160 facts.set_nested(&output, Value::Boolean(is_not_empty))?;
161 }
162 Ok(())
163 });
164
165 Ok(())
166 }
167
168 fn register_functions(&self, engine: &mut RustRuleEngine) -> Result<()> {
169 engine.register_function("isEmail", |args, _facts| {
171 if args.len() != 1 {
172 return Err(RuleEngineError::EvaluationError {
173 message: "isEmail requires exactly 1 argument".to_string(),
174 });
175 }
176
177 let email = value_to_string(&args[0])?;
178 Ok(Value::Boolean(is_valid_email(&email)))
179 });
180
181 engine.register_function("isPhone", |args, _facts| {
183 if args.len() != 1 {
184 return Err(RuleEngineError::EvaluationError {
185 message: "isPhone requires exactly 1 argument".to_string(),
186 });
187 }
188
189 let phone = value_to_string(&args[0])?;
190 Ok(Value::Boolean(is_valid_phone(&phone)))
191 });
192
193 engine.register_function("isUrl", |args, _facts| {
195 if args.len() != 1 {
196 return Err(RuleEngineError::EvaluationError {
197 message: "isUrl requires exactly 1 argument".to_string(),
198 });
199 }
200
201 let url = value_to_string(&args[0])?;
202 Ok(Value::Boolean(is_valid_url(&url)))
203 });
204
205 engine.register_function("isNumeric", |args, _facts| {
207 if args.len() != 1 {
208 return Err(RuleEngineError::EvaluationError {
209 message: "isNumeric requires exactly 1 argument".to_string(),
210 });
211 }
212
213 let text = value_to_string(&args[0])?;
214 let is_numeric = text.parse::<f64>().is_ok();
215 Ok(Value::Boolean(is_numeric))
216 });
217
218 engine.register_function("isEmpty", |args, _facts| {
220 if args.len() != 1 {
221 return Err(RuleEngineError::EvaluationError {
222 message: "isEmpty requires exactly 1 argument".to_string(),
223 });
224 }
225
226 let is_empty = match &args[0] {
227 Value::String(s) => s.trim().is_empty(),
228 Value::Array(arr) => arr.is_empty(),
229 Value::Object(obj) => obj.is_empty(),
230 Value::Null => true,
231 _ => false,
232 };
233 Ok(Value::Boolean(is_empty))
234 });
235
236 engine.register_function("inRange", |args, _facts| {
238 if args.len() != 3 {
239 return Err(RuleEngineError::EvaluationError {
240 message: "inRange requires exactly 3 arguments: value, min, max".to_string(),
241 });
242 }
243
244 let value = value_to_number(&args[0])?;
245 let min = value_to_number(&args[1])?;
246 let max = value_to_number(&args[2])?;
247
248 let in_range = value >= min && value <= max;
249 Ok(Value::Boolean(in_range))
250 });
251
252 Ok(())
253 }
254
255 fn unload(&mut self) -> Result<()> {
256 self.metadata.state = PluginState::Unloaded;
257 Ok(())
258 }
259
260 fn health_check(&mut self) -> PluginHealth {
261 match self.metadata.state {
262 PluginState::Loaded => PluginHealth::Healthy,
263 PluginState::Loading => PluginHealth::Warning("Plugin is loading".to_string()),
264 PluginState::Error => PluginHealth::Error("Plugin is in error state".to_string()),
265 PluginState::Unloaded => PluginHealth::Warning("Plugin is unloaded".to_string()),
266 }
267 }
268}
269
270fn get_string_param(
272 params: &std::collections::HashMap<String, Value>,
273 name: &str,
274 pos: &str,
275) -> Result<String> {
276 let value = params
277 .get(name)
278 .or_else(|| params.get(pos))
279 .ok_or_else(|| RuleEngineError::ActionError {
280 message: format!("Missing parameter: {}", name),
281 })?;
282
283 match value {
284 Value::String(s) => Ok(s.clone()),
285 _ => Err(RuleEngineError::ActionError {
286 message: format!("Parameter {} must be string", name),
287 }),
288 }
289}
290
291fn get_number_param(
292 params: &std::collections::HashMap<String, Value>,
293 facts: &crate::Facts,
294 name: &str,
295 pos: &str,
296) -> Result<f64> {
297 let value = params
298 .get(name)
299 .or_else(|| params.get(pos))
300 .ok_or_else(|| RuleEngineError::ActionError {
301 message: format!("Missing parameter: {}", name),
302 })?;
303
304 if let Value::String(s) = value {
305 if s.contains('.') {
306 if let Some(fact_value) = facts.get(s) {
307 return value_to_number(&fact_value);
308 }
309 }
310 }
311
312 value_to_number(value)
313}
314
315fn value_to_string(value: &Value) -> Result<String> {
316 match value {
317 Value::String(s) => Ok(s.clone()),
318 Value::Integer(i) => Ok(i.to_string()),
319 Value::Number(f) => Ok(f.to_string()),
320 Value::Boolean(b) => Ok(b.to_string()),
321 _ => Err(RuleEngineError::ActionError {
322 message: "Value cannot be converted to string".to_string(),
323 }),
324 }
325}
326
327fn value_to_number(value: &Value) -> Result<f64> {
328 match value {
329 Value::Number(f) => Ok(*f),
330 Value::Integer(i) => Ok(*i as f64),
331 Value::String(s) => s.parse::<f64>().map_err(|_| RuleEngineError::ActionError {
332 message: format!("Cannot convert '{}' to number", s),
333 }),
334 _ => Err(RuleEngineError::ActionError {
335 message: "Value cannot be converted to number".to_string(),
336 }),
337 }
338}
339
340fn is_valid_email(email: &str) -> bool {
341 is_valid_email_literal(email)
342}
343
344fn is_valid_phone(phone: &str) -> bool {
345 is_valid_phone_literal(phone)
346}
347
348fn is_valid_url(url: &str) -> bool {
349 is_valid_url_literal(url)
350}