use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Value {
String(String),
Number(f64),
Integer(i64),
Boolean(bool),
Array(Vec<Value>),
Object(HashMap<String, Value>),
Null,
Expression(String),
}
impl Value {
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
match self {
Value::String(s) => s.clone(),
Value::Number(n) => n.to_string(),
Value::Integer(i) => i.to_string(),
Value::Boolean(b) => b.to_string(),
Value::Array(_) => "[Array]".to_string(),
Value::Object(_) => "[Object]".to_string(),
Value::Null => "null".to_string(),
Value::Expression(expr) => format!("[Expr: {}]", expr),
}
}
pub fn to_number(&self) -> Option<f64> {
match self {
Value::Number(n) => Some(*n),
Value::Integer(i) => Some(*i as f64),
Value::String(s) => s.parse::<f64>().ok(),
_ => None,
}
}
pub fn as_string(&self) -> Option<String> {
match self {
Value::String(s) => Some(s.clone()),
_ => None,
}
}
pub fn as_integer(&self) -> Option<i64> {
match self {
Value::Integer(i) => Some(*i),
_ => None,
}
}
pub fn as_boolean(&self) -> Option<bool> {
match self {
Value::Boolean(b) => Some(*b),
_ => None,
}
}
pub fn as_number(&self) -> Option<f64> {
match self {
Value::Number(n) => Some(*n),
_ => None,
}
}
pub fn to_bool(&self) -> bool {
match self {
Value::Boolean(b) => *b,
Value::String(s) => !s.is_empty(),
Value::Number(n) => *n != 0.0,
Value::Integer(i) => *i != 0,
Value::Array(arr) => !arr.is_empty(),
Value::Object(obj) => !obj.is_empty(),
Value::Null => false,
Value::Expression(_) => false, }
}
pub fn call_method(&mut self, method: &str, args: Vec<Value>) -> Result<Value, String> {
match self {
Value::Object(ref mut obj) => match method {
"setSpeed" => {
if let Some(Value::Number(speed)) = args.first() {
obj.insert("Speed".to_string(), Value::Number(*speed));
Ok(Value::Null)
} else if let Some(Value::Integer(speed)) = args.first() {
obj.insert("Speed".to_string(), Value::Integer(*speed));
Ok(Value::Null)
} else {
Err("setSpeed requires a number argument".to_string())
}
}
"setTotalDistance" => {
if let Some(Value::Number(distance)) = args.first() {
obj.insert("TotalDistance".to_string(), Value::Number(*distance));
Ok(Value::Null)
} else if let Some(Value::Integer(distance)) = args.first() {
obj.insert("TotalDistance".to_string(), Value::Integer(*distance));
Ok(Value::Null)
} else {
Err("setTotalDistance requires a number argument".to_string())
}
}
"getTotalDistance" => Ok(obj
.get("TotalDistance")
.cloned()
.unwrap_or(Value::Number(0.0))),
"getSpeed" => Ok(obj.get("Speed").cloned().unwrap_or(Value::Number(0.0))),
_ => Err(format!("Method {} not found", method)),
},
_ => Err("Cannot call method on non-object value".to_string()),
}
}
pub fn get_property(&self, property: &str) -> Option<Value> {
match self {
Value::Object(obj) => obj.get(property).cloned(),
_ => None,
}
}
pub fn set_property(&mut self, property: &str, value: Value) -> Result<(), String> {
match self {
Value::Object(ref mut obj) => {
obj.insert(property.to_string(), value);
Ok(())
}
_ => Err("Cannot set property on non-object value".to_string()),
}
}
}
impl From<String> for Value {
fn from(s: String) -> Self {
Value::String(s)
}
}
impl From<&str> for Value {
fn from(s: &str) -> Self {
Value::String(s.to_string())
}
}
impl From<f64> for Value {
fn from(n: f64) -> Self {
Value::Number(n)
}
}
impl From<i64> for Value {
fn from(i: i64) -> Self {
Value::Integer(i)
}
}
impl From<bool> for Value {
fn from(b: bool) -> Self {
Value::Boolean(b)
}
}
impl From<serde_json::Value> for Value {
fn from(json_value: serde_json::Value) -> Self {
match json_value {
serde_json::Value::String(s) => Value::String(s),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
Value::Integer(i)
} else if let Some(f) = n.as_f64() {
Value::Number(f)
} else {
Value::Null
}
}
serde_json::Value::Bool(b) => Value::Boolean(b),
serde_json::Value::Array(arr) => {
Value::Array(arr.into_iter().map(Value::from).collect())
}
serde_json::Value::Object(obj) => {
let mut map = HashMap::new();
for (k, v) in obj {
map.insert(k, Value::from(v));
}
Value::Object(map)
}
serde_json::Value::Null => Value::Null,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Operator {
Equal,
NotEqual,
GreaterThan,
GreaterThanOrEqual,
LessThan,
LessThanOrEqual,
Contains,
NotContains,
StartsWith,
EndsWith,
Matches,
}
impl Operator {
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Option<Self> {
match s {
"==" | "eq" => Some(Operator::Equal),
"!=" | "ne" => Some(Operator::NotEqual),
">" | "gt" => Some(Operator::GreaterThan),
">=" | "gte" => Some(Operator::GreaterThanOrEqual),
"<" | "lt" => Some(Operator::LessThan),
"<=" | "lte" => Some(Operator::LessThanOrEqual),
"contains" => Some(Operator::Contains),
"not_contains" => Some(Operator::NotContains),
"starts_with" => Some(Operator::StartsWith),
"ends_with" => Some(Operator::EndsWith),
"matches" => Some(Operator::Matches),
_ => None,
}
}
pub fn evaluate(&self, left: &Value, right: &Value) -> bool {
match self {
Operator::Equal => {
if matches!(left, Value::Null) || matches!(right, Value::Null) {
let left_is_null = matches!(left, Value::Null)
|| (matches!(left, Value::String(s) if s == "null"));
let right_is_null = matches!(right, Value::Null)
|| (matches!(right, Value::String(s) if s == "null"));
left_is_null == right_is_null
} else {
left == right
}
}
Operator::NotEqual => {
if matches!(left, Value::Null) || matches!(right, Value::Null) {
let left_is_null = matches!(left, Value::Null)
|| (matches!(left, Value::String(s) if s == "null"));
let right_is_null = matches!(right, Value::Null)
|| (matches!(right, Value::String(s) if s == "null"));
left_is_null != right_is_null
} else {
left != right
}
}
Operator::GreaterThan => {
if let (Some(l), Some(r)) = (left.to_number(), right.to_number()) {
l > r
} else {
false
}
}
Operator::GreaterThanOrEqual => {
if let (Some(l), Some(r)) = (left.to_number(), right.to_number()) {
l >= r
} else {
false
}
}
Operator::LessThan => {
if let (Some(l), Some(r)) = (left.to_number(), right.to_number()) {
l < r
} else {
false
}
}
Operator::LessThanOrEqual => {
if let (Some(l), Some(r)) = (left.to_number(), right.to_number()) {
l <= r
} else {
false
}
}
Operator::Contains => {
if let (Some(l), Some(r)) = (left.as_string(), right.as_string()) {
l.contains(&r)
} else {
false
}
}
Operator::NotContains => {
if let (Some(l), Some(r)) = (left.as_string(), right.as_string()) {
!l.contains(&r)
} else {
false
}
}
Operator::StartsWith => {
if let (Some(l), Some(r)) = (left.as_string(), right.as_string()) {
l.starts_with(&r)
} else {
false
}
}
Operator::EndsWith => {
if let (Some(l), Some(r)) = (left.as_string(), right.as_string()) {
l.ends_with(&r)
} else {
false
}
}
Operator::Matches => {
if let (Some(l), Some(r)) = (left.as_string(), right.as_string()) {
l.contains(&r)
} else {
false
}
}
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum LogicalOperator {
And,
Or,
Not,
}
impl LogicalOperator {
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"and" | "&&" => Some(LogicalOperator::And),
"or" | "||" => Some(LogicalOperator::Or),
"not" | "!" => Some(LogicalOperator::Not),
_ => None,
}
}
}
pub type Context = HashMap<String, Value>;
#[derive(Debug, Clone, PartialEq)]
pub enum ActionType {
Set {
field: String,
value: Value,
},
Log {
message: String,
},
MethodCall {
object: String,
method: String,
args: Vec<Value>,
},
Retract {
object: String,
},
Custom {
action_type: String,
params: HashMap<String, Value>,
},
ActivateAgendaGroup {
group: String,
},
ScheduleRule {
rule_name: String,
delay_ms: u64,
},
CompleteWorkflow {
workflow_name: String,
},
SetWorkflowData {
key: String,
value: Value,
},
Append {
field: String,
value: Value,
},
}