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