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!(
56 "key '{}' has no value, cannot check contains",
57 pre.key
58 )),
59 Some(val) => {
60 let val_str = match &val {
61 Value::String(s) => s.clone(),
62 other => other.to_string(),
63 };
64 let needle = match &pre.value {
65 Value::String(s) => s.clone(),
66 other => other.to_string(),
67 };
68 if val_str.contains(&needle) {
69 None
70 } else {
71 Some(format!(
72 "key '{}' does not contain {:?}",
73 pre.key, pre.value
74 ))
75 }
76 }
77 };
78 }
79
80 if state.is_unknown(&pre.key) {
82 return None; }
84
85 let current = state.get_value(&pre.key);
86 if current.is_none() {
87 return Some(format!("key '{}' has no value", pre.key));
88 }
89 let current = current.unwrap();
90
91 match op {
92 "eq" => {
93 if current != pre.value {
94 Some(format!(
95 "'{}' is {:?}, need {:?}",
96 pre.key, current, pre.value
97 ))
98 } else {
99 None
100 }
101 }
102 "neq" => {
103 if current == pre.value {
104 Some(format!("'{}' is {:?}, must not be", pre.key, current))
105 } else {
106 None
107 }
108 }
109 "gt" | "lt" | "gte" | "lte" => {
110 let c = current.as_f64();
111 let e = pre.value.as_f64();
112 match (c, e) {
113 (Some(c), Some(e)) => {
114 let pass = match op {
115 "gt" => c > e,
116 "lt" => c < e,
117 "gte" => c >= e,
118 "lte" => c <= e,
119 _ => unreachable!(),
120 };
121 if !pass {
122 Some(format!(
123 "'{}' is {:?}, need {} {:?}",
124 pre.key, current, op, pre.value
125 ))
126 } else {
127 None
128 }
129 }
130 _ => Some(format!(
131 "cannot compare '{}': {:?} {} {:?}",
132 pre.key, current, op, pre.value
133 )),
134 }
135 }
136 _ => Some(format!("unknown operator '{}'", op)),
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use std::collections::HashMap;
144
145 struct TestState {
147 data: HashMap<String, Value>,
148 }
149
150 impl StateView for TestState {
151 fn get_value(&self, key: &str) -> Option<Value> {
152 self.data.get(key).cloned()
153 }
154 fn key_exists(&self, key: &str) -> bool {
155 self.data.contains_key(key)
156 }
157 }
158
159 fn pre(key: &str, op: &str, value: Value) -> Precondition {
160 Precondition {
161 key: key.to_string(),
162 operator: op.to_string(),
163 value,
164 description: String::new(),
165 }
166 }
167
168 #[test]
169 fn exists_passes_when_present() {
170 let state = TestState {
171 data: [("x".into(), Value::from(1))].into(),
172 };
173 assert!(check_precondition(&pre("x", "exists", Value::Null), &state).is_none());
174 }
175
176 #[test]
177 fn exists_fails_when_absent() {
178 let state = TestState {
179 data: HashMap::new(),
180 };
181 assert!(check_precondition(&pre("x", "exists", Value::Null), &state).is_some());
182 }
183
184 #[test]
185 fn not_exists_passes_when_absent() {
186 let state = TestState {
187 data: HashMap::new(),
188 };
189 assert!(check_precondition(&pre("x", "not_exists", Value::Null), &state).is_none());
190 }
191
192 #[test]
193 fn eq_passes() {
194 let state = TestState {
195 data: [("x".into(), Value::from(42))].into(),
196 };
197 assert!(check_precondition(&pre("x", "eq", Value::from(42)), &state).is_none());
198 }
199
200 #[test]
201 fn eq_fails() {
202 let state = TestState {
203 data: [("x".into(), Value::from(1))].into(),
204 };
205 assert!(check_precondition(&pre("x", "eq", Value::from(2)), &state).is_some());
206 }
207
208 #[test]
209 fn neq_passes() {
210 let state = TestState {
211 data: [("x".into(), Value::from(1))].into(),
212 };
213 assert!(check_precondition(&pre("x", "neq", Value::from(2)), &state).is_none());
214 }
215
216 #[test]
217 fn gt_lt_gte_lte() {
218 let state = TestState {
219 data: [("n".into(), Value::from(10))].into(),
220 };
221 assert!(check_precondition(&pre("n", "gt", Value::from(5)), &state).is_none());
222 assert!(check_precondition(&pre("n", "gt", Value::from(20)), &state).is_some());
223 assert!(check_precondition(&pre("n", "lt", Value::from(20)), &state).is_none());
224 assert!(check_precondition(&pre("n", "gte", Value::from(10)), &state).is_none());
225 assert!(check_precondition(&pre("n", "lte", Value::from(10)), &state).is_none());
226 }
227
228 #[test]
229 fn contains_passes() {
230 let state = TestState {
231 data: [("msg".into(), Value::from("hello world"))].into(),
232 };
233 assert!(
234 check_precondition(&pre("msg", "contains", Value::from("world")), &state).is_none()
235 );
236 }
237
238 #[test]
239 fn contains_fails() {
240 let state = TestState {
241 data: [("msg".into(), Value::from("hello"))].into(),
242 };
243 assert!(
244 check_precondition(&pre("msg", "contains", Value::from("world")), &state).is_some()
245 );
246 }
247}