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, Transform};
12
13pub struct FilterExpr {
15 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 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 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 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 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 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 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}