package main import ( "fmt" "os" "path/filepath" "sort" "strings" "gopkg.in/yaml.v3" ) type sqlcUUIDViolation struct { file string rule string detail string } type sqlcConfigFile struct { SQL []sqlcConfigEntry `yaml:"sql"` } type sqlcConfigEntry struct { Gen sqlcGenConfig `yaml:"gen"` } type sqlcGenConfig struct { Go sqlcGoConfig `yaml:"go"` } type sqlcGoConfig struct { Overrides []sqlcOverride `yaml:"overrides"` } type sqlcOverride struct { DBType string `yaml:"db_type"` Nullable bool `yaml:"nullable"` GoType sqlcGoType `yaml:"go_type"` } type sqlcGoType struct { Scalar string Import string Type string } func (g *sqlcGoType) UnmarshalYAML(node *yaml.Node) error { switch node.Kind { case yaml.ScalarNode: return node.Decode(&g.Scalar) case yaml.MappingNode: var decoded struct { Import string `yaml:"import"` Type string `yaml:"type"` } if err := node.Decode(&decoded); err != nil { return err } g.Import = decoded.Import g.Type = decoded.Type return nil default: return fmt.Errorf("unsupported go_type yaml node kind %d", node.Kind) } } func (g sqlcGoType) matchesGoogleUUID(nullable bool) bool { if nullable { return g.Scalar == "github.com/google/uuid.NullUUID" || (g.Import == "github.com/google/uuid" && g.Type == "NullUUID") } return g.Scalar == "github.com/google/uuid.UUID" || (g.Import == "github.com/google/uuid" && g.Type == "UUID") } func findSQLCConfigFiles(root string, includeRoot bool) []string { var files []string _ = filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { return nil } if info.IsDir() { switch info.Name() { case ".git", ".worktrees", "vendor", "node_modules", "dist": return filepath.SkipDir } return nil } base := filepath.Base(path) if base != "sqlc.yaml" && base != "sqlc.yml" { return nil } if includeRoot && samePath(path, filepath.Join(root, base)) { files = append(files, path) return nil } rel, relErr := filepath.Rel(root, path) if relErr != nil { return nil } if strings.Contains(filepath.ToSlash(rel), "/cmd/") || strings.HasPrefix(filepath.ToSlash(rel), "cmd/") { files = append(files, path) } return nil }) sort.Strings(files) return files } func checkSQLCUUIDOverrides(root string, includeRoot bool) []sqlcUUIDViolation { var violations []sqlcUUIDViolation for _, path := range findSQLCConfigFiles(root, includeRoot) { data, err := os.ReadFile(path) if err != nil { violations = append(violations, sqlcUUIDViolation{ file: displayLintPath(root, path), rule: "sqlc-read", detail: fmt.Sprintf("failed to read sqlc config: %v", err), }) continue } var cfg sqlcConfigFile if err := yaml.Unmarshal(data, &cfg); err != nil { violations = append(violations, sqlcUUIDViolation{ file: displayLintPath(root, path), rule: "sqlc-parse", detail: fmt.Sprintf("failed to parse sqlc config: %v", err), }) continue } hasUUID := false hasNullUUID := false for _, block := range cfg.SQL { for _, override := range block.Gen.Go.Overrides { if override.DBType != "uuid" { continue } if override.Nullable { if override.GoType.matchesGoogleUUID(true) { hasNullUUID = true } continue } if override.GoType.matchesGoogleUUID(false) { hasUUID = true } } } relPath := displayLintPath(root, path) if !hasUUID { violations = append(violations, sqlcUUIDViolation{ file: relPath, rule: "sqlc-uuid-type", detail: `db_type "uuid" must map to github.com/google/uuid.UUID`, }) } if !hasNullUUID { violations = append(violations, sqlcUUIDViolation{ file: relPath, rule: "sqlc-null-uuid-type", detail: `nullable db_type "uuid" must map to github.com/google/uuid.NullUUID`, }) } } return violations }