use crate::Signature;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Condition {
Always,
Never,
Equals {
field: Option<String>,
value: serde_json::Value,
},
NotEquals {
field: Option<String>,
value: serde_json::Value,
},
GreaterThan {
field: Option<String>,
threshold: f64,
},
LessThan {
field: Option<String>,
threshold: f64,
},
Truthy {
field: Option<String>,
},
Falsy {
field: Option<String>,
},
Contains {
field: Option<String>,
value: serde_json::Value,
},
Matches {
field: Option<String>,
pattern: String,
},
And(Vec<Condition>),
Or(Vec<Condition>),
Not(Box<Condition>),
Custom {
task: String,
args: Vec<serde_json::Value>,
},
}
impl Condition {
pub fn always() -> Self {
Self::Always
}
pub fn never() -> Self {
Self::Never
}
pub fn equals(value: serde_json::Value) -> Self {
Self::Equals { field: None, value }
}
pub fn field_equals(field: impl Into<String>, value: serde_json::Value) -> Self {
Self::Equals {
field: Some(field.into()),
value,
}
}
pub fn not_equals(value: serde_json::Value) -> Self {
Self::NotEquals { field: None, value }
}
pub fn greater_than(threshold: f64) -> Self {
Self::GreaterThan {
field: None,
threshold,
}
}
pub fn field_greater_than(field: impl Into<String>, threshold: f64) -> Self {
Self::GreaterThan {
field: Some(field.into()),
threshold,
}
}
pub fn less_than(threshold: f64) -> Self {
Self::LessThan {
field: None,
threshold,
}
}
pub fn truthy() -> Self {
Self::Truthy { field: None }
}
pub fn field_truthy(field: impl Into<String>) -> Self {
Self::Truthy {
field: Some(field.into()),
}
}
pub fn falsy() -> Self {
Self::Falsy { field: None }
}
pub fn contains(value: serde_json::Value) -> Self {
Self::Contains { field: None, value }
}
pub fn matches(pattern: impl Into<String>) -> Self {
Self::Matches {
field: None,
pattern: pattern.into(),
}
}
pub fn custom(task: impl Into<String>, args: Vec<serde_json::Value>) -> Self {
Self::Custom {
task: task.into(),
args,
}
}
pub fn and(self, other: Condition) -> Self {
match self {
Self::And(mut conditions) => {
conditions.push(other);
Self::And(conditions)
}
_ => Self::And(vec![self, other]),
}
}
pub fn or(self, other: Condition) -> Self {
match self {
Self::Or(mut conditions) => {
conditions.push(other);
Self::Or(conditions)
}
_ => Self::Or(vec![self, other]),
}
}
pub fn negate(self) -> Self {
Self::Not(Box::new(self))
}
pub fn evaluate(&self, value: &serde_json::Value) -> bool {
match self {
Self::Always => true,
Self::Never => false,
Self::Equals {
field,
value: expected,
} => {
let actual = extract_field(value, field.as_deref());
actual == *expected
}
Self::NotEquals {
field,
value: expected,
} => {
let actual = extract_field(value, field.as_deref());
actual != *expected
}
Self::GreaterThan { field, threshold } => {
let actual = extract_field(value, field.as_deref());
actual.as_f64().is_some_and(|v| v > *threshold)
}
Self::LessThan { field, threshold } => {
let actual = extract_field(value, field.as_deref());
actual.as_f64().is_some_and(|v| v < *threshold)
}
Self::Truthy { field } => {
let actual = extract_field(value, field.as_deref());
is_truthy(&actual)
}
Self::Falsy { field } => {
let actual = extract_field(value, field.as_deref());
!is_truthy(&actual)
}
Self::Contains {
field,
value: needle,
} => {
let actual = extract_field(value, field.as_deref());
contains_value(&actual, needle)
}
Self::Matches { field, pattern } => {
let actual = extract_field(value, field.as_deref());
if let Some(s) = actual.as_str() {
regex::Regex::new(pattern)
.map(|re| re.is_match(s))
.unwrap_or(false)
} else {
false
}
}
Self::And(conditions) => conditions.iter().all(|c| c.evaluate(value)),
Self::Or(conditions) => conditions.iter().any(|c| c.evaluate(value)),
Self::Not(condition) => !condition.evaluate(value),
Self::Custom { .. } => {
false
}
}
}
pub fn is_custom(&self) -> bool {
match self {
Self::Custom { .. } => true,
Self::And(conditions) => conditions.iter().any(|c| c.is_custom()),
Self::Or(conditions) => conditions.iter().any(|c| c.is_custom()),
Self::Not(condition) => condition.is_custom(),
_ => false,
}
}
}
fn extract_field(value: &serde_json::Value, field: Option<&str>) -> serde_json::Value {
match field {
None => value.clone(),
Some(path) => {
let mut current = value;
for part in path.split('.') {
current = match current {
serde_json::Value::Object(map) => {
map.get(part).unwrap_or(&serde_json::Value::Null)
}
serde_json::Value::Array(arr) => {
if let Ok(idx) = part.parse::<usize>() {
arr.get(idx).unwrap_or(&serde_json::Value::Null)
} else {
&serde_json::Value::Null
}
}
_ => &serde_json::Value::Null,
};
}
current.clone()
}
}
}
fn is_truthy(value: &serde_json::Value) -> bool {
match value {
serde_json::Value::Null => false,
serde_json::Value::Bool(b) => *b,
serde_json::Value::Number(n) => n.as_f64().is_some_and(|v| v != 0.0),
serde_json::Value::String(s) => !s.is_empty(),
serde_json::Value::Array(a) => !a.is_empty(),
serde_json::Value::Object(o) => !o.is_empty(),
}
}
fn contains_value(haystack: &serde_json::Value, needle: &serde_json::Value) -> bool {
match haystack {
serde_json::Value::String(s) => {
if let Some(needle_str) = needle.as_str() {
s.contains(needle_str)
} else {
false
}
}
serde_json::Value::Array(arr) => arr.contains(needle),
serde_json::Value::Object(map) => {
if let Some(key) = needle.as_str() {
map.contains_key(key)
} else {
false
}
}
_ => false,
}
}
impl std::fmt::Display for Condition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Always => write!(f, "always"),
Self::Never => write!(f, "never"),
Self::Equals { field, value } => {
if let Some(field) = field {
write!(f, "{} == {}", field, value)
} else {
write!(f, "result == {}", value)
}
}
Self::NotEquals { field, value } => {
if let Some(field) = field {
write!(f, "{} != {}", field, value)
} else {
write!(f, "result != {}", value)
}
}
Self::GreaterThan { field, threshold } => {
if let Some(field) = field {
write!(f, "{} > {}", field, threshold)
} else {
write!(f, "result > {}", threshold)
}
}
Self::LessThan { field, threshold } => {
if let Some(field) = field {
write!(f, "{} < {}", field, threshold)
} else {
write!(f, "result < {}", threshold)
}
}
Self::Truthy { field } => {
if let Some(field) = field {
write!(f, "truthy({})", field)
} else {
write!(f, "truthy(result)")
}
}
Self::Falsy { field } => {
if let Some(field) = field {
write!(f, "falsy({})", field)
} else {
write!(f, "falsy(result)")
}
}
Self::Contains { field, value } => {
if let Some(field) = field {
write!(f, "{} contains {}", field, value)
} else {
write!(f, "result contains {}", value)
}
}
Self::Matches { field, pattern } => {
if let Some(field) = field {
write!(f, "{} matches /{}/", field, pattern)
} else {
write!(f, "result matches /{}/", pattern)
}
}
Self::And(conditions) => {
let parts: Vec<String> = conditions.iter().map(|c| format!("{}", c)).collect();
write!(f, "({})", parts.join(" AND "))
}
Self::Or(conditions) => {
let parts: Vec<String> = conditions.iter().map(|c| format!("{}", c)).collect();
write!(f, "({})", parts.join(" OR "))
}
Self::Not(condition) => write!(f, "NOT ({})", condition),
Self::Custom { task, .. } => write!(f, "custom({})", task),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Branch {
pub condition: Condition,
pub then_branch: Box<Signature>,
pub else_branch: Option<Box<Signature>>,
pub pass_result: bool,
}
impl Branch {
pub fn new(condition: Condition, then_sig: Signature) -> Self {
Self {
condition,
then_branch: Box::new(then_sig),
else_branch: None,
pass_result: true,
}
}
pub fn otherwise(mut self, else_sig: Signature) -> Self {
self.else_branch = Some(Box::new(else_sig));
self
}
pub fn else_do(self, else_sig: Signature) -> Self {
self.otherwise(else_sig)
}
pub fn with_pass_result(mut self, pass: bool) -> Self {
self.pass_result = pass;
self
}
pub fn has_else(&self) -> bool {
self.else_branch.is_some()
}
pub fn evaluate(&self, result: &serde_json::Value) -> Option<Signature> {
let should_then = self.condition.evaluate(result);
let sig = if should_then {
Some((*self.then_branch).clone())
} else {
self.else_branch.as_ref().map(|s| (**s).clone())
};
if let Some(mut sig) = sig {
if self.pass_result && !sig.immutable {
sig.args.insert(0, result.clone());
}
Some(sig)
} else {
None
}
}
}
impl std::fmt::Display for Branch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(else_branch) = &self.else_branch {
write!(
f,
"Branch[if {} then {} else {}]",
self.condition, self.then_branch.task, else_branch.task
)
} else {
write!(
f,
"Branch[if {} then {}]",
self.condition, self.then_branch.task
)
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Maybe {
pub condition: Condition,
pub task: Signature,
pub pass_result: bool,
}
impl Maybe {
pub fn new(condition: Condition, task: Signature) -> Self {
Self {
condition,
task,
pass_result: true,
}
}
pub fn with_pass_result(mut self, pass: bool) -> Self {
self.pass_result = pass;
self
}
pub fn evaluate(&self, result: &serde_json::Value) -> Option<Signature> {
if self.condition.evaluate(result) {
let mut task = self.task.clone();
if self.pass_result && !task.immutable {
task.args.insert(0, result.clone());
}
Some(task)
} else {
None
}
}
pub fn to_branch(self) -> Branch {
Branch {
condition: self.condition,
then_branch: Box::new(self.task),
else_branch: None,
pass_result: self.pass_result,
}
}
}
impl std::fmt::Display for Maybe {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Maybe[if {} then {}]", self.condition, self.task.task)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Switch {
pub cases: Vec<(Condition, Signature)>,
pub default: Option<Signature>,
pub pass_result: bool,
}
impl Switch {
pub fn new() -> Self {
Self {
cases: Vec::new(),
default: None,
pass_result: true,
}
}
pub fn case(mut self, condition: Condition, task: Signature) -> Self {
self.cases.push((condition, task));
self
}
pub fn default(mut self, task: Signature) -> Self {
self.default = Some(task);
self
}
pub fn with_pass_result(mut self, pass: bool) -> Self {
self.pass_result = pass;
self
}
pub fn is_empty(&self) -> bool {
self.cases.is_empty()
}
pub fn len(&self) -> usize {
self.cases.len()
}
pub fn evaluate(&self, result: &serde_json::Value) -> Option<Signature> {
for (condition, task) in &self.cases {
if condition.evaluate(result) {
let mut task = task.clone();
if self.pass_result && !task.immutable {
task.args.insert(0, result.clone());
}
return Some(task);
}
}
if let Some(default) = &self.default {
let mut task = default.clone();
if self.pass_result && !task.immutable {
task.args.insert(0, result.clone());
}
Some(task)
} else {
None
}
}
}
impl Default for Switch {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for Switch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let case_strs: Vec<String> = self
.cases
.iter()
.map(|(c, t)| format!("{} => {}", c, t.task))
.collect();
if let Some(default) = &self.default {
write!(
f,
"Switch[{}, default => {}]",
case_strs.join(", "),
default.task
)
} else {
write!(f, "Switch[{}]", case_strs.join(", "))
}
}
}