From 511ab51ca2a71979204bae4440034398e40171a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Louren=C3=A7o?= Date: Thu, 2 Nov 2023 13:41:29 +0000 Subject: [PATCH] fix: provide a Versions method on VersionIndex (#311) Provides a Versions method on the VersionIndex type which returns a copy of the VersionSlice used to build the index. This is a pre-requisite to upgrade the vervet version of vervet-underground. --- go.mod | 1 + go.sum | 2 + resource.go | 13 ++--- spec.go | 14 ++--- version.go | 101 ++++++++++++++++++++----------------- versionware/example/go.mod | 1 + versionware/example/go.sum | 2 + 7 files changed, 69 insertions(+), 65 deletions(-) diff --git a/go.mod b/go.mod index ab00bb56..53a2cd09 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/urfave/cli/v2 v2.25.7 github.com/vmware-labs/yaml-jsonpath v0.3.2 go.uber.org/multierr v1.11.0 + golang.org/x/exp v0.0.0-20231006140011-7918f672742d gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 139c8057..ac64a146 100644 --- a/go.sum +++ b/go.sum @@ -211,6 +211,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= diff --git a/resource.go b/resource.go index c19105fc..437076c6 100644 --- a/resource.go +++ b/resource.go @@ -6,11 +6,11 @@ import ( "errors" "fmt" "path/filepath" - "sort" "time" "github.com/bmatcuk/doublestar/v4" "github.com/getkin/kin-openapi/openapi3" + "golang.org/x/exp/maps" ) const ( @@ -125,14 +125,7 @@ func (rv *ResourceVersions) Name() string { // Versions returns each Version defined for this resource. func (rv *ResourceVersions) Versions() VersionSlice { - result := make(VersionSlice, len(rv.versions)) - i := 0 - for v := range rv.versions { - result[i] = v - i++ - } - sort.Sort(result) - return result + return rv.index.Versions() } // ErrNoMatchingVersion indicates the requested version cannot be satisfied by @@ -275,7 +268,7 @@ func LoadResourceVersionsFileset(specYamls []string) (*ResourceVersions, error) } } } - resourceVersions.index = NewVersionIndex((resourceVersions.Versions())) + resourceVersions.index = NewVersionIndex(maps.Keys(resourceVersions.versions)) return &resourceVersions, nil } diff --git a/spec.go b/spec.go index 44acfcf8..9912c37e 100644 --- a/spec.go +++ b/spec.go @@ -10,6 +10,7 @@ import ( "github.com/bmatcuk/doublestar/v4" "github.com/getkin/kin-openapi/openapi3" + "golang.org/x/exp/maps" ) // SpecGlobPattern defines the expected directory structure for the versioned @@ -19,7 +20,6 @@ const SpecGlobPattern = "**/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/spec.yaml // SpecVersions stores a collection of versioned OpenAPI specs. type SpecVersions struct { - versions VersionSlice index VersionIndex documents map[Version]*openapi3.T } @@ -68,7 +68,7 @@ func LoadSpecVersionsFileset(epPaths []string) (*SpecVersions, error) { // Versions returns the distinct API versions in this collection of OpenAPI // documents. func (sv *SpecVersions) Versions() VersionSlice { - return sv.versions + return sv.index.Versions() } // At returns the OpenAPI document that matches the given version. If the @@ -104,7 +104,7 @@ func (sv *SpecVersions) resolveOperations() { } type operationVersionMap map[operationKey]operationVersion activeOpsByStability := map[Stability]operationVersionMap{} - for _, v := range sv.versions { + for _, v := range sv.index.versions { doc := sv.documents[v] currentActiveOps, ok := activeOpsByStability[v.Stability] if !ok { @@ -245,14 +245,8 @@ func newSpecVersions(specs resourceVersionsSlice) (*SpecVersions, error) { documentVersions[v] = doc } } - versions = VersionSlice{} - for v := range documentVersions { - versions = append(versions, v) - } - sort.Sort(versions) sv := &SpecVersions{ - versions: versions, - index: NewVersionIndex(versions), + index: NewVersionIndex(maps.Keys(documentVersions)), documents: documentVersions, } sv.resolveOperations() diff --git a/version.go b/version.go index 11b5549e..f9354f26 100644 --- a/version.go +++ b/version.go @@ -238,7 +238,8 @@ type VersionSlice []Version // VersionIndex provides a search over versions, resolving which version is in // effect for a given date and stability level. type VersionIndex struct { - versions []effectiveVersion + effectiveVersions []effectiveVersion + versions VersionSlice } type effectiveVersion struct { @@ -250,38 +251,46 @@ type effectiveVersion struct { // VersionSlice will be sorted. func NewVersionIndex(vs VersionSlice) (vi VersionIndex) { sort.Sort(vs) + vi.versions = make(VersionSlice, len(vs)) + copy(vi.versions, vs) + evIndex := -1 currentStabilities := [numStabilityLevels]time.Time{} - for i := range vs { - if evIndex == -1 || !vi.versions[evIndex].date.Equal(vs[i].Date) { - vi.versions = append(vi.versions, effectiveVersion{ - date: vs[i].Date, + for i := range vi.versions { + if evIndex == -1 || !vi.effectiveVersions[evIndex].date.Equal(vi.versions[i].Date) { + vi.effectiveVersions = append(vi.effectiveVersions, effectiveVersion{ + date: vi.versions[i].Date, stabilities: currentStabilities, }) evIndex++ } - vi.versions[evIndex].stabilities[vs[i].Stability] = vs[i].Date - currentStabilities[vs[i].Stability] = vs[i].Date + vi.effectiveVersions[evIndex].stabilities[vi.versions[i].Stability] = vi.versions[i].Date + currentStabilities[vi.versions[i].Stability] = vi.versions[i].Date } return vi } -// resolveIndex performs a binary search on the stability versions in effect on -// the query date. -func (vi *VersionIndex) resolveIndex(query time.Time) (int, error) { - if len(vi.versions) == 0 || vi.versions[0].date.After(query) { - return -1, ErrNoMatchingVersion +// Deprecates returns the version that deprecates the given version in the +// slice. +func (vi *VersionIndex) Deprecates(q Version) (Version, bool) { + match, err := vi.resolveIndex(q.Date) + if err == ErrNoMatchingVersion { + return Version{}, false } - lower, curr, upper := 0, len(vi.versions)/2, len(vi.versions) - for lower < upper-1 { - if vi.versions[curr].date.After(query) { - upper = curr - } else { - lower = curr + if err != nil { + panic(err) + } + for i := match + 1; i < len(vi.effectiveVersions); i++ { + for stab := q.Stability; stab < numStabilityLevels; stab++ { + if stabDate := vi.effectiveVersions[i].stabilities[stab]; stabDate.After(q.Date) { + return Version{ + Date: vi.effectiveVersions[i].date, + Stability: stab, + }, true + } } - curr = lower + (upper-lower)/2 } - return lower, nil + return Version{}, false } // Resolve returns the released version effective on the query version date at @@ -295,13 +304,38 @@ func (vi *VersionIndex) Resolve(query Version) (Version, error) { return Version{}, err } for stab := query.Stability; stab < numStabilityLevels; stab++ { - if stabDate := vi.versions[i].stabilities[stab]; !stabDate.IsZero() { + if stabDate := vi.effectiveVersions[i].stabilities[stab]; !stabDate.IsZero() { return Version{Date: stabDate, Stability: stab}, nil } } return Version{}, ErrNoMatchingVersion } +// Versions returns each Version defined. +func (vi *VersionIndex) Versions() VersionSlice { + vs := make(VersionSlice, len(vi.versions)) + copy(vs, vi.versions) + return vs +} + +// resolveIndex performs a binary search on the stability versions in effect on +// the query date. +func (vi *VersionIndex) resolveIndex(query time.Time) (int, error) { + if len(vi.effectiveVersions) == 0 || vi.effectiveVersions[0].date.After(query) { + return -1, ErrNoMatchingVersion + } + lower, curr, upper := 0, len(vi.effectiveVersions)/2, len(vi.effectiveVersions) + for lower < upper-1 { + if vi.effectiveVersions[curr].date.After(query) { + upper = curr + } else { + lower = curr + } + curr = lower + (upper-lower)/2 + } + return lower, nil +} + // resolveForBuild returns the most stable version effective on the query // version date with respect to the given version stability. Returns // ErrNoMatchingVersion if no version matches. @@ -316,7 +350,7 @@ func (vi *VersionIndex) resolveForBuild(query Version) (Version, error) { var matchDate time.Time var matchStab Stability for stab := query.Stability; stab < numStabilityLevels; stab++ { - stabDate := vi.versions[i].stabilities[stab] + stabDate := vi.effectiveVersions[i].stabilities[stab] if !stabDate.IsZero() && !stabDate.Before(matchDate) && !stabDate.After(query.Date) { matchDate, matchStab = stabDate, stab } @@ -327,29 +361,6 @@ func (vi *VersionIndex) resolveForBuild(query Version) (Version, error) { return Version{Date: matchDate, Stability: matchStab}, nil } -// Deprecates returns the version that deprecates the given version in the -// slice. -func (vi *VersionIndex) Deprecates(q Version) (Version, bool) { - match, err := vi.resolveIndex(q.Date) - if err == ErrNoMatchingVersion { - return Version{}, false - } - if err != nil { - panic(err) - } - for i := match + 1; i < len(vi.versions); i++ { - for stab := q.Stability; stab < numStabilityLevels; stab++ { - if stabDate := vi.versions[i].stabilities[stab]; stabDate.After(q.Date) { - return Version{ - Date: vi.versions[i].date, - Stability: stab, - }, true - } - } - } - return Version{}, false -} - // Len implements sort.Interface. func (vs VersionSlice) Len() int { return len(vs) } diff --git a/versionware/example/go.mod b/versionware/example/go.mod index a0ec725b..c701260c 100644 --- a/versionware/example/go.mod +++ b/versionware/example/go.mod @@ -35,6 +35,7 @@ require ( github.com/prometheus/procfs v0.12.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/sys v0.13.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/versionware/example/go.sum b/versionware/example/go.sum index 45528da1..5cc6c624 100644 --- a/versionware/example/go.sum +++ b/versionware/example/go.sum @@ -72,6 +72,8 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=