diff --git a/tools/cli/internal/apiversion/version.go b/tools/cli/internal/apiversion/version.go index 72fe4b8a99..27a7c08578 100644 --- a/tools/cli/internal/apiversion/version.go +++ b/tools/cli/internal/apiversion/version.go @@ -31,9 +31,10 @@ type APIVersion struct { } const ( - dateFormat = "2006-01-02" - StableStabilityLevel = "stable" - PreviewStabilityLevel = "preview" + dateFormat = "2006-01-02" + StableStabilityLevel = "stable" + PreviewStabilityLevel = "preview" + PrivatePreviewStabilityLevel = "private-preview" ) var contentPattern = regexp.MustCompile(`application/vnd\.atlas\.((\d{4})-(\d{2})-(\d{2})|preview)\+(.+)`) @@ -150,7 +151,8 @@ func (v *APIVersion) IsPreview() bool { } func IsPreviewSabilityLevel(value string) bool { - return strings.EqualFold(value, PreviewStabilityLevel) + // we also need string match given private preview versions like "private-preview-" + return strings.EqualFold(value, PreviewStabilityLevel) || strings.Contains(value, PrivatePreviewStabilityLevel) } func IsStableSabilityLevel(value string) bool { diff --git a/tools/cli/internal/openapi/versions.go b/tools/cli/internal/openapi/versions.go index fb8e92d6e2..ac9e9f4c9d 100644 --- a/tools/cli/internal/openapi/versions.go +++ b/tools/cli/internal/openapi/versions.go @@ -15,7 +15,10 @@ package openapi import ( + "errors" + "fmt" "sort" + "strings" "github.com/getkin/kin-openapi/openapi3" "github.com/mongodb/openapi/tools/cli/internal/apiversion" @@ -53,11 +56,22 @@ func extractVersions(oas *openapi3.T) ([]string, error) { if response.Value == nil || response.Value.Content == nil { continue } - for contentType := range response.Value.Content { + for contentType, contentTypeValue := range response.Value.Content { version, err := apiversion.Parse(contentType) - if err == nil { - versions[version] = struct{}{} + if err != nil { + continue } + + if apiversion.IsPreviewSabilityLevel(version) { + // parse if it is public or not + version, err = getPreviewVersionName(contentTypeValue) + if err != nil { + fmt.Printf("failed to parse preview version name: %v\n", err) + continue + } + } + + versions[version] = struct{}{} } } } @@ -66,6 +80,66 @@ func extractVersions(oas *openapi3.T) ([]string, error) { return mapKeysToSortedSlice(versions), nil } +func getPreviewVersionName(contentTypeValue *openapi3.MediaType) (name string, err error) { + 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 + } + + return public, name, nil +} + // mapKeysToSortedSlice converts map keys to a sorted slice. func mapKeysToSortedSlice(m map[string]struct{}) []string { keys := make([]string, 0, len(m)) diff --git a/tools/cli/internal/openapi/versions_test.go b/tools/cli/internal/openapi/versions_test.go index 36ab6d45e4..31a5b70e60 100644 --- a/tools/cli/internal/openapi/versions_test.go +++ b/tools/cli/internal/openapi/versions_test.go @@ -27,6 +27,18 @@ func TestVersions(t *testing.T) { assert.Equal(t, []string{"2023-01-01", "2023-02-01"}, versions) } +func TestVersions_PrivatePreview(t *testing.T) { + versions, err := ExtractVersionsWithEnv(NewVersionedResponses(t), "dev") + require.NoError(t, err) + assert.ElementsMatch(t, []string{"2023-01-01", "2023-02-01", "private-preview-info-resource", "preview"}, versions) +} + +func TestVersions_PublicPreview(t *testing.T) { + versions, err := ExtractVersionsWithEnv(NewVersionedResponses(t), "qa") + require.NoError(t, err) + assert.Equal(t, []string{"2023-01-01", "2023-02-01", "preview"}, versions) +} + func NewVersionedResponses(t *testing.T) *openapi3.T { t.Helper() inputPath := &openapi3.Paths{} @@ -83,6 +95,83 @@ func NewVersionedResponses(t *testing.T) *openapi3.T { }, }) + extensionThree := map[string]any{ + "x-xgen-version": "preview", + "x-xgen-preview": map[string]any{ + "name": "info-resource", + }, + } + + responseThree := &openapi3.ResponseRef{ + Value: &openapi3.Response{ + Extensions: nil, + Content: map[string]*openapi3.MediaType{ + "application/vnd.atlas.preview+json": { + Extensions: extensionThree, + }, + }, + }, + } + + hiddenEnvExtension := map[string]any{ + "x-xgen-hidden-env": map[string]any{ + "envs": "qa,prod", + }, + } + + responsesThree := &openapi3.Responses{} + responsesThree.Set("200", responseThree) + + inputPath.Set("pathBase3", &openapi3.PathItem{ + Extensions: hiddenEnvExtension, + Ref: "", + Summary: "pathBase3", + Description: "pathBase3Description", + Delete: &openapi3.Operation{ + Tags: []string{"tag1"}, + Responses: responsesThree, + }, + }) + + extensionFour := map[string]any{ + "x-xgen-version": "preview", + "x-xgen-preview": map[string]any{ + "public": "true", + }, + } + + responseFour := &openapi3.ResponseRef{ + Value: &openapi3.Response{ + Extensions: nil, + Content: map[string]*openapi3.MediaType{ + "application/vnd.atlas.preview+json": { + Extensions: extensionFour, + }, + }, + }, + } + + hiddenEnvExtensionTwo := map[string]any{ + "x-xgen-hidden-env": map[string]any{ + "envs": "prod", + }, + } + + responsesFour := &openapi3.Responses{} + responsesFour.Set("200", responseFour) + + inputPath.Set("pathBase4", &openapi3.PathItem{ + Extensions: hiddenEnvExtensionTwo, + Ref: "", + Summary: "pathBase4", + Description: "pathBase4Description", + Post: &openapi3.Operation{ + + Tags: []string{"tag1"}, + Responses: responsesFour, + }, + }) + oas := &openapi3.T{ Paths: inputPath, } diff --git a/tools/cli/test/data/base_spec_with_preview.json b/tools/cli/test/data/base_spec_with_preview.json index 2b5c89ee28..f6c0bbc7ea 100644 --- a/tools/cli/test/data/base_spec_with_preview.json +++ b/tools/cli/test/data/base_spec_with_preview.json @@ -4038,7 +4038,10 @@ "$ref": "#/components/schemas/PaginatedAtlasGroupView" }, "x-xgen-version": "preview", - "x-sunset": "2025-06-30" + "x-sunset": "2025-06-30", + "x-xgen-preview": { + "name": "new-feature" + } } } },