1use crate::status::Status;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use strum::VariantNames;
5use strum_macros::EnumVariantNames;
6
7#[derive(Clone, Debug, Serialize, Deserialize, EnumVariantNames)]
8#[serde(rename_all = "snake_case")]
9#[strum(serialize_all = "snake_case")]
10#[serde(tag = "operator", content = "value")]
11pub enum Constraint {
12 StringEquals(String),
13 StringNotEquals(String),
14 StringContains(String),
15 StringContainsAll(Vec<String>),
16 StringContainsAny(Vec<String>),
17 StringDoesNotContain(String),
18 StringDoesNotContainAny(Vec<String>),
19 StringIn(Vec<String>),
20 StringNotIn(Vec<String>),
21 IntEquals(i64),
22 IntNotEquals(i64),
23 IntContains(i64),
24 IntContainsAll(Vec<i64>),
25 IntContainsAny(Vec<i64>),
26 IntDoesNotContain(i64),
27 IntDoesNotContainAny(Vec<i64>),
28 IntIn(Vec<i64>),
29 IntNotIn(Vec<i64>),
30 IntInRange(i64, i64),
31 IntNotInRange(i64, i64),
32 IntLessThan(i64),
33 IntLessThanInclusive(i64),
34 IntGreaterThan(i64),
35 IntGreaterThanInclusive(i64),
36 FloatEquals(f64),
37 FloatNotEquals(f64),
38 FloatContains(f64),
39 FloatDoesNotContain(f64),
40 FloatIn(Vec<f64>),
41 FloatNotIn(Vec<f64>),
42 FloatInRange(f64, f64),
43 FloatNotInRange(f64, f64),
44 FloatLessThan(f64),
45 FloatLessThanInclusive(f64),
46 FloatGreaterThan(f64),
47 FloatGreaterThanInclusive(f64),
48 BoolEquals(bool),
49}
50
51impl Constraint {
52 fn value_as_str_array(v: &Value) -> Option<Vec<&str>> {
53 v.as_array()
54 .map(|x| x.iter().filter_map(|y| y.as_str()).collect::<Vec<_>>())
55 }
56
57 fn value_as_i64_array(v: &Value) -> Option<Vec<i64>> {
58 v.as_array()
59 .map(|x| x.iter().filter_map(|y| y.as_i64()).collect::<Vec<_>>())
60 }
61
62 fn value_as_f64_array(v: &Value) -> Option<Vec<f64>> {
63 v.as_array()
64 .map(|x| x.iter().filter_map(|y| y.as_f64()).collect::<Vec<_>>())
65 }
66
67 pub fn check_value(&self, v: &Value) -> Status {
68 match *self {
69 Constraint::StringEquals(ref s) => match v.as_str() {
70 None => Status::NotMet,
71 Some(v) => {
72 if v == s {
73 Status::Met
74 } else {
75 Status::NotMet
76 }
77 }
78 },
79 Constraint::StringNotEquals(ref s) => match v.as_str() {
80 None => Status::NotMet,
81 Some(v) => {
82 if v != s {
83 Status::Met
84 } else {
85 Status::NotMet
86 }
87 }
88 },
89 Constraint::StringContains(ref s) => {
90 match Self::value_as_str_array(v) {
91 None => Status::NotMet,
92 Some(v) => {
93 if v.contains(&s.as_str()) {
94 Status::Met
95 } else {
96 Status::NotMet
97 }
98 }
99 }
100 }
101 Constraint::StringContainsAll(ref s) => {
102 match Self::value_as_str_array(v) {
103 None => Status::NotMet,
104 Some(v) => {
105 if s.iter().all(|y| v.contains(&y.as_str())) {
106 Status::Met
107 } else {
108 Status::NotMet
109 }
110 }
111 }
112 }
113 Constraint::StringContainsAny(ref s) => {
114 match Self::value_as_str_array(v) {
115 None => Status::NotMet,
116 Some(v) => {
117 if s.iter().any(|y| v.contains(&y.as_str())) {
118 Status::Met
119 } else {
120 Status::NotMet
121 }
122 }
123 }
124 }
125 Constraint::StringDoesNotContain(ref s) => {
126 match Self::value_as_str_array(v) {
127 None => Status::NotMet,
128 Some(v) => {
129 if !v.contains(&s.as_str()) {
130 Status::Met
131 } else {
132 Status::NotMet
133 }
134 }
135 }
136 }
137 Constraint::StringDoesNotContainAny(ref s) => {
138 match Self::value_as_str_array(v) {
139 None => Status::NotMet,
140 Some(v) => {
141 if s.iter().all(|y| !v.contains(&y.as_str())) {
142 Status::Met
143 } else {
144 Status::NotMet
145 }
146 }
147 }
148 }
149 Constraint::StringIn(ref ss) => match v.as_str() {
150 None => Status::NotMet,
151 Some(v) => {
152 if ss.iter().any(|s| s == v) {
153 Status::Met
154 } else {
155 Status::NotMet
156 }
157 }
158 },
159 Constraint::StringNotIn(ref ss) => match v.as_str() {
160 None => Status::NotMet,
161 Some(v) => {
162 if ss.iter().all(|s| s != v) {
163 Status::Met
164 } else {
165 Status::NotMet
166 }
167 }
168 },
169 Constraint::IntEquals(num) => match v.as_i64() {
170 None => Status::NotMet,
171 Some(v) => {
172 if v == num {
173 Status::Met
174 } else {
175 Status::NotMet
176 }
177 }
178 },
179 Constraint::IntNotEquals(num) => match v.as_i64() {
180 None => Status::NotMet,
181 Some(v) => {
182 if v != num {
183 Status::Met
184 } else {
185 Status::NotMet
186 }
187 }
188 },
189 Constraint::IntContains(num) => match Self::value_as_i64_array(v) {
190 None => Status::NotMet,
191 Some(v) => {
192 if v.contains(&num) {
193 Status::Met
194 } else {
195 Status::NotMet
196 }
197 }
198 },
199 Constraint::IntContainsAll(ref nums) => {
200 match Self::value_as_i64_array(v) {
201 None => Status::NotMet,
202 Some(v) => {
203 if nums.iter().all(|num| v.contains(&num)) {
204 Status::Met
205 } else {
206 Status::NotMet
207 }
208 }
209 }
210 }
211 Constraint::IntContainsAny(ref nums) => {
212 match Self::value_as_i64_array(v) {
213 None => Status::NotMet,
214 Some(v) => {
215 if nums.iter().any(|num| v.contains(&num)) {
216 Status::Met
217 } else {
218 Status::NotMet
219 }
220 }
221 }
222 }
223 Constraint::IntDoesNotContain(num) => {
224 match Self::value_as_i64_array(v) {
225 None => Status::NotMet,
226 Some(v) => {
227 if !v.contains(&num) {
228 Status::Met
229 } else {
230 Status::NotMet
231 }
232 }
233 }
234 }
235 Constraint::IntDoesNotContainAny(ref nums) => {
236 match Self::value_as_i64_array(v) {
237 None => Status::NotMet,
238 Some(v) => {
239 if nums.iter().all(|num| !v.contains(&num)) {
240 Status::Met
241 } else {
242 Status::NotMet
243 }
244 }
245 }
246 }
247 Constraint::IntIn(ref nums) => match v.as_i64() {
248 None => Status::NotMet,
249 Some(v) => {
250 if nums.iter().any(|&num| num == v) {
251 Status::Met
252 } else {
253 Status::NotMet
254 }
255 }
256 },
257 Constraint::IntNotIn(ref nums) => match v.as_i64() {
258 None => Status::NotMet,
259 Some(v) => {
260 if nums.iter().all(|&num| num != v) {
261 Status::Met
262 } else {
263 Status::NotMet
264 }
265 }
266 },
267 Constraint::IntInRange(start, end) => match v.as_i64() {
268 None => Status::NotMet,
269 Some(v) => {
270 if start <= v && v <= end {
271 Status::Met
272 } else {
273 Status::NotMet
274 }
275 }
276 },
277 Constraint::IntNotInRange(start, end) => match v.as_i64() {
278 None => Status::NotMet,
279 Some(v) => {
280 if start <= v && v <= end {
281 Status::NotMet
282 } else {
283 Status::Met
284 }
285 }
286 },
287 Constraint::IntLessThan(num) => match v.as_i64() {
288 None => Status::NotMet,
289 Some(v) => {
290 if v < num {
291 Status::Met
292 } else {
293 Status::NotMet
294 }
295 }
296 },
297 Constraint::IntLessThanInclusive(num) => match v.as_i64() {
298 None => Status::NotMet,
299 Some(v) => {
300 if v <= num {
301 Status::Met
302 } else {
303 Status::NotMet
304 }
305 }
306 },
307 Constraint::IntGreaterThan(num) => match v.as_i64() {
308 None => Status::NotMet,
309 Some(v) => {
310 if v > num {
311 Status::Met
312 } else {
313 Status::NotMet
314 }
315 }
316 },
317 Constraint::IntGreaterThanInclusive(num) => match v.as_i64() {
318 None => Status::NotMet,
319 Some(v) => {
320 if v >= num {
321 Status::Met
322 } else {
323 Status::NotMet
324 }
325 }
326 },
327 Constraint::FloatEquals(num) => match v.as_f64() {
328 None => Status::NotMet,
329 Some(v) => {
330 if (v - num).abs() < f64::EPSILON {
331 Status::Met
332 } else {
333 Status::NotMet
334 }
335 }
336 },
337 Constraint::FloatNotEquals(num) => match v.as_f64() {
338 None => Status::NotMet,
339 Some(v) => {
340 if (v - num).abs() > f64::EPSILON {
341 Status::Met
342 } else {
343 Status::NotMet
344 }
345 }
346 },
347 Constraint::FloatContains(num) => {
348 match Self::value_as_f64_array(v) {
349 None => Status::NotMet,
350 Some(v) => {
351 if v.contains(&num) {
352 Status::Met
353 } else {
354 Status::NotMet
355 }
356 }
357 }
358 }
359 Constraint::FloatDoesNotContain(num) => {
360 match Self::value_as_f64_array(v) {
361 None => Status::NotMet,
362 Some(v) => {
363 if !v.contains(&num) {
364 Status::Met
365 } else {
366 Status::NotMet
367 }
368 }
369 }
370 }
371 Constraint::FloatIn(ref nums) => match v.as_f64() {
372 None => Status::NotMet,
373 Some(v) => {
374 if nums.iter().any(|&num| (v - num).abs() < f64::EPSILON) {
375 Status::Met
376 } else {
377 Status::NotMet
378 }
379 }
380 },
381 Constraint::FloatNotIn(ref nums) => match v.as_f64() {
382 None => Status::NotMet,
383 Some(v) => {
384 if nums.iter().all(|&num| (v - num).abs() > f64::EPSILON) {
385 Status::Met
386 } else {
387 Status::NotMet
388 }
389 }
390 },
391 Constraint::FloatInRange(start, end) => match v.as_f64() {
392 None => Status::NotMet,
393 Some(v) => {
394 if start <= v && v <= end {
395 Status::Met
396 } else {
397 Status::NotMet
398 }
399 }
400 },
401 Constraint::FloatNotInRange(start, end) => match v.as_f64() {
402 None => Status::NotMet,
403 Some(v) => {
404 if start <= v && v <= end {
405 Status::NotMet
406 } else {
407 Status::Met
408 }
409 }
410 },
411 Constraint::FloatLessThan(num) => match v.as_f64() {
412 None => Status::NotMet,
413 Some(v) => {
414 if v < num {
415 Status::Met
416 } else {
417 Status::NotMet
418 }
419 }
420 },
421 Constraint::FloatLessThanInclusive(num) => match v.as_f64() {
422 None => Status::NotMet,
423 Some(v) => {
424 if v <= num {
425 Status::Met
426 } else {
427 Status::NotMet
428 }
429 }
430 },
431 Constraint::FloatGreaterThan(num) => match v.as_f64() {
432 None => Status::NotMet,
433 Some(v) => {
434 if v > num {
435 Status::Met
436 } else {
437 Status::NotMet
438 }
439 }
440 },
441 Constraint::FloatGreaterThanInclusive(num) => match v.as_f64() {
442 None => Status::NotMet,
443 Some(v) => {
444 if v >= num {
445 Status::Met
446 } else {
447 Status::NotMet
448 }
449 }
450 },
451 Constraint::BoolEquals(b) => match v.as_bool() {
452 None => Status::NotMet,
453 Some(v) => {
454 if v == b {
455 Status::Met
456 } else {
457 Status::NotMet
458 }
459 }
460 },
461 }
462 }
463
464 pub fn operators() -> &'static [&'static str] {
465 Constraint::VARIANTS
466 }
467}
468
469#[cfg(test)]
470mod tests {
471 use super::Constraint;
472
473 #[test]
474 fn available_operators() {
475 assert_eq!(Constraint::operators().len(), 37);
476 }
477}