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