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 super::*;
235 use phs::build_engine;
236
237 #[test]
238 fn test_condition_execute_equal() {
239 let engine = build_engine(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();
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(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();
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(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();
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(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();
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(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();
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(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();
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(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();
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(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();
368
369 let result = condition.evaluate(&context).unwrap();
370 assert_eq!(result, true);
371 }
372}