package inspector import ( "fmt" "time" "git.warky.dev/wdevs/relspecgo/pkg/models" ) // Inspector performs validation on database models type Inspector struct { config *Config db *models.Database } // ValidationResult represents the result of a single validation check type ValidationResult struct { RuleName string `json:"rule_name"` Level string `json:"level"` // "error" or "warning" Message string `json:"message"` Location string `json:"location"` // e.g., "schema.table.column" Context map[string]interface{} `json:"context"` Passed bool `json:"passed"` } // InspectorReport contains the complete validation report type InspectorReport struct { Summary ReportSummary `json:"summary"` Violations []ValidationResult `json:"violations"` GeneratedAt time.Time `json:"generated_at"` Database string `json:"database"` SourceFormat string `json:"source_format"` } // ReportSummary contains aggregate statistics type ReportSummary struct { TotalRules int `json:"total_rules"` RulesChecked int `json:"rules_checked"` RulesSkipped int `json:"rules_skipped"` ErrorCount int `json:"error_count"` WarningCount int `json:"warning_count"` PassedCount int `json:"passed_count"` } // NewInspector creates a new inspector with the given database and configuration func NewInspector(db *models.Database, config *Config) *Inspector { return &Inspector{ config: config, db: db, } } // Inspect runs all enabled validation rules and returns a report func (i *Inspector) Inspect() (*InspectorReport, error) { results := []ValidationResult{} // Run all enabled validators for ruleName, rule := range i.config.Rules { if !rule.IsEnabled() { continue } // Get the validator function for this rule using the function field validator, exists := getValidator(rule.Function) if !exists { // Skip unknown validator functions continue } // Run the validator ruleResults := validator(i.db, rule, ruleName) // Set the level based on rule configuration level := "warning" if rule.IsEnforced() { level = "error" } for idx := range ruleResults { ruleResults[idx].Level = level } results = append(results, ruleResults...) } // Generate summary summary := i.generateSummary(results) report := &InspectorReport{ Summary: summary, Violations: results, GeneratedAt: time.Now(), Database: i.db.Name, SourceFormat: i.db.SourceFormat, } return report, nil } // generateSummary creates summary statistics from validation results func (i *Inspector) generateSummary(results []ValidationResult) ReportSummary { summary := ReportSummary{ TotalRules: len(i.config.Rules), } // Count enabled rules for _, rule := range i.config.Rules { if rule.IsEnabled() { summary.RulesChecked++ } else { summary.RulesSkipped++ } } // Count violations by level for _, result := range results { if result.Passed { summary.PassedCount++ } else { if result.Level == "error" { summary.ErrorCount++ } else { summary.WarningCount++ } } } return summary } // HasErrors returns true if the report contains any errors func (r *InspectorReport) HasErrors() bool { return r.Summary.ErrorCount > 0 } // validatorFunc is a function that validates a rule against a database type validatorFunc func(*models.Database, Rule, string) []ValidationResult // getValidator returns the validator function for a given function name func getValidator(functionName string) (validatorFunc, bool) { validators := map[string]validatorFunc{ "primary_key_naming": validatePrimaryKeyNaming, "primary_key_datatype": validatePrimaryKeyDatatype, "primary_key_auto_increment": validatePrimaryKeyAutoIncrement, "foreign_key_column_naming": validateForeignKeyColumnNaming, "foreign_key_constraint_naming": validateForeignKeyConstraintNaming, "foreign_key_index": validateForeignKeyIndex, "table_regexpr": validateTableNamingCase, "column_regexpr": validateColumnNamingCase, "table_name_length": validateTableNameLength, "column_name_length": validateColumnNameLength, "reserved_words": validateReservedKeywords, "have_primary_key": validateMissingPrimaryKey, "orphaned_foreign_key": validateOrphanedForeignKey, "circular_dependency": validateCircularDependency, } fn, exists := validators[functionName] return fn, exists } // createResult is a helper to create a validation result func createResult(ruleName string, passed bool, message string, location string, context map[string]interface{}) ValidationResult { return ValidationResult{ RuleName: ruleName, Message: message, Location: location, Context: context, Passed: passed, } } // formatLocation creates a location string from schema, table, and optional column func formatLocation(schema, table, column string) string { if column != "" { return fmt.Sprintf("%s.%s.%s", schema, table, column) } if table != "" { return fmt.Sprintf("%s.%s", schema, table) } return schema }