Skip to main content

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