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