1use serde::{Deserialize, Serialize};
29
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32pub struct Query {
33 pub root: QueryNode,
35}
36
37impl Query {
38 pub fn new(root: QueryNode) -> Self {
40 Self { root }
41 }
42
43 pub fn field_eq(field: impl Into<String>, value: impl Into<String>) -> Self {
45 Self::new(QueryNode::Field(FieldQuery {
46 field: field.into(),
47 operator: FieldOperator::Equals,
48 value: QueryValue::Text(value.into()),
49 }))
50 }
51
52 pub fn tags(field: impl Into<String>, values: Vec<String>) -> Self {
54 Self::new(QueryNode::Field(FieldQuery {
55 field: field.into(),
56 operator: FieldOperator::In,
57 value: QueryValue::Tags(values),
58 }))
59 }
60
61 pub fn numeric_range(field: impl Into<String>, min: Option<f64>, max: Option<f64>) -> Self {
63 Self::new(QueryNode::Field(FieldQuery {
64 field: field.into(),
65 operator: FieldOperator::Range,
66 value: QueryValue::NumericRange { min, max },
67 }))
68 }
69
70 pub fn text_search(field: impl Into<String>, text: impl Into<String>) -> Self {
72 Self::new(QueryNode::Field(FieldQuery {
73 field: field.into(),
74 operator: FieldOperator::Contains,
75 value: QueryValue::Text(text.into()),
76 }))
77 }
78
79 pub fn prefix(field: impl Into<String>, prefix: impl Into<String>) -> Self {
81 Self::new(QueryNode::Field(FieldQuery {
82 field: field.into(),
83 operator: FieldOperator::Prefix,
84 value: QueryValue::Text(prefix.into()),
85 }))
86 }
87
88 pub fn fuzzy(field: impl Into<String>, text: impl Into<String>) -> Self {
90 Self::new(QueryNode::Field(FieldQuery {
91 field: field.into(),
92 operator: FieldOperator::Fuzzy,
93 value: QueryValue::Text(text.into()),
94 }))
95 }
96
97 pub fn and(self, other: Query) -> Self {
99 Self::new(QueryNode::And(vec![self.root, other.root]))
100 }
101
102 pub fn or(self, other: Query) -> Self {
104 Self::new(QueryNode::Or(vec![self.root, other.root]))
105 }
106
107 pub fn negate(self) -> Self {
109 Self::new(QueryNode::Not(Box::new(self.root)))
110 }
111}
112
113#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
115pub enum QueryNode {
116 Field(FieldQuery),
118 And(Vec<QueryNode>),
120 Or(Vec<QueryNode>),
122 Not(Box<QueryNode>),
124}
125
126#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
128pub struct FieldQuery {
129 pub field: String,
131 pub operator: FieldOperator,
133 pub value: QueryValue,
135}
136
137#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
139pub enum FieldOperator {
140 Equals,
142 Contains,
144 Range,
146 In,
148 Prefix,
150 Fuzzy,
152}
153
154#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
156pub enum QueryValue {
157 Text(String),
159 Numeric(f64),
161 NumericRange { min: Option<f64>, max: Option<f64> },
163 Tags(Vec<String>),
165 Boolean(bool),
167}
168
169#[derive(Default)]
171pub struct QueryBuilder {
172 nodes: Vec<QueryNode>,
173}
174
175impl QueryBuilder {
176 pub fn new() -> Self {
178 Self { nodes: Vec::new() }
179 }
180
181 pub fn field_eq(mut self, field: impl Into<String>, value: impl Into<String>) -> Self {
183 self.nodes.push(QueryNode::Field(FieldQuery {
184 field: field.into(),
185 operator: FieldOperator::Equals,
186 value: QueryValue::Text(value.into()),
187 }));
188 self
189 }
190
191 pub fn numeric_eq(mut self, field: impl Into<String>, value: f64) -> Self {
193 self.nodes.push(QueryNode::Field(FieldQuery {
194 field: field.into(),
195 operator: FieldOperator::Equals,
196 value: QueryValue::Numeric(value),
197 }));
198 self
199 }
200
201 pub fn numeric_range(mut self, field: impl Into<String>, min: Option<f64>, max: Option<f64>) -> Self {
203 self.nodes.push(QueryNode::Field(FieldQuery {
204 field: field.into(),
205 operator: FieldOperator::Range,
206 value: QueryValue::NumericRange { min, max },
207 }));
208 self
209 }
210
211 pub fn tags(mut self, field: impl Into<String>, values: Vec<String>) -> Self {
213 self.nodes.push(QueryNode::Field(FieldQuery {
214 field: field.into(),
215 operator: FieldOperator::In,
216 value: QueryValue::Tags(values),
217 }));
218 self
219 }
220
221 pub fn contains(mut self, field: impl Into<String>, text: impl Into<String>) -> Self {
223 self.nodes.push(QueryNode::Field(FieldQuery {
224 field: field.into(),
225 operator: FieldOperator::Contains,
226 value: QueryValue::Text(text.into()),
227 }));
228 self
229 }
230
231 pub fn prefix(mut self, field: impl Into<String>, prefix: impl Into<String>) -> Self {
233 self.nodes.push(QueryNode::Field(FieldQuery {
234 field: field.into(),
235 operator: FieldOperator::Prefix,
236 value: QueryValue::Text(prefix.into()),
237 }));
238 self
239 }
240
241 pub fn fuzzy(mut self, field: impl Into<String>, text: impl Into<String>) -> Self {
243 self.nodes.push(QueryNode::Field(FieldQuery {
244 field: field.into(),
245 operator: FieldOperator::Fuzzy,
246 value: QueryValue::Text(text.into()),
247 }));
248 self
249 }
250
251 pub fn build_and(self) -> Query {
253 if self.nodes.is_empty() {
254 Query::new(QueryNode::Field(FieldQuery {
256 field: "*".to_string(),
257 operator: FieldOperator::Equals,
258 value: QueryValue::Text("*".to_string()),
259 }))
260 } else if self.nodes.len() == 1 {
261 Query::new(self.nodes.into_iter().next().unwrap())
262 } else {
263 Query::new(QueryNode::And(self.nodes))
264 }
265 }
266
267 pub fn build_or(self) -> Query {
269 if self.nodes.is_empty() {
270 Query::new(QueryNode::Field(FieldQuery {
272 field: "*".to_string(),
273 operator: FieldOperator::Equals,
274 value: QueryValue::Text("*".to_string()),
275 }))
276 } else if self.nodes.len() == 1 {
277 Query::new(self.nodes.into_iter().next().unwrap())
278 } else {
279 Query::new(QueryNode::Or(self.nodes))
280 }
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 #[test]
289 fn test_simple_field_query() {
290 let query = Query::field_eq("name", "Alice");
291 assert_eq!(
292 query.root,
293 QueryNode::Field(FieldQuery {
294 field: "name".to_string(),
295 operator: FieldOperator::Equals,
296 value: QueryValue::Text("Alice".to_string()),
297 })
298 );
299 }
300
301 #[test]
302 fn test_and_query() {
303 let query = Query::field_eq("name", "Alice")
304 .and(Query::numeric_range("age", Some(25.0), Some(40.0)));
305
306 match query.root {
307 QueryNode::And(nodes) => {
308 assert_eq!(nodes.len(), 2);
309 }
310 _ => panic!("Expected And node"),
311 }
312 }
313
314 #[test]
315 fn test_or_query() {
316 let query = Query::field_eq("status", "active")
317 .or(Query::field_eq("status", "pending"));
318
319 match query.root {
320 QueryNode::Or(nodes) => {
321 assert_eq!(nodes.len(), 2);
322 }
323 _ => panic!("Expected Or node"),
324 }
325 }
326
327 #[test]
328 fn test_not_query() {
329 let query = Query::field_eq("deleted", "true").negate();
330
331 match query.root {
332 QueryNode::Not(_) => {}
333 _ => panic!("Expected Not node"),
334 }
335 }
336
337 #[test]
338 fn test_tag_query() {
339 let query = Query::tags("tags", vec!["rust".to_string(), "database".to_string()]);
340
341 match query.root {
342 QueryNode::Field(FieldQuery { field, operator, value }) => {
343 assert_eq!(field, "tags");
344 assert_eq!(operator, FieldOperator::In);
345 assert_eq!(value, QueryValue::Tags(vec!["rust".to_string(), "database".to_string()]));
346 }
347 _ => panic!("Expected Field node"),
348 }
349 }
350
351 #[test]
352 fn test_query_builder_and() {
353 let query = QueryBuilder::new()
354 .field_eq("name", "Alice")
355 .numeric_range("age", Some(25.0), Some(40.0))
356 .tags("tags", vec!["rust".to_string()])
357 .build_and();
358
359 match query.root {
360 QueryNode::And(nodes) => {
361 assert_eq!(nodes.len(), 3);
362 }
363 _ => panic!("Expected And node"),
364 }
365 }
366
367 #[test]
368 fn test_query_builder_or() {
369 let query = QueryBuilder::new()
370 .field_eq("status", "active")
371 .field_eq("status", "pending")
372 .build_or();
373
374 match query.root {
375 QueryNode::Or(nodes) => {
376 assert_eq!(nodes.len(), 2);
377 }
378 _ => panic!("Expected Or node"),
379 }
380 }
381
382 #[test]
383 fn test_complex_query() {
384 let alice_query = Query::field_eq("name", "Alice")
386 .and(Query::numeric_range("age", Some(25.0), Some(40.0)));
387
388 let bob_query = Query::field_eq("name", "Bob")
389 .and(Query::numeric_range("age", Some(30.0), Some(50.0)));
390
391 let query = alice_query.or(bob_query);
392
393 match query.root {
394 QueryNode::Or(nodes) => {
395 assert_eq!(nodes.len(), 2);
396 for node in nodes {
398 match node {
399 QueryNode::And(inner_nodes) => {
400 assert_eq!(inner_nodes.len(), 2);
401 }
402 _ => panic!("Expected And node"),
403 }
404 }
405 }
406 _ => panic!("Expected Or node"),
407 }
408 }
409
410 #[test]
411 fn test_prefix_query() {
412 let query = Query::prefix("email", "admin@");
413 match query.root {
414 QueryNode::Field(FieldQuery { operator, .. }) => {
415 assert_eq!(operator, FieldOperator::Prefix);
416 }
417 _ => panic!("Expected Field node"),
418 }
419 }
420
421 #[test]
422 fn test_fuzzy_query() {
423 let query = Query::fuzzy("name", "alice");
424 match query.root {
425 QueryNode::Field(FieldQuery { operator, .. }) => {
426 assert_eq!(operator, FieldOperator::Fuzzy);
427 }
428 _ => panic!("Expected Field node"),
429 }
430 }
431
432 #[test]
433 fn test_empty_builder_and() {
434 let query = QueryBuilder::new().build_and();
435 match query.root {
437 QueryNode::Field(FieldQuery { field, .. }) => {
438 assert_eq!(field, "*");
439 }
440 _ => panic!("Expected Field node"),
441 }
442 }
443}