Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions tools/cli/internal/apiversion/stabilitylevel.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,17 @@ import (
const (
StableStabilityLevel = "stable"
PreviewStabilityLevel = "preview"
UpcomingStabilityLevel = "upcoming"
PrivatePreviewStabilityLevel = "private-preview"
PublicPreviewStabilityLevel = "public-preview"
)

var supportedValues = []string{StableStabilityLevel, PublicPreviewStabilityLevel, PrivatePreviewStabilityLevel}
var supportedValues = []string{StableStabilityLevel, UpcomingStabilityLevel, PublicPreviewStabilityLevel, PrivatePreviewStabilityLevel}

// IsPreviewStabilityLevel checks if the version is a preview version, public or private.
func IsPreviewStabilityLevel(value string) bool {
return IsPrivatePreviewStabilityLevel(value) || IsPublicPreviewStabilityLevel(value)
lowerCaseValue := strings.ToLower(value)
return IsPrivatePreviewStabilityLevel(lowerCaseValue) || IsPublicPreviewStabilityLevel(lowerCaseValue)
}

// IsPrivatePreviewStabilityLevel checks if the version is a private preview version.
Expand All @@ -40,18 +42,24 @@ func IsPrivatePreviewStabilityLevel(value string) bool {

// IsPublicPreviewStabilityLevel checks if the version is a public preview version.
func IsPublicPreviewStabilityLevel(value string) bool {
return strings.EqualFold(value, PublicPreviewStabilityLevel) || strings.EqualFold(value, PreviewStabilityLevel)
lowerCaseValue := strings.ToLower(value)
return strings.EqualFold(lowerCaseValue, PublicPreviewStabilityLevel) || strings.EqualFold(lowerCaseValue, PreviewStabilityLevel)
}

// IsStableStabilityLevel checks if the version is a stable version.
func IsStableStabilityLevel(value string) bool {
return strings.EqualFold(value, StableStabilityLevel)
return strings.EqualFold(strings.ToLower(value), StableStabilityLevel)
}

// IsUpcomingStabilityLevel checks if the version is an "upcoming" version.
func IsUpcomingStabilityLevel(value string) bool {
return strings.Contains(strings.ToLower(value), UpcomingStabilityLevel)
}

// IsValidStabilityLevel checks if the version is a valid stability level.
// ValidateStabilityLevel checks if the version is a valid stability level.
func ValidateStabilityLevel(value string) error {
if IsStableStabilityLevel(value) || IsPreviewStabilityLevel(value) {
lowerCaseValue := strings.ToLower(value)
if IsStableStabilityLevel(lowerCaseValue) || IsPreviewStabilityLevel(lowerCaseValue) || IsUpcomingStabilityLevel(lowerCaseValue) {
return nil
}

Expand Down
11 changes: 10 additions & 1 deletion tools/cli/internal/apiversion/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ const (
previewDate = "3000-01-01"
)

var contentPattern = regexp.MustCompile(`application/vnd\.atlas\.((\d{4})-(\d{2})-(\d{2})|preview)\+(.+)`)
// This regex will match:
// 1. application/vnd.atlas.2025-01-01.json
// 2. application/vnd.atlas.preview.json
// 3. application/vnd.atlas.2025-01-01.upcoming.json
// 4. application/vnd.atlas.2025-01-01.upcoming.yaml
var contentPattern = regexp.MustCompile(`application/vnd\.atlas\.((\d{4})-(\d{2})-(\d{2})(\.upcoming)?|preview)\+(.+)`)

// Option is a function that sets a value on the APIVersion.
type Option func(v *APIVersion) error
Expand Down Expand Up @@ -121,6 +126,10 @@ func DateFromVersion(version string) (time.Time, error) {
if IsPreviewStabilityLevel(version) {
return time.Parse(dateFormat, previewDate)
}

if IsUpcomingStabilityLevel(version) {
return time.Parse(dateFormat, strings.ReplaceAll(version, ".upcoming", ""))
}
return time.Parse(dateFormat, version)
}

Expand Down
2 changes: 1 addition & 1 deletion tools/cli/internal/cli/usage/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@ const (
SlackChannelID = "Slack Channel ID."
From = "Date in the format YYYY-MM-DD that indicates the start of a date range"
To = "Date in the format YYYY-MM-DD that indicates the end of a date range"
StabilityLevel = "Stability level related to the API Version. Valid values: [STABLE, PUBLIC-PREVIEW, PRIVATE-PREVIEW]"
StabilityLevel = "Stability level related to the API Version. Valid values: [STABLE, UPCOMING, PUBLIC-PREVIEW, PRIVATE-PREVIEW]"
Version = "Version of the API."
)
6 changes: 5 additions & 1 deletion tools/cli/internal/cli/versions/versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ func (o *Opts) filterStabilityLevelVersions(apiVersions []string) []string {
if (apiversion.IsPublicPreviewStabilityLevel(stabilityLevel)) && apiversion.IsPublicPreviewStabilityLevel(v) {
out = append(out, v)
}

if apiversion.IsUpcomingStabilityLevel(stabilityLevel) && apiversion.IsUpcomingStabilityLevel(v) {
out = append(out, v)
}
}
}

Expand Down Expand Up @@ -145,7 +149,7 @@ func (o *Opts) PreRunE(_ []string) error {
}

// Builder builds the versions command with the following signature:
// versions -s oas --env dev|qa|staging|prod -stability-level STABLE|PREVIEW.
// versions -s oas --env dev|qa|staging|prod -stability-level STABLE|UPCOMING|PREVIEW.
func Builder() *cobra.Command {
opts := &Opts{
fs: afero.NewOsFs(),
Expand Down
35 changes: 35 additions & 0 deletions tools/cli/internal/cli/versions/versions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,41 @@ func TestVersion_RunWithPreview(t *testing.T) {
assert.Contains(t, string(b), "preview")
}

func TestVersion_RunWithUpcoming(t *testing.T) {
fs := afero.NewMemMapFs()
opts := &Opts{
basePath: "../../../test/data/openapi_with_upcoming.json",
outputPath: "foas.json",
fs: fs,
env: "dev",
}

require.NoError(t, opts.Run())
b, err := afero.ReadFile(fs, opts.outputPath)
require.NoError(t, err)

// Check initial versions
assert.NotEmpty(t, b)
assert.Contains(t, string(b), "2023-02-01")
assert.Contains(t, string(b), "2025-09-22.upcoming")

opts = &Opts{
basePath: "../../../test/data/openapi_with_upcoming.json",
outputPath: "foas.json",
fs: fs,
env: "prod",
}

require.NoError(t, opts.Run())
b, err = afero.ReadFile(fs, opts.outputPath)
require.NoError(t, err)

assert.NotEmpty(t, b)
assert.Contains(t, string(b), "2023-02-01")
// Upcoming is only available for dev env
assert.NotContains(t, string(b), "2025-09-22.upcoming")
}

func TestVersion_RunStabilityLevelPreviewAndPrivatePreview(t *testing.T) {
fs := afero.NewMemMapFs()
opts := &Opts{
Expand Down
3 changes: 2 additions & 1 deletion tools/cli/internal/openapi/filter/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package filter

import (
Expand Down Expand Up @@ -48,6 +49,6 @@ func replaceVersion(input string, v *apiversion.APIVersion) string {
return input // No match found, return the original string
}

replacement := fmt.Sprintf("application/vnd.atlas.%s+%s", v.String(), matches[5])
replacement := fmt.Sprintf("application/vnd.atlas.%s+%s", v.String(), matches[6])
return apiversion.ReplaceContentType(input, replacement)
}
31 changes: 30 additions & 1 deletion tools/cli/internal/openapi/versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ package openapi

import (
"fmt"
"log"
"sort"
"strings"

"github.com/getkin/kin-openapi/openapi3"
"github.com/mongodb/openapi/tools/cli/internal/apiversion"
Expand Down Expand Up @@ -75,7 +77,34 @@ func extractVersions(oas *openapi3.T) ([]string, error) {
}
}

return mapKeysToSortedSlice(versions), nil
return mapKeysToSortedSlice(filterUpcomingVersions(versions)), nil
}

// filterUpcomingVersions removes the "YYYY.MM.DD.upcoming" apis version if the
// related stable api YYYY.MM.DD is available.
func filterUpcomingVersions(apiVersions map[string]struct{}) map[string]struct{} {
apiVersionSet := make(map[string]struct{}, len(apiVersions))
upcomingVersions := make([]string, 0, len(apiVersions))

for v := range apiVersions {
if apiversion.IsUpcomingStabilityLevel(strings.ToLower(v)) {
upcomingVersions = append(upcomingVersions, v)
continue
}
apiVersionSet[v] = struct{}{}
}

for _, upcomingVersion := range upcomingVersions {
date := strings.ReplaceAll(upcomingVersion, ".upcoming", "")
if _, ok := apiVersionSet[date]; !ok {
apiVersionSet[upcomingVersion] = struct{}{}
} else {
log.Printf("The API Version %[1]q was removed as the Stable API Version %[2]q was detected."+
" Please, remove %[1]q from the OpenAPI Spec.\n", upcomingVersion, date)
}
}

return apiVersionSet
}

// mapKeysToSortedSlice converts map keys to a sorted slice.
Expand Down
89 changes: 89 additions & 0 deletions tools/cli/internal/openapi/versions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ func TestVersions_PublicPreview(t *testing.T) {
assert.Equal(t, []string{"2023-01-01", "2023-02-01", "preview"}, versions)
}

func TestVersions_UpcomingAPI(t *testing.T) {
versions, err := ExtractVersionsWithEnv(NewVersionedResponsesWithUpcoming(t), "")
require.NoError(t, err)
assert.Equal(t, []string{"2023-01-01", "2025-09-22.upcoming"}, versions)
}

func TestVersions_UpcomingAndStableAPI(t *testing.T) {
versions, err := ExtractVersionsWithEnv(NewVersionedResponsesWithUpcomingAndStable(t), "")
require.NoError(t, err)
assert.Equal(t, []string{"2023-01-01", "2025-09-22"}, versions)
}

func TestVersions_InvalidPreviewData(t *testing.T) {
r := NewVersionedResponses(t)
// override the extension so something invalid like "public": true
Expand Down Expand Up @@ -194,3 +206,80 @@ func NewVersionedResponses(t *testing.T) *openapi3.T {

return oas
}

func NewVersionedResponsesWithUpcoming(t *testing.T) *openapi3.T {
t.Helper()
inputPath := &openapi3.Paths{}

extension := map[string]any{
"x-xgen-version": "2023-01-01",
}
response := &openapi3.ResponseRef{
Value: &openapi3.Response{
Extensions: extension,
Content: map[string]*openapi3.MediaType{
"application/vnd.atlas.2023-01-01+json": {},
"application/vnd.atlas.2025-09-22.upcoming+json": {},
},
},
}

responses := &openapi3.Responses{}
responses.Set("200", response)

inputPath.Set("pathBase1", &openapi3.PathItem{
Extensions: nil,
Ref: "",
Summary: "pathBase1",
Description: "pathBase1Description",
Get: &openapi3.Operation{
Tags: []string{"tag1"},
Responses: responses,
},
})

oas := &openapi3.T{
Paths: inputPath,
}

return oas
}

func NewVersionedResponsesWithUpcomingAndStable(t *testing.T) *openapi3.T {
t.Helper()
inputPath := &openapi3.Paths{}

extension := map[string]any{
"x-xgen-version": "2023-01-01",
}
response := &openapi3.ResponseRef{
Value: &openapi3.Response{
Extensions: extension,
Content: map[string]*openapi3.MediaType{
"application/vnd.atlas.2023-01-01+json": {},
"application/vnd.atlas.2025-09-22.upcoming+json": {},
"application/vnd.atlas.2025-09-22+json": {},
},
},
}

responses := &openapi3.Responses{}
responses.Set("200", response)

inputPath.Set("pathBase1", &openapi3.PathItem{
Extensions: nil,
Ref: "",
Summary: "pathBase1",
Description: "pathBase1Description",
Get: &openapi3.Operation{
Tags: []string{"tag1"},
Responses: responses,
},
})

oas := &openapi3.T{
Paths: inputPath,
}

return oas
}
Loading
Loading