postrust_graphql/input/
filter.rs

1//! Filter input types for GraphQL queries.
2//!
3//! Provides type-specific filter inputs (StringFilterInput, IntFilterInput, etc.)
4//! that can be combined with AND/OR/NOT logic to form complex queries.
5
6use postrust_core::api_request::{
7    Field, Filter, LogicOperator, LogicTree, OpExpr, Operation, QuantOperator,
8};
9use serde::{Deserialize, Serialize};
10
11/// Filter input for String fields.
12#[derive(Debug, Clone, Default, Serialize, Deserialize)]
13pub struct StringFilterInput {
14    /// Equals
15    pub eq: Option<String>,
16    /// Not equals
17    pub neq: Option<String>,
18    /// LIKE pattern match (case-sensitive)
19    pub like: Option<String>,
20    /// ILIKE pattern match (case-insensitive)
21    pub ilike: Option<String>,
22    /// In list
23    #[serde(rename = "in")]
24    pub in_list: Option<Vec<String>>,
25    /// Is null check
26    #[serde(rename = "isNull")]
27    pub is_null: Option<bool>,
28    /// Starts with
29    #[serde(rename = "startsWith")]
30    pub starts_with: Option<String>,
31    /// Ends with
32    #[serde(rename = "endsWith")]
33    pub ends_with: Option<String>,
34    /// Contains
35    pub contains: Option<String>,
36}
37
38impl StringFilterInput {
39    /// Convert to a list of Filters for a given field.
40    pub fn to_filters(&self, field_name: &str) -> Vec<Filter> {
41        let mut filters = Vec::new();
42        let field = Field::simple(field_name);
43
44        if let Some(ref value) = self.eq {
45            filters.push(Filter::new(
46                field.clone(),
47                OpExpr::new(Operation::Quant {
48                    op: QuantOperator::Equal,
49                    quantifier: None,
50                    value: value.clone(),
51                }),
52            ));
53        }
54
55        if let Some(ref value) = self.neq {
56            filters.push(Filter::new(
57                field.clone(),
58                OpExpr::new(Operation::Quant {
59                    op: QuantOperator::Equal,
60                    quantifier: None,
61                    value: value.clone(),
62                })
63                .with_negated(true),
64            ));
65        }
66
67        if let Some(ref value) = self.like {
68            filters.push(Filter::new(
69                field.clone(),
70                OpExpr::new(Operation::Quant {
71                    op: QuantOperator::Like,
72                    quantifier: None,
73                    value: value.clone(),
74                }),
75            ));
76        }
77
78        if let Some(ref value) = self.ilike {
79            filters.push(Filter::new(
80                field.clone(),
81                OpExpr::new(Operation::Quant {
82                    op: QuantOperator::ILike,
83                    quantifier: None,
84                    value: value.clone(),
85                }),
86            ));
87        }
88
89        if let Some(ref values) = self.in_list {
90            filters.push(Filter::new(
91                field.clone(),
92                OpExpr::new(Operation::In(values.clone())),
93            ));
94        }
95
96        if let Some(is_null) = self.is_null {
97            let op_expr = OpExpr::new(Operation::Is(
98                postrust_core::api_request::IsValue::Null,
99            ));
100            filters.push(Filter::new(
101                field.clone(),
102                if is_null {
103                    op_expr
104                } else {
105                    op_expr.with_negated(true)
106                },
107            ));
108        }
109
110        if let Some(ref value) = self.starts_with {
111            filters.push(Filter::new(
112                field.clone(),
113                OpExpr::new(Operation::Quant {
114                    op: QuantOperator::Like,
115                    quantifier: None,
116                    value: format!("{}%", value),
117                }),
118            ));
119        }
120
121        if let Some(ref value) = self.ends_with {
122            filters.push(Filter::new(
123                field.clone(),
124                OpExpr::new(Operation::Quant {
125                    op: QuantOperator::Like,
126                    quantifier: None,
127                    value: format!("%{}", value),
128                }),
129            ));
130        }
131
132        if let Some(ref value) = self.contains {
133            filters.push(Filter::new(
134                field.clone(),
135                OpExpr::new(Operation::Quant {
136                    op: QuantOperator::Like,
137                    quantifier: None,
138                    value: format!("%{}%", value),
139                }),
140            ));
141        }
142
143        filters
144    }
145
146    /// Check if any filter is set.
147    pub fn is_empty(&self) -> bool {
148        self.eq.is_none()
149            && self.neq.is_none()
150            && self.like.is_none()
151            && self.ilike.is_none()
152            && self.in_list.is_none()
153            && self.is_null.is_none()
154            && self.starts_with.is_none()
155            && self.ends_with.is_none()
156            && self.contains.is_none()
157    }
158}
159
160/// Filter input for Int fields.
161#[derive(Debug, Clone, Default, Serialize, Deserialize)]
162pub struct IntFilterInput {
163    /// Equals
164    pub eq: Option<i64>,
165    /// Not equals
166    pub neq: Option<i64>,
167    /// Greater than
168    pub gt: Option<i64>,
169    /// Greater than or equal
170    pub gte: Option<i64>,
171    /// Less than
172    pub lt: Option<i64>,
173    /// Less than or equal
174    pub lte: Option<i64>,
175    /// In list
176    #[serde(rename = "in")]
177    pub in_list: Option<Vec<i64>>,
178    /// Is null check
179    #[serde(rename = "isNull")]
180    pub is_null: Option<bool>,
181}
182
183impl IntFilterInput {
184    /// Convert to a list of Filters for a given field.
185    pub fn to_filters(&self, field_name: &str) -> Vec<Filter> {
186        let mut filters = Vec::new();
187        let field = Field::simple(field_name);
188
189        if let Some(value) = self.eq {
190            filters.push(Filter::new(
191                field.clone(),
192                OpExpr::new(Operation::Quant {
193                    op: QuantOperator::Equal,
194                    quantifier: None,
195                    value: value.to_string(),
196                }),
197            ));
198        }
199
200        if let Some(value) = self.neq {
201            filters.push(Filter::new(
202                field.clone(),
203                OpExpr::new(Operation::Quant {
204                    op: QuantOperator::Equal,
205                    quantifier: None,
206                    value: value.to_string(),
207                })
208                .with_negated(true),
209            ));
210        }
211
212        if let Some(value) = self.gt {
213            filters.push(Filter::new(
214                field.clone(),
215                OpExpr::new(Operation::Quant {
216                    op: QuantOperator::GreaterThan,
217                    quantifier: None,
218                    value: value.to_string(),
219                }),
220            ));
221        }
222
223        if let Some(value) = self.gte {
224            filters.push(Filter::new(
225                field.clone(),
226                OpExpr::new(Operation::Quant {
227                    op: QuantOperator::GreaterThanEqual,
228                    quantifier: None,
229                    value: value.to_string(),
230                }),
231            ));
232        }
233
234        if let Some(value) = self.lt {
235            filters.push(Filter::new(
236                field.clone(),
237                OpExpr::new(Operation::Quant {
238                    op: QuantOperator::LessThan,
239                    quantifier: None,
240                    value: value.to_string(),
241                }),
242            ));
243        }
244
245        if let Some(value) = self.lte {
246            filters.push(Filter::new(
247                field.clone(),
248                OpExpr::new(Operation::Quant {
249                    op: QuantOperator::LessThanEqual,
250                    quantifier: None,
251                    value: value.to_string(),
252                }),
253            ));
254        }
255
256        if let Some(ref values) = self.in_list {
257            filters.push(Filter::new(
258                field.clone(),
259                OpExpr::new(Operation::In(
260                    values.iter().map(|v| v.to_string()).collect(),
261                )),
262            ));
263        }
264
265        if let Some(is_null) = self.is_null {
266            let op_expr = OpExpr::new(Operation::Is(
267                postrust_core::api_request::IsValue::Null,
268            ));
269            filters.push(Filter::new(
270                field.clone(),
271                if is_null {
272                    op_expr
273                } else {
274                    op_expr.with_negated(true)
275                },
276            ));
277        }
278
279        filters
280    }
281
282    /// Check if any filter is set.
283    pub fn is_empty(&self) -> bool {
284        self.eq.is_none()
285            && self.neq.is_none()
286            && self.gt.is_none()
287            && self.gte.is_none()
288            && self.lt.is_none()
289            && self.lte.is_none()
290            && self.in_list.is_none()
291            && self.is_null.is_none()
292    }
293}
294
295/// Filter input for Float fields.
296#[derive(Debug, Clone, Default, Serialize, Deserialize)]
297pub struct FloatFilterInput {
298    /// Equals
299    pub eq: Option<f64>,
300    /// Not equals
301    pub neq: Option<f64>,
302    /// Greater than
303    pub gt: Option<f64>,
304    /// Greater than or equal
305    pub gte: Option<f64>,
306    /// Less than
307    pub lt: Option<f64>,
308    /// Less than or equal
309    pub lte: Option<f64>,
310    /// Is null check
311    #[serde(rename = "isNull")]
312    pub is_null: Option<bool>,
313}
314
315impl FloatFilterInput {
316    /// Convert to a list of Filters for a given field.
317    pub fn to_filters(&self, field_name: &str) -> Vec<Filter> {
318        let mut filters = Vec::new();
319        let field = Field::simple(field_name);
320
321        if let Some(value) = self.eq {
322            filters.push(Filter::new(
323                field.clone(),
324                OpExpr::new(Operation::Quant {
325                    op: QuantOperator::Equal,
326                    quantifier: None,
327                    value: value.to_string(),
328                }),
329            ));
330        }
331
332        if let Some(value) = self.neq {
333            filters.push(Filter::new(
334                field.clone(),
335                OpExpr::new(Operation::Quant {
336                    op: QuantOperator::Equal,
337                    quantifier: None,
338                    value: value.to_string(),
339                })
340                .with_negated(true),
341            ));
342        }
343
344        if let Some(value) = self.gt {
345            filters.push(Filter::new(
346                field.clone(),
347                OpExpr::new(Operation::Quant {
348                    op: QuantOperator::GreaterThan,
349                    quantifier: None,
350                    value: value.to_string(),
351                }),
352            ));
353        }
354
355        if let Some(value) = self.gte {
356            filters.push(Filter::new(
357                field.clone(),
358                OpExpr::new(Operation::Quant {
359                    op: QuantOperator::GreaterThanEqual,
360                    quantifier: None,
361                    value: value.to_string(),
362                }),
363            ));
364        }
365
366        if let Some(value) = self.lt {
367            filters.push(Filter::new(
368                field.clone(),
369                OpExpr::new(Operation::Quant {
370                    op: QuantOperator::LessThan,
371                    quantifier: None,
372                    value: value.to_string(),
373                }),
374            ));
375        }
376
377        if let Some(value) = self.lte {
378            filters.push(Filter::new(
379                field.clone(),
380                OpExpr::new(Operation::Quant {
381                    op: QuantOperator::LessThanEqual,
382                    quantifier: None,
383                    value: value.to_string(),
384                }),
385            ));
386        }
387
388        if let Some(is_null) = self.is_null {
389            let op_expr = OpExpr::new(Operation::Is(
390                postrust_core::api_request::IsValue::Null,
391            ));
392            filters.push(Filter::new(
393                field.clone(),
394                if is_null {
395                    op_expr
396                } else {
397                    op_expr.with_negated(true)
398                },
399            ));
400        }
401
402        filters
403    }
404
405    /// Check if any filter is set.
406    pub fn is_empty(&self) -> bool {
407        self.eq.is_none()
408            && self.neq.is_none()
409            && self.gt.is_none()
410            && self.gte.is_none()
411            && self.lt.is_none()
412            && self.lte.is_none()
413            && self.is_null.is_none()
414    }
415}
416
417/// Filter input for Boolean fields.
418#[derive(Debug, Clone, Default, Serialize, Deserialize)]
419pub struct BooleanFilterInput {
420    /// Equals
421    pub eq: Option<bool>,
422    /// Is null check
423    #[serde(rename = "isNull")]
424    pub is_null: Option<bool>,
425}
426
427impl BooleanFilterInput {
428    /// Convert to a list of Filters for a given field.
429    pub fn to_filters(&self, field_name: &str) -> Vec<Filter> {
430        let mut filters = Vec::new();
431        let field = Field::simple(field_name);
432
433        if let Some(value) = self.eq {
434            filters.push(Filter::new(
435                field.clone(),
436                OpExpr::new(Operation::Is(if value {
437                    postrust_core::api_request::IsValue::True
438                } else {
439                    postrust_core::api_request::IsValue::False
440                })),
441            ));
442        }
443
444        if let Some(is_null) = self.is_null {
445            let op_expr = OpExpr::new(Operation::Is(
446                postrust_core::api_request::IsValue::Null,
447            ));
448            filters.push(Filter::new(
449                field.clone(),
450                if is_null {
451                    op_expr
452                } else {
453                    op_expr.with_negated(true)
454                },
455            ));
456        }
457
458        filters
459    }
460
461    /// Check if any filter is set.
462    pub fn is_empty(&self) -> bool {
463        self.eq.is_none() && self.is_null.is_none()
464    }
465}
466
467/// Filter input for UUID fields.
468#[derive(Debug, Clone, Default, Serialize, Deserialize)]
469pub struct UuidFilterInput {
470    /// Equals
471    pub eq: Option<String>,
472    /// Not equals
473    pub neq: Option<String>,
474    /// In list
475    #[serde(rename = "in")]
476    pub in_list: Option<Vec<String>>,
477    /// Is null check
478    #[serde(rename = "isNull")]
479    pub is_null: Option<bool>,
480}
481
482impl UuidFilterInput {
483    /// Convert to a list of Filters for a given field.
484    pub fn to_filters(&self, field_name: &str) -> Vec<Filter> {
485        let mut filters = Vec::new();
486        let field = Field::simple(field_name);
487
488        if let Some(ref value) = self.eq {
489            filters.push(Filter::new(
490                field.clone(),
491                OpExpr::new(Operation::Quant {
492                    op: QuantOperator::Equal,
493                    quantifier: None,
494                    value: value.clone(),
495                }),
496            ));
497        }
498
499        if let Some(ref value) = self.neq {
500            filters.push(Filter::new(
501                field.clone(),
502                OpExpr::new(Operation::Quant {
503                    op: QuantOperator::Equal,
504                    quantifier: None,
505                    value: value.clone(),
506                })
507                .with_negated(true),
508            ));
509        }
510
511        if let Some(ref values) = self.in_list {
512            filters.push(Filter::new(
513                field.clone(),
514                OpExpr::new(Operation::In(values.clone())),
515            ));
516        }
517
518        if let Some(is_null) = self.is_null {
519            let op_expr = OpExpr::new(Operation::Is(
520                postrust_core::api_request::IsValue::Null,
521            ));
522            filters.push(Filter::new(
523                field.clone(),
524                if is_null {
525                    op_expr
526                } else {
527                    op_expr.with_negated(true)
528                },
529            ));
530        }
531
532        filters
533    }
534
535    /// Check if any filter is set.
536    pub fn is_empty(&self) -> bool {
537        self.eq.is_none()
538            && self.neq.is_none()
539            && self.in_list.is_none()
540            && self.is_null.is_none()
541    }
542}
543
544/// Convert a list of filters to a LogicTree with AND logic.
545pub fn filters_to_logic_tree(filters: Vec<Filter>) -> Option<LogicTree> {
546    if filters.is_empty() {
547        return None;
548    }
549
550    if filters.len() == 1 {
551        return Some(LogicTree::Stmt(filters.into_iter().next().unwrap()));
552    }
553
554    Some(LogicTree::Expr {
555        negated: false,
556        op: LogicOperator::And,
557        children: filters.into_iter().map(LogicTree::Stmt).collect(),
558    })
559}
560
561/// Combine multiple LogicTrees with AND logic.
562pub fn combine_with_and(trees: Vec<LogicTree>) -> Option<LogicTree> {
563    if trees.is_empty() {
564        return None;
565    }
566
567    if trees.len() == 1 {
568        return Some(trees.into_iter().next().unwrap());
569    }
570
571    Some(LogicTree::Expr {
572        negated: false,
573        op: LogicOperator::And,
574        children: trees,
575    })
576}
577
578/// Combine multiple LogicTrees with OR logic.
579pub fn combine_with_or(trees: Vec<LogicTree>) -> Option<LogicTree> {
580    if trees.is_empty() {
581        return None;
582    }
583
584    if trees.len() == 1 {
585        return Some(trees.into_iter().next().unwrap());
586    }
587
588    Some(LogicTree::Expr {
589        negated: false,
590        op: LogicOperator::Or,
591        children: trees,
592    })
593}
594
595/// Negate a LogicTree.
596pub fn negate_tree(tree: LogicTree) -> LogicTree {
597    match tree {
598        LogicTree::Expr {
599            negated,
600            op,
601            children,
602        } => LogicTree::Expr {
603            negated: !negated,
604            op,
605            children,
606        },
607        LogicTree::Stmt(filter) => {
608            let negated_expr = OpExpr {
609                negated: !filter.op_expr.negated,
610                operation: filter.op_expr.operation,
611            };
612            LogicTree::Stmt(Filter::new(filter.field, negated_expr))
613        }
614    }
615}
616
617/// Extension trait to add with_negated to OpExpr.
618trait OpExprExt {
619    fn with_negated(self, negated: bool) -> Self;
620}
621
622impl OpExprExt for OpExpr {
623    fn with_negated(mut self, negated: bool) -> Self {
624        self.negated = negated;
625        self
626    }
627}
628
629#[cfg(test)]
630mod tests {
631    use super::*;
632    use pretty_assertions::assert_eq;
633
634    // ============================================================================
635    // StringFilterInput Tests
636    // ============================================================================
637
638    #[test]
639    fn test_string_filter_eq() {
640        let filter = StringFilterInput {
641            eq: Some("test".to_string()),
642            ..Default::default()
643        };
644
645        let filters = filter.to_filters("name");
646        assert_eq!(filters.len(), 1);
647        assert_eq!(filters[0].field.name, "name");
648
649        match &filters[0].op_expr.operation {
650            Operation::Quant { op, value, .. } => {
651                assert_eq!(*op, QuantOperator::Equal);
652                assert_eq!(value, "test");
653            }
654            _ => panic!("Expected Quant operation"),
655        }
656        assert!(!filters[0].op_expr.negated);
657    }
658
659    #[test]
660    fn test_string_filter_neq() {
661        let filter = StringFilterInput {
662            neq: Some("test".to_string()),
663            ..Default::default()
664        };
665
666        let filters = filter.to_filters("name");
667        assert_eq!(filters.len(), 1);
668
669        match &filters[0].op_expr.operation {
670            Operation::Quant { op, value, .. } => {
671                assert_eq!(*op, QuantOperator::Equal);
672                assert_eq!(value, "test");
673            }
674            _ => panic!("Expected Quant operation"),
675        }
676        assert!(filters[0].op_expr.negated);
677    }
678
679    #[test]
680    fn test_string_filter_like() {
681        let filter = StringFilterInput {
682            like: Some("%test%".to_string()),
683            ..Default::default()
684        };
685
686        let filters = filter.to_filters("name");
687        assert_eq!(filters.len(), 1);
688
689        match &filters[0].op_expr.operation {
690            Operation::Quant { op, value, .. } => {
691                assert_eq!(*op, QuantOperator::Like);
692                assert_eq!(value, "%test%");
693            }
694            _ => panic!("Expected Quant operation"),
695        }
696    }
697
698    #[test]
699    fn test_string_filter_ilike() {
700        let filter = StringFilterInput {
701            ilike: Some("%TEST%".to_string()),
702            ..Default::default()
703        };
704
705        let filters = filter.to_filters("name");
706        assert_eq!(filters.len(), 1);
707
708        match &filters[0].op_expr.operation {
709            Operation::Quant { op, value, .. } => {
710                assert_eq!(*op, QuantOperator::ILike);
711                assert_eq!(value, "%TEST%");
712            }
713            _ => panic!("Expected Quant operation"),
714        }
715    }
716
717    #[test]
718    fn test_string_filter_in() {
719        let filter = StringFilterInput {
720            in_list: Some(vec!["a".to_string(), "b".to_string(), "c".to_string()]),
721            ..Default::default()
722        };
723
724        let filters = filter.to_filters("status");
725        assert_eq!(filters.len(), 1);
726
727        match &filters[0].op_expr.operation {
728            Operation::In(values) => {
729                assert_eq!(values.len(), 3);
730                assert_eq!(values[0], "a");
731                assert_eq!(values[1], "b");
732                assert_eq!(values[2], "c");
733            }
734            _ => panic!("Expected In operation"),
735        }
736    }
737
738    #[test]
739    fn test_string_filter_is_null_true() {
740        let filter = StringFilterInput {
741            is_null: Some(true),
742            ..Default::default()
743        };
744
745        let filters = filter.to_filters("name");
746        assert_eq!(filters.len(), 1);
747
748        match &filters[0].op_expr.operation {
749            Operation::Is(postrust_core::api_request::IsValue::Null) => {}
750            _ => panic!("Expected Is Null operation"),
751        }
752        assert!(!filters[0].op_expr.negated);
753    }
754
755    #[test]
756    fn test_string_filter_is_null_false() {
757        let filter = StringFilterInput {
758            is_null: Some(false),
759            ..Default::default()
760        };
761
762        let filters = filter.to_filters("name");
763        assert_eq!(filters.len(), 1);
764
765        match &filters[0].op_expr.operation {
766            Operation::Is(postrust_core::api_request::IsValue::Null) => {}
767            _ => panic!("Expected Is Null operation"),
768        }
769        assert!(filters[0].op_expr.negated);
770    }
771
772    #[test]
773    fn test_string_filter_starts_with() {
774        let filter = StringFilterInput {
775            starts_with: Some("hello".to_string()),
776            ..Default::default()
777        };
778
779        let filters = filter.to_filters("name");
780        assert_eq!(filters.len(), 1);
781
782        match &filters[0].op_expr.operation {
783            Operation::Quant { op, value, .. } => {
784                assert_eq!(*op, QuantOperator::Like);
785                assert_eq!(value, "hello%");
786            }
787            _ => panic!("Expected Quant operation"),
788        }
789    }
790
791    #[test]
792    fn test_string_filter_ends_with() {
793        let filter = StringFilterInput {
794            ends_with: Some("world".to_string()),
795            ..Default::default()
796        };
797
798        let filters = filter.to_filters("name");
799        assert_eq!(filters.len(), 1);
800
801        match &filters[0].op_expr.operation {
802            Operation::Quant { op, value, .. } => {
803                assert_eq!(*op, QuantOperator::Like);
804                assert_eq!(value, "%world");
805            }
806            _ => panic!("Expected Quant operation"),
807        }
808    }
809
810    #[test]
811    fn test_string_filter_contains() {
812        let filter = StringFilterInput {
813            contains: Some("foo".to_string()),
814            ..Default::default()
815        };
816
817        let filters = filter.to_filters("name");
818        assert_eq!(filters.len(), 1);
819
820        match &filters[0].op_expr.operation {
821            Operation::Quant { op, value, .. } => {
822                assert_eq!(*op, QuantOperator::Like);
823                assert_eq!(value, "%foo%");
824            }
825            _ => panic!("Expected Quant operation"),
826        }
827    }
828
829    #[test]
830    fn test_string_filter_multiple() {
831        let filter = StringFilterInput {
832            eq: Some("test".to_string()),
833            starts_with: Some("t".to_string()),
834            ..Default::default()
835        };
836
837        let filters = filter.to_filters("name");
838        assert_eq!(filters.len(), 2);
839    }
840
841    #[test]
842    fn test_string_filter_is_empty() {
843        let filter = StringFilterInput::default();
844        assert!(filter.is_empty());
845
846        let filter = StringFilterInput {
847            eq: Some("test".to_string()),
848            ..Default::default()
849        };
850        assert!(!filter.is_empty());
851    }
852
853    // ============================================================================
854    // IntFilterInput Tests
855    // ============================================================================
856
857    #[test]
858    fn test_int_filter_eq() {
859        let filter = IntFilterInput {
860            eq: Some(42),
861            ..Default::default()
862        };
863
864        let filters = filter.to_filters("age");
865        assert_eq!(filters.len(), 1);
866
867        match &filters[0].op_expr.operation {
868            Operation::Quant { op, value, .. } => {
869                assert_eq!(*op, QuantOperator::Equal);
870                assert_eq!(value, "42");
871            }
872            _ => panic!("Expected Quant operation"),
873        }
874    }
875
876    #[test]
877    fn test_int_filter_neq() {
878        let filter = IntFilterInput {
879            neq: Some(42),
880            ..Default::default()
881        };
882
883        let filters = filter.to_filters("age");
884        assert_eq!(filters.len(), 1);
885        assert!(filters[0].op_expr.negated);
886    }
887
888    #[test]
889    fn test_int_filter_gt() {
890        let filter = IntFilterInput {
891            gt: Some(18),
892            ..Default::default()
893        };
894
895        let filters = filter.to_filters("age");
896        assert_eq!(filters.len(), 1);
897
898        match &filters[0].op_expr.operation {
899            Operation::Quant { op, value, .. } => {
900                assert_eq!(*op, QuantOperator::GreaterThan);
901                assert_eq!(value, "18");
902            }
903            _ => panic!("Expected Quant operation"),
904        }
905    }
906
907    #[test]
908    fn test_int_filter_gte() {
909        let filter = IntFilterInput {
910            gte: Some(18),
911            ..Default::default()
912        };
913
914        let filters = filter.to_filters("age");
915        assert_eq!(filters.len(), 1);
916
917        match &filters[0].op_expr.operation {
918            Operation::Quant { op, value, .. } => {
919                assert_eq!(*op, QuantOperator::GreaterThanEqual);
920                assert_eq!(value, "18");
921            }
922            _ => panic!("Expected Quant operation"),
923        }
924    }
925
926    #[test]
927    fn test_int_filter_lt() {
928        let filter = IntFilterInput {
929            lt: Some(65),
930            ..Default::default()
931        };
932
933        let filters = filter.to_filters("age");
934        assert_eq!(filters.len(), 1);
935
936        match &filters[0].op_expr.operation {
937            Operation::Quant { op, value, .. } => {
938                assert_eq!(*op, QuantOperator::LessThan);
939                assert_eq!(value, "65");
940            }
941            _ => panic!("Expected Quant operation"),
942        }
943    }
944
945    #[test]
946    fn test_int_filter_lte() {
947        let filter = IntFilterInput {
948            lte: Some(65),
949            ..Default::default()
950        };
951
952        let filters = filter.to_filters("age");
953        assert_eq!(filters.len(), 1);
954
955        match &filters[0].op_expr.operation {
956            Operation::Quant { op, value, .. } => {
957                assert_eq!(*op, QuantOperator::LessThanEqual);
958                assert_eq!(value, "65");
959            }
960            _ => panic!("Expected Quant operation"),
961        }
962    }
963
964    #[test]
965    fn test_int_filter_in() {
966        let filter = IntFilterInput {
967            in_list: Some(vec![1, 2, 3]),
968            ..Default::default()
969        };
970
971        let filters = filter.to_filters("id");
972        assert_eq!(filters.len(), 1);
973
974        match &filters[0].op_expr.operation {
975            Operation::In(values) => {
976                assert_eq!(values.len(), 3);
977                assert_eq!(values[0], "1");
978                assert_eq!(values[1], "2");
979                assert_eq!(values[2], "3");
980            }
981            _ => panic!("Expected In operation"),
982        }
983    }
984
985    #[test]
986    fn test_int_filter_range() {
987        let filter = IntFilterInput {
988            gte: Some(18),
989            lte: Some(65),
990            ..Default::default()
991        };
992
993        let filters = filter.to_filters("age");
994        assert_eq!(filters.len(), 2);
995    }
996
997    #[test]
998    fn test_int_filter_is_empty() {
999        let filter = IntFilterInput::default();
1000        assert!(filter.is_empty());
1001
1002        let filter = IntFilterInput {
1003            eq: Some(42),
1004            ..Default::default()
1005        };
1006        assert!(!filter.is_empty());
1007    }
1008
1009    // ============================================================================
1010    // BooleanFilterInput Tests
1011    // ============================================================================
1012
1013    #[test]
1014    fn test_boolean_filter_eq_true() {
1015        let filter = BooleanFilterInput {
1016            eq: Some(true),
1017            ..Default::default()
1018        };
1019
1020        let filters = filter.to_filters("active");
1021        assert_eq!(filters.len(), 1);
1022
1023        match &filters[0].op_expr.operation {
1024            Operation::Is(postrust_core::api_request::IsValue::True) => {}
1025            _ => panic!("Expected Is True operation"),
1026        }
1027    }
1028
1029    #[test]
1030    fn test_boolean_filter_eq_false() {
1031        let filter = BooleanFilterInput {
1032            eq: Some(false),
1033            ..Default::default()
1034        };
1035
1036        let filters = filter.to_filters("active");
1037        assert_eq!(filters.len(), 1);
1038
1039        match &filters[0].op_expr.operation {
1040            Operation::Is(postrust_core::api_request::IsValue::False) => {}
1041            _ => panic!("Expected Is False operation"),
1042        }
1043    }
1044
1045    #[test]
1046    fn test_boolean_filter_is_empty() {
1047        let filter = BooleanFilterInput::default();
1048        assert!(filter.is_empty());
1049
1050        let filter = BooleanFilterInput {
1051            eq: Some(true),
1052            ..Default::default()
1053        };
1054        assert!(!filter.is_empty());
1055    }
1056
1057    // ============================================================================
1058    // FloatFilterInput Tests
1059    // ============================================================================
1060
1061    #[test]
1062    fn test_float_filter_eq() {
1063        let filter = FloatFilterInput {
1064            eq: Some(3.14),
1065            ..Default::default()
1066        };
1067
1068        let filters = filter.to_filters("price");
1069        assert_eq!(filters.len(), 1);
1070
1071        match &filters[0].op_expr.operation {
1072            Operation::Quant { op, value, .. } => {
1073                assert_eq!(*op, QuantOperator::Equal);
1074                assert_eq!(value, "3.14");
1075            }
1076            _ => panic!("Expected Quant operation"),
1077        }
1078    }
1079
1080    #[test]
1081    fn test_float_filter_gt() {
1082        let filter = FloatFilterInput {
1083            gt: Some(100.0),
1084            ..Default::default()
1085        };
1086
1087        let filters = filter.to_filters("price");
1088        assert_eq!(filters.len(), 1);
1089
1090        match &filters[0].op_expr.operation {
1091            Operation::Quant { op, value, .. } => {
1092                assert_eq!(*op, QuantOperator::GreaterThan);
1093                assert_eq!(value, "100");
1094            }
1095            _ => panic!("Expected Quant operation"),
1096        }
1097    }
1098
1099    #[test]
1100    fn test_float_filter_is_empty() {
1101        let filter = FloatFilterInput::default();
1102        assert!(filter.is_empty());
1103    }
1104
1105    // ============================================================================
1106    // UuidFilterInput Tests
1107    // ============================================================================
1108
1109    #[test]
1110    fn test_uuid_filter_eq() {
1111        let filter = UuidFilterInput {
1112            eq: Some("550e8400-e29b-41d4-a716-446655440000".to_string()),
1113            ..Default::default()
1114        };
1115
1116        let filters = filter.to_filters("id");
1117        assert_eq!(filters.len(), 1);
1118
1119        match &filters[0].op_expr.operation {
1120            Operation::Quant { op, value, .. } => {
1121                assert_eq!(*op, QuantOperator::Equal);
1122                assert_eq!(value, "550e8400-e29b-41d4-a716-446655440000");
1123            }
1124            _ => panic!("Expected Quant operation"),
1125        }
1126    }
1127
1128    #[test]
1129    fn test_uuid_filter_in() {
1130        let filter = UuidFilterInput {
1131            in_list: Some(vec![
1132                "550e8400-e29b-41d4-a716-446655440000".to_string(),
1133                "550e8400-e29b-41d4-a716-446655440001".to_string(),
1134            ]),
1135            ..Default::default()
1136        };
1137
1138        let filters = filter.to_filters("id");
1139        assert_eq!(filters.len(), 1);
1140
1141        match &filters[0].op_expr.operation {
1142            Operation::In(values) => {
1143                assert_eq!(values.len(), 2);
1144            }
1145            _ => panic!("Expected In operation"),
1146        }
1147    }
1148
1149    #[test]
1150    fn test_uuid_filter_is_empty() {
1151        let filter = UuidFilterInput::default();
1152        assert!(filter.is_empty());
1153    }
1154
1155    // ============================================================================
1156    // LogicTree Tests
1157    // ============================================================================
1158
1159    #[test]
1160    fn test_filters_to_logic_tree_empty() {
1161        let tree = filters_to_logic_tree(vec![]);
1162        assert!(tree.is_none());
1163    }
1164
1165    #[test]
1166    fn test_filters_to_logic_tree_single() {
1167        let filter = Filter::new(
1168            Field::simple("name"),
1169            OpExpr::new(Operation::Quant {
1170                op: QuantOperator::Equal,
1171                quantifier: None,
1172                value: "test".to_string(),
1173            }),
1174        );
1175
1176        let tree = filters_to_logic_tree(vec![filter]).unwrap();
1177        match tree {
1178            LogicTree::Stmt(_) => {}
1179            _ => panic!("Expected Stmt for single filter"),
1180        }
1181    }
1182
1183    #[test]
1184    fn test_filters_to_logic_tree_multiple() {
1185        let filter1 = Filter::new(
1186            Field::simple("name"),
1187            OpExpr::new(Operation::Quant {
1188                op: QuantOperator::Equal,
1189                quantifier: None,
1190                value: "test".to_string(),
1191            }),
1192        );
1193        let filter2 = Filter::new(
1194            Field::simple("age"),
1195            OpExpr::new(Operation::Quant {
1196                op: QuantOperator::GreaterThan,
1197                quantifier: None,
1198                value: "18".to_string(),
1199            }),
1200        );
1201
1202        let tree = filters_to_logic_tree(vec![filter1, filter2]).unwrap();
1203        match tree {
1204            LogicTree::Expr { op, children, .. } => {
1205                assert_eq!(op, LogicOperator::And);
1206                assert_eq!(children.len(), 2);
1207            }
1208            _ => panic!("Expected Expr for multiple filters"),
1209        }
1210    }
1211
1212    #[test]
1213    fn test_combine_with_and() {
1214        let filter1 = Filter::new(
1215            Field::simple("a"),
1216            OpExpr::new(Operation::Quant {
1217                op: QuantOperator::Equal,
1218                quantifier: None,
1219                value: "1".to_string(),
1220            }),
1221        );
1222        let filter2 = Filter::new(
1223            Field::simple("b"),
1224            OpExpr::new(Operation::Quant {
1225                op: QuantOperator::Equal,
1226                quantifier: None,
1227                value: "2".to_string(),
1228            }),
1229        );
1230
1231        let tree1 = LogicTree::Stmt(filter1);
1232        let tree2 = LogicTree::Stmt(filter2);
1233
1234        let combined = combine_with_and(vec![tree1, tree2]).unwrap();
1235        match combined {
1236            LogicTree::Expr { op, children, .. } => {
1237                assert_eq!(op, LogicOperator::And);
1238                assert_eq!(children.len(), 2);
1239            }
1240            _ => panic!("Expected Expr"),
1241        }
1242    }
1243
1244    #[test]
1245    fn test_combine_with_or() {
1246        let filter1 = Filter::new(
1247            Field::simple("a"),
1248            OpExpr::new(Operation::Quant {
1249                op: QuantOperator::Equal,
1250                quantifier: None,
1251                value: "1".to_string(),
1252            }),
1253        );
1254        let filter2 = Filter::new(
1255            Field::simple("b"),
1256            OpExpr::new(Operation::Quant {
1257                op: QuantOperator::Equal,
1258                quantifier: None,
1259                value: "2".to_string(),
1260            }),
1261        );
1262
1263        let tree1 = LogicTree::Stmt(filter1);
1264        let tree2 = LogicTree::Stmt(filter2);
1265
1266        let combined = combine_with_or(vec![tree1, tree2]).unwrap();
1267        match combined {
1268            LogicTree::Expr { op, children, .. } => {
1269                assert_eq!(op, LogicOperator::Or);
1270                assert_eq!(children.len(), 2);
1271            }
1272            _ => panic!("Expected Expr"),
1273        }
1274    }
1275
1276    #[test]
1277    fn test_negate_tree_stmt() {
1278        let filter = Filter::new(
1279            Field::simple("a"),
1280            OpExpr::new(Operation::Quant {
1281                op: QuantOperator::Equal,
1282                quantifier: None,
1283                value: "1".to_string(),
1284            }),
1285        );
1286
1287        let tree = LogicTree::Stmt(filter);
1288        let negated = negate_tree(tree);
1289
1290        match negated {
1291            LogicTree::Stmt(f) => {
1292                assert!(f.op_expr.negated);
1293            }
1294            _ => panic!("Expected Stmt"),
1295        }
1296    }
1297
1298    #[test]
1299    fn test_negate_tree_expr() {
1300        let filter = Filter::new(
1301            Field::simple("a"),
1302            OpExpr::new(Operation::Quant {
1303                op: QuantOperator::Equal,
1304                quantifier: None,
1305                value: "1".to_string(),
1306            }),
1307        );
1308
1309        let tree = LogicTree::Expr {
1310            negated: false,
1311            op: LogicOperator::And,
1312            children: vec![LogicTree::Stmt(filter)],
1313        };
1314
1315        let negated = negate_tree(tree);
1316
1317        match negated {
1318            LogicTree::Expr { negated, .. } => {
1319                assert!(negated);
1320            }
1321            _ => panic!("Expected Expr"),
1322        }
1323    }
1324}