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