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}