Skip to main content

use_sql_operator/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7/// SQL comparison operators.
8#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub enum SqlComparisonOperator {
10    #[default]
11    Equal,
12    NotEqual,
13    NotEqualBang,
14    Less,
15    LessOrEqual,
16    Greater,
17    GreaterOrEqual,
18}
19
20impl SqlComparisonOperator {
21    /// Returns the stable operator label.
22    #[must_use]
23    pub const fn as_str(self) -> &'static str {
24        match self {
25            Self::Equal => "=",
26            Self::NotEqual => "<>",
27            Self::NotEqualBang => "!=",
28            Self::Less => "<",
29            Self::LessOrEqual => "<=",
30            Self::Greater => ">",
31            Self::GreaterOrEqual => ">=",
32        }
33    }
34}
35
36impl fmt::Display for SqlComparisonOperator {
37    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
38        formatter.write_str(self.as_str())
39    }
40}
41
42/// SQL logical operators.
43#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
44pub enum SqlLogicalOperator {
45    #[default]
46    And,
47    Or,
48    Not,
49}
50
51impl SqlLogicalOperator {
52    /// Returns the stable operator label.
53    #[must_use]
54    pub const fn as_str(self) -> &'static str {
55        match self {
56            Self::And => "AND",
57            Self::Or => "OR",
58            Self::Not => "NOT",
59        }
60    }
61}
62
63impl fmt::Display for SqlLogicalOperator {
64    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
65        formatter.write_str(self.as_str())
66    }
67}
68
69/// SQL null-check operators.
70#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
71pub enum SqlNullOperator {
72    #[default]
73    IsNull,
74    IsNotNull,
75}
76
77impl SqlNullOperator {
78    /// Returns the stable operator label.
79    #[must_use]
80    pub const fn as_str(self) -> &'static str {
81        match self {
82            Self::IsNull => "IS NULL",
83            Self::IsNotNull => "IS NOT NULL",
84        }
85    }
86}
87
88impl fmt::Display for SqlNullOperator {
89    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
90        formatter.write_str(self.as_str())
91    }
92}
93
94/// SQL pattern and membership operators.
95#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
96pub enum SqlPatternOperator {
97    #[default]
98    Like,
99    In,
100}
101
102impl SqlPatternOperator {
103    /// Returns the stable operator label.
104    #[must_use]
105    pub const fn as_str(self) -> &'static str {
106        match self {
107            Self::Like => "LIKE",
108            Self::In => "IN",
109        }
110    }
111}
112
113impl fmt::Display for SqlPatternOperator {
114    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
115        formatter.write_str(self.as_str())
116    }
117}
118
119/// Common SQL operators.
120#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
121pub enum SqlOperator {
122    Comparison(SqlComparisonOperator),
123    Logical(SqlLogicalOperator),
124    Null(SqlNullOperator),
125    Pattern(SqlPatternOperator),
126}
127
128impl SqlOperator {
129    /// Returns the stable operator label.
130    #[must_use]
131    pub const fn as_str(self) -> &'static str {
132        match self {
133            Self::Comparison(operator) => operator.as_str(),
134            Self::Logical(operator) => operator.as_str(),
135            Self::Null(operator) => operator.as_str(),
136            Self::Pattern(operator) => operator.as_str(),
137        }
138    }
139}
140
141impl fmt::Display for SqlOperator {
142    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
143        formatter.write_str(self.as_str())
144    }
145}
146
147impl FromStr for SqlOperator {
148    type Err = SqlOperatorParseError;
149
150    fn from_str(input: &str) -> Result<Self, Self::Err> {
151        match normalized_operator(input)?.as_str() {
152            "=" => Ok(Self::Comparison(SqlComparisonOperator::Equal)),
153            "<>" => Ok(Self::Comparison(SqlComparisonOperator::NotEqual)),
154            "!=" => Ok(Self::Comparison(SqlComparisonOperator::NotEqualBang)),
155            "<" => Ok(Self::Comparison(SqlComparisonOperator::Less)),
156            "<=" => Ok(Self::Comparison(SqlComparisonOperator::LessOrEqual)),
157            ">" => Ok(Self::Comparison(SqlComparisonOperator::Greater)),
158            ">=" => Ok(Self::Comparison(SqlComparisonOperator::GreaterOrEqual)),
159            "AND" => Ok(Self::Logical(SqlLogicalOperator::And)),
160            "OR" => Ok(Self::Logical(SqlLogicalOperator::Or)),
161            "NOT" => Ok(Self::Logical(SqlLogicalOperator::Not)),
162            "LIKE" => Ok(Self::Pattern(SqlPatternOperator::Like)),
163            "IN" => Ok(Self::Pattern(SqlPatternOperator::In)),
164            "IS NULL" => Ok(Self::Null(SqlNullOperator::IsNull)),
165            "IS NOT NULL" => Ok(Self::Null(SqlNullOperator::IsNotNull)),
166            _ => Err(SqlOperatorParseError::Unknown),
167        }
168    }
169}
170
171impl TryFrom<&str> for SqlOperator {
172    type Error = SqlOperatorParseError;
173
174    fn try_from(value: &str) -> Result<Self, Self::Error> {
175        value.parse()
176    }
177}
178
179/// Error returned when parsing SQL operators fails.
180#[derive(Clone, Copy, Debug, Eq, PartialEq)]
181pub enum SqlOperatorParseError {
182    Empty,
183    Unknown,
184}
185
186impl fmt::Display for SqlOperatorParseError {
187    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
188        match self {
189            Self::Empty => formatter.write_str("SQL operator cannot be empty"),
190            Self::Unknown => formatter.write_str("unknown SQL operator"),
191        }
192    }
193}
194
195impl Error for SqlOperatorParseError {}
196
197fn normalized_operator(input: &str) -> Result<String, SqlOperatorParseError> {
198    let trimmed = input.trim();
199    if trimmed.is_empty() {
200        return Err(SqlOperatorParseError::Empty);
201    }
202    Ok(trimmed
203        .split_whitespace()
204        .collect::<Vec<_>>()
205        .join(" ")
206        .to_ascii_uppercase())
207}
208
209#[cfg(test)]
210mod tests {
211    use super::{SqlComparisonOperator, SqlOperator, SqlOperatorParseError};
212
213    #[test]
214    fn parses_operator_labels() -> Result<(), SqlOperatorParseError> {
215        assert_eq!("<=".parse::<SqlOperator>()?.to_string(), "<=");
216        assert_eq!(
217            "is not null".parse::<SqlOperator>()?.to_string(),
218            "IS NOT NULL"
219        );
220        assert_eq!(SqlComparisonOperator::NotEqualBang.to_string(), "!=");
221        Ok(())
222    }
223
224    #[test]
225    fn rejects_unknown_operators() {
226        assert_eq!("".parse::<SqlOperator>(), Err(SqlOperatorParseError::Empty));
227        assert_eq!(
228            "~~".parse::<SqlOperator>(),
229            Err(SqlOperatorParseError::Unknown)
230        );
231    }
232}