183 lines
5.1 KiB
Go
183 lines
5.1 KiB
Go
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
|
|
}
|