Skip to main content

use_sql_keyword/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7/// Common SQL keywords included by `use-sql-keyword`.
8#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub enum SqlKeyword {
10    Select,
11    Insert,
12    Update,
13    Delete,
14    Create,
15    Alter,
16    Drop,
17    Table,
18    View,
19    Index,
20    Where,
21    From,
22    Join,
23    Group,
24    Order,
25    Limit,
26    Offset,
27    Returning,
28    Primary,
29    Foreign,
30    Key,
31    Unique,
32    Not,
33    Null,
34    Check,
35    Default,
36}
37
38impl SqlKeyword {
39    /// Returns the uppercase SQL keyword label.
40    #[must_use]
41    pub const fn as_str(self) -> &'static str {
42        match self {
43            Self::Select => "SELECT",
44            Self::Insert => "INSERT",
45            Self::Update => "UPDATE",
46            Self::Delete => "DELETE",
47            Self::Create => "CREATE",
48            Self::Alter => "ALTER",
49            Self::Drop => "DROP",
50            Self::Table => "TABLE",
51            Self::View => "VIEW",
52            Self::Index => "INDEX",
53            Self::Where => "WHERE",
54            Self::From => "FROM",
55            Self::Join => "JOIN",
56            Self::Group => "GROUP",
57            Self::Order => "ORDER",
58            Self::Limit => "LIMIT",
59            Self::Offset => "OFFSET",
60            Self::Returning => "RETURNING",
61            Self::Primary => "PRIMARY",
62            Self::Foreign => "FOREIGN",
63            Self::Key => "KEY",
64            Self::Unique => "UNIQUE",
65            Self::Not => "NOT",
66            Self::Null => "NULL",
67            Self::Check => "CHECK",
68            Self::Default => "DEFAULT",
69        }
70    }
71
72    /// Returns a broad keyword category.
73    #[must_use]
74    pub const fn kind(self) -> SqlKeywordKind {
75        match self {
76            Self::Select => SqlKeywordKind::DataQuery,
77            Self::Insert | Self::Update | Self::Delete => SqlKeywordKind::DataMutation,
78            Self::Create | Self::Alter | Self::Drop => SqlKeywordKind::Definition,
79            Self::Table | Self::View | Self::Index => SqlKeywordKind::Object,
80            Self::Where
81            | Self::From
82            | Self::Join
83            | Self::Group
84            | Self::Order
85            | Self::Limit
86            | Self::Offset
87            | Self::Returning => SqlKeywordKind::Clause,
88            Self::Primary | Self::Foreign | Self::Key | Self::Unique | Self::Check => {
89                SqlKeywordKind::Constraint
90            },
91            Self::Not | Self::Null | Self::Default => SqlKeywordKind::Modifier,
92        }
93    }
94}
95
96impl fmt::Display for SqlKeyword {
97    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
98        formatter.write_str(self.as_str())
99    }
100}
101
102impl FromStr for SqlKeyword {
103    type Err = SqlKeywordParseError;
104
105    fn from_str(input: &str) -> Result<Self, Self::Err> {
106        match normalized_word(input)?.as_str() {
107            "SELECT" => Ok(Self::Select),
108            "INSERT" => Ok(Self::Insert),
109            "UPDATE" => Ok(Self::Update),
110            "DELETE" => Ok(Self::Delete),
111            "CREATE" => Ok(Self::Create),
112            "ALTER" => Ok(Self::Alter),
113            "DROP" => Ok(Self::Drop),
114            "TABLE" => Ok(Self::Table),
115            "VIEW" => Ok(Self::View),
116            "INDEX" => Ok(Self::Index),
117            "WHERE" => Ok(Self::Where),
118            "FROM" => Ok(Self::From),
119            "JOIN" => Ok(Self::Join),
120            "GROUP" => Ok(Self::Group),
121            "ORDER" => Ok(Self::Order),
122            "LIMIT" => Ok(Self::Limit),
123            "OFFSET" => Ok(Self::Offset),
124            "RETURNING" => Ok(Self::Returning),
125            "PRIMARY" => Ok(Self::Primary),
126            "FOREIGN" => Ok(Self::Foreign),
127            "KEY" => Ok(Self::Key),
128            "UNIQUE" => Ok(Self::Unique),
129            "NOT" => Ok(Self::Not),
130            "NULL" => Ok(Self::Null),
131            "CHECK" => Ok(Self::Check),
132            "DEFAULT" => Ok(Self::Default),
133            _ => Err(SqlKeywordParseError::Unknown),
134        }
135    }
136}
137
138impl TryFrom<&str> for SqlKeyword {
139    type Error = SqlKeywordParseError;
140
141    fn try_from(value: &str) -> Result<Self, Self::Error> {
142        value.parse()
143    }
144}
145
146/// Broad categories for common SQL keywords.
147#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
148pub enum SqlKeywordKind {
149    #[default]
150    DataQuery,
151    DataMutation,
152    Definition,
153    Object,
154    Clause,
155    Constraint,
156    Modifier,
157}
158
159/// Error returned when parsing a SQL keyword fails.
160#[derive(Clone, Copy, Debug, Eq, PartialEq)]
161pub enum SqlKeywordParseError {
162    Empty,
163    Unknown,
164}
165
166impl fmt::Display for SqlKeywordParseError {
167    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
168        match self {
169            Self::Empty => formatter.write_str("SQL keyword cannot be empty"),
170            Self::Unknown => formatter.write_str("unknown SQL keyword"),
171        }
172    }
173}
174
175impl Error for SqlKeywordParseError {}
176
177/// Returns whether `input` is one of the common keywords in this crate.
178#[must_use]
179pub fn is_common_keyword(input: &str) -> bool {
180    input.parse::<SqlKeyword>().is_ok()
181}
182
183/// Returns whether `input` is reserved-like for conservative SQL helper purposes.
184#[must_use]
185pub fn is_reserved_like(input: &str) -> bool {
186    is_common_keyword(input)
187}
188
189fn normalized_word(input: &str) -> Result<String, SqlKeywordParseError> {
190    let trimmed = input.trim();
191    if trimmed.is_empty() {
192        Err(SqlKeywordParseError::Empty)
193    } else {
194        Ok(trimmed.to_ascii_uppercase())
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use super::{SqlKeyword, SqlKeywordKind, SqlKeywordParseError, is_common_keyword};
201
202    #[test]
203    fn parses_common_keywords() -> Result<(), SqlKeywordParseError> {
204        let keyword: SqlKeyword = "select".parse()?;
205        assert_eq!(keyword, SqlKeyword::Select);
206        assert_eq!(keyword.kind(), SqlKeywordKind::DataQuery);
207        assert_eq!(keyword.to_string(), "SELECT");
208        assert!(is_common_keyword("where"));
209        Ok(())
210    }
211
212    #[test]
213    fn rejects_unknown_keywords() {
214        assert_eq!("".parse::<SqlKeyword>(), Err(SqlKeywordParseError::Empty));
215        assert_eq!(
216            "UPSERT".parse::<SqlKeyword>(),
217            Err(SqlKeywordParseError::Unknown)
218        );
219    }
220}