use crate::models::Table;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Serialize, Deserialize)]
#[must_use = "validation results should be checked for conflicts and violations"]
pub struct TableValidationResult {
pub naming_conflicts: Vec<NamingConflict>,
pub pattern_violations: Vec<PatternViolation>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NamingConflict {
pub new_table_id: Uuid,
pub new_table_name: String,
pub existing_table_id: Uuid,
pub existing_table_name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatternViolation {
pub table_id: Uuid,
pub table_name: String,
pub message: String,
}
#[derive(Debug, thiserror::Error, Serialize, Deserialize)]
pub enum TableValidationError {
#[error("Validation error: {0}")]
ValidationError(String),
}
#[derive(Default)]
pub struct TableValidator;
impl TableValidator {
pub fn new() -> Self {
Self
}
pub fn detect_naming_conflicts(
&self,
existing_tables: &[Table],
new_tables: &[Table],
) -> Vec<NamingConflict> {
let mut conflicts = Vec::new();
let mut existing_map = std::collections::HashMap::new();
for table in existing_tables {
let key = table.get_unique_key();
existing_map.insert(key, table);
}
for new_table in new_tables {
let key = new_table.get_unique_key();
if let Some(existing) = existing_map.get(&key) {
conflicts.push(NamingConflict {
new_table_id: new_table.id,
new_table_name: new_table.name.clone(),
existing_table_id: existing.id,
existing_table_name: existing.name.clone(),
});
}
}
conflicts
}
pub fn validate_pattern_exclusivity(
&self,
table: &Table,
) -> std::result::Result<(), PatternViolation> {
if table.scd_pattern.is_some() && table.data_vault_classification.is_some() {
return Err(PatternViolation {
table_id: table.id,
table_name: table.name.clone(),
message: "SCD pattern and Data Vault classification are mutually exclusive"
.to_string(),
});
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::column::Column;
use crate::models::table::Table as SdkTable;
#[test]
fn detects_naming_conflicts_using_unique_key() {
let t1 = SdkTable::new(
"users".to_string(),
vec![Column::new("id".to_string(), "int".to_string())],
);
let t2 = SdkTable {
id: Uuid::new_v4(),
..t1.clone()
};
let v = TableValidator::new().detect_naming_conflicts(&[t1], &[t2]);
assert_eq!(v.len(), 1);
assert_eq!(v[0].new_table_name, "users");
}
#[test]
fn enforces_pattern_exclusivity() {
let mut t = SdkTable::new("t".to_string(), vec![]);
t.scd_pattern = Some(crate::models::enums::SCDPattern::Type2);
t.data_vault_classification = Some(crate::models::enums::DataVaultClassification::Hub);
let err = TableValidator::new()
.validate_pattern_exclusivity(&t)
.unwrap_err();
assert!(err.message.contains("mutually exclusive"));
}
}