From 6e3ff81381e336237be5990cd2f7cc76952a0020 Mon Sep 17 00:00:00 2001 From: Aneurin Barker Snook Date: Thu, 25 Jun 2026 13:52:56 +0100 Subject: [PATCH] improve constraint matching, migration structure --- list.go | 12 +++++- migration.go | 110 +++++++++++++++++++++++++++++++-------------------- 2 files changed, 77 insertions(+), 45 deletions(-) diff --git a/list.go b/list.go index 9bd7bc0..89f9352 100644 --- a/list.go +++ b/list.go @@ -4,11 +4,19 @@ package version type List []*Version // Match tests versions against a constraint and returns a new List of matching versions only. -func (list List) Match(c *Constraint) List { +func (list List) Match(constraints ...*Constraint) List { filtered := List{} for _, v := range list { - if v.Match(c) { + ok := true + for _, c := range constraints { + if !v.Match(c) { + ok = false + break + } + } + + if ok { filtered = append(filtered, v) } } diff --git a/migration.go b/migration.go index f3e4c04..934c9cb 100644 --- a/migration.go +++ b/migration.go @@ -4,11 +4,71 @@ import ( "sort" ) -// Migration maps provide a simple way to run functions wrapped in version constraints. -type Migration map[string]func() error +// Migration maps provide a simple way to store and run versioned 'patch' functions. +type Migration map[string]Patch -// AllVersions returns a List of all versions in the migration map. -func (m Migration) AllVersions() (List, error) { +// Down reverts all patches in reverse version order. +func (m Migration) Down(after func(*Version)) error { + versions, err := m.Versions() + if err != nil { + return err + } + + for i := len(versions); i > 0; i-- { + v := versions[i-1] + patch := m[v.Text] + if err := patch.Down(); err != nil { + return err + } + + if after != nil { + after(v) + } + } + + return nil +} + +// Match tests versions against a constraint and returns a new migration map of matching versions only. +func (m Migration) Match(constraints ...*Constraint) (Migration, error) { + l, err := m.Versions() + if err != nil { + return nil, err + } + + l = l.Match(constraints...) + + m2 := Migration{} + for _, v := range l { + m2[v.Text] = m[v.Text] + } + + return m2, nil +} + +// Up executes all patches in version order. +func (m Migration) Up(after func(*Version)) error { + versions, err := m.Versions() + if err != nil { + return err + } + + for _, v := range versions { + patch := m[v.Text] + if err := patch.Up(); err != nil { + return err + } + + if after != nil { + after(v) + } + } + + return nil +} + +// Versions returns a List of all versions in the migration map. +func (m Migration) Versions() (List, error) { l := List{} for str := range m { v, err := Parse(str) @@ -24,43 +84,7 @@ func (m Migration) AllVersions() (List, error) { return l, nil } -// RequiredVersions returns a List of all versions in the migration map that are newer than a given (presumed current) version. -// If currentVersion is nil, this is identical to AllVersions. -func (m Migration) RequiredVersions(currentVersion *Version) (List, error) { - l, err := m.AllVersions() - if err != nil { - return nil, err - } - - if currentVersion != nil { - c := &Constraint{ - Gt: currentVersion, - } - l = l.Match(c) - } - - return l, nil -} - -// Run all required migration functions in the migration map. -// If currentVersion is nil, all migrations will be run. -// If afterEachCallback is not nil, it will be called after each successful migration. -func (m Migration) Run(currentVersion *Version, afterEachCallback func(*Version)) error { - versions, err := m.RequiredVersions(currentVersion) - if err != nil { - return err - } - - for _, v := range versions { - f := m[v.Text] - if err := f(); err != nil { - return err - } - - if afterEachCallback != nil { - afterEachCallback(v) - } - } - - return nil +type Patch struct { + Down func() error + Up func() error }