phlow_engine/
condition.rs

1use std::{fmt::Display, sync::Arc};
2
3use rhai::Engine;
4use serde::Serialize;
5use valu3::{prelude::StringBehavior, traits::ToValueBehavior, value::Value};
6
7use crate::{context::Context, script::Script};
8
9#[derive(Debug)]
10pub enum ConditionError {
11    InvalidOperator(String),
12    RightInvalid(String),
13    LeftInvalid(String),
14    AssertInvalid(String),
15    ScriptError(phs::ScriptError),
16}
17
18impl Display for ConditionError {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        match self {
21            ConditionError::InvalidOperator(err) => write!(f, "Invalid operator: {}", err),
22            ConditionError::RightInvalid(err) => write!(f, "Right invalid: {}", err),
23            ConditionError::LeftInvalid(err) => write!(f, "Left invalid: {}", err),
24            ConditionError::AssertInvalid(err) => write!(f, "Assert invalid: {}", err),
25            ConditionError::ScriptError(err) => write!(f, "Script error: {}", err),
26        }
27    }
28}
29
30impl std::error::Error for ConditionError {
31    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
32        match self {
33            ConditionError::InvalidOperator(_) => None,
34            ConditionError::RightInvalid(_) => None,
35            ConditionError::LeftInvalid(_) => None,
36            ConditionError::AssertInvalid(_) => None,
37            ConditionError::ScriptError(_) => None, // ScriptError doesn't implement std::error::Error
38        }
39    }
40}
41
42#[derive(Debug, Clone, PartialEq, Serialize)]
43pub enum Operator {
44    Or,
45    And,
46    Equal,
47    NotEqual,
48    GreaterThan,
49    LessThan,
50    GreaterThanOrEqual,
51    LessThanOrEqual,
52    Contains,
53    NotContains,
54}
55
56impl ToValueBehavior for Operator {
57    fn to_value(&self) -> Value {
58        match self {
59            Operator::Or => "or".to_value(),
60            Operator::And => "and".to_value(),
61            Operator::Equal => "equal".to_value(),
62            Operator::NotEqual => "not_equal".to_value(),
63            Operator::GreaterThan => "greater_than".to_value(),
64            Operator::LessThan => "less_than".to_value(),
65            Operator::GreaterThanOrEqual => "greater_than_or_equal".to_value(),
66            Operator::LessThanOrEqual => "less_than_or_equal".to_value(),
67            Operator::Contains => "contains".to_value(),
68            Operator::NotContains => "not_contains".to_value(),
69        }
70    }
71}
72
73impl From<&Value> for Operator {
74    fn from(value: &Value) -> Self {
75        match value.as_str() {
76            "or" => Operator::Or,
77            "and" => Operator::And,
78            "equal" => Operator::Equal,
79            "not_equal" => Operator::NotEqual,
80            "greater_than" => Operator::GreaterThan,
81            "less_than" => Operator::LessThan,
82            "greater_than_or_equal" => Operator::GreaterThanOrEqual,
83            "less_than_or_equal" => Operator::LessThanOrEqual,
84            "contains" => Operator::Contains,
85            "not_contains" => Operator::NotContains,
86            _ => panic!("Invalid operator"),
87        }
88    }
89}
90
91#[derive(Debug, Clone)]
92pub struct Condition {
93    pub(crate) expression: Script,
94    pub(crate) raw: Value,
95}
96
97impl Condition {
98    pub fn try_from_value(engine: Arc<Engine>, value: &Value) -> Result<Self, ConditionError> {
99        if let Some(assert) = value.get("assert") {
100            return Ok(Self::try_build_with_assert(engine, assert.to_string())?);
101        }
102
103        let left = match value.get("left") {
104            Some(left) => left.to_string(),
105            None => return Err(ConditionError::LeftInvalid("does not exist".to_string())),
106        };
107
108        let right = match value.get("right") {
109            Some(right) => {
110                if let Value::String(right) = right {
111                    right.to_string()
112                } else {
113                    right.to_json(valu3::prelude::JsonMode::Inline)
114                }
115            }
116            None => return Err(ConditionError::RightInvalid("does not exist".to_string())),
117        };
118
119        let operator = match value.get("operator") {
120            Some(operator) => Operator::from(operator),
121            None => {
122                return Err(ConditionError::InvalidOperator(
123                    "does not exist".to_string(),
124                ))
125            }
126        };
127
128        let condition = Self::try_build_with_operator(engine, left, right, operator)?;
129
130        Ok(condition)
131    }
132
133    pub fn try_build_with_assert(
134        engine: Arc<Engine>,
135        assert: String,
136    ) -> Result<Self, ConditionError> {
137        let expression =
138            Script::try_build(engine, &assert.to_value()).map_err(ConditionError::ScriptError)?;
139
140        Ok(Self {
141            expression,
142            raw: assert.to_value(),
143        })
144    }
145
146    pub fn try_build_with_operator(
147        engine: Arc<Engine>,
148        left: String,
149        right: String,
150        operator: Operator,
151    ) -> Result<Self, ConditionError> {
152        let left = phs::Script::to_code_string(&left);
153        let right = phs::Script::to_code_string(&right);
154
155        let assert = {
156            match operator {
157                Operator::Or => {
158                    let query = format!("{{{{{} || {}}}}}", left, right);
159                    query
160                }
161                Operator::And => {
162                    let query = format!("{{{{{} && {}}}}}", left, right);
163                    query
164                }
165                Operator::Equal => {
166                    let query = format!("{{{{{} == {}}}}}", left, right);
167                    query
168                }
169                Operator::NotEqual => {
170                    let query = format!("{{{{{} != {}}}}}", left, right);
171                    query
172                }
173                Operator::GreaterThan => {
174                    let query = format!("{{{{{} > {}}}}}", left, right);
175                    query
176                }
177                Operator::LessThan => {
178                    let query = format!("{{{{{} < {}}}}}", left, right);
179                    query
180                }
181                Operator::GreaterThanOrEqual => {
182                    let query = format!("{{{{{} >= {}}}}}", left, right);
183                    query
184                }
185                Operator::LessThanOrEqual => {
186                    let query = format!("{{{{{} <= {}}}}}", left, right);
187                    query
188                }
189                Operator::Contains => {
190                    let query = format!("{{{{{} in {}}}}}", right, left);
191                    query
192                }
193                Operator::NotContains => {
194                    let query = format!("{{{{!({} in {})}}}}", right, left);
195                    query
196                }
197            }
198        };
199
200        let expression =
201            Script::try_build(engine, &assert.to_value()).map_err(ConditionError::ScriptError)?;
202
203        Ok(Self {
204            expression,
205            raw: assert.to_value(),
206        })
207    }
208
209    pub fn evaluate(&self, context: &Context) -> Result<bool, ConditionError> {
210        let result = self
211            .expression
212            .evaluate(context)
213            .map_err(ConditionError::ScriptError)?;
214
215        match result {
216            Value::Boolean(result) => Ok(result),
217            _ => Err(ConditionError::ScriptError(phs::ScriptError::InvalidType(
218                result,
219            ))),
220        }
221    }
222}
223
224#[cfg(test)]
225mod test {
226    use super::*;
227    use phs::build_engine;
228
229    #[test]
230    fn test_condition_execute_equal() {
231        let engine = build_engine(None);
232        let condition = Condition::try_build_with_operator(
233            engine,
234            "10".to_string(),
235            "20".to_string(),
236            Operator::Equal,
237        )
238        .unwrap();
239
240        let context = Context::new();
241
242        let result = condition.evaluate(&context).unwrap();
243        assert_eq!(result, false);
244    }
245
246    #[test]
247    fn test_condition_execute_not_equal() {
248        let engine = build_engine(None);
249        let condition = Condition::try_build_with_operator(
250            engine,
251            "10".to_string(),
252            "20".to_string(),
253            Operator::NotEqual,
254        )
255        .unwrap();
256
257        let context = Context::new();
258
259        let result = condition.evaluate(&context).unwrap();
260        assert_eq!(result, true);
261    }
262
263    #[test]
264    fn test_condition_execute_greater_than() {
265        let engine = build_engine(None);
266        let condition = Condition::try_build_with_operator(
267            engine,
268            "10".to_string(),
269            "20".to_string(),
270            Operator::GreaterThan,
271        )
272        .unwrap();
273
274        let context = Context::new();
275
276        let result = condition.evaluate(&context).unwrap();
277        assert_eq!(result, false);
278    }
279
280    #[test]
281    fn test_condition_execute_contains() {
282        let engine = build_engine(None);
283        let condition = Condition::try_build_with_operator(
284            engine,
285            "hello world".to_string(),
286            "hello".to_string(),
287            Operator::Contains,
288        )
289        .unwrap();
290
291        let context = Context::new();
292
293        let result = condition.evaluate(&context).unwrap();
294        assert_eq!(result, true);
295    }
296}