feat: tipos Migration/MigrationStatus y funciones puras migration_parse + migration_validate
Fase 1 del issue 0015. Tipos Go en functions/infra/migration.go con metadata en types/infra/. Funciones puras: MigrationParse (parsea filename NNN_name.sql + bloques -- +up/-- +down) y MigrationValidate (verifica secuencia, huecos, duplicados, bloques vacios). 16 tests, todos pasan. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// MigrationValidate checks a slice of migrations for consistency errors.
|
||||
// It verifies that:
|
||||
// - Versions are sequential starting from 1 with no gaps (1, 2, 3...)
|
||||
// - No duplicate versions exist
|
||||
// - Each migration has non-empty UpSQL
|
||||
// - Each migration has a non-empty Name
|
||||
//
|
||||
// Returns a slice of human-readable error strings. An empty slice means all
|
||||
// migrations are valid. The function does not mutate the input slice.
|
||||
func MigrationValidate(migrations []Migration) []string {
|
||||
var errs []string
|
||||
|
||||
if len(migrations) == 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
// Work on a sorted copy to detect gaps and duplicates
|
||||
sorted := make([]Migration, len(migrations))
|
||||
copy(sorted, migrations)
|
||||
sort.Slice(sorted, func(i, j int) bool {
|
||||
return sorted[i].Version < sorted[j].Version
|
||||
})
|
||||
|
||||
// Check individual fields and collect duplicates
|
||||
seen := make(map[int]int) // version -> count
|
||||
for _, m := range sorted {
|
||||
seen[m.Version]++
|
||||
|
||||
if m.Name == "" {
|
||||
errs = append(errs, fmt.Sprintf("version %d has empty name", m.Version))
|
||||
}
|
||||
if m.UpSQL == "" {
|
||||
errs = append(errs, fmt.Sprintf("version %d (%s) has empty up_sql", m.Version, m.Name))
|
||||
}
|
||||
}
|
||||
|
||||
// Report duplicates
|
||||
for v, count := range seen {
|
||||
if count > 1 {
|
||||
errs = append(errs, fmt.Sprintf("duplicate version %d appears %d times", v, count))
|
||||
}
|
||||
}
|
||||
|
||||
// Check sequential numbering starting from 1 (no gaps)
|
||||
// Build unique sorted versions
|
||||
versions := make([]int, 0, len(seen))
|
||||
for v := range seen {
|
||||
versions = append(versions, v)
|
||||
}
|
||||
sort.Ints(versions)
|
||||
|
||||
if len(versions) > 0 && versions[0] != 1 {
|
||||
errs = append(errs, fmt.Sprintf("versions must start at 1, got %d", versions[0]))
|
||||
}
|
||||
|
||||
for i := 1; i < len(versions); i++ {
|
||||
expected := versions[i-1] + 1
|
||||
if versions[i] != expected {
|
||||
errs = append(errs, fmt.Sprintf("gap in versions: missing %d (have %d then %d)", expected, versions[i-1], versions[i]))
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
Reference in New Issue
Block a user