Skip to main content

samsvar/
lib.rs

1pub use serde_json::{json, Value};
2
3mod parser {
4    use super::{Expr, Schema};
5    use nom::{
6        branch::alt,
7        character::complete::{char, multispace0},
8        multi::separated_list0,
9        sequence::{delimited, pair, preceded},
10        IResult,
11    };
12
13    // Top-level parser for logical expressions (AND/OR combined)
14    pub fn evaluate(input: &str) -> IResult<&str, Schema> {
15        alt((or_expr, and_expr, parens, condition))(input)
16    }
17
18    fn condition(input: &str) -> IResult<&str, Schema> {
19        let (input, cond) = nom::bytes::complete::take_while(|c: char| {
20            c.is_alphanumeric() || c.is_whitespace() || "><=!.".contains(c)
21        })(input)?;
22        Ok((
23            input,
24            Schema::Expr(Expr::from_string(cond.trim().to_string()).unwrap()),
25        ))
26    }
27
28    fn parens(input: &str) -> IResult<&str, Schema> {
29        delimited(
30            pair(char('('), multispace0),
31            evaluate,
32            pair(multispace0, char(')')),
33        )(input)
34    }
35
36    fn and_expr(input: &str) -> IResult<&str, Schema> {
37        let (input, exprs) = separated_list0(
38            preceded(multispace0, char('&')),
39            preceded(multispace0, alt((parens, condition))),
40        )(input)?;
41        Ok((input, Schema::And(exprs)))
42    }
43
44    fn or_expr(input: &str) -> IResult<&str, Schema> {
45        let (input, exprs) = separated_list0(
46            preceded(multispace0, char('|')),
47            preceded(multispace0, alt((parens, and_expr))),
48        )(input)?;
49        Ok((input, Schema::Or(exprs)))
50    }
51}
52
53#[derive(Debug, Clone, Copy, Eq, PartialEq)]
54pub enum Operand {
55    GreaterThan,
56    Equal,
57    LessThan,
58    GreaterOrEqual,
59    LessOrEqual,
60    Contains,
61}
62
63impl Operand {
64    fn from_str(s: &str) -> Option<Self> {
65        match s {
66            ">" => Some(Self::GreaterThan),
67            "==" => Some(Self::Equal),
68            "<" => Some(Self::LessThan),
69            ">=" => Some(Self::GreaterOrEqual),
70            "<=" => Some(Self::LessOrEqual),
71            "contains" => Some(Self::Contains),
72            _ => None,
73        }
74    }
75}
76
77#[derive(Debug, Eq, PartialEq)]
78pub struct Expr {
79    key: String,
80    op: Operand,
81    val: Value,
82    // if set, won't panic if key is missing but instead provide the default value
83    default: Option<bool>,
84    /// Should be set if the [`Value`] is a serde object, it will then get the value with this key
85    dict_key: Option<String>,
86}
87
88impl Expr {
89    fn new(key: &str, op: &str, val: &str) -> Self {
90        Self {
91            key: key.trim().to_string(),
92            op: Operand::from_str(op).unwrap(),
93            val: string_to_serde_value(val.trim()),
94            default: None,
95            dict_key: None,
96        }
97    }
98
99    fn from_string(s: String) -> Option<Self> {
100        let s = s.trim();
101
102        for op in ["==", "<", ">", "<=", ">=", "contains"] {
103            if let Some((key, val)) = s.split_once(op) {
104                return Some(Self::new(key, op, val));
105            }
106        }
107
108        None
109    }
110}
111
112#[derive(Debug, Eq, PartialEq)]
113pub enum Schema {
114    Expr(Expr),
115    And(Vec<Schema>),
116    Or(Vec<Schema>),
117}
118
119impl Schema {
120    fn new(s: String) -> Option<Self> {
121        let mut schema = parser::evaluate(&s).unwrap().1;
122        schema.simplify();
123        Some(schema)
124    }
125
126    fn simplify(&mut self) {
127        let inner = match self {
128            Schema::Expr(_) => return,
129            Schema::And(s) | Schema::Or(s) => {
130                if s.len() == 1 {
131                    Some(s.remove(0))
132                } else {
133                    for x in s.iter_mut() {
134                        x.simplify();
135                    }
136                    None
137                }
138            }
139        };
140
141        if let Some(i) = inner {
142            *self = i;
143        }
144    }
145}
146
147fn string_to_serde_value(input: &str) -> Value {
148    if let Ok(boolean_value) = input.parse::<bool>() {
149        return Value::Bool(boolean_value);
150    }
151
152    if let Ok(number_value) = input.parse::<f64>() {
153        return Value::Number(serde_json::Number::from_f64(number_value).unwrap());
154    }
155
156    Value::String(input.to_string())
157}
158
159pub trait Matcher: std::fmt::Debug {
160    fn get_val(&self, key: &str) -> Option<Value>;
161
162    fn eval(&self, s: String) -> bool {
163        let schema = Schema::new(s).unwrap();
164        self.eval_schema(&schema)
165    }
166
167    fn eval_schema(&self, schema: &Schema) -> bool {
168        match schema {
169            Schema::And(exps) => exps.iter().all(|exp| self.eval_schema(exp)),
170            Schema::Or(exps) => exps.iter().any(|exp| self.eval_schema(exp)),
171            Schema::Expr(exp) => self.is_match(exp),
172        }
173    }
174
175    fn is_match(&self, x: &Expr) -> bool {
176        let op = x.op;
177        let val = x.val.to_owned();
178        let arg = self.get_val(&x.key).unwrap().into();
179
180        use Operand as OP;
181        use Value::*;
182
183        match (arg, op, val) {
184            (Number(val), OP::GreaterOrEqual, Number(arg)) => val.as_f64() >= arg.as_f64(),
185            (Number(val), OP::LessOrEqual, Number(arg)) => val.as_f64() <= arg.as_f64(),
186            (Number(val), OP::GreaterThan, Number(arg)) => val.as_f64() > arg.as_f64(),
187            (Number(val), OP::LessThan, Number(arg)) => val.as_f64() < arg.as_f64(),
188            (String(val), OP::Contains, String(arg)) => val.contains(&arg),
189            (String(val), OP::Equal, String(arg)) => val == arg,
190            (Number(val), OP::Equal, Number(arg)) => val.as_f64() == arg.as_f64(),
191            (Bool(val), OP::Equal, Bool(arg)) => val == arg,
192            (val, op, arg) => {
193                panic!(
194                    "incorrect combination of operands and operator: {} {:?} {}",
195                    val, op, arg
196                );
197            }
198        }
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[derive(Debug)]
207    struct TestStruct {}
208
209    use serde_json::json;
210
211    impl Matcher for TestStruct {
212        fn get_val(&self, key: &str) -> Option<Value> {
213            match key {
214                "age" => Some(json!(5.)),
215                "height" => Some(json!(180)),
216                "msg" => Some(json!("hello world")),
217                _ => None,
218            }
219        }
220    }
221
222    #[test]
223    fn loltest() {
224        let x = TestStruct {};
225        let input = "height > 170 && msg contains worl".to_string();
226        let res = x.eval(input);
227        dbg!(res);
228        panic!();
229    }
230
231    #[test]
232    fn test1() {
233        let input = "a>4".to_string();
234        assert_eq!(
235            Schema::new(input).unwrap(),
236            Schema::Expr(Expr::new("a", ">", "4"))
237        );
238    }
239
240    #[test]
241    fn test2() {
242        let input = "a < 3".to_string();
243        assert_eq!(
244            Schema::new(input).unwrap(),
245            Schema::Expr(Expr::new("a", "<", "3"))
246        );
247    }
248
249    #[test]
250    fn test3() {
251        let input = "a contains helloworld & b < 4 | c == true".to_string();
252        assert_eq!(
253            Schema::new(input).unwrap(),
254            Schema::Or(vec![
255                Schema::And(vec![
256                    Schema::Expr(Expr::new("a", "contains", "helloworld")),
257                    Schema::Expr(Expr::new("b", "<", "4"))
258                ]),
259                Schema::Expr(Expr::new("c", "==", "true"))
260            ])
261        );
262    }
263
264    #[test]
265    fn test4() {
266        let input = "a contains helloworld & (b < 4 | c == true)".to_string();
267        assert_eq!(
268            Schema::new(input).unwrap(),
269            Schema::And(vec![
270                Schema::Expr(Expr::new("a", "contains", "helloworld")),
271                Schema::Or(vec![
272                    Schema::Expr(Expr::new("b", "<", "4")),
273                    Schema::Expr(Expr::new("c", "==", "true"))
274                ]),
275            ])
276        );
277    }
278
279    #[test]
280    fn test5() {
281        let input = "a contains heyworld".to_string();
282        assert_eq!(
283            Schema::new(input).unwrap(),
284            Schema::Expr(Expr::new("a", "contains", "heyworld"))
285        );
286    }
287
288    #[test]
289    fn tokentest() {
290        let input = "a contains helloworld & (b < 4 | c == true)".to_string();
291        let result = Schema::new(input);
292        dbg!(result);
293        panic!();
294    }
295}