1use crate::Precondition;
8use serde_json::Value;
9
10pub trait StateView {
15 fn get_value(&self, key: &str) -> Option<Value>;
17
18 fn key_exists(&self, key: &str) -> bool;
20
21 fn is_unknown(&self, _key: &str) -> bool {
24 false
25 }
26}
27
28pub fn check_precondition(pre: &Precondition, state: &dyn StateView) -> Option<String> {
33 let op = pre.operator.as_str();
34
35 if op == "exists" {
36 return if !state.key_exists(&pre.key) && !state.is_unknown(&pre.key) {
37 Some(format!("key '{}' does not exist", pre.key))
38 } else {
39 None
40 };
41 }
42
43 if op == "not_exists" {
44 return if state.key_exists(&pre.key) || state.is_unknown(&pre.key) {
45 Some(format!("key '{}' exists", pre.key))
46 } else {
47 None
48 };
49 }
50
51 if op == "contains" {
52 let current = state.get_value(&pre.key);
53 return match current {
54 None if state.is_unknown(&pre.key) => None,
55 None => Some(format!("key '{}' has no value, cannot check contains", pre.key)),
56 Some(val) => {
57 let val_str = match &val {
58 Value::String(s) => s.clone(),
59 other => other.to_string(),
60 };
61 let needle = match &pre.value {
62 Value::String(s) => s.clone(),
63 other => other.to_string(),
64 };
65 if val_str.contains(&needle) {
66 None
67 } else {
68 Some(format!("key '{}' does not contain {:?}", pre.key, pre.value))
69 }
70 }
71 };
72 }
73
74 if state.is_unknown(&pre.key) {
76 return None; }
78
79 let current = state.get_value(&pre.key);
80 if current.is_none() {
81 return Some(format!("key '{}' has no value", pre.key));
82 }
83 let current = current.unwrap();
84
85 match op {
86 "eq" => {
87 if current != pre.value {
88 Some(format!("'{}' is {:?}, need {:?}", pre.key, current, pre.value))
89 } else {
90 None
91 }
92 }
93 "neq" => {
94 if current == pre.value {
95 Some(format!("'{}' is {:?}, must not be", pre.key, current))
96 } else {
97 None
98 }
99 }
100 "gt" | "lt" | "gte" | "lte" => {
101 let c = current.as_f64();
102 let e = pre.value.as_f64();
103 match (c, e) {
104 (Some(c), Some(e)) => {
105 let pass = match op {
106 "gt" => c > e,
107 "lt" => c < e,
108 "gte" => c >= e,
109 "lte" => c <= e,
110 _ => unreachable!(),
111 };
112 if !pass {
113 Some(format!("'{}' is {:?}, need {} {:?}", pre.key, current, op, pre.value))
114 } else {
115 None
116 }
117 }
118 _ => Some(format!("cannot compare '{}': {:?} {} {:?}", pre.key, current, op, pre.value)),
119 }
120 }
121 _ => Some(format!("unknown operator '{}'", op)),
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use std::collections::HashMap;
129
130 struct TestState {
132 data: HashMap<String, Value>,
133 }
134
135 impl StateView for TestState {
136 fn get_value(&self, key: &str) -> Option<Value> {
137 self.data.get(key).cloned()
138 }
139 fn key_exists(&self, key: &str) -> bool {
140 self.data.contains_key(key)
141 }
142 }
143
144 fn pre(key: &str, op: &str, value: Value) -> Precondition {
145 Precondition {
146 key: key.to_string(),
147 operator: op.to_string(),
148 value,
149 description: String::new(),
150 }
151 }
152
153 #[test]
154 fn exists_passes_when_present() {
155 let state = TestState { data: [("x".into(), Value::from(1))].into() };
156 assert!(check_precondition(&pre("x", "exists", Value::Null), &state).is_none());
157 }
158
159 #[test]
160 fn exists_fails_when_absent() {
161 let state = TestState { data: HashMap::new() };
162 assert!(check_precondition(&pre("x", "exists", Value::Null), &state).is_some());
163 }
164
165 #[test]
166 fn not_exists_passes_when_absent() {
167 let state = TestState { data: HashMap::new() };
168 assert!(check_precondition(&pre("x", "not_exists", Value::Null), &state).is_none());
169 }
170
171 #[test]
172 fn eq_passes() {
173 let state = TestState { data: [("x".into(), Value::from(42))].into() };
174 assert!(check_precondition(&pre("x", "eq", Value::from(42)), &state).is_none());
175 }
176
177 #[test]
178 fn eq_fails() {
179 let state = TestState { data: [("x".into(), Value::from(1))].into() };
180 assert!(check_precondition(&pre("x", "eq", Value::from(2)), &state).is_some());
181 }
182
183 #[test]
184 fn neq_passes() {
185 let state = TestState { data: [("x".into(), Value::from(1))].into() };
186 assert!(check_precondition(&pre("x", "neq", Value::from(2)), &state).is_none());
187 }
188
189 #[test]
190 fn gt_lt_gte_lte() {
191 let state = TestState { data: [("n".into(), Value::from(10))].into() };
192 assert!(check_precondition(&pre("n", "gt", Value::from(5)), &state).is_none());
193 assert!(check_precondition(&pre("n", "gt", Value::from(20)), &state).is_some());
194 assert!(check_precondition(&pre("n", "lt", Value::from(20)), &state).is_none());
195 assert!(check_precondition(&pre("n", "gte", Value::from(10)), &state).is_none());
196 assert!(check_precondition(&pre("n", "lte", Value::from(10)), &state).is_none());
197 }
198
199 #[test]
200 fn contains_passes() {
201 let state = TestState { data: [("msg".into(), Value::from("hello world"))].into() };
202 assert!(check_precondition(&pre("msg", "contains", Value::from("world")), &state).is_none());
203 }
204
205 #[test]
206 fn contains_fails() {
207 let state = TestState { data: [("msg".into(), Value::from("hello"))].into() };
208 assert!(check_precondition(&pre("msg", "contains", Value::from("world")), &state).is_some());
209 }
210}