Skip to content

Commit

Permalink
fix: provide a Versions method on VersionIndex (#311)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jlourenc authored Nov 2, 2023
1 parent 0a47225 commit 511ab51
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 65 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
13 changes: 3 additions & 10 deletions resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down
14 changes: 4 additions & 10 deletions spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
Expand Down
101 changes: 56 additions & 45 deletions version.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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
}
Expand All @@ -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) }

Expand Down
1 change: 1 addition & 0 deletions versionware/example/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions versionware/example/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down

0 comments on commit 511ab51

Please sign in to comment.