1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
6#[serde(tag = "type", rename_all = "snake_case")]
7pub enum FilterExpression {
8 Eq {
10 field: String,
11 value: Value,
12 },
13 Ne {
14 field: String,
15 value: Value,
16 },
17 Gt {
18 field: String,
19 value: Value,
20 },
21 Gte {
22 field: String,
23 value: Value,
24 },
25 Lt {
26 field: String,
27 value: Value,
28 },
29 Lte {
30 field: String,
31 value: Value,
32 },
33
34 Range {
36 field: String,
37 gte: Option<Value>,
38 lte: Option<Value>,
39 },
40
41 In {
43 field: String,
44 values: Vec<Value>,
45 },
46
47 Match {
49 field: String,
50 text: String,
51 },
52
53 GeoRadius {
55 field: String,
56 lat: f64,
57 lon: f64,
58 radius_m: f64,
59 },
60 GeoBoundingBox {
61 field: String,
62 top_left: (f64, f64),
63 bottom_right: (f64, f64),
64 },
65
66 And(Vec<FilterExpression>),
68 Or(Vec<FilterExpression>),
69 Not(Box<FilterExpression>),
70
71 Exists {
73 field: String,
74 },
75 IsNull {
76 field: String,
77 },
78}
79
80impl FilterExpression {
81 pub fn eq(field: impl Into<String>, value: Value) -> Self {
83 Self::Eq {
84 field: field.into(),
85 value,
86 }
87 }
88
89 pub fn ne(field: impl Into<String>, value: Value) -> Self {
91 Self::Ne {
92 field: field.into(),
93 value,
94 }
95 }
96
97 pub fn gt(field: impl Into<String>, value: Value) -> Self {
99 Self::Gt {
100 field: field.into(),
101 value,
102 }
103 }
104
105 pub fn gte(field: impl Into<String>, value: Value) -> Self {
107 Self::Gte {
108 field: field.into(),
109 value,
110 }
111 }
112
113 pub fn lt(field: impl Into<String>, value: Value) -> Self {
115 Self::Lt {
116 field: field.into(),
117 value,
118 }
119 }
120
121 pub fn lte(field: impl Into<String>, value: Value) -> Self {
123 Self::Lte {
124 field: field.into(),
125 value,
126 }
127 }
128
129 pub fn range(field: impl Into<String>, gte: Option<Value>, lte: Option<Value>) -> Self {
131 Self::Range {
132 field: field.into(),
133 gte,
134 lte,
135 }
136 }
137
138 pub fn in_values(field: impl Into<String>, values: Vec<Value>) -> Self {
140 Self::In {
141 field: field.into(),
142 values,
143 }
144 }
145
146 pub fn match_text(field: impl Into<String>, text: impl Into<String>) -> Self {
148 Self::Match {
149 field: field.into(),
150 text: text.into(),
151 }
152 }
153
154 pub fn geo_radius(field: impl Into<String>, lat: f64, lon: f64, radius_m: f64) -> Self {
156 Self::GeoRadius {
157 field: field.into(),
158 lat,
159 lon,
160 radius_m,
161 }
162 }
163
164 pub fn geo_bounding_box(
166 field: impl Into<String>,
167 top_left: (f64, f64),
168 bottom_right: (f64, f64),
169 ) -> Self {
170 Self::GeoBoundingBox {
171 field: field.into(),
172 top_left,
173 bottom_right,
174 }
175 }
176
177 pub fn and(filters: Vec<FilterExpression>) -> Self {
179 Self::And(filters)
180 }
181
182 pub fn or(filters: Vec<FilterExpression>) -> Self {
184 Self::Or(filters)
185 }
186
187 pub fn not(filter: FilterExpression) -> Self {
189 Self::Not(Box::new(filter))
190 }
191
192 pub fn exists(field: impl Into<String>) -> Self {
194 Self::Exists {
195 field: field.into(),
196 }
197 }
198
199 pub fn is_null(field: impl Into<String>) -> Self {
201 Self::IsNull {
202 field: field.into(),
203 }
204 }
205
206 pub fn get_fields(&self) -> Vec<String> {
208 let mut fields = Vec::new();
209 self.collect_fields(&mut fields);
210 fields.sort();
211 fields.dedup();
212 fields
213 }
214
215 fn collect_fields(&self, fields: &mut Vec<String>) {
216 match self {
217 Self::Eq { field, .. }
218 | Self::Ne { field, .. }
219 | Self::Gt { field, .. }
220 | Self::Gte { field, .. }
221 | Self::Lt { field, .. }
222 | Self::Lte { field, .. }
223 | Self::Range { field, .. }
224 | Self::In { field, .. }
225 | Self::Match { field, .. }
226 | Self::GeoRadius { field, .. }
227 | Self::GeoBoundingBox { field, .. }
228 | Self::Exists { field }
229 | Self::IsNull { field } => {
230 fields.push(field.clone());
231 }
232 Self::And(exprs) | Self::Or(exprs) => {
233 for expr in exprs {
234 expr.collect_fields(fields);
235 }
236 }
237 Self::Not(expr) => {
238 expr.collect_fields(fields);
239 }
240 }
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247 use serde_json::json;
248
249 #[test]
250 fn test_filter_builders() {
251 let filter = FilterExpression::eq("status", json!("active"));
252 assert!(matches!(filter, FilterExpression::Eq { .. }));
253
254 let filter = FilterExpression::and(vec![
255 FilterExpression::eq("status", json!("active")),
256 FilterExpression::gte("age", json!(18)),
257 ]);
258 assert!(matches!(filter, FilterExpression::And(_)));
259 }
260
261 #[test]
262 fn test_get_fields() {
263 let filter = FilterExpression::and(vec![
264 FilterExpression::eq("status", json!("active")),
265 FilterExpression::or(vec![
266 FilterExpression::gte("age", json!(18)),
267 FilterExpression::lt("score", json!(100)),
268 ]),
269 ]);
270
271 let fields = filter.get_fields();
272 assert_eq!(fields, vec!["age", "score", "status"]);
273 }
274
275 #[test]
276 fn test_serialization() {
277 let filter = FilterExpression::eq("status", json!("active"));
278 let json = serde_json::to_string(&filter).unwrap();
279 let deserialized: FilterExpression = serde_json::from_str(&json).unwrap();
280 assert!(matches!(deserialized, FilterExpression::Eq { .. }));
281 }
282}