1use std::sync::Arc;
2
3use phlow_sdk::valu3;
4use rhai::Engine;
5use serde::Serialize;
6use valu3::{prelude::StringBehavior, traits::ToValueBehavior, value::Value};
7
8use crate::{
9 context::Context,
10 script::{Script, ScriptError},
11};
12
13#[derive(Debug)]
14pub enum ConditionError {
15 InvalidOperator(String),
16 RightInvalid(String),
17 LeftInvalid(String),
18 AssertInvalid(String),
19 ScriptError(ScriptError),
20}
21
22#[derive(Debug, Clone, PartialEq, Serialize)]
23pub enum Operator {
24 Or,
25 And,
26 Equal,
27 NotEqual,
28 GreaterThan,
29 LessThan,
30 GreaterThanOrEqual,
31 LessThanOrEqual,
32 Contains,
33 NotContains,
34 StartsWith,
35 EndsWith,
36 Regex,
37 NotRegex,
38}
39
40impl ToValueBehavior for Operator {
41 fn to_value(&self) -> Value {
42 match self {
43 Operator::Or => "or".to_value(),
44 Operator::And => "and".to_value(),
45 Operator::Equal => "equal".to_value(),
46 Operator::NotEqual => "not_equal".to_value(),
47 Operator::GreaterThan => "greater_than".to_value(),
48 Operator::LessThan => "less_than".to_value(),
49 Operator::GreaterThanOrEqual => "greater_than_or_equal".to_value(),
50 Operator::LessThanOrEqual => "less_than_or_equal".to_value(),
51 Operator::Contains => "contains".to_value(),
52 Operator::NotContains => "not_contains".to_value(),
53 Operator::StartsWith => "starts_with".to_value(),
54 Operator::EndsWith => "ends_with".to_value(),
55 Operator::Regex => "regex".to_value(),
56 Operator::NotRegex => "not_regex".to_value(),
57 }
58 }
59}
60
61impl From<&Value> for Operator {
62 fn from(value: &Value) -> Self {
63 match value.as_str() {
64 "or" => Operator::Or,
65 "and" => Operator::And,
66 "equal" => Operator::Equal,
67 "not_equal" => Operator::NotEqual,
68 "greater_than" => Operator::GreaterThan,
69 "less_than" => Operator::LessThan,
70 "greater_than_or_equal" => Operator::GreaterThanOrEqual,
71 "less_than_or_equal" => Operator::LessThanOrEqual,
72 "contains" => Operator::Contains,
73 "not_contains" => Operator::NotContains,
74 "starts_with" => Operator::StartsWith,
75 "ends_with" => Operator::EndsWith,
76 "regex" => Operator::Regex,
77 "not_regex" => Operator::NotRegex,
78 _ => panic!("Invalid operator"),
79 }
80 }
81}
82
83#[derive(Debug, Clone)]
84pub struct Condition {
85 pub(crate) expression: Script,
86 pub(crate) raw: Value,
87}
88
89impl Condition {
90 pub fn try_from_value(engine: Arc<Engine>, value: &Value) -> Result<Self, ConditionError> {
91 if let Some(assert) = value.get("assert") {
92 return Ok(Self::try_build_with_assert(engine, assert.to_string())?);
93 }
94
95 let left = match value.get("left") {
96 Some(left) => left.to_string(),
97 None => return Err(ConditionError::LeftInvalid("does not exist".to_string())),
98 };
99
100 let right = match value.get("right") {
101 Some(right) => {
102 if let Value::String(right) = right {
103 right.to_string()
104 } else {
105 right.to_json(valu3::prelude::JsonMode::Inline)
106 }
107 }
108 None => return Err(ConditionError::RightInvalid("does not exist".to_string())),
109 };
110
111 let operator = match value.get("operator") {
112 Some(operator) => Operator::from(operator),
113 None => {
114 return Err(ConditionError::InvalidOperator(
115 "does not exist".to_string(),
116 ))
117 }
118 };
119
120 let condition = Self::try_build_with_operator(engine, left, right, operator)?;
121
122 Ok(condition)
123 }
124
125 pub fn try_build_with_assert(
126 engine: Arc<Engine>,
127 assert: String,
128 ) -> Result<Self, ConditionError> {
129 let expression =
130 Script::try_build(engine, &assert.to_value()).map_err(ConditionError::ScriptError)?;
131
132 Ok(Self {
133 expression,
134 raw: assert.to_value(),
135 })
136 }
137
138 pub fn try_build_with_operator(
139 engine: Arc<Engine>,
140 left: String,
141 right: String,
142 operator: Operator,
143 ) -> Result<Self, ConditionError> {
144 let left = Script::to_code_string(&left);
145 let right = Script::to_code_string(&right);
146
147 let assert = {
148 match operator {
149 Operator::Or => {
150 let query = format!("{{{{{} || {}}}}}", left, right);
151 query
152 }
153 Operator::And => {
154 let query = format!("{{{{{} && {}}}}}", left, right);
155 query
156 }
157 Operator::Equal => {
158 let query = format!("{{{{{} == {}}}}}", left, right);
159 query
160 }
161 Operator::NotEqual => {
162 let query = format!("{{{{{} != {}}}}}", left, right);
163 query
164 }
165 Operator::GreaterThan => {
166 let query = format!("{{{{{} > {}}}}}", left, right);
167 query
168 }
169 Operator::LessThan => {
170 let query = format!("{{{{{} < {}}}}}", left, right);
171 query
172 }
173 Operator::GreaterThanOrEqual => {
174 let query = format!("{{{{{} >= {}}}}}", left, right);
175 query
176 }
177 Operator::LessThanOrEqual => {
178 let query = format!("{{{{{} <= {}}}}}", left, right);
179 query
180 }
181 Operator::Contains => {
182 let query = format!("{{{{{} in {}}}}}", right, left);
183 query
184 }
185 Operator::NotContains => {
186 let query = format!("{{{{!({} in {})}}}}", right, left);
187 query
188 }
189 Operator::StartsWith => {
190 let query = format!("{{{{{} starts_with {}}}}}", left, right);
191 query
192 }
193 Operator::EndsWith => {
194 let query = format!("{{{{{} ends_with {}}}}}", left, right);
195 query
196 }
197 Operator::Regex => {
198 let query = format!("{{{{{} search {}}}}}", left, right);
199 query
200 }
201 Operator::NotRegex => {
202 let query = format!("{{{{!({} search {})}}}}", left, right);
203 query
204 }
205 }
206 };
207
208 let expression =
209 Script::try_build(engine, &assert.to_value()).map_err(ConditionError::ScriptError)?;
210
211 Ok(Self {
212 expression,
213 raw: assert.to_value(),
214 })
215 }
216
217 pub fn evaluate(&self, context: &Context) -> Result<bool, ConditionError> {
218 let result = self
219 .expression
220 .evaluate(context)
221 .map_err(ConditionError::ScriptError)?;
222
223 match result {
224 Value::Boolean(result) => Ok(result),
225 _ => Err(ConditionError::ScriptError(ScriptError::InvalidType(
226 result,
227 ))),
228 }
229 }
230}
231
232#[cfg(test)]
233mod test {
234 use crate::engine::build_engine_async;
235
236 use super::*;
237
238 #[test]
239 fn test_condition_execute_equal() {
240 let engine = build_engine_async(None);
241 let condition = Condition::try_build_with_operator(
242 engine,
243 "10".to_string(),
244 "20".to_string(),
245 Operator::Equal,
246 )
247 .unwrap();
248
249 let context = Context::new();
250
251 let result = condition.evaluate(&context).unwrap();
252 assert_eq!(result, false);
253 }
254
255 #[test]
256 fn test_condition_execute_not_equal() {
257 let engine = build_engine_async(None);
258 let condition = Condition::try_build_with_operator(
259 engine,
260 "10".to_string(),
261 "20".to_string(),
262 Operator::NotEqual,
263 )
264 .unwrap();
265
266 let context = Context::new();
267
268 let result = condition.evaluate(&context).unwrap();
269 assert_eq!(result, true);
270 }
271
272 #[test]
273 fn test_condition_execute_greater_than() {
274 let engine = build_engine_async(None);
275 let condition = Condition::try_build_with_operator(
276 engine,
277 "10".to_string(),
278 "20".to_string(),
279 Operator::GreaterThan,
280 )
281 .unwrap();
282
283 let context = Context::new();
284
285 let result = condition.evaluate(&context).unwrap();
286 assert_eq!(result, false);
287 }
288
289 #[test]
290 fn test_condition_execute_contains() {
291 let engine = build_engine_async(None);
292 let condition = Condition::try_build_with_operator(
293 engine,
294 "hello world".to_string(),
295 "hello".to_string(),
296 Operator::Contains,
297 )
298 .unwrap();
299
300 let context = Context::new();
301
302 let result = condition.evaluate(&context).unwrap();
303 assert_eq!(result, true);
304 }
305
306 #[test]
307 fn test_condition_execute_regex() {
308 let engine = build_engine_async(None);
309 let condition = Condition::try_build_with_operator(
310 engine,
311 "hello".to_string(),
312 "hello world".to_string(),
313 Operator::Regex,
314 )
315 .unwrap();
316
317 let context = Context::new();
318
319 let result = condition.evaluate(&context).unwrap();
320 assert_eq!(result, true);
321 }
322
323 #[test]
324 fn test_condition_execute_not_regex() {
325 let engine = build_engine_async(None);
326 let condition = Condition::try_build_with_operator(
327 engine,
328 "hello".to_string(),
329 "hello world".to_string(),
330 Operator::NotRegex,
331 )
332 .unwrap();
333
334 let context = Context::new();
335
336 let result = condition.evaluate(&context).unwrap();
337 assert_eq!(result, false);
338 }
339
340 #[test]
341 fn test_condition_execute_start_with() {
342 let engine = build_engine_async(None);
343 let condition = Condition::try_build_with_operator(
344 engine,
345 "hello world".to_string(),
346 "hello".to_string(),
347 Operator::StartsWith,
348 )
349 .unwrap();
350
351 let context = Context::new();
352
353 let result = condition.evaluate(&context).unwrap();
354 assert_eq!(result, true);
355 }
356
357 #[test]
358 fn test_condition_execute_end_with() {
359 let engine = build_engine_async(None);
360 let condition = Condition::try_build_with_operator(
361 engine,
362 "hello world".to_string(),
363 "world".to_string(),
364 Operator::EndsWith,
365 )
366 .unwrap();
367
368 let context = Context::new();
369
370 let result = condition.evaluate(&context).unwrap();
371 assert_eq!(result, true);
372 }
373}