Skip to main content

vespertide_core/schema/table/
validation.rs

1use crate::schema::names::{ColumnName, TableName};
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub enum TableValidationError {
5    DuplicateColumnName {
6        table: TableName,
7        column: ColumnName,
8    },
9    DuplicateIndexColumn {
10        index_name: String,
11        column_name: String,
12    },
13    InvalidForeignKeyFormat {
14        column_name: String,
15        value: String,
16    },
17    /// Internal invariant violation in normalization; valid user input should not trigger this.
18    InvariantViolation {
19        context: String,
20    },
21}
22
23impl std::fmt::Display for TableValidationError {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        match self {
26            TableValidationError::DuplicateColumnName { table, column } => {
27                write!(f, "table '{table}' has duplicate column name '{column}'")
28            }
29            TableValidationError::DuplicateIndexColumn {
30                index_name,
31                column_name,
32            } => {
33                write!(
34                    f,
35                    "Duplicate index '{index_name}' on column '{column_name}': the same index name cannot be applied to the same column multiple times"
36                )
37            }
38            TableValidationError::InvalidForeignKeyFormat { column_name, value } => {
39                write!(
40                    f,
41                    "Invalid foreign key format '{value}' on column '{column_name}': expected 'table.column' format"
42                )
43            }
44            TableValidationError::InvariantViolation { context } => {
45                write!(
46                    f,
47                    "internal table normalization invariant violated: {context}"
48                )
49            }
50        }
51    }
52}
53
54impl std::error::Error for TableValidationError {}
55
56#[cfg(test)]
57mod tests {
58    //! Coverage-closure tests for the `InvalidForeignKeyFormat` Display arm.
59    //! Targets `uncovered-detail.json` lines 35, 36
60    //! (the multi-line `write!` body for `DuplicateIndexColumn` / `InvalidForeignKeyFormat`).
61    use super::*;
62    use rstest::rstest;
63
64    #[rstest]
65    #[case::duplicate_index(
66        TableValidationError::DuplicateIndexColumn {
67            index_name: "ix_user__email".into(),
68            column_name: "email".into(),
69        },
70        "Duplicate index 'ix_user__email' on column 'email'",
71    )]
72    #[case::invalid_fk(
73        TableValidationError::InvalidForeignKeyFormat {
74            column_name: "author_id".into(),
75            value: "broken".into(),
76        },
77        "Invalid foreign key format 'broken' on column 'author_id'",
78    )]
79    fn display_emits_descriptive_messages(#[case] err: TableValidationError, #[case] needle: &str) {
80        let rendered = err.to_string();
81        assert!(
82            rendered.contains(needle),
83            "expected `{needle}` to appear in `{rendered}`"
84        );
85    }
86
87    #[test]
88    fn display_duplicate_column_name() {
89        // Pre-existing covered arm; included so the file's `mod tests` also
90        // documents the canonical case.
91        let err = TableValidationError::DuplicateColumnName {
92            table: "user".into(),
93            column: "email".into(),
94        };
95        assert_eq!(
96            err.to_string(),
97            "table 'user' has duplicate column name 'email'",
98        );
99    }
100
101    #[test]
102    fn display_invariant_violation() {
103        let err = TableValidationError::InvariantViolation {
104            context: "normalize() collapsed two unique constraints".into(),
105        };
106        assert!(err.to_string().contains("normalize()"));
107    }
108}