filter_expr/
lib.rs

1//! A library for parsing the filter expression.
2
3mod ctx;
4mod error;
5mod expr;
6mod parser;
7mod token;
8
9pub use ctx::{SimpleContext, Context};
10pub use error::Error;
11pub use expr::{ExprValue, BoxedExprFn, ExprFn};
12
13use crate::expr::Expr;
14
15/// The filter expression.
16pub struct FilterExpr {
17    /// The expression of the filter. Possibly empty.
18    #[allow(unused)]
19    expr: Option<Expr>,
20}
21
22impl FilterExpr {
23    pub fn parse(expr: &str) -> Result<Self, Error> {
24        let expr = parse_expr(expr)?;
25        Ok(Self { expr: Some(expr) })
26    }
27
28    pub async fn eval(&self, ctx: &dyn Context) -> Result<bool, Error> {
29        if let Some(expr) = &self.expr {
30            let value = expr.eval(ctx).await?;
31            match value {
32                ExprValue::Bool(b) => Ok(b),
33                _ => Err(Error::InvalidValue(format!("{:?}", value))),
34            }
35        } else {
36            Ok(true)
37        }
38    }
39}
40
41fn parse_expr(input: &str) -> Result<Expr, Error> {
42    let tokens = token::parse_token(input)?;
43    let mut parser = parser::Parser::new(tokens);
44    Ok(parser.parse_expr()?)
45}
46
47#[cfg(test)]
48mod tests {
49    use crate::expr::{ExprFn, ExprFnContext};
50
51    use super::*;
52
53    #[tokio::test]
54    async fn test_parse_expr_and_then_eval() {
55        // Parse the filter-expr:
56        //
57        //     name = 'John' AND age > 18 AND 1 > 0
58        // =====================================================================
59
60        let input = "name = 'John' AND age > 18 AND 1 > 0";
61        let filter_expr = FilterExpr::parse(input).unwrap();
62
63        let ctx = simple_context! {
64            "name": "John",
65            "age": 19,
66        };
67        let result = filter_expr.eval(&ctx).await.unwrap();
68        assert_eq!(result, true);
69
70        let ctx = simple_context! {
71            "name": "John",
72            "age": 18,
73        };
74        let result = filter_expr.eval(&ctx).await.unwrap();
75        assert_eq!(result, false);
76
77        // Parse the filter-expr:
78        //
79        //     name = "John" AND age IN [18, 19, 20, 22] AND 1 > 0
80        // =====================================================================
81
82        let input = r#"name = "John" AND age IN [18, 19, 20, 22] AND 1 > 0"#;
83        let filter_expr = FilterExpr::parse(input).unwrap();
84
85        let ctx = simple_context! {
86            "name": "John",
87            "age": 19,
88        };
89        let result = filter_expr.eval(&ctx).await.unwrap();
90        assert_eq!(result, true.into());
91
92        let ctx = simple_context! {
93            "name": "John",
94            "age": 23,
95        };
96        let result = filter_expr.eval(&ctx).await.unwrap();
97        assert_eq!(result, false.into());
98
99        // Parse the filter-expr:
100        //
101        //     matches(name, "^J.*n$")
102        // =====================================================================
103
104        let input = r#"matches(name, "^J.*n$")"#;
105        let filter_expr = FilterExpr::parse(input).unwrap();
106
107        let ctx = simple_context! {
108            "name": "John",
109        };
110        let result = filter_expr.eval(&ctx).await.unwrap();
111        assert_eq!(result, true);
112
113        let ctx = simple_context! {
114            "name": "Jane",
115        };
116        let result = filter_expr.eval(&ctx).await.unwrap();
117        assert_eq!(result, false);
118
119        // Parse the filter-expr:
120        //
121        //     custom_add(1, 2) = 3
122        // =====================================================================
123
124        let input = r#"custom_add(1, 2) = a"#;
125        let filter_expr = FilterExpr::parse(input).unwrap();
126
127        struct CustomAddFn;
128        #[async_trait::async_trait]
129        impl ExprFn for CustomAddFn {
130            async fn call(&self, ctx: ExprFnContext) -> Result<ExprValue, Error> {
131                if ctx.args.len() != 2 {
132                    return Err(Error::InvalidArgumentCount {
133                        expected: 2,
134                        got: ctx.args.len(),
135                    });
136                }
137                let a = match ctx.args[0] {
138                    ExprValue::Int(a) => a,
139                    _ => {
140                        return Err(Error::InvalidArgumentType {
141                            expected: "integer".to_string(),
142                            got: format!("{:?}", ctx.args[0]),
143                        });
144                    }
145                };
146                let b = match ctx.args[1] {
147                    ExprValue::Int(b) => b,
148                    _ => {
149                        return Err(Error::InvalidArgumentType {
150                            expected: "integer".to_string(),
151                            got: format!("{:?}", ctx.args[1]),
152                        });
153                    }
154                };
155                Ok((a + b).into())
156            }
157        }
158
159        let mut ctx = simple_context! {
160            "a": 3,
161        };
162        ctx.add_fn("custom_add".to_string(), Box::new(CustomAddFn));
163        let result = filter_expr.eval(&ctx).await.unwrap();
164        assert_eq!(result, true);
165
166        let mut ctx = simple_context! {
167            "a": 4,
168        };
169        ctx.add_fn("custom_add".to_string(), Box::new(CustomAddFn));
170        let result = filter_expr.eval(&ctx).await.unwrap();
171        assert_eq!(result, false);
172
173        // Parse the filter-expr:
174        //
175        //     name != null
176        // =====================================================================
177
178        let input = r#"name != null"#;
179        let filter_expr = FilterExpr::parse(input).unwrap();
180
181        let ctx = simple_context! {
182            "name": ExprValue::Null,
183        };
184        let result = filter_expr.eval(&ctx).await.unwrap();
185        assert_eq!(result, false);
186
187        let ctx = simple_context! {
188            "name": "John",
189        };
190        let result = filter_expr.eval(&ctx).await.unwrap();
191        assert_eq!(result, true);
192    }
193}