package inspector import ( "fmt" "os" "gopkg.in/yaml.v3" ) // Config represents the inspector rules configuration type Config struct { Version string `yaml:"version"` Rules map[string]Rule `yaml:"rules"` } // Rule represents a single validation rule type Rule struct { Enabled string `yaml:"enabled"` // "enforce", "warn", "off" Function string `yaml:"function"` // validator function name Message string `yaml:"message"` Pattern string `yaml:"pattern,omitempty"` AllowedTypes []string `yaml:"allowed_types,omitempty"` MaxLength int `yaml:"max_length,omitempty"` Case string `yaml:"case,omitempty"` RequireIndex bool `yaml:"require_index,omitempty"` CheckTables bool `yaml:"check_tables,omitempty"` CheckColumns bool `yaml:"check_columns,omitempty"` RequireAutoIncrement bool `yaml:"require_auto_increment,omitempty"` } // LoadConfig loads configuration from a YAML file // If the file doesn't exist, returns default configuration // If the file exists but is invalid, returns an error func LoadConfig(path string) (*Config, error) { // Check if file exists if _, err := os.Stat(path); os.IsNotExist(err) { // File doesn't exist, use defaults return GetDefaultConfig(), nil } // Read file data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("failed to read config file: %w", err) } // Parse YAML var config Config if err := yaml.Unmarshal(data, &config); err != nil { return nil, fmt.Errorf("failed to parse config YAML: %w", err) } return &config, nil } // GetDefaultConfig returns the default inspector configuration // All rules are enabled at "warn" level by default func GetDefaultConfig() *Config { return &Config{ Version: "1.0", Rules: map[string]Rule{ // Primary Key Rules "primary_key_naming": { Enabled: "warn", Function: "primary_key_naming", Pattern: "^id_", Message: "Primary key columns should start with 'id_'", }, "primary_key_datatype": { Enabled: "warn", Function: "primary_key_datatype", AllowedTypes: []string{"bigserial", "bigint", "int", "serial", "integer", "int4", "int8"}, Message: "Primary keys should use integer types (bigserial, bigint, int, serial)", }, "primary_key_auto_increment": { Enabled: "off", Function: "primary_key_auto_increment", RequireAutoIncrement: true, Message: "Primary key without auto-increment detected", }, // Foreign Key Rules "foreign_key_column_naming": { Enabled: "warn", Function: "foreign_key_column_naming", Pattern: "^rid_", Message: "Foreign key columns should start with 'rid_'", }, "foreign_key_constraint_naming": { Enabled: "warn", Function: "foreign_key_constraint_naming", Pattern: "^fk_", Message: "Foreign key constraint names should start with 'fk_'", }, "foreign_key_index": { Enabled: "warn", Function: "foreign_key_index", RequireIndex: true, Message: "Foreign key columns should have indexes for optimal performance", }, // Naming Convention Rules "table_naming_case": { Enabled: "warn", Function: "table_regexpr", Case: "lowercase", Pattern: "^[a-z][a-z0-9_]*$", Message: "Table names should be lowercase with underscores (snake_case)", }, "column_naming_case": { Enabled: "warn", Function: "column_regexpr", Case: "lowercase", Pattern: "^[a-z][a-z0-9_]*$", Message: "Column names should be lowercase with underscores (snake_case)", }, // Length Rules "table_name_length": { Enabled: "warn", Function: "table_name_length", MaxLength: 64, Message: "Table name exceeds recommended maximum length of 64 characters", }, "column_name_length": { Enabled: "warn", Function: "column_name_length", MaxLength: 64, Message: "Column name exceeds recommended maximum length of 64 characters", }, // Reserved Keywords "reserved_keywords": { Enabled: "warn", Function: "reserved_words", CheckTables: true, CheckColumns: true, Message: "Using SQL reserved keywords as identifiers can cause issues", }, // Schema Integrity Rules "missing_primary_key": { Enabled: "warn", Function: "have_primary_key", Message: "Table is missing a primary key", }, "orphaned_foreign_key": { Enabled: "warn", Function: "orphaned_foreign_key", Message: "Foreign key references a non-existent table", }, "circular_dependency": { Enabled: "warn", Function: "circular_dependency", Message: "Circular foreign key dependency detected", }, }, } } // IsEnabled returns true if the rule is enabled (either "enforce" or "warn") func (r *Rule) IsEnabled() bool { return r.Enabled == "enforce" || r.Enabled == "warn" } // IsEnforced returns true if the rule is set to "enforce" level func (r *Rule) IsEnforced() bool { return r.Enabled == "enforce" }