bamboo_domain/session/composition/
condition.rs1use regex::Regex;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12#[serde(rename_all = "snake_case", tag = "type")]
13pub enum Condition {
14 Success,
16 Contains { path: String, value: String },
18 Matches { path: String, pattern: String },
20 And { conditions: Vec<Condition> },
22 Or { conditions: Vec<Condition> },
24}
25
26pub fn evaluate_condition(condition: &Condition, success: bool, result_json: &str) -> bool {
31 match condition {
32 Condition::Success => success,
33 Condition::Contains { path, value } => evaluate_contains(result_json, path, value),
34 Condition::Matches { path, pattern } => evaluate_matches(result_json, path, pattern),
35 Condition::And { conditions } => conditions
36 .iter()
37 .all(|c| evaluate_condition(c, success, result_json)),
38 Condition::Or { conditions } => conditions
39 .iter()
40 .any(|c| evaluate_condition(c, success, result_json)),
41 }
42}
43
44fn extract_at_path(json_str: &str, path: &str) -> Option<String> {
45 let value: serde_json::Value = serde_json::from_str(json_str).ok()?;
46 let parts: Vec<&str> = path.split('.').collect();
47 let mut current = &value;
48 for part in parts {
49 if let Ok(index) = part.parse::<usize>() {
50 current = current.get(index)?;
51 } else {
52 current = current.get(part)?;
53 }
54 }
55 Some(current.to_string().trim_matches('"').to_string())
56}
57
58fn evaluate_contains(json_str: &str, path: &str, expected: &str) -> bool {
59 extract_at_path(json_str, path)
60 .map(|v| v.contains(expected))
61 .unwrap_or(false)
62}
63
64fn evaluate_matches(json_str: &str, path: &str, pattern: &str) -> bool {
65 extract_at_path(json_str, path)
66 .and_then(|v| Regex::new(pattern).ok().map(|re| re.is_match(&v)))
67 .unwrap_or(false)
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn test_success_condition() {
76 assert!(evaluate_condition(&Condition::Success, true, "{}"));
77 assert!(!evaluate_condition(&Condition::Success, false, "{}"));
78 }
79
80 #[test]
81 fn test_contains_condition() {
82 let json = r#"{"status": "completed", "data": {"name": "test"}}"#;
83 let cond = Condition::Contains {
84 path: "status".into(),
85 value: "complete".into(),
86 };
87 assert!(evaluate_condition(&cond, true, json));
88
89 let cond = Condition::Contains {
90 path: "data.name".into(),
91 value: "test".into(),
92 };
93 assert!(evaluate_condition(&cond, true, json));
94
95 let cond = Condition::Contains {
96 path: "status".into(),
97 value: "failed".into(),
98 };
99 assert!(!evaluate_condition(&cond, true, json));
100 }
101
102 #[test]
103 fn test_matches_condition() {
104 let json = r#"{"email": "user@example.com"}"#;
105 let cond = Condition::Matches {
106 path: "email".into(),
107 pattern: r"^\S+@\S+\.\S+$".into(),
108 };
109 assert!(evaluate_condition(&cond, true, json));
110 }
111
112 #[test]
113 fn test_json_serialization() {
114 let cond = Condition::And {
115 conditions: vec![
116 Condition::Success,
117 Condition::Contains {
118 path: "status".into(),
119 value: "ok".into(),
120 },
121 ],
122 };
123 let json = serde_json::to_string(&cond).unwrap();
124 assert!(json.contains("\"type\":\"and\"") || json.contains("\"type\": \"and\""));
125 let deserialized: Condition = serde_json::from_str(&json).unwrap();
126 assert_eq!(cond, deserialized);
127 }
128}