phlow_engine/
condition.rs

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