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
7 changes: 3 additions & 4 deletions tools/cli/internal/openapi/filter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ The Atlas Admin API OpenAPI specifications are used not only to document REST en
- Filtering per version, so that only the endpoints that are available in that version are shown.
## What filters are available?
### List of filters
[ExtensionFilter is a filter that deletes the x-xgen-ipa-exception extensions, updates the x-sunset and x-xgen-version](../internal/openapi/filter/extension.go?plain=1#L24)
[ExtensionFilter is a filter that removes the x-xgen-IPA-exception extension from the OpenAPI spec.](../internal/openapi/filter/extension.go?plain=1#L21)
[HiddenEnvsFilter is a filter that removes paths, operations,](../internal/openapi/filter/hidden_envs.go?plain=1#L28)
[InfoFilter is a filter that modifies the Info object in the OpenAPI spec.](../internal/openapi/filter/info.go?plain=1#L23)
[OperationsFilter is a filter that removes the x-xgen-owner-team extension from operations](../internal/openapi/filter/operations.go?plain=1#L20)
[TagsFilter removes tags that are not used in the operations.](../internal/openapi/filter/tags.go?plain=1#L22)
[VersioningFilter is a filter that modifies the OpenAPI spec by removing operations and responses](../internal/openapi/filter/versioning.go?plain=1#L24)
[VersioningExtensionFilter is a filter that updates the x-sunset and x-xgen-version extensions to a date string](../internal/openapi/filter/versioning_extension.go?plain=1#L25)
[VersioningFilter is a filter that modifies the OpenAPI spec by removing operations and responses](../internal/openapi/filter/versioning.go?plain=1#L25)
94 changes: 1 addition & 93 deletions tools/cli/internal/openapi/filter/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,16 @@
package filter

import (
"log"
"time"

"github.com/getkin/kin-openapi/openapi3"
"github.com/mongodb/openapi/tools/cli/internal/apiversion"
)

// Filter: ExtensionFilter is a filter that updates the x-sunset and x-xgen-version extensions to a date string
// and deletes the x-sunset extension if the latest matched version is deprecated by hidden versions
// for the target environment.
// Filter: ExtensionFilter is a filter that removes the x-xgen-IPA-exception extension from the OpenAPI spec.
type ExtensionFilter struct {
oas *openapi3.T
metadata *Metadata
}

const (
sunsetExtension = "x-sunset"
xGenExtension = "x-xgen-version"
ipaExceptionExtension = "x-xgen-IPA-exception"
format = "2006-01-02T15:04:05Z07:00"
)
Expand All @@ -42,15 +34,13 @@ func (f *ExtensionFilter) Apply() error {
if pathItem == nil {
continue
}
updateExtensionToDateString(pathItem.Extensions)
deleteIpaExceptionExtension(pathItem.Extensions)

for _, operation := range pathItem.Operations() {
if operation == nil {
continue
}

updateExtensionToDateString(operation.Extensions)
deleteIpaExceptionExtension(operation.Extensions)

if operation.Parameters != nil {
Expand All @@ -59,37 +49,28 @@ func (f *ExtensionFilter) Apply() error {

updateExtensionsForRequestBody(operation.RequestBody)

latestVersionMatch := apiversion.FindLatestContentVersionMatched(operation, f.metadata.targetVersion)

for _, response := range operation.Responses.Map() {
if response == nil {
continue
}

updateExtensionToDateString(response.Extensions)
deleteIpaExceptionExtension(response.Extensions)

if response.Value == nil {
continue
}

updateExtensionToDateString(response.Value.Extensions)
deleteIpaExceptionExtension(response.Value.Extensions)

if response.Value.Content == nil {
continue
}

f.deleteSunsetIfDeprecatedByHiddenVersions(latestVersionMatch, response.Value.Content)
updateToDateString(response.Value.Content)
}

request := operation.RequestBody
if request == nil || request.Value == nil || request.Value.Content == nil {
continue
}
updateToDateString(request.Value.Content)
f.deleteSunsetIfDeprecatedByHiddenVersions(latestVersionMatch, request.Value.Content)
}
}
if f.oas.Tags != nil {
Expand Down Expand Up @@ -196,76 +177,3 @@ func deleteIpaExceptionExtension(extensions map[string]any) {

delete(extensions, ipaExceptionExtension)
}

func updateExtensionToDateString(extensions map[string]any) {
if extensions == nil {
return
}

for k, v := range extensions {
if k != sunsetExtension && k != xGenExtension {
continue
}
date, err := time.Parse(format, v.(string))
if err != nil {
continue
}
extensions[k] = date.Format("2006-01-02")
}
}

func updateToDateString(content openapi3.Content) {
for _, mediaType := range content {
if mediaType.Extensions == nil {
continue
}

updateExtensionToDateString(mediaType.Extensions)
}
}

// deleteSunsetIfDeprecatedByHiddenVersions deletes the sunset extension if the latest matched version is deprecated by hidden versions.
func (f *ExtensionFilter) deleteSunsetIfDeprecatedByHiddenVersions(latestMatchedVersion *apiversion.APIVersion, content openapi3.Content) {
versions, versionToContentType := getVersionsInContentType(content)

deprecatedByHiddenVersions := make([]*apiversion.APIVersion, 0)
deprecatedByVersions := make([]*apiversion.APIVersion, 0)

for _, v := range versions {
if v.GreaterThan(latestMatchedVersion) {
if value, ok := versionToContentType[v.String()]; ok {
if isContentTypeHiddenForEnv(value, f.metadata.targetEnv) {
deprecatedByHiddenVersions = append(deprecatedByHiddenVersions, v)
continue
}
deprecatedByVersions = append(deprecatedByVersions, v)
}
}
}

// If the exact requested version is marked for sunset for a list of hidden versions
if value, ok := versionToContentType[latestMatchedVersion.String()]; ok {
if len(deprecatedByHiddenVersions) > 0 && len(deprecatedByVersions) == 0 && value.Extensions != nil {
delete(value.Extensions, sunsetExtension)
}
}
}

func getVersionsInContentType(content map[string]*openapi3.MediaType) (
versions []*apiversion.APIVersion, contentsInVersion map[string]*openapi3.MediaType) {
contentsInVersion = make(map[string]*openapi3.MediaType)
versionsInContentType := make(map[string]*apiversion.APIVersion)

for contentType, contentValue := range content {
v, err := apiversion.New(apiversion.WithFullContent(contentType, contentValue))
if err != nil {
log.Printf("Ignoring invalid content type: %s", contentType)
continue
}
versions = append(versions, v)
versionsInContentType[v.String()] = v
contentsInVersion[v.String()] = content[contentType]
}

return versions, contentsInVersion
}
121 changes: 0 additions & 121 deletions tools/cli/internal/openapi/filter/extension_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,59 +24,6 @@ import (
"github.com/stretchr/testify/require"
)

func TestXSunsetFilter_removeSunset(t *testing.T) {
tests := []struct {
name string
oas *openapi3.T
version string
sunsetDate string
}{
{
name: "sunset 2023-01-01",
oas: getOasSunset(),
version: "2023-01-01",
sunsetDate: "2024-05-30",
},
{
name: "sunset 2024-05-30",
oas: getOasSunset(),
version: "2024-05-30",
sunsetDate: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
version, err := apiversion.New(apiversion.WithVersion(tt.version))
require.NoError(t, err)
oas := tt.oas

filter := &ExtensionFilter{
oas: oas,
metadata: &Metadata{targetVersion: version, targetEnv: "dev"},
}

contentKey := fmt.Sprintf("application/vnd.atlas.%s+json", tt.version)
require.NoError(t, filter.Apply())
assert.NotNil(t, oas.Paths.Find("/path").Get)
assert.NotEmpty(t, oas.Paths.Find("/path").Get.Responses)
assert.NotNil(t, oas.Paths.Find("/path").Get.Responses.Map()["200"])

versionExtension := oas.Paths.Find("/path").Get.Responses.Map()["200"].Value.Content.Get(contentKey).Extensions[xGenExtension]
assert.Equal(t, tt.version, versionExtension)

if tt.sunsetDate == "" {
assert.Empty(t, oas.Paths.Find("/path").Get.Responses.Map()["200"].Value.Content.Get(contentKey).Extensions[sunsetExtension])
return
}

assert.NotNil(t, oas.Paths.Find("/path").Get.Responses.Map()["200"].Value.Content.Get(contentKey))
contentExtensions := oas.Paths.Find("/path").Get.Responses.Map()["200"].Value.Content.Get(contentKey).Extensions
assert.Contains(t, contentExtensions, sunsetExtension)
assert.Equal(t, tt.sunsetDate, contentExtensions[sunsetExtension])
})
}
}

func TestExtensionFilter_removeIpaException(t *testing.T) {
oas := getOasIpaExceptions()
version, err := apiversion.New(apiversion.WithVersion("2023-01-01"))
Expand Down Expand Up @@ -196,74 +143,6 @@ func TestExtensionFilter_removeIpaException(t *testing.T) {
}
}

func getOasSunset() *openapi3.T {
oas := &openapi3.T{}
oas.Paths = &openapi3.Paths{}

operation := &openapi3.Operation{
Responses: &openapi3.Responses{},
}

operation.Responses.Set("200", &openapi3.ResponseRef{
Value: &openapi3.Response{
Content: map[string]*openapi3.MediaType{
"application/vnd.atlas.2023-01-01+json": {
Schema: &openapi3.SchemaRef{
Value: &openapi3.Schema{
Description: "description",
},
},
Extensions: map[string]any{
"x-sunset": "2024-05-30T00:00:00Z",
xGenExtension: "2023-01-01T00:00:00Z",
},
},
"application/vnd.atlas.2024-02-30+json": {
Schema: &openapi3.SchemaRef{
Value: &openapi3.Schema{
Description: "description",
},
},
Extensions: map[string]any{
"x-sunset": "2024-04-10",
xGenExtension: "2024-02-30T00:00:00Z",
},
},
"application/vnd.atlas.2025-01-01+json": {
Schema: &openapi3.SchemaRef{
Value: &openapi3.Schema{
Description: "description",
},
Extensions: map[string]any{
"x-sunset": "2025-01-01T00:00:00Z",
xGenExtension: "2025-01-01",
},
},
Extensions: map[string]any{
hiddenEnvsExtension: map[string]any{
"envs": "dev,qa,prod,stage",
},
},
},
"application/vnd.atlas.2024-05-30+json": {
Schema: &openapi3.SchemaRef{
Value: &openapi3.Schema{
Description: "description",
},
},
Extensions: map[string]any{
"x-sunset": "2025-01-01T00:00:00Z",
xGenExtension: "2024-05-30",
},
},
},
},
})

oas.Paths.Set("/path", &openapi3.PathItem{Get: operation})
return oas
}

func getOasIpaExceptions() *openapi3.T {
extension := map[string]any{
ipaExceptionExtension: map[string]string{"IPA-104-resource-has-GET": "reason"},
Expand Down
1 change: 1 addition & 0 deletions tools/cli/internal/openapi/filter/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func validateMetadata(metadata *Metadata) error {
func DefaultFilters(oas *openapi3.T, metadata *Metadata) []Filter {
return []Filter{
&ExtensionFilter{oas: oas, metadata: metadata},
&VersioningExtensionFilter{oas: oas, metadata: metadata},
&VersioningFilter{oas: oas, metadata: metadata},
&InfoFilter{oas: oas, metadata: metadata},
&HiddenEnvsFilter{oas: oas, metadata: metadata},
Expand Down
27 changes: 14 additions & 13 deletions tools/cli/internal/openapi/filter/hidden_envs.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,19 +165,6 @@ func (f *HiddenEnvsFilter) isResponseHiddenForEnv(response *openapi3.ResponseRef
return false
}

func isContentTypeHiddenForEnv(contentType *openapi3.MediaType, targetEnv string) bool {
if contentType == nil {
return false
}

if extension, ok := contentType.Extensions[hiddenEnvsExtension]; ok {
log.Printf("Found x-hidden-envs: K: %q, V: %q", hiddenEnvsExtension, extension)
return isHiddenExtensionEqualToTargetEnv(extension, targetEnv)
}

return false
}

func (f *HiddenEnvsFilter) isRequestBodyHiddenForEnv(requestBody *openapi3.RequestBodyRef) bool {
if requestBody == nil {
return false
Expand Down Expand Up @@ -208,3 +195,17 @@ func isHiddenExtensionEqualToTargetEnv(extension any, target string) bool {
}
return false
}

// isContentTypeHiddenForEnv returns true if the content type is hidden for the target environment.
func isContentTypeHiddenForEnv(contentType *openapi3.MediaType, targetEnv string) bool {
if contentType == nil {
return false
}

if extension, ok := contentType.Extensions[hiddenEnvsExtension]; ok {
log.Printf("Found x-hidden-envs: K: %q, V: %q", hiddenEnvsExtension, extension)
return isHiddenExtensionEqualToTargetEnv(extension, targetEnv)
}

return false
}
Loading
Loading