| """CatalogValidator — Pydantic + business-rule validation for a catalog. |
| |
| Pydantic handles shape; this layer adds invariants that span fields. |
| """ |
|
|
| from .models import Catalog |
|
|
|
|
| class CatalogValidationError(Exception): |
| pass |
|
|
|
|
| class CatalogValidator: |
| """Validates a Catalog beyond Pydantic schema checks. |
| |
| Business rules: |
| - All source_ids unique within a user |
| - All table_ids unique within a source |
| - All column_ids unique within a table |
| - foreign_keys (when added) reference existing tables/columns |
| """ |
|
|
| def validate(self, catalog: Catalog) -> None: |
| seen_sources: set[str] = set() |
| for source in catalog.sources: |
| if source.source_id in seen_sources: |
| raise CatalogValidationError( |
| f"duplicate source_id {source.source_id!r} in catalog " |
| f"for user_id={catalog.user_id!r}" |
| ) |
| seen_sources.add(source.source_id) |
|
|
| seen_tables: set[str] = set() |
| for table in source.tables: |
| if table.table_id in seen_tables: |
| raise CatalogValidationError( |
| f"duplicate table_id {table.table_id!r} in source " |
| f"{source.source_id!r}" |
| ) |
| seen_tables.add(table.table_id) |
|
|
| seen_columns: set[str] = set() |
| for column in table.columns: |
| if column.column_id in seen_columns: |
| raise CatalogValidationError( |
| f"duplicate column_id {column.column_id!r} in table " |
| f"{table.table_id!r} (source {source.source_id!r})" |
| ) |
| seen_columns.add(column.column_id) |
|
|