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}