use_sql_constraint/
lib.rs1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7use use_sql_ident::{SqlIdentifier, SqlIdentifierError};
8
9#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
11pub enum SqlConstraintKind {
12 #[default]
13 PrimaryKey,
14 ForeignKey,
15 Unique,
16 NotNull,
17 Check,
18 Default,
19 Generated,
20}
21
22impl SqlConstraintKind {
23 #[must_use]
25 pub const fn as_str(self) -> &'static str {
26 match self {
27 Self::PrimaryKey => "PRIMARY KEY",
28 Self::ForeignKey => "FOREIGN KEY",
29 Self::Unique => "UNIQUE",
30 Self::NotNull => "NOT NULL",
31 Self::Check => "CHECK",
32 Self::Default => "DEFAULT",
33 Self::Generated => "GENERATED",
34 }
35 }
36}
37
38impl fmt::Display for SqlConstraintKind {
39 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
40 formatter.write_str(self.as_str())
41 }
42}
43
44impl FromStr for SqlConstraintKind {
45 type Err = SqlConstraintError;
46
47 fn from_str(input: &str) -> Result<Self, Self::Err> {
48 match normalized_constraint(input)?.as_str() {
49 "PRIMARY KEY" | "PRIMARY" => Ok(Self::PrimaryKey),
50 "FOREIGN KEY" | "FOREIGN" => Ok(Self::ForeignKey),
51 "UNIQUE" => Ok(Self::Unique),
52 "NOT NULL" => Ok(Self::NotNull),
53 "CHECK" => Ok(Self::Check),
54 "DEFAULT" => Ok(Self::Default),
55 "GENERATED" => Ok(Self::Generated),
56 _ => Err(SqlConstraintError::UnknownKind),
57 }
58 }
59}
60
61#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
63pub struct SqlConstraintName(SqlIdentifier);
64
65impl SqlConstraintName {
66 pub fn new(input: impl AsRef<str>) -> Result<Self, SqlConstraintError> {
72 SqlIdentifier::new(input)
73 .map(Self)
74 .map_err(SqlConstraintError::Identifier)
75 }
76
77 #[must_use]
79 pub fn as_str(&self) -> &str {
80 self.0.as_str()
81 }
82}
83
84impl fmt::Display for SqlConstraintName {
85 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
86 self.0.fmt(formatter)
87 }
88}
89
90#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
92pub struct SqlConstraint {
93 kind: SqlConstraintKind,
94 name: Option<SqlConstraintName>,
95}
96
97impl SqlConstraint {
98 #[must_use]
100 pub const fn new(kind: SqlConstraintKind) -> Self {
101 Self { kind, name: None }
102 }
103
104 #[must_use]
106 pub fn with_name(mut self, name: SqlConstraintName) -> Self {
107 self.name = Some(name);
108 self
109 }
110
111 #[must_use]
113 pub const fn kind(&self) -> SqlConstraintKind {
114 self.kind
115 }
116
117 #[must_use]
119 pub const fn name(&self) -> Option<&SqlConstraintName> {
120 self.name.as_ref()
121 }
122}
123
124impl fmt::Display for SqlConstraint {
125 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
126 if let Some(name) = &self.name {
127 write!(formatter, "CONSTRAINT {name} ")?;
128 }
129 write!(formatter, "{}", self.kind)
130 }
131}
132
133#[derive(Clone, Debug, Eq, PartialEq)]
135pub enum SqlConstraintError {
136 Empty,
137 UnknownKind,
138 Identifier(SqlIdentifierError),
139}
140
141impl fmt::Display for SqlConstraintError {
142 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
143 match self {
144 Self::Empty => formatter.write_str("SQL constraint label cannot be empty"),
145 Self::UnknownKind => formatter.write_str("unknown SQL constraint kind"),
146 Self::Identifier(error) => {
147 write!(formatter, "invalid SQL constraint identifier: {error}")
148 },
149 }
150 }
151}
152
153impl Error for SqlConstraintError {}
154
155fn normalized_constraint(input: &str) -> Result<String, SqlConstraintError> {
156 let trimmed = input.trim();
157 if trimmed.is_empty() {
158 return Err(SqlConstraintError::Empty);
159 }
160 Ok(trimmed
161 .replace('_', " ")
162 .split_whitespace()
163 .collect::<Vec<_>>()
164 .join(" ")
165 .to_ascii_uppercase())
166}
167
168#[cfg(test)]
169mod tests {
170 use super::{SqlConstraint, SqlConstraintError, SqlConstraintKind, SqlConstraintName};
171
172 #[test]
173 fn parses_and_renders_constraints() -> Result<(), SqlConstraintError> {
174 assert_eq!(
175 "primary key".parse::<SqlConstraintKind>()?,
176 SqlConstraintKind::PrimaryKey
177 );
178 let constraint = SqlConstraint::new(SqlConstraintKind::Unique)
179 .with_name(SqlConstraintName::new("users_email_key")?);
180 assert_eq!(constraint.to_string(), "CONSTRAINT users_email_key UNIQUE");
181 Ok(())
182 }
183}