Files
relspecgo/pkg/inspector/inspector.go
Hein 97a57f5dc8
Some checks failed
CI / Test (1.24) (push) Successful in -25m44s
CI / Test (1.25) (push) Successful in -25m40s
CI / Build (push) Successful in -25m53s
CI / Lint (push) Successful in -25m45s
Integration Tests / Integration Tests (push) Failing after -26m2s
feature: Inspector Gadget
2025-12-31 01:40:08 +02:00

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
}