Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
135 changes: 128 additions & 7 deletions tools/cli/internal/apiversion/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package apiversion

import (
"errors"
"fmt"
"log"
"regexp"
Expand Down Expand Up @@ -85,8 +86,8 @@ func WithDate(date time.Time) Option {
}
}

// WithContent returns an Option to generate a new APIVersion given the contentType.
func WithContent(contentType string) Option {
// withContent returns an Option to generate a new APIVersion given the contentType.
func withContent(contentType string) Option {
return func(v *APIVersion) error {
version, err := Parse(contentType)
if err != nil {
Expand All @@ -103,6 +104,22 @@ func WithContent(contentType string) Option {
}
}

// WithFullContent returns an Option to generate a new APIVersion given the contentType and contentValue.
func WithFullContent(contentType string, contentValue *openapi3.MediaType) Option {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

core change: we need to consider contentValue to gather version data, since for preview and private preview we have the same content type atlas.vnd.preview+json, we can only differentiate them based on the x-xgen-preview extension

return func(v *APIVersion) error {
if !IsPreviewSabilityLevel(contentType) {
return withContent(contentType)(v)
}

name, err := GetPreviewVersionName(contentValue)
if err != nil {
return err
}
// version will be based on the name, either 'preview' or 'private-preview-<name>'
return WithVersion(name)(v)
}
}

func DateFromVersion(version string) (time.Time, error) {
if IsPreviewSabilityLevel(version) {
return time.Now(), nil
Expand All @@ -111,7 +128,7 @@ func DateFromVersion(version string) (time.Time, error) {
}

func (v *APIVersion) Equal(v2 *APIVersion) bool {
return v.version == v2.version
return strings.EqualFold(v.version, v2.version)
}

func (v *APIVersion) GreaterThan(v2 *APIVersion) bool {
Expand Down Expand Up @@ -150,9 +167,17 @@ func (v *APIVersion) IsPreview() bool {
return IsPreviewSabilityLevel(v.version)
}

func (v *APIVersion) IsPrivatePreview() bool {
return strings.Contains(v.version, PrivatePreviewStabilityLevel)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't you call IsPreviewSabilityLevel(v.version) here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsPreviewSabilityLevel returns true to either Public or Private previews so we need to check for private preview here, but open to other suggestions

}

func (v *APIVersion) IsPublicPreview() bool {
return v.IsPreview() && !v.IsPrivatePreview()
}

func IsPreviewSabilityLevel(value string) bool {
// we also need string match given private preview versions like "private-preview-<name>"
return strings.EqualFold(value, PreviewStabilityLevel) || strings.Contains(value, PrivatePreviewStabilityLevel)
return strings.EqualFold(value, PreviewStabilityLevel) || strings.Contains(value, PreviewStabilityLevel)
}

func IsStableSabilityLevel(value string) bool {
Expand Down Expand Up @@ -205,8 +230,8 @@ func FindLatestContentVersionMatched(op *openapi3.Operation, requestedVersion *A
continue
}

for contentType := range response.Value.Content {
contentVersion, err := New(WithContent(contentType))
for contentType, contentValue := range response.Value.Content {
contentVersion, err := New(WithFullContent(contentType, contentValue))
if err != nil {
log.Printf("Ignoring invalid content type: %q", contentType)
continue
Expand All @@ -216,7 +241,11 @@ func FindLatestContentVersionMatched(op *openapi3.Operation, requestedVersion *A
return contentVersion
}

if contentVersion.ExactMatchOnly() || requestedVersion.ExactMatchOnly() {
if requestedVersion.ExactMatchOnly() {
// for private preview, we will need to match with "preview" and x-xgen-preview name extension
if privatePreviewContentMatch(contentVersion, contentValue, requestedVersion) {
return contentVersion
}
continue
}

Expand All @@ -237,6 +266,21 @@ func FindLatestContentVersionMatched(op *openapi3.Operation, requestedVersion *A
return latestVersionMatch
}

func privatePreviewContentMatch(contentVersion *APIVersion, contentValue *openapi3.MediaType, requestedVersion *APIVersion) bool {
log.Printf("trying to match in case one of the versions is private preview")
if !contentVersion.IsPrivatePreview() && !requestedVersion.IsPrivatePreview() {
return false
}

name, err := GetPreviewVersionName(contentValue)
if err != nil {
log.Printf("failed to parse preview version name for content=%v err=%v", contentVersion.version, err)
return false
}

return strings.EqualFold(name, requestedVersion.version)
}

// Sort versions.
func Sort(versions []*APIVersion) {
for i := 0; i < len(versions); i++ {
Expand All @@ -247,3 +291,80 @@ func Sort(versions []*APIVersion) {
}
}
}

// GetPreviewVersionName returns the preview version name.
func GetPreviewVersionName(contentTypeValue *openapi3.MediaType) (name string, err error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be public? 👀

Suggested change
func GetPreviewVersionName(contentTypeValue *openapi3.MediaType) (name string, err error) {
func getPreviewVersionName(contentTypeValue *openapi3.MediaType) (name string, err error) {

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think yes cause we use it in the versions package

public, name, err := parsePreviewExtensionData(contentTypeValue)
if err != nil {
return "", err
}

if public {
return "preview", nil
}

if !public && name != "" {
return "private-preview-" + name, nil
}

return "", errors.New("no preview extension found")
}

func parsePreviewExtensionData(contentTypeValue *openapi3.MediaType) (public bool, name string, err error) {
// Expected formats:
//
// "x-xgen-preview": {
// "name": "api-registry-private-preview"
// }
//
// "x-xgen-preview": {
// "public": "true"
// }

name = ""
public = false

if contentTypeValue.Extensions == nil {
return false, "", errors.New("no preview extension found")
}

previewExtension, ok := contentTypeValue.Extensions["x-xgen-preview"]
if !ok {
return false, "", errors.New("no preview extension found")
}

previewExtensionMap, ok := previewExtension.(map[string]any)
if !ok {
return false, "", errors.New("no preview extension found")
}

// Reading if it's public or not
publicV, ok := previewExtensionMap["public"].(string)
if ok {
public = strings.EqualFold(publicV, "true")
}

// Reading the name
nameV, ok := previewExtensionMap["name"].(string)
if ok {
name = nameV
}

if err := validatePreviewExtensionData(name, publicV); err != nil {
return false, "", err
}

return public, name, nil
}

func validatePreviewExtensionData(name, public string) error {
if name != "" && (public == "true") {
return errors.New("both name and public = true fields are set, only one is allowed")
}

if name == "" && public != "" && public != "true" && public != "false" {
return errors.New("invalid value for 'public' field, only 'true' or 'false' are allowed")
}

return nil
}
137 changes: 136 additions & 1 deletion tools/cli/internal/apiversion/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func TestNewAPIVersionFromContentType(t *testing.T) {
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
version, err := New(WithContent(tt.contentType))
version, err := New(withContent(tt.contentType))
if tt.wantErr {
assert.Error(t, err)
} else {
Expand All @@ -163,6 +163,136 @@ func TestNewAPIVersionFromContentType(t *testing.T) {
}
}

func TestApiVersion_WithFullContent(t *testing.T) {
testCases := []struct {
name string
contentType string
contentValue *openapi3.MediaType
expectedMatch string
wantErr bool
}{
{
name: "json",
contentType: "application/vnd.atlas.2023-01-01+json",
contentValue: &openapi3.MediaType{},
expectedMatch: "2023-01-01",
wantErr: false,
},
{
name: "csv",
contentType: "application/vnd.atlas.2023-01-02+csv",
contentValue: &openapi3.MediaType{},

expectedMatch: "2023-01-02",
wantErr: false,
},
{
name: "yaml",
contentType: "application/vnd.atlas.2030-02-20+yaml",
contentValue: &openapi3.MediaType{},
expectedMatch: "2030-02-20",
wantErr: false,
},
{
name: "invalid",

contentType: "application/vnd.test.2023-01-01",
contentValue: &openapi3.MediaType{},
expectedMatch: "",
wantErr: true,
},
{
name: "notVersioned",
contentType: "application/json",
contentValue: &openapi3.MediaType{},
expectedMatch: "",
wantErr: true,
},
{
name: "empty",
contentType: "",
contentValue: &openapi3.MediaType{},
expectedMatch: "",
wantErr: true,
},
{
name: "invalidFormat",
contentType: "application/vnd.atlas.2023-01-01",
expectedMatch: "",
contentValue: &openapi3.MediaType{},
wantErr: true,
},
{
name: "invalidDate",
contentType: "application/vnd.atlas.2023111-01-01",
expectedMatch: "",
contentValue: &openapi3.MediaType{},
wantErr: true,
},

{
name: "preview",
contentType: "application/vnd.atlas.preview+json",
contentValue: &openapi3.MediaType{
Extensions: map[string]any{
"x-xgen-preview": map[string]any{
"public": "true",
},
},
},
expectedMatch: "preview",
wantErr: false,
},
{
name: "private-preview",
contentType: "application/vnd.atlas.preview+json",
contentValue: &openapi3.MediaType{
Extensions: map[string]any{
"x-xgen-preview": map[string]any{
"name": "feature",
},
},
},
expectedMatch: "private-preview-feature",
wantErr: false,
},
{
name: "invalid-preview",
contentType: "application/vnd.atlas.preview+json",
contentValue: &openapi3.MediaType{
Extensions: map[string]any{
"x-xgen-preview": map[string]any{
"public": "true",
"name": "feature",
},
},
},
expectedMatch: "private-preview-feature",
wantErr: true,
},
{
name: "invalid-preview",
contentType: "application/vnd.atlas.preview+json",
contentValue: &openapi3.MediaType{},
expectedMatch: "preview",
wantErr: true,
},
}

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
version, err := New(WithFullContent(tt.contentType, tt.contentValue))
if tt.wantErr {
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tt.expectedMatch, version.String())
}
})
}
}

func TestApiVersion_GreaterThan(t *testing.T) {
testCases := []struct {
name string
Expand Down Expand Up @@ -445,6 +575,11 @@ func TestFindLatestContentVersionMatched(t *testing.T) {
targetVersion: "preview",
expectedMatch: "preview",
},
{
name: "exact match private-preview-feature",
targetVersion: "private-preview-feature",
expectedMatch: "private-preview-feature",
},
{
name: "exact match 2023-11-15",
targetVersion: "2023-11-15",
Expand Down
Loading
Loading