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