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> {
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 pub fn new(expr: Option<Expr>) -> Self {
38 Self { expr }
39 }
40
41 pub fn expr(&self) -> Option<&Expr> {
43 self.expr.as_ref()
44 }
45
46 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 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 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 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 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 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 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 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}