Skip to main content

sockudo_filter/
ops.rs

1/// Comparison operators for leaf node filters.
2///
3/// These operators are used to compare tag values against filter values.
4/// All string representations are lowercase for consistency with the protocol.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum CompareOp {
7    /// Equality: tag_value == filter_value
8    Equal,
9    /// Inequality: tag_value != filter_value
10    NotEqual,
11    /// Set membership: tag_value in filter_values
12    In,
13    /// Set non-membership: tag_value not in filter_values
14    NotIn,
15    /// Key exists in tags
16    Exists,
17    /// Key does not exist in tags
18    NotExists,
19    /// String starts with: tag_value.starts_with(filter_value)
20    StartsWith,
21    /// String ends with: tag_value.ends_with(filter_value)
22    EndsWith,
23    /// String contains: tag_value.contains(filter_value)
24    Contains,
25    /// Numeric greater than: tag_value > filter_value
26    GreaterThan,
27    /// Numeric greater than or equal: tag_value >= filter_value
28    GreaterThanOrEqual,
29    /// Numeric less than: tag_value < filter_value
30    LessThan,
31    /// Numeric less than or equal: tag_value <= filter_value
32    LessThanOrEqual,
33}
34
35impl CompareOp {
36    /// Converts a string to a CompareOp.
37    ///
38    /// Returns None if the string is not a valid operator.
39    /// Note: This method is kept for backward compatibility. Prefer using the FromStr trait.
40    #[inline]
41    pub fn parse(s: &str) -> Option<Self> {
42        match s {
43            "eq" => Some(Self::Equal),
44            "neq" => Some(Self::NotEqual),
45            "in" => Some(Self::In),
46            "nin" => Some(Self::NotIn),
47            "ex" => Some(Self::Exists),
48            "nex" => Some(Self::NotExists),
49            "sw" => Some(Self::StartsWith),
50            "ew" => Some(Self::EndsWith),
51            "ct" => Some(Self::Contains),
52            "gt" => Some(Self::GreaterThan),
53            "gte" => Some(Self::GreaterThanOrEqual),
54            "lt" => Some(Self::LessThan),
55            "lte" => Some(Self::LessThanOrEqual),
56            _ => None,
57        }
58    }
59
60    /// Returns the string representation as a static str (zero allocation).
61    #[inline]
62    pub const fn as_str(self) -> &'static str {
63        match self {
64            Self::Equal => "eq",
65            Self::NotEqual => "neq",
66            Self::In => "in",
67            Self::NotIn => "nin",
68            Self::Exists => "ex",
69            Self::NotExists => "nex",
70            Self::StartsWith => "sw",
71            Self::EndsWith => "ew",
72            Self::Contains => "ct",
73            Self::GreaterThan => "gt",
74            Self::GreaterThanOrEqual => "gte",
75            Self::LessThan => "lt",
76            Self::LessThanOrEqual => "lte",
77        }
78    }
79}
80
81// Implement Display trait for CompareOp
82impl std::fmt::Display for CompareOp {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        write!(f, "{}", self.as_str())
85    }
86}
87
88// Implement FromStr trait for CompareOp
89impl std::str::FromStr for CompareOp {
90    type Err = String;
91
92    fn from_str(s: &str) -> Result<Self, Self::Err> {
93        Self::parse(s).ok_or_else(|| format!("Invalid comparison operator: {}", s))
94    }
95}
96
97/// Logical operators for branch nodes in the filter tree.
98///
99/// These operators combine multiple filter nodes into more complex expressions.
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub enum LogicalOp {
102    /// Logical AND: all child nodes must match
103    And,
104    /// Logical OR: at least one child node must match
105    Or,
106    /// Logical NOT: negates the single child node
107    Not,
108}
109
110impl LogicalOp {
111    /// Converts a string to a LogicalOp.
112    ///
113    /// Returns None if the string is not a valid operator.
114    /// Note: This method is kept for backward compatibility. Prefer using the FromStr trait.
115    #[inline]
116    pub fn parse(s: &str) -> Option<Self> {
117        match s {
118            "and" => Some(Self::And),
119            "or" => Some(Self::Or),
120            "not" => Some(Self::Not),
121            _ => None,
122        }
123    }
124
125    /// Returns the string representation as a static str (zero allocation).
126    #[inline]
127    pub const fn as_str(self) -> &'static str {
128        match self {
129            Self::And => "and",
130            Self::Or => "or",
131            Self::Not => "not",
132        }
133    }
134}
135
136// Implement Display trait for LogicalOp
137impl std::fmt::Display for LogicalOp {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        write!(f, "{}", self.as_str())
140    }
141}
142
143// Implement FromStr trait for LogicalOp
144impl std::str::FromStr for LogicalOp {
145    type Err = String;
146
147    fn from_str(s: &str) -> Result<Self, Self::Err> {
148        Self::parse(s).ok_or_else(|| format!("Invalid logical operator: {}", s))
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_compare_op_from_str() {
158        assert_eq!(CompareOp::parse("eq"), Some(CompareOp::Equal));
159        assert_eq!(CompareOp::parse("neq"), Some(CompareOp::NotEqual));
160        assert_eq!(CompareOp::parse("in"), Some(CompareOp::In));
161        assert_eq!(CompareOp::parse("nin"), Some(CompareOp::NotIn));
162        assert_eq!(CompareOp::parse("ex"), Some(CompareOp::Exists));
163        assert_eq!(CompareOp::parse("nex"), Some(CompareOp::NotExists));
164        assert_eq!(CompareOp::parse("sw"), Some(CompareOp::StartsWith));
165        assert_eq!(CompareOp::parse("ew"), Some(CompareOp::EndsWith));
166        assert_eq!(CompareOp::parse("ct"), Some(CompareOp::Contains));
167        assert_eq!(CompareOp::parse("gt"), Some(CompareOp::GreaterThan));
168        assert_eq!(CompareOp::parse("gte"), Some(CompareOp::GreaterThanOrEqual));
169        assert_eq!(CompareOp::parse("lt"), Some(CompareOp::LessThan));
170        assert_eq!(CompareOp::parse("lte"), Some(CompareOp::LessThanOrEqual));
171        assert_eq!(CompareOp::parse("invalid"), None);
172    }
173
174    #[test]
175    fn test_compare_op_as_str() {
176        assert_eq!(CompareOp::Equal.as_str(), "eq");
177        assert_eq!(CompareOp::NotEqual.as_str(), "neq");
178        assert_eq!(CompareOp::In.as_str(), "in");
179        assert_eq!(CompareOp::NotIn.as_str(), "nin");
180        assert_eq!(CompareOp::Exists.as_str(), "ex");
181        assert_eq!(CompareOp::NotExists.as_str(), "nex");
182        assert_eq!(CompareOp::StartsWith.as_str(), "sw");
183        assert_eq!(CompareOp::EndsWith.as_str(), "ew");
184        assert_eq!(CompareOp::Contains.as_str(), "ct");
185        assert_eq!(CompareOp::GreaterThan.as_str(), "gt");
186        assert_eq!(CompareOp::GreaterThanOrEqual.as_str(), "gte");
187        assert_eq!(CompareOp::LessThan.as_str(), "lt");
188        assert_eq!(CompareOp::LessThanOrEqual.as_str(), "lte");
189    }
190
191    #[test]
192    fn test_logical_op_from_str() {
193        assert_eq!(LogicalOp::parse("and"), Some(LogicalOp::And));
194        assert_eq!(LogicalOp::parse("or"), Some(LogicalOp::Or));
195        assert_eq!(LogicalOp::parse("not"), Some(LogicalOp::Not));
196        assert_eq!(LogicalOp::parse("invalid"), None);
197    }
198
199    #[test]
200    fn test_logical_op_as_str() {
201        assert_eq!(LogicalOp::And.as_str(), "and");
202        assert_eq!(LogicalOp::Or.as_str(), "or");
203        assert_eq!(LogicalOp::Not.as_str(), "not");
204    }
205
206    #[test]
207    fn test_round_trip_compare_ops() {
208        let ops = [
209            CompareOp::Equal,
210            CompareOp::NotEqual,
211            CompareOp::In,
212            CompareOp::NotIn,
213            CompareOp::Exists,
214            CompareOp::NotExists,
215            CompareOp::StartsWith,
216            CompareOp::EndsWith,
217            CompareOp::Contains,
218            CompareOp::GreaterThan,
219            CompareOp::GreaterThanOrEqual,
220            CompareOp::LessThan,
221            CompareOp::LessThanOrEqual,
222        ];
223
224        for op in ops {
225            let s = op.as_str();
226            let parsed = CompareOp::parse(s);
227            assert_eq!(parsed, Some(op));
228        }
229    }
230
231    #[test]
232    fn test_round_trip_logical_ops() {
233        let ops = [LogicalOp::And, LogicalOp::Or, LogicalOp::Not];
234
235        for op in ops {
236            let s = op.as_str();
237            let parsed = LogicalOp::parse(s);
238            assert_eq!(parsed, Some(op));
239        }
240    }
241}