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