synapse_rpc/
criterion.rs

1use std::f32;
2
3use regex::{self, Regex};
4use chrono::{DateTime, Utc};
5
6use resource::ResourceKind;
7
8#[derive(Clone, Debug, Serialize, Deserialize)]
9#[serde(deny_unknown_fields)]
10pub struct Criterion {
11    pub field: String,
12    pub op: Operation,
13    pub value: Value,
14}
15
16#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
17#[serde(deny_unknown_fields)]
18pub enum Operation {
19    #[serde(rename = "==")]
20    Eq,
21    #[serde(rename = "!=")]
22    Neq,
23    #[serde(rename = ">")]
24    GT,
25    #[serde(rename = ">=")]
26    GTE,
27    #[serde(rename = "<")]
28    LT,
29    #[serde(rename = "<=")]
30    LTE,
31    #[serde(rename = "like")]
32    Like,
33    #[serde(rename = "ilike")]
34    ILike,
35    #[serde(rename = "in")]
36    In,
37    #[serde(rename = "!in")]
38    NotIn,
39    #[serde(rename = "has")]
40    Has,
41    #[serde(rename = "!has")]
42    NotHas,
43}
44
45#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
46#[serde(untagged)]
47#[serde(deny_unknown_fields)]
48pub enum Value {
49    B(bool),
50    S(String),
51    N(i64),
52    F(f32),
53    D(DateTime<Utc>),
54    E(Option<()>),
55    V(Vec<Value>),
56}
57
58#[derive(Debug, Clone, PartialEq)]
59pub enum Field<'a> {
60    B(bool),
61    S(&'a str),
62    N(i64),
63    F(f32),
64    D(DateTime<Utc>),
65    E(Option<()>),
66    V(Vec<Field<'a>>),
67}
68
69pub const FNULL: Field<'static> = Field::E(None);
70
71pub trait Queryable {
72    fn field(&self, field: &str) -> Option<Field>;
73}
74
75impl Criterion {
76    pub fn matches<Q: Queryable>(&self, q: &Q) -> bool {
77        if let Some(f) = q.field(&self.field) {
78            self.match_field(&f, self.op, &self.value)
79        } else {
80            false
81        }
82    }
83
84    fn match_field(&self, field: &Field, op: Operation, value: &Value) -> bool {
85        match (field, value) {
86            (&Field::V(ref items), &Value::V(ref vals)) => match op {
87                Operation::Eq => items
88                    .iter()
89                    .zip(vals)
90                    .all(|(f, v)| self.match_field(f, Operation::Eq, v)),
91                Operation::Neq => items
92                    .iter()
93                    .zip(vals)
94                    .any(|(f, v)| self.match_field(f, Operation::Neq, v)),
95                _ => false,
96            },
97            (&Field::V(ref items), v) => match op {
98                Operation::Has => items.iter().any(|f| {
99                    self.match_field(f, Operation::Eq, v)
100                        || self.match_field(f, Operation::ILike, v)
101                }),
102                Operation::NotHas => items.iter().all(|f| {
103                    self.match_field(f, Operation::Neq, v)
104                        && !self.match_field(f, Operation::ILike, v)
105                }),
106                _ => false,
107            },
108            (f, &Value::V(ref v)) => match op {
109                Operation::In => v.iter()
110                    .any(|item| self.match_field(f, Operation::Eq, item)),
111                Operation::NotIn => v.iter()
112                    .all(|item| self.match_field(f, Operation::Neq, item)),
113                _ => false,
114            },
115            (&Field::B(f), &Value::B(v)) => match op {
116                Operation::Eq => f == v,
117                Operation::Neq => f != v,
118                _ => false,
119            },
120            (&Field::S(ref f), &Value::S(ref v)) => match op {
121                Operation::Eq => f == v,
122                Operation::Neq => f != v,
123                Operation::Like => match_like(v, f),
124                Operation::ILike => match_ilike(v, f),
125                _ => false,
126            },
127            (&Field::N(f), &Value::N(v)) => match op {
128                Operation::Eq => f == v,
129                Operation::Neq => f != v,
130                Operation::GTE => f >= v,
131                Operation::GT => f > v,
132                Operation::LTE => f <= v,
133                Operation::LT => f < v,
134                _ => false,
135            },
136            (&Field::N(f), &Value::F(v)) => match op {
137                Operation::Eq => f as f32 - v <= f32::EPSILON,
138                Operation::Neq => f as f32 - v > f32::EPSILON,
139                Operation::GTE => f as f32 >= v,
140                Operation::GT => f as f32 > v,
141                Operation::LTE => f as f32 <= v,
142                Operation::LT => (f as f32) < v,
143                _ => false,
144            },
145            (&Field::F(f), &Value::N(v)) => match op {
146                Operation::Eq => f - v as f32 <= f32::EPSILON,
147                Operation::Neq => f - v as f32 > f32::EPSILON,
148                Operation::GTE => f >= v as f32,
149                Operation::GT => f > v as f32,
150                Operation::LTE => f <= v as f32,
151                Operation::LT => f < v as f32,
152                _ => false,
153            },
154            (&Field::F(f), &Value::F(v)) => match op {
155                Operation::Eq => f - v <= f32::EPSILON,
156                Operation::Neq => f - v > f32::EPSILON,
157                Operation::GTE => f >= v,
158                Operation::GT => f > v,
159                Operation::LTE => f <= v,
160                Operation::LT => f < v,
161                _ => false,
162            },
163            (&Field::D(f), &Value::D(v)) => match op {
164                Operation::Eq => f == v,
165                Operation::Neq => f != v,
166                Operation::GTE => f >= v,
167                Operation::GT => f > v,
168                Operation::LTE => f <= v,
169                Operation::LT => f < v,
170                _ => false,
171            },
172            (&Field::E(_), &Value::E(_)) => match op {
173                Operation::Eq => true,
174                _ => false,
175            },
176            (&Field::E(_), _) => match op {
177                Operation::Neq => true,
178                _ => false,
179            },
180            _ => match op {
181                Operation::Neq => true,
182                _ => false,
183            },
184        }
185    }
186}
187
188impl Default for ResourceKind {
189    fn default() -> ResourceKind {
190        ResourceKind::Torrent
191    }
192}
193
194fn match_like(pat: &str, s: &str) -> bool {
195    let mut p = regex::escape(pat);
196    p = p.replace("%", ".*");
197    p = p.replace("_", ".");
198    if let Ok(re) = Regex::new(&p) {
199        re.is_match(s)
200    } else {
201        false
202    }
203}
204
205fn match_ilike(pat: &str, s: &str) -> bool {
206    match_like(&pat.to_lowercase(), &s.to_lowercase())
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn test_like() {
215        assert!(match_like("hello", "hello"));
216        assert!(match_like("hello %", "hello world"));
217        assert!(match_like("%world", "hello world"));
218        assert!(!match_like("% world", "helloworld"));
219        assert!(match_like("%", "foo bar"));
220        assert!(match_like("fo%", "foo"));
221    }
222
223    struct Q;
224    impl Queryable for Q {
225        fn field(&self, f: &str) -> Option<Field> {
226            match f {
227                "s" => Some(Field::S("foo")),
228                "n" => Some(Field::N(1)),
229                "ob" => Some(Field::B(true)),
230                "on" => Some(Field::E(None)),
231                _ => None,
232            }
233        }
234    }
235
236    #[test]
237    fn test_match_bad_field() {
238        let c = Criterion {
239            field: "asdf".to_owned(),
240            op: Operation::Like,
241            value: Value::S("fo%".to_owned()),
242        };
243
244        let q = Q;
245        assert_eq!(q.field("asdf"), None);
246        assert!(!c.matches(&q));
247    }
248
249    #[test]
250    fn test_match() {
251        let c = Criterion {
252            field: "s".to_owned(),
253            op: Operation::Like,
254            value: Value::S("fo%".to_owned()),
255        };
256
257        let q = Q;
258        assert!(c.matches(&q));
259    }
260
261    #[test]
262    fn test_match_none() {
263        let c = Criterion {
264            field: "on".to_owned(),
265            op: Operation::Eq,
266            value: Value::E(None),
267        };
268
269        let q = Q;
270        assert!(c.matches(&q));
271    }
272
273    #[test]
274    fn test_match_some() {
275        let c = Criterion {
276            field: "ob".to_owned(),
277            op: Operation::Eq,
278            value: Value::B(true),
279        };
280
281        let q = Q;
282        assert!(c.matches(&q));
283    }
284
285    #[test]
286    fn test_match_none_in() {
287        let c = Criterion {
288            field: "on".to_owned(),
289            op: Operation::In,
290            value: Value::V(vec![Value::B(false), Value::E(None)]),
291        };
292
293        let q = Q;
294        assert!(c.matches(&q));
295    }
296
297    #[test]
298    fn test_match_none_not_in() {
299        let c = Criterion {
300            field: "on".to_owned(),
301            op: Operation::NotIn,
302            value: Value::V(vec![Value::B(false), Value::B(true)]),
303        };
304
305        let q = Q;
306        assert!(c.matches(&q));
307    }
308}