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