Skip to main content

use_sql_constraint/
lib.rs

1#![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/// SQL constraint kind labels.
10#[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    /// Returns the stable constraint label.
24    #[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/// SQL constraint name primitive.
62#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
63pub struct SqlConstraintName(SqlIdentifier);
64
65impl SqlConstraintName {
66    /// Creates a constraint name.
67    ///
68    /// # Errors
69    ///
70    /// Returns [`SqlConstraintError`] when identifier validation fails.
71    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    /// Returns the constraint name text.
78    #[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/// SQL constraint metadata.
91#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
92pub struct SqlConstraint {
93    kind: SqlConstraintKind,
94    name: Option<SqlConstraintName>,
95}
96
97impl SqlConstraint {
98    /// Creates constraint metadata from a kind.
99    #[must_use]
100    pub const fn new(kind: SqlConstraintKind) -> Self {
101        Self { kind, name: None }
102    }
103
104    /// Adds a constraint name.
105    #[must_use]
106    pub fn with_name(mut self, name: SqlConstraintName) -> Self {
107        self.name = Some(name);
108        self
109    }
110
111    /// Returns the constraint kind.
112    #[must_use]
113    pub const fn kind(&self) -> SqlConstraintKind {
114        self.kind
115    }
116
117    /// Returns the optional constraint name.
118    #[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/// Error returned when SQL constraint metadata is invalid.
134#[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}