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, }
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}