Skip to main content

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 Operator {
74    pub fn try_from_value(value: &Value) -> Result<Self, ConditionError> {
75        match value.as_str() {
76            "or" => Ok(Operator::Or),
77            "and" => Ok(Operator::And),
78            "equal" => Ok(Operator::Equal),
79            "not_equal" => Ok(Operator::NotEqual),
80            "greater_than" => Ok(Operator::GreaterThan),
81            "less_than" => Ok(Operator::LessThan),
82            "greater_than_or_equal" => Ok(Operator::GreaterThanOrEqual),
83            "less_than_or_equal" => Ok(Operator::LessThanOrEqual),
84            "contains" => Ok(Operator::Contains),
85            "not_contains" => Ok(Operator::NotContains),
86            _ => Err(ConditionError::InvalidOperator(value.as_str().to_string())),
87        }
88    }
89}
90
91impl From<&Value> for Operator {
92    fn from(value: &Value) -> Self {
93        // Fall back to Equal to avoid panics; prefer try_from_value for error handling.
94        Operator::try_from_value(value).unwrap_or(Operator::Equal)
95    }
96}
97
98#[derive(Debug, Clone)]
99pub struct Condition {
100    pub(crate) expression: Script,
101    pub(crate) raw: Value,
102}
103
104impl Condition {
105    pub fn try_from_value(engine: Arc<Engine>, value: &Value) -> Result<Self, ConditionError> {
106        if let Some(assert) = value.get("assert") {
107            return Ok(Self::try_build_with_assert(engine, assert.to_string())?);
108        }
109
110        return Err(ConditionError::AssertInvalid("does not exist".to_string()));
111    }
112
113    pub fn try_build_with_assert(
114        engine: Arc<Engine>,
115        assert: String,
116    ) -> Result<Self, ConditionError> {
117        let expression =
118            Script::try_build(engine, &assert.to_value()).map_err(ConditionError::ScriptError)?;
119
120        Ok(Self {
121            expression,
122            raw: assert.to_value(),
123        })
124    }
125
126    pub fn evaluate(&self, context: &Context) -> Result<bool, ConditionError> {
127        let result = self
128            .expression
129            .evaluate(context)
130            .map_err(ConditionError::ScriptError)?;
131
132        match result {
133            Value::Boolean(result) => Ok(result),
134            _ => Err(ConditionError::ScriptError(phs::ScriptError::InvalidType(
135                result,
136            ))),
137        }
138    }
139}
140
141#[cfg(test)]
142mod test {
143    use std::collections::HashMap;
144
145    use super::*;
146    use phs::build_engine;
147
148    #[test]
149    fn test_condition_execute_assert() {
150        let engine = build_engine(None);
151        let assert_map = HashMap::from([("assert".to_string(), "{{ 5 > 3 }}".to_value())]);
152        let condition = Condition::try_from_value(engine, &assert_map.to_value()).unwrap();
153
154        let context = Context::new();
155
156        let result = condition.evaluate(&context).unwrap();
157        assert_eq!(result, true);
158    }
159}