From a73bf489e9dcb95f7c0b9f905a31dec1efe9c53e Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Wed, 5 Mar 2025 18:45:58 +0000 Subject: [PATCH 01/11] chore: decouple extension filters from versioning extension filters --- tools/cli/internal/openapi/filter/README.md | 6 +- .../cli/internal/openapi/filter/extension.go | 94 +--------- .../internal/openapi/filter/extension_test.go | 121 ------------- tools/cli/internal/openapi/filter/filter.go | 1 + .../internal/openapi/filter/hidden_envs.go | 13 -- .../openapi/filter/versioning_extension.go | 166 ++++++++++++++++++ .../filter/versioning_extension_test.go | 146 +++++++++++++++ 7 files changed, 316 insertions(+), 231 deletions(-) create mode 100644 tools/cli/internal/openapi/filter/versioning_extension.go create mode 100644 tools/cli/internal/openapi/filter/versioning_extension_test.go diff --git a/tools/cli/internal/openapi/filter/README.md b/tools/cli/internal/openapi/filter/README.md index 3981b6c1a0..a98d2964b9 100644 --- a/tools/cli/internal/openapi/filter/README.md +++ b/tools/cli/internal/openapi/filter/README.md @@ -8,9 +8,7 @@ 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 updates the x-sunset and x-xgen-version extensions to a date string](../internal/openapi/filter/extension.go?plain=1#L25) [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) +[VersioningFilter is a filter that modifies the OpenAPI spec by removing operations and responses](../internal/openapi/filter/versioning.go?plain=1#L25) diff --git a/tools/cli/internal/openapi/filter/extension.go b/tools/cli/internal/openapi/filter/extension.go index e55a137624..d04d71df66 100644 --- a/tools/cli/internal/openapi/filter/extension.go +++ b/tools/cli/internal/openapi/filter/extension.go @@ -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" ) @@ -42,7 +34,6 @@ func (f *ExtensionFilter) Apply() error { if pathItem == nil { continue } - updateExtensionToDateString(pathItem.Extensions) deleteIpaExceptionExtension(pathItem.Extensions) for _, operation := range pathItem.Operations() { @@ -50,7 +41,6 @@ func (f *ExtensionFilter) Apply() error { continue } - updateExtensionToDateString(operation.Extensions) deleteIpaExceptionExtension(operation.Extensions) if operation.Parameters != nil { @@ -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 { @@ -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 -} diff --git a/tools/cli/internal/openapi/filter/extension_test.go b/tools/cli/internal/openapi/filter/extension_test.go index 1236be09f9..fd7fc13556 100644 --- a/tools/cli/internal/openapi/filter/extension_test.go +++ b/tools/cli/internal/openapi/filter/extension_test.go @@ -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")) @@ -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"}, diff --git a/tools/cli/internal/openapi/filter/filter.go b/tools/cli/internal/openapi/filter/filter.go index 158be49b1e..03a7b8955e 100644 --- a/tools/cli/internal/openapi/filter/filter.go +++ b/tools/cli/internal/openapi/filter/filter.go @@ -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}, diff --git a/tools/cli/internal/openapi/filter/hidden_envs.go b/tools/cli/internal/openapi/filter/hidden_envs.go index 23b6d33469..3c6bcfa494 100644 --- a/tools/cli/internal/openapi/filter/hidden_envs.go +++ b/tools/cli/internal/openapi/filter/hidden_envs.go @@ -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 diff --git a/tools/cli/internal/openapi/filter/versioning_extension.go b/tools/cli/internal/openapi/filter/versioning_extension.go new file mode 100644 index 0000000000..17b8cbf038 --- /dev/null +++ b/tools/cli/internal/openapi/filter/versioning_extension.go @@ -0,0 +1,166 @@ +// Copyright 2024 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 ( + "log" + "time" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/mongodb/openapi/tools/cli/internal/apiversion" +) + +// Filter: VersioningExtensionFilter 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. +type VersioningExtensionFilter struct { + oas *openapi3.T + metadata *Metadata +} + +const ( + sunsetExtension = "x-sunset" + xGenExtension = "x-xgen-version" +) + +func (f *VersioningExtensionFilter) Apply() error { + for _, pathItem := range f.oas.Paths.Map() { + if pathItem == nil { + continue + } + + updateExtensionToDateString(pathItem.Extensions) + + for _, operation := range pathItem.Operations() { + if operation == nil { + continue + } + + updateExtensionToDateString(operation.Extensions) + + latestVersionMatch := apiversion.FindLatestContentVersionMatched(operation, f.metadata.targetVersion) + + for _, response := range operation.Responses.Map() { + if response == nil { + continue + } + + updateExtensionToDateString(response.Extensions) + + if response.Value == nil { + continue + } + + updateExtensionToDateString(response.Value.Extensions) + if response.Value.Content == nil { + continue + } + + updateToDateString(response.Value.Content) + f.deleteSunsetIfDeprecatedByHiddenVersions(latestVersionMatch, 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) + } + } + + return nil +} + +// deleteSunsetIfDeprecatedByHiddenVersions deletes the sunset extension if the latest matched version is deprecated by hidden versions. +func (f *VersioningExtensionFilter) 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) + } + } + } +} + +// 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 +} + +// getVersionsInContentType returns a list of versions and a map of versions to content types. +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 +} + +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) + } +} diff --git a/tools/cli/internal/openapi/filter/versioning_extension_test.go b/tools/cli/internal/openapi/filter/versioning_extension_test.go new file mode 100644 index 0000000000..bbd1d2ce2a --- /dev/null +++ b/tools/cli/internal/openapi/filter/versioning_extension_test.go @@ -0,0 +1,146 @@ +// Copyright 2024 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 ( + "fmt" + "testing" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/mongodb/openapi/tools/cli/internal/apiversion" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestVersioningExtensionFilter_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 := &VersioningExtensionFilter{ + 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 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 +} From 8dc841be79ca811b918472ae38b52190b9a16a04 Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Thu, 6 Mar 2025 10:40:13 +0000 Subject: [PATCH 02/11] Update --- .../internal/openapi/filter/hidden_envs.go | 14 ++++++++++++++ .../openapi/filter/versioning_extension.go | 19 ++++++------------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/tools/cli/internal/openapi/filter/hidden_envs.go b/tools/cli/internal/openapi/filter/hidden_envs.go index 3c6bcfa494..b053bc837e 100644 --- a/tools/cli/internal/openapi/filter/hidden_envs.go +++ b/tools/cli/internal/openapi/filter/hidden_envs.go @@ -195,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 +} diff --git a/tools/cli/internal/openapi/filter/versioning_extension.go b/tools/cli/internal/openapi/filter/versioning_extension.go index 17b8cbf038..d6597afa04 100644 --- a/tools/cli/internal/openapi/filter/versioning_extension.go +++ b/tools/cli/internal/openapi/filter/versioning_extension.go @@ -68,8 +68,8 @@ func (f *VersioningExtensionFilter) Apply() error { continue } - updateToDateString(response.Value.Content) f.deleteSunsetIfDeprecatedByHiddenVersions(latestVersionMatch, response.Value.Content) + updateToDateString(response.Value.Content) } request := operation.RequestBody @@ -102,20 +102,13 @@ func (f *VersioningExtensionFilter) deleteSunsetIfDeprecatedByHiddenVersions(lat } } } -} -// 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) + // 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) + } } - - return false } // getVersionsInContentType returns a list of versions and a map of versions to content types. From 043c7be70a28e4261f15624465c93722067c24d4 Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Thu, 6 Mar 2025 10:41:44 +0000 Subject: [PATCH 03/11] update --- tools/cli/internal/openapi/filter/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/cli/internal/openapi/filter/README.md b/tools/cli/internal/openapi/filter/README.md index a98d2964b9..f42557ac96 100644 --- a/tools/cli/internal/openapi/filter/README.md +++ b/tools/cli/internal/openapi/filter/README.md @@ -8,7 +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 updates the x-sunset and x-xgen-version extensions to a date string](../internal/openapi/filter/extension.go?plain=1#L25) +[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) [OperationsFilter is a filter that removes the x-xgen-owner-team extension from operations](../internal/openapi/filter/operations.go?plain=1#L20) +[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) From 0385316f47f809cb2044bb2b271b6f0f8d623703 Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Thu, 6 Mar 2025 12:12:50 +0000 Subject: [PATCH 04/11] WIP --- tools/cli/internal/cli/filter/filter.go | 145 +++++++++++++ tools/cli/internal/cli/filter/filter_test.go | 201 +++++++++++++++++++ 2 files changed, 346 insertions(+) create mode 100644 tools/cli/internal/cli/filter/filter.go create mode 100644 tools/cli/internal/cli/filter/filter_test.go diff --git a/tools/cli/internal/cli/filter/filter.go b/tools/cli/internal/cli/filter/filter.go new file mode 100644 index 0000000000..5ac18ed200 --- /dev/null +++ b/tools/cli/internal/cli/filter/filter.go @@ -0,0 +1,145 @@ +// Copyright 2024 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 ( + "fmt" + "log" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/mongodb/openapi/tools/cli/internal/apiversion" + "github.com/mongodb/openapi/tools/cli/internal/cli/flag" + "github.com/mongodb/openapi/tools/cli/internal/cli/usage" + "github.com/mongodb/openapi/tools/cli/internal/openapi" + "github.com/mongodb/openapi/tools/cli/internal/openapi/filter" + "github.com/spf13/afero" + "github.com/spf13/cobra" +) + +type Opts struct { + fs afero.Fs + basePath string + outputPath string + env string + format string + gitSha string +} + +func (o *Opts) Run() error { + loader := openapi.NewOpenAPI3() + specInfo, err := loader.CreateOpenAPISpecFromPath(o.basePath) + if err != nil { + return err + } + + versions, err := openapi.ExtractVersionsWithEnv(specInfo.Spec, o.env) + if err != nil { + return err + } + + for _, version := range versions { + filteredOAS, err := o.filter(specInfo.Spec, version) + if err != nil { + return err + } + + if o.gitSha != "" { + filteredOAS.Info.Extensions = map[string]any{ + "x-xgen-sha": o.gitSha, + } + } + + if err := o.saveVersionedOas(filteredOAS, version); err != nil { + return err + } + + if err := filteredOAS.Validate(loader.Loader.Context); err != nil { + log.Printf("[WARN] OpenAPI document is invalid: %v", err) + } + } + + return nil +} + +func (o *Opts) filter(oas *openapi3.T, version string) (result *openapi3.T, err error) { + log.Printf("Filtering OpenAPI document by version %q", version) + apiVersion, err := apiversion.New(apiversion.WithVersion(version)) + if err != nil { + return nil, err + } + + return filter.ApplyFilters(oas, filter.NewMetadata(apiVersion, o.env), filter.DefaultFilters) +} + +func (o *Opts) saveVersionedOas(oas *openapi3.T, version string) error { + path := o.basePath + if o.outputPath != "" { + path = o.outputPath + } + + path = strings.Replace(path, "."+o.format, fmt.Sprintf("-%s.%s", version, o.format), 1) + return openapi.Save(path, oas, o.format, o.fs) +} + +func (o *Opts) PreRunE(_ []string) error { + if o.basePath == "" { + return fmt.Errorf("no OAS detected. Please, use the flag %s to include the base OAS", flag.Base) + } + + if o.outputPath != "" && !strings.Contains(o.outputPath, openapi.DotJSON) && !strings.Contains(o.outputPath, openapi.DotYAML) { + return fmt.Errorf("output file must be either a JSON or YAML file, got %s", o.outputPath) + } + + if o.format != openapi.JSON && o.format != openapi.YAML { + return fmt.Errorf("output format must be either 'json' or 'yaml', got %s", o.format) + } + + if strings.Contains(o.basePath, openapi.DotYAML) { + o.format = openapi.YAML + } + + return nil +} + +// Builder builds the filter command with the following signature: +// filter -b base-oas -o output-oas.json. +func Builder() *cobra.Command { + opts := &Opts{ + fs: afero.NewOsFs(), + } + + cmd := &cobra.Command{ + Use: "filter -s spec ", + Short: "Filter Open API specification given a list of filters.", + Args: cobra.NoArgs, + PreRunE: func(_ *cobra.Command, args []string) error { + return opts.PreRunE(args) + }, + RunE: func(_ *cobra.Command, _ []string) error { + return opts.Run() + }, + } + + cmd.Flags().StringVarP(&opts.basePath, flag.Spec, flag.SpecShort, "-", usage.Spec) + cmd.Flags().StringVar(&opts.env, flag.Environment, "", usage.Environment) + cmd.Flags().StringVarP(&opts.outputPath, flag.Output, flag.OutputShort, "", usage.Output) + cmd.Flags().StringVarP(&opts.format, flag.Format, flag.FormatShort, openapi.JSON, usage.Format) + cmd.Flags().StringVar(&opts.gitSha, flag.GitSha, "", usage.GitSha) + + _ = cmd.MarkFlagRequired(flag.Output) + + return cmd +} diff --git a/tools/cli/internal/cli/filter/filter_test.go b/tools/cli/internal/cli/filter/filter_test.go new file mode 100644 index 0000000000..83cda3135e --- /dev/null +++ b/tools/cli/internal/cli/filter/filter_test.go @@ -0,0 +1,201 @@ +// Copyright 2024 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 ( + "net/url" + "testing" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/mongodb/openapi/tools/cli/internal/openapi" + "github.com/spf13/afero" + "github.com/stretchr/testify/require" + "github.com/tufin/oasdiff/load" +) + +func TestSuccessfulfilter_Run(t *testing.T) { + fs := afero.NewMemMapFs() + opts := &Opts{ + basePath: "../../../test/data/base_spec.json", + outputPath: "foas.yaml", + fs: fs, + } + + if err := opts.Run(); err != nil { + t.Fatalf("Run() unexpected error: %v", err) + } +} + +func TestfilterPublicPreviewRun(t *testing.T) { + fs := afero.NewMemMapFs() + opts := &Opts{ + basePath: "../../../test/data/base_spec_with_public_preview.json", + outputPath: "foas.yaml", + fs: fs, + } + + if err := opts.Run(); err != nil { + t.Fatalf("Run() unexpected error: %v", err) + } + + info, err := loadRunResultOas(fs, "foas-preview.yaml") + require.NoError(t, err) + + // check paths has only one + require.Len(t, info.Spec.Paths.Map(), 1) + require.Contains(t, info.Spec.Paths.Map(), "/api/atlas/v2/groups") +} + +func TestfilterPrivatePreviewRun(t *testing.T) { + fs := afero.NewMemMapFs() + opts := &Opts{ + basePath: "../../../test/data/base_spec_with_private_preview.json", + outputPath: "foas.yaml", + fs: fs, + } + + if err := opts.Run(); err != nil { + t.Fatalf("Run() unexpected error: %v", err) + } + + info, err := loadRunResultOas(fs, "foas-private-preview-new-feature.yaml") + require.NoError(t, err) + + // check paths has only one + require.Len(t, info.Spec.Paths.Map(), 1) + require.Contains(t, info.Spec.Paths.Map(), "/api/atlas/v2/groups") +} + +func TestfilterMulitplePreviewsRun(t *testing.T) { + fs := afero.NewMemMapFs() + opts := &Opts{ + basePath: "../../../test/data/base_spec_with_multiple_private_and_public_previews.json", + outputPath: "foas.yaml", + fs: fs, + } + + if err := opts.Run(); err != nil { + t.Fatalf("Run() unexpected error: %v", err) + } + + // private preview feature 1 + info, err := loadRunResultOas(fs, "foas-private-preview-new-feature.yaml") + require.NoError(t, err) + + // check paths has only one + require.Len(t, info.Spec.Paths.Map(), 1) + require.Contains(t, info.Spec.Paths.Map(), "/api/atlas/v2/groups") + + // private preview feature 2 + info, err = loadRunResultOas(fs, "foas-private-preview-secrets-feature.yaml") + require.NoError(t, err) + + // check paths has only one + require.Len(t, info.Spec.Paths.Map(), 1) + require.Contains(t, info.Spec.Paths.Map(), "/api/atlas/v2/groups/{groupId}/serviceAccounts/{clientId}/secrets") + + // public preview + info, err = loadRunResultOas(fs, "foas-preview.yaml") + require.NoError(t, err) + + // check paths has only one + require.Len(t, info.Spec.Paths.Map(), 1) + require.Contains(t, info.Spec.Paths.Map(), "/api/atlas/v2/groups/{groupId}/serviceAccounts") +} + +func TestInjectSha_Run(t *testing.T) { + fs := afero.NewMemMapFs() + opts := &Opts{ + basePath: "../../../test/data/base_spec.json", + outputPath: "foas.yaml", + fs: fs, + gitSha: "123456", + } + + if err := opts.Run(); err != nil { + t.Fatalf("Run() unexpected error: %v", err) + } + + result, err := loadRunResultOas(fs, "foas-2023-01-01.yaml") + require.NoError(t, err) + // check sha + require.Contains(t, result.Spec.Info.Extensions, "x-xgen-sha") + require.Equal(t, "123456", result.Spec.Info.Extensions["x-xgen-sha"]) +} + +func TestOpts_PreRunE(t *testing.T) { + testCases := []struct { + wantErr require.ErrorAssertionFunc + basePath string + name string + }{ + { + wantErr: require.Error, + name: "NoBasePath", + }, + { + wantErr: require.NoError, + basePath: "../../../test/data/base_spec.json", + name: "Successful", + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + o := &Opts{ + basePath: tt.basePath, + format: "json", + } + tt.wantErr(t, o.PreRunE(nil)) + }) + } +} + +func TestInvalidFormat_PreRun(t *testing.T) { + opts := &Opts{ + outputPath: "foas.json", + basePath: "base.json", + format: "html", + } + + err := opts.PreRunE(nil) + require.Error(t, err) + require.EqualError(t, err, "output format must be either 'json' or 'yaml', got html") +} + +func TestInvalidPath_PreRun(t *testing.T) { + opts := &Opts{ + outputPath: "foas.html", + basePath: "base.json", + } + + err := opts.PreRunE(nil) + require.Error(t, err) + require.EqualError(t, err, "output file must be either a JSON or YAML file, got foas.html") +} + +func loadRunResultOas(fs afero.Fs, fileName string) (*load.SpecInfo, error) { + oas := openapi.NewOpenAPI3() + oas.Loader.ReadFromURIFunc = func(_ *openapi3.Loader, _ *url.URL) ([]byte, error) { + f, err := fs.OpenFile(fileName, 0, 0) + if err != nil { + return nil, err + } + defer f.Close() + return afero.ReadAll(f) + } + + return oas.CreateOpenAPISpecFromPath(fileName) +} From ddef8738d02ff01acf204baa3b80be456c338a75 Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Fri, 7 Mar 2025 11:19:47 +0000 Subject: [PATCH 05/11] chore: fix filters docs --- tools/cli/internal/openapi/filter/README.md | 12 +++++++----- tools/cli/internal/openapi/filter/extension.go | 2 +- tools/cli/internal/openapi/filter/filter.go | 12 +++++++++++- tools/cli/internal/openapi/filter/filter_test.go | 14 ++++++++++++++ tools/cli/internal/openapi/filter/hidden_envs.go | 2 +- tools/cli/internal/openapi/filter/info.go | 6 +++--- tools/cli/internal/openapi/filter/info_test.go | 2 +- tools/cli/internal/openapi/filter/operations.go | 2 +- tools/cli/internal/openapi/filter/tags.go | 2 +- tools/cli/internal/openapi/filter/versioning.go | 2 +- .../openapi/filter/versioning_extension.go | 2 +- tools/cli/scripts/doc_filters.sh | 5 ++--- 12 files changed, 44 insertions(+), 19 deletions(-) create mode 100644 tools/cli/internal/openapi/filter/filter_test.go diff --git a/tools/cli/internal/openapi/filter/README.md b/tools/cli/internal/openapi/filter/README.md index f42557ac96..3c6584d74e 100644 --- a/tools/cli/internal/openapi/filter/README.md +++ b/tools/cli/internal/openapi/filter/README.md @@ -8,8 +8,10 @@ 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 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) -[OperationsFilter is a filter that removes the x-xgen-owner-team extension from operations](../internal/openapi/filter/operations.go?plain=1#L20) -[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) +[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) +[InfoVersioningFilter: Filter that modifies the Info object in the OpenAPI spec with the target version.](../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#L23) +[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) diff --git a/tools/cli/internal/openapi/filter/extension.go b/tools/cli/internal/openapi/filter/extension.go index d04d71df66..37e4db299e 100644 --- a/tools/cli/internal/openapi/filter/extension.go +++ b/tools/cli/internal/openapi/filter/extension.go @@ -18,7 +18,7 @@ import ( "github.com/getkin/kin-openapi/openapi3" ) -// Filter: ExtensionFilter is a filter that removes the x-xgen-IPA-exception extension from the OpenAPI spec. +// ExtensionFilter: is a filter that removes the x-xgen-IPA-exception extension from the OpenAPI spec. type ExtensionFilter struct { oas *openapi3.T metadata *Metadata diff --git a/tools/cli/internal/openapi/filter/filter.go b/tools/cli/internal/openapi/filter/filter.go index 03a7b8955e..0aca33e07c 100644 --- a/tools/cli/internal/openapi/filter/filter.go +++ b/tools/cli/internal/openapi/filter/filter.go @@ -52,7 +52,17 @@ func DefaultFilters(oas *openapi3.T, metadata *Metadata) []Filter { &ExtensionFilter{oas: oas, metadata: metadata}, &VersioningExtensionFilter{oas: oas, metadata: metadata}, &VersioningFilter{oas: oas, metadata: metadata}, - &InfoFilter{oas: oas, metadata: metadata}, + &InfoVersioningFilter{oas: oas, metadata: metadata}, + &HiddenEnvsFilter{oas: oas, metadata: metadata}, + &TagsFilter{oas: oas}, + &OperationsFilter{oas: oas}, + } +} + +func FiltersWithoutVersioning(oas *openapi3.T, metadata *Metadata) []Filter { + return []Filter{ + &ExtensionFilter{oas: oas, metadata: metadata}, + &InfoVersioningFilter{oas: oas, metadata: metadata}, &HiddenEnvsFilter{oas: oas, metadata: metadata}, &TagsFilter{oas: oas}, &OperationsFilter{oas: oas}, diff --git a/tools/cli/internal/openapi/filter/filter_test.go b/tools/cli/internal/openapi/filter/filter_test.go new file mode 100644 index 0000000000..e1c828550c --- /dev/null +++ b/tools/cli/internal/openapi/filter/filter_test.go @@ -0,0 +1,14 @@ +// Copyright 2024 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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 diff --git a/tools/cli/internal/openapi/filter/hidden_envs.go b/tools/cli/internal/openapi/filter/hidden_envs.go index b053bc837e..10c01de940 100644 --- a/tools/cli/internal/openapi/filter/hidden_envs.go +++ b/tools/cli/internal/openapi/filter/hidden_envs.go @@ -25,7 +25,7 @@ const ( hiddenEnvsExtKey = "envs" ) -// Filter: HiddenEnvsFilter is a filter that removes paths, operations, +// HiddenEnvsFilter: is a filter that removes paths, operations, // request/response bodies and content types that are hidden for the target environment. type HiddenEnvsFilter struct { oas *openapi3.T diff --git a/tools/cli/internal/openapi/filter/info.go b/tools/cli/internal/openapi/filter/info.go index ef77dc9c7c..5abb959565 100644 --- a/tools/cli/internal/openapi/filter/info.go +++ b/tools/cli/internal/openapi/filter/info.go @@ -20,13 +20,13 @@ import ( "github.com/mongodb/openapi/tools/cli/internal/apiversion" ) -// InfoFilter is a filter that modifies the Info object in the OpenAPI spec. -type InfoFilter struct { +// InfoVersioningFilter: Filter that modifies the Info object in the OpenAPI spec with the target version. +type InfoVersioningFilter struct { oas *openapi3.T metadata *Metadata } -func (f *InfoFilter) Apply() error { +func (f *InfoVersioningFilter) Apply() error { if f.oas.Info == nil { return nil } diff --git a/tools/cli/internal/openapi/filter/info_test.go b/tools/cli/internal/openapi/filter/info_test.go index e56264513f..5b8f099cbb 100644 --- a/tools/cli/internal/openapi/filter/info_test.go +++ b/tools/cli/internal/openapi/filter/info_test.go @@ -39,7 +39,7 @@ func TestInfoFilter(t *testing.T) { }, } - filter := &InfoFilter{ + filter := &InfoVersioningFilter{ metadata: NewMetadata(targetVersion, "test"), oas: oas, } diff --git a/tools/cli/internal/openapi/filter/operations.go b/tools/cli/internal/openapi/filter/operations.go index b5f435fb0c..ce1d3e1519 100644 --- a/tools/cli/internal/openapi/filter/operations.go +++ b/tools/cli/internal/openapi/filter/operations.go @@ -17,7 +17,7 @@ import ( "github.com/getkin/kin-openapi/openapi3" ) -// Filter: OperationsFilter is a filter that removes the x-xgen-owner-team extension from operations +// OperationsFilter: is a filter that removes the x-xgen-owner-team extension from operations // and moves the x-sunset extension to the operation level. type OperationsFilter struct { oas *openapi3.T diff --git a/tools/cli/internal/openapi/filter/tags.go b/tools/cli/internal/openapi/filter/tags.go index bb8678eb4d..088e5b9d6d 100644 --- a/tools/cli/internal/openapi/filter/tags.go +++ b/tools/cli/internal/openapi/filter/tags.go @@ -20,7 +20,7 @@ import ( "github.com/getkin/kin-openapi/openapi3" ) -// TagsFilter removes tags that are not used in the operations. +// TagsFilter: removes tags that are not used in the operations. type TagsFilter struct { oas *openapi3.T } diff --git a/tools/cli/internal/openapi/filter/versioning.go b/tools/cli/internal/openapi/filter/versioning.go index 8a06b6299c..d51b3a042c 100644 --- a/tools/cli/internal/openapi/filter/versioning.go +++ b/tools/cli/internal/openapi/filter/versioning.go @@ -22,7 +22,7 @@ import ( "github.com/mongodb/openapi/tools/cli/internal/apiversion" ) -// Filter: VersioningFilter is a filter that modifies the OpenAPI spec by removing operations and responses +// VersioningFilter: is a filter that modifies the OpenAPI spec by removing operations and responses // that are not supported by the target version. type VersioningFilter struct { oas *openapi3.T diff --git a/tools/cli/internal/openapi/filter/versioning_extension.go b/tools/cli/internal/openapi/filter/versioning_extension.go index d6597afa04..0bd108e5d7 100644 --- a/tools/cli/internal/openapi/filter/versioning_extension.go +++ b/tools/cli/internal/openapi/filter/versioning_extension.go @@ -22,7 +22,7 @@ import ( "github.com/mongodb/openapi/tools/cli/internal/apiversion" ) -// Filter: VersioningExtensionFilter is a filter that updates the x-sunset and x-xgen-version extensions to a date string +// VersioningExtensionFilter: 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. type VersioningExtensionFilter struct { diff --git a/tools/cli/scripts/doc_filters.sh b/tools/cli/scripts/doc_filters.sh index 8e72146acd..c6160c8854 100755 --- a/tools/cli/scripts/doc_filters.sh +++ b/tools/cli/scripts/doc_filters.sh @@ -4,7 +4,7 @@ echo "# List of filters applied to the OpenAPI specification" echo "These examples are automatically generated from filters docs." # Expected format: -# // Filter: InfoFilter is a filter that modifies the Info object in the OpenAPI spec. +# // PlaceHolderFilter: is a filter that modifies the Info object in the OpenAPI spec. echo "# OpenAPI Filters" @@ -19,5 +19,4 @@ echo " - Filtering per version, so that only the endpoints that are available in echo "## What filters are available?" echo "### List of filters" -grep -n '// Filter:' internal/openapi/filter/*.go | sort -u -k2 | sed -n "s/\([^0-9]*\):\([0-9]*\):\/\/ Filter: \(.*\)/[\3](\.\.\/\1?plain=1#L\2) /p" | sort - +grep -n '// .*Filter: .*' internal/openapi/filter/*.go | sort -u -k2 | sed -n "s/\([^0-9]*\):\([0-9]*\):\/\/ \(.*Filter: .*\)/[\3](\.\.\/\1?plain=1#L\2) /p" | sort From 7119a8dbd3c133cdd12a9232083473c286fc14f9 Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Fri, 7 Mar 2025 11:21:36 +0000 Subject: [PATCH 06/11] remove command --- tools/cli/internal/cli/filter/filter.go | 145 ------------- tools/cli/internal/cli/filter/filter_test.go | 201 ------------------- 2 files changed, 346 deletions(-) delete mode 100644 tools/cli/internal/cli/filter/filter.go delete mode 100644 tools/cli/internal/cli/filter/filter_test.go diff --git a/tools/cli/internal/cli/filter/filter.go b/tools/cli/internal/cli/filter/filter.go deleted file mode 100644 index 5ac18ed200..0000000000 --- a/tools/cli/internal/cli/filter/filter.go +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2024 MongoDB Inc -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// 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 ( - "fmt" - "log" - "strings" - - "github.com/getkin/kin-openapi/openapi3" - "github.com/mongodb/openapi/tools/cli/internal/apiversion" - "github.com/mongodb/openapi/tools/cli/internal/cli/flag" - "github.com/mongodb/openapi/tools/cli/internal/cli/usage" - "github.com/mongodb/openapi/tools/cli/internal/openapi" - "github.com/mongodb/openapi/tools/cli/internal/openapi/filter" - "github.com/spf13/afero" - "github.com/spf13/cobra" -) - -type Opts struct { - fs afero.Fs - basePath string - outputPath string - env string - format string - gitSha string -} - -func (o *Opts) Run() error { - loader := openapi.NewOpenAPI3() - specInfo, err := loader.CreateOpenAPISpecFromPath(o.basePath) - if err != nil { - return err - } - - versions, err := openapi.ExtractVersionsWithEnv(specInfo.Spec, o.env) - if err != nil { - return err - } - - for _, version := range versions { - filteredOAS, err := o.filter(specInfo.Spec, version) - if err != nil { - return err - } - - if o.gitSha != "" { - filteredOAS.Info.Extensions = map[string]any{ - "x-xgen-sha": o.gitSha, - } - } - - if err := o.saveVersionedOas(filteredOAS, version); err != nil { - return err - } - - if err := filteredOAS.Validate(loader.Loader.Context); err != nil { - log.Printf("[WARN] OpenAPI document is invalid: %v", err) - } - } - - return nil -} - -func (o *Opts) filter(oas *openapi3.T, version string) (result *openapi3.T, err error) { - log.Printf("Filtering OpenAPI document by version %q", version) - apiVersion, err := apiversion.New(apiversion.WithVersion(version)) - if err != nil { - return nil, err - } - - return filter.ApplyFilters(oas, filter.NewMetadata(apiVersion, o.env), filter.DefaultFilters) -} - -func (o *Opts) saveVersionedOas(oas *openapi3.T, version string) error { - path := o.basePath - if o.outputPath != "" { - path = o.outputPath - } - - path = strings.Replace(path, "."+o.format, fmt.Sprintf("-%s.%s", version, o.format), 1) - return openapi.Save(path, oas, o.format, o.fs) -} - -func (o *Opts) PreRunE(_ []string) error { - if o.basePath == "" { - return fmt.Errorf("no OAS detected. Please, use the flag %s to include the base OAS", flag.Base) - } - - if o.outputPath != "" && !strings.Contains(o.outputPath, openapi.DotJSON) && !strings.Contains(o.outputPath, openapi.DotYAML) { - return fmt.Errorf("output file must be either a JSON or YAML file, got %s", o.outputPath) - } - - if o.format != openapi.JSON && o.format != openapi.YAML { - return fmt.Errorf("output format must be either 'json' or 'yaml', got %s", o.format) - } - - if strings.Contains(o.basePath, openapi.DotYAML) { - o.format = openapi.YAML - } - - return nil -} - -// Builder builds the filter command with the following signature: -// filter -b base-oas -o output-oas.json. -func Builder() *cobra.Command { - opts := &Opts{ - fs: afero.NewOsFs(), - } - - cmd := &cobra.Command{ - Use: "filter -s spec ", - Short: "Filter Open API specification given a list of filters.", - Args: cobra.NoArgs, - PreRunE: func(_ *cobra.Command, args []string) error { - return opts.PreRunE(args) - }, - RunE: func(_ *cobra.Command, _ []string) error { - return opts.Run() - }, - } - - cmd.Flags().StringVarP(&opts.basePath, flag.Spec, flag.SpecShort, "-", usage.Spec) - cmd.Flags().StringVar(&opts.env, flag.Environment, "", usage.Environment) - cmd.Flags().StringVarP(&opts.outputPath, flag.Output, flag.OutputShort, "", usage.Output) - cmd.Flags().StringVarP(&opts.format, flag.Format, flag.FormatShort, openapi.JSON, usage.Format) - cmd.Flags().StringVar(&opts.gitSha, flag.GitSha, "", usage.GitSha) - - _ = cmd.MarkFlagRequired(flag.Output) - - return cmd -} diff --git a/tools/cli/internal/cli/filter/filter_test.go b/tools/cli/internal/cli/filter/filter_test.go deleted file mode 100644 index 83cda3135e..0000000000 --- a/tools/cli/internal/cli/filter/filter_test.go +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright 2024 MongoDB Inc -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// 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 ( - "net/url" - "testing" - - "github.com/getkin/kin-openapi/openapi3" - "github.com/mongodb/openapi/tools/cli/internal/openapi" - "github.com/spf13/afero" - "github.com/stretchr/testify/require" - "github.com/tufin/oasdiff/load" -) - -func TestSuccessfulfilter_Run(t *testing.T) { - fs := afero.NewMemMapFs() - opts := &Opts{ - basePath: "../../../test/data/base_spec.json", - outputPath: "foas.yaml", - fs: fs, - } - - if err := opts.Run(); err != nil { - t.Fatalf("Run() unexpected error: %v", err) - } -} - -func TestfilterPublicPreviewRun(t *testing.T) { - fs := afero.NewMemMapFs() - opts := &Opts{ - basePath: "../../../test/data/base_spec_with_public_preview.json", - outputPath: "foas.yaml", - fs: fs, - } - - if err := opts.Run(); err != nil { - t.Fatalf("Run() unexpected error: %v", err) - } - - info, err := loadRunResultOas(fs, "foas-preview.yaml") - require.NoError(t, err) - - // check paths has only one - require.Len(t, info.Spec.Paths.Map(), 1) - require.Contains(t, info.Spec.Paths.Map(), "/api/atlas/v2/groups") -} - -func TestfilterPrivatePreviewRun(t *testing.T) { - fs := afero.NewMemMapFs() - opts := &Opts{ - basePath: "../../../test/data/base_spec_with_private_preview.json", - outputPath: "foas.yaml", - fs: fs, - } - - if err := opts.Run(); err != nil { - t.Fatalf("Run() unexpected error: %v", err) - } - - info, err := loadRunResultOas(fs, "foas-private-preview-new-feature.yaml") - require.NoError(t, err) - - // check paths has only one - require.Len(t, info.Spec.Paths.Map(), 1) - require.Contains(t, info.Spec.Paths.Map(), "/api/atlas/v2/groups") -} - -func TestfilterMulitplePreviewsRun(t *testing.T) { - fs := afero.NewMemMapFs() - opts := &Opts{ - basePath: "../../../test/data/base_spec_with_multiple_private_and_public_previews.json", - outputPath: "foas.yaml", - fs: fs, - } - - if err := opts.Run(); err != nil { - t.Fatalf("Run() unexpected error: %v", err) - } - - // private preview feature 1 - info, err := loadRunResultOas(fs, "foas-private-preview-new-feature.yaml") - require.NoError(t, err) - - // check paths has only one - require.Len(t, info.Spec.Paths.Map(), 1) - require.Contains(t, info.Spec.Paths.Map(), "/api/atlas/v2/groups") - - // private preview feature 2 - info, err = loadRunResultOas(fs, "foas-private-preview-secrets-feature.yaml") - require.NoError(t, err) - - // check paths has only one - require.Len(t, info.Spec.Paths.Map(), 1) - require.Contains(t, info.Spec.Paths.Map(), "/api/atlas/v2/groups/{groupId}/serviceAccounts/{clientId}/secrets") - - // public preview - info, err = loadRunResultOas(fs, "foas-preview.yaml") - require.NoError(t, err) - - // check paths has only one - require.Len(t, info.Spec.Paths.Map(), 1) - require.Contains(t, info.Spec.Paths.Map(), "/api/atlas/v2/groups/{groupId}/serviceAccounts") -} - -func TestInjectSha_Run(t *testing.T) { - fs := afero.NewMemMapFs() - opts := &Opts{ - basePath: "../../../test/data/base_spec.json", - outputPath: "foas.yaml", - fs: fs, - gitSha: "123456", - } - - if err := opts.Run(); err != nil { - t.Fatalf("Run() unexpected error: %v", err) - } - - result, err := loadRunResultOas(fs, "foas-2023-01-01.yaml") - require.NoError(t, err) - // check sha - require.Contains(t, result.Spec.Info.Extensions, "x-xgen-sha") - require.Equal(t, "123456", result.Spec.Info.Extensions["x-xgen-sha"]) -} - -func TestOpts_PreRunE(t *testing.T) { - testCases := []struct { - wantErr require.ErrorAssertionFunc - basePath string - name string - }{ - { - wantErr: require.Error, - name: "NoBasePath", - }, - { - wantErr: require.NoError, - basePath: "../../../test/data/base_spec.json", - name: "Successful", - }, - } - - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - o := &Opts{ - basePath: tt.basePath, - format: "json", - } - tt.wantErr(t, o.PreRunE(nil)) - }) - } -} - -func TestInvalidFormat_PreRun(t *testing.T) { - opts := &Opts{ - outputPath: "foas.json", - basePath: "base.json", - format: "html", - } - - err := opts.PreRunE(nil) - require.Error(t, err) - require.EqualError(t, err, "output format must be either 'json' or 'yaml', got html") -} - -func TestInvalidPath_PreRun(t *testing.T) { - opts := &Opts{ - outputPath: "foas.html", - basePath: "base.json", - } - - err := opts.PreRunE(nil) - require.Error(t, err) - require.EqualError(t, err, "output file must be either a JSON or YAML file, got foas.html") -} - -func loadRunResultOas(fs afero.Fs, fileName string) (*load.SpecInfo, error) { - oas := openapi.NewOpenAPI3() - oas.Loader.ReadFromURIFunc = func(_ *openapi3.Loader, _ *url.URL) ([]byte, error) { - f, err := fs.OpenFile(fileName, 0, 0) - if err != nil { - return nil, err - } - defer f.Close() - return afero.ReadAll(f) - } - - return oas.CreateOpenAPISpecFromPath(fileName) -} From 87aed69d16b14873896243028a3c2217e2178844 Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Fri, 7 Mar 2025 11:22:50 +0000 Subject: [PATCH 07/11] update --- tools/cli/internal/openapi/filter/README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tools/cli/internal/openapi/filter/README.md b/tools/cli/internal/openapi/filter/README.md index b00fff4287..3c6584d74e 100644 --- a/tools/cli/internal/openapi/filter/README.md +++ b/tools/cli/internal/openapi/filter/README.md @@ -15,8 +15,3 @@ The Atlas Admin API OpenAPI specifications are used not only to document REST en [TagsFilter: removes tags that are not used in the operations.](../internal/openapi/filter/tags.go?plain=1#L23) [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) -[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) -[OperationsFilter is a filter that removes the x-xgen-owner-team extension from operations](../internal/openapi/filter/operations.go?plain=1#L20) -[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) From fd281e9fbb45f624c1a5be9777e28e391ef90b37 Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Fri, 7 Mar 2025 11:45:37 +0000 Subject: [PATCH 08/11] Add metadata validation per filter and unit tests --- .../cli/internal/openapi/filter/extension.go | 4 + tools/cli/internal/openapi/filter/filter.go | 28 ++++- .../internal/openapi/filter/filter_test.go | 119 ++++++++++++++++++ .../internal/openapi/filter/hidden_envs.go | 4 + tools/cli/internal/openapi/filter/info.go | 4 + .../cli/internal/openapi/filter/operations.go | 4 + tools/cli/internal/openapi/filter/tags.go | 4 + .../cli/internal/openapi/filter/versioning.go | 8 ++ .../openapi/filter/versioning_extension.go | 4 + 9 files changed, 175 insertions(+), 4 deletions(-) diff --git a/tools/cli/internal/openapi/filter/extension.go b/tools/cli/internal/openapi/filter/extension.go index 37e4db299e..c8ccb71ecf 100644 --- a/tools/cli/internal/openapi/filter/extension.go +++ b/tools/cli/internal/openapi/filter/extension.go @@ -29,6 +29,10 @@ const ( format = "2006-01-02T15:04:05Z07:00" ) +func (f *ExtensionFilter) ValidateMetadata() error { + return validateMetadata(f.metadata) +} + func (f *ExtensionFilter) Apply() error { for _, pathItem := range f.oas.Paths.Map() { if pathItem == nil { diff --git a/tools/cli/internal/openapi/filter/filter.go b/tools/cli/internal/openapi/filter/filter.go index 0aca33e07c..0bbfe89163 100644 --- a/tools/cli/internal/openapi/filter/filter.go +++ b/tools/cli/internal/openapi/filter/filter.go @@ -17,6 +17,7 @@ import ( "encoding/json" "errors" "fmt" + reflect "reflect" "github.com/getkin/kin-openapi/openapi3" "github.com/mongodb/openapi/tools/cli/internal/apiversion" @@ -25,6 +26,7 @@ import ( //go:generate mockgen -destination=../filter/mock_filter.go -package=filter github.com/mongodb/openapi/tools/cli/internal/openapi/filter Filter type Filter interface { Apply() error + ValidateMetadata() error } type Metadata struct { @@ -39,11 +41,29 @@ func NewMetadata(targetVersion *apiversion.APIVersion, targetEnv string) *Metada } } +// validateMetadata validates the metadata object, ensuring its not nil and has a target env. func validateMetadata(metadata *Metadata) error { if metadata == nil { return errors.New("metadata is nil") } + if metadata.targetEnv == "" { + return errors.New("target environment is empty") + } + + return nil +} + +// validateMetadataWithVersion validates the metadata object, ensuring its not nil and has a target version. +func validateMetadataWithVersion(metadata *Metadata) error { + if err := validateMetadata(metadata); err != nil { + return err + } + + if metadata.targetVersion == nil { + return errors.New("target version is nil") + } + return nil } @@ -81,10 +101,6 @@ func ApplyFilters(doc *openapi3.T, metadata *Metadata, filters func(oas *openapi return nil, errors.New("openapi document is nil") } - if err := validateMetadata(metadata); err != nil { - return nil, err - } - // make a copy of the oas to avoid modifying the original document when applying filters oas, err := duplicateOas(doc) if err != nil { @@ -92,6 +108,10 @@ func ApplyFilters(doc *openapi3.T, metadata *Metadata, filters func(oas *openapi } for _, filter := range filters(oas, metadata) { + if err := filter.ValidateMetadata(); err != nil { + s := reflect.TypeOf(filter) + return nil, fmt.Errorf("failed to validate metadata for filter %s with: %w", s, err) + } if err := filter.Apply(); err != nil { return nil, err } diff --git a/tools/cli/internal/openapi/filter/filter_test.go b/tools/cli/internal/openapi/filter/filter_test.go index e1c828550c..19f236a001 100644 --- a/tools/cli/internal/openapi/filter/filter_test.go +++ b/tools/cli/internal/openapi/filter/filter_test.go @@ -12,3 +12,122 @@ // See the License for the specific language governing permissions and // limitations under the License. package filter + +import ( + "testing" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/mongodb/openapi/tools/cli/internal/apiversion" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewMetadata(t *testing.T) { + version := &apiversion.APIVersion{} + env := "test-env" + metadata := NewMetadata(version, env) + + assert.Equal(t, version, metadata.targetVersion) + assert.Equal(t, env, metadata.targetEnv) +} + +func TestValidateMetadata(t *testing.T) { + t.Run("Valid metadata", func(t *testing.T) { + metadata := &Metadata{} + err := validateMetadata(metadata) + assert.NoError(t, err) + }) + + t.Run("Nil metadata", func(t *testing.T) { + err := validateMetadata(nil) + assert.Error(t, err) + assert.Equal(t, "metadata is nil", err.Error()) + }) +} + +func TestDuplicateOas(t *testing.T) { + doc := &openapi3.T{ + Info: &openapi3.Info{ + Title: "Test API", + Version: "1.0.0", + }, + } + + duplicateDoc, err := duplicateOas(doc) + require.NoError(t, err) + require.NotNil(t, duplicateDoc) + assert.Equal(t, doc.Info.Title, duplicateDoc.Info.Title) + assert.Equal(t, doc.Info.Version, duplicateDoc.Info.Version) +} + +func TestApplyFilters(t *testing.T) { + doc := &openapi3.T{ + Info: &openapi3.Info{ + Title: "Test API", + Version: "1.0.0", + }, + } + metadata := &Metadata{} + + t.Run("Nil document", func(t *testing.T) { + _, err := ApplyFilters(nil, metadata, DefaultFilters) + assert.Error(t, err) + assert.Equal(t, "openapi document is nil", err.Error()) + }) + + t.Run("Nil metadata", func(t *testing.T) { + _, err := ApplyFilters(doc, nil, DefaultFilters) + assert.Error(t, err) + assert.Equal(t, "metadata is nil", err.Error()) + }) + + t.Run("Invalid metadata", func(t *testing.T) { + _, err := ApplyFilters(doc, metadata, DefaultFilters) + assert.Equal(t, "target environment is empty", err.Error()) + }) + + t.Run("Missing versioning metadata", func(t *testing.T) { + metadata := &Metadata{ + targetEnv: "dev", + } + _, err := ApplyFilters(doc, metadata, DefaultFilters) + assert.Equal(t, "failed to validate metadata for filter *filter.VersioningExtensionFilter with: target version is nil", err.Error()) + }) + + t.Run("Valid metadata for default filters", func(t *testing.T) { + version, err := apiversion.New(apiversion.WithVersion("2023-11-15")) + require.NoError(t, err) + metadata := &Metadata{ + targetEnv: "dev", + targetVersion: version, + } + + filteredDoc, err := ApplyFilters(doc, metadata, DefaultFilters) + require.NoError(t, err) + assert.NotNil(t, filteredDoc) + }) +} + +func TestDefaultFilters(t *testing.T) { + doc := &openapi3.T{} + metadata := &Metadata{} + filters := DefaultFilters(doc, metadata) + + assert.Len(t, filters, 7) +} + +func TestFiltersWithoutVersioning(t *testing.T) { + doc := &openapi3.T{} + metadata := &Metadata{} + filters := FiltersWithoutVersioning(doc, metadata) + + assert.Len(t, filters, 5) +} + +func TestFiltersToGetVersions(t *testing.T) { + doc := &openapi3.T{} + metadata := &Metadata{} + filters := FiltersToGetVersions(doc, metadata) + + assert.Len(t, filters, 1) +} diff --git a/tools/cli/internal/openapi/filter/hidden_envs.go b/tools/cli/internal/openapi/filter/hidden_envs.go index 10c01de940..2eb0a068d7 100644 --- a/tools/cli/internal/openapi/filter/hidden_envs.go +++ b/tools/cli/internal/openapi/filter/hidden_envs.go @@ -32,6 +32,10 @@ type HiddenEnvsFilter struct { metadata *Metadata } +func (f *HiddenEnvsFilter) ValidateMetadata() error { + return validateMetadata(f.metadata) +} + func (f *HiddenEnvsFilter) Apply() error { // delete hidden paths first before processing for pathName, pathItem := range f.oas.Paths.Map() { diff --git a/tools/cli/internal/openapi/filter/info.go b/tools/cli/internal/openapi/filter/info.go index 5abb959565..872b7df7b2 100644 --- a/tools/cli/internal/openapi/filter/info.go +++ b/tools/cli/internal/openapi/filter/info.go @@ -26,6 +26,10 @@ type InfoVersioningFilter struct { metadata *Metadata } +func (f *InfoVersioningFilter) ValidateMetadata() error { + return validateMetadataWithVersion(f.metadata) +} + func (f *InfoVersioningFilter) Apply() error { if f.oas.Info == nil { return nil diff --git a/tools/cli/internal/openapi/filter/operations.go b/tools/cli/internal/openapi/filter/operations.go index ce1d3e1519..5d628e018e 100644 --- a/tools/cli/internal/openapi/filter/operations.go +++ b/tools/cli/internal/openapi/filter/operations.go @@ -23,6 +23,10 @@ type OperationsFilter struct { oas *openapi3.T } +func (f *OperationsFilter) ValidateMetadata() error { + return nil +} + func (f *OperationsFilter) Apply() error { if f.oas.Paths == nil { return nil diff --git a/tools/cli/internal/openapi/filter/tags.go b/tools/cli/internal/openapi/filter/tags.go index 088e5b9d6d..772165b861 100644 --- a/tools/cli/internal/openapi/filter/tags.go +++ b/tools/cli/internal/openapi/filter/tags.go @@ -25,6 +25,10 @@ type TagsFilter struct { oas *openapi3.T } +func (f *TagsFilter) ValidateMetadata() error { + return nil +} + func (f *TagsFilter) Apply() error { if f.oas.Tags == nil { return nil diff --git a/tools/cli/internal/openapi/filter/versioning.go b/tools/cli/internal/openapi/filter/versioning.go index d51b3a042c..236c33cbb4 100644 --- a/tools/cli/internal/openapi/filter/versioning.go +++ b/tools/cli/internal/openapi/filter/versioning.go @@ -57,7 +57,15 @@ func newOperationConfig(op *openapi3.Operation) *OperationConfig { } } +func (f *VersioningFilter) ValidateMetadata() error { + return validateMetadataWithVersion(f.metadata) +} + func (f *VersioningFilter) Apply() error { + if f.oas.Paths == nil { + return nil + } + newPaths := &openapi3.Paths{ Extensions: f.oas.Paths.Extensions, } diff --git a/tools/cli/internal/openapi/filter/versioning_extension.go b/tools/cli/internal/openapi/filter/versioning_extension.go index 0bd108e5d7..fcc38c2b2c 100644 --- a/tools/cli/internal/openapi/filter/versioning_extension.go +++ b/tools/cli/internal/openapi/filter/versioning_extension.go @@ -35,6 +35,10 @@ const ( xGenExtension = "x-xgen-version" ) +func (f *VersioningExtensionFilter) ValidateMetadata() error { + return validateMetadataWithVersion(f.metadata) +} + func (f *VersioningExtensionFilter) Apply() error { for _, pathItem := range f.oas.Paths.Map() { if pathItem == nil { From a1900f1d020f1ef665f111bf8e84f7a2a25d3761 Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Fri, 7 Mar 2025 11:53:44 +0000 Subject: [PATCH 09/11] lint --- tools/cli/internal/openapi/filter/filter_test.go | 13 +++++-------- tools/cli/internal/openapi/filter/operations.go | 2 +- tools/cli/internal/openapi/filter/tags.go | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/tools/cli/internal/openapi/filter/filter_test.go b/tools/cli/internal/openapi/filter/filter_test.go index 19f236a001..d645929ac9 100644 --- a/tools/cli/internal/openapi/filter/filter_test.go +++ b/tools/cli/internal/openapi/filter/filter_test.go @@ -40,8 +40,7 @@ func TestValidateMetadata(t *testing.T) { t.Run("Nil metadata", func(t *testing.T) { err := validateMetadata(nil) - assert.Error(t, err) - assert.Equal(t, "metadata is nil", err.Error()) + require.ErrorContains(t, err, "metadata is nil") }) } @@ -71,19 +70,17 @@ func TestApplyFilters(t *testing.T) { t.Run("Nil document", func(t *testing.T) { _, err := ApplyFilters(nil, metadata, DefaultFilters) - assert.Error(t, err) - assert.Equal(t, "openapi document is nil", err.Error()) + require.ErrorContains(t, err, "openapi document is nil") }) t.Run("Nil metadata", func(t *testing.T) { _, err := ApplyFilters(doc, nil, DefaultFilters) - assert.Error(t, err) - assert.Equal(t, "metadata is nil", err.Error()) + require.ErrorContains(t, err, "metadata is nil") }) t.Run("Invalid metadata", func(t *testing.T) { _, err := ApplyFilters(doc, metadata, DefaultFilters) - assert.Equal(t, "target environment is empty", err.Error()) + require.ErrorContains(t, err, "target environment is empty") }) t.Run("Missing versioning metadata", func(t *testing.T) { @@ -91,7 +88,7 @@ func TestApplyFilters(t *testing.T) { targetEnv: "dev", } _, err := ApplyFilters(doc, metadata, DefaultFilters) - assert.Equal(t, "failed to validate metadata for filter *filter.VersioningExtensionFilter with: target version is nil", err.Error()) + require.ErrorContains(t, err, "failed to validate metadata for filter *filter.VersioningExtensionFilter with: target version is nil") }) t.Run("Valid metadata for default filters", func(t *testing.T) { diff --git a/tools/cli/internal/openapi/filter/operations.go b/tools/cli/internal/openapi/filter/operations.go index 5d628e018e..1f84680836 100644 --- a/tools/cli/internal/openapi/filter/operations.go +++ b/tools/cli/internal/openapi/filter/operations.go @@ -23,7 +23,7 @@ type OperationsFilter struct { oas *openapi3.T } -func (f *OperationsFilter) ValidateMetadata() error { +func (_ *OperationsFilter) ValidateMetadata() error { return nil } diff --git a/tools/cli/internal/openapi/filter/tags.go b/tools/cli/internal/openapi/filter/tags.go index 772165b861..a8b3380207 100644 --- a/tools/cli/internal/openapi/filter/tags.go +++ b/tools/cli/internal/openapi/filter/tags.go @@ -25,7 +25,7 @@ type TagsFilter struct { oas *openapi3.T } -func (f *TagsFilter) ValidateMetadata() error { +func (_ *TagsFilter) ValidateMetadata() error { return nil } From 9cc82a7243a79a223234cc079039a7d50799f477 Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Fri, 7 Mar 2025 12:01:01 +0000 Subject: [PATCH 10/11] lint --- tools/cli/internal/openapi/filter/operations.go | 2 +- tools/cli/internal/openapi/filter/tags.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/cli/internal/openapi/filter/operations.go b/tools/cli/internal/openapi/filter/operations.go index 1f84680836..f2278ac37a 100644 --- a/tools/cli/internal/openapi/filter/operations.go +++ b/tools/cli/internal/openapi/filter/operations.go @@ -23,7 +23,7 @@ type OperationsFilter struct { oas *openapi3.T } -func (_ *OperationsFilter) ValidateMetadata() error { +func (*OperationsFilter) ValidateMetadata() error { return nil } diff --git a/tools/cli/internal/openapi/filter/tags.go b/tools/cli/internal/openapi/filter/tags.go index a8b3380207..e48d27e0e7 100644 --- a/tools/cli/internal/openapi/filter/tags.go +++ b/tools/cli/internal/openapi/filter/tags.go @@ -25,7 +25,7 @@ type TagsFilter struct { oas *openapi3.T } -func (_ *TagsFilter) ValidateMetadata() error { +func (*TagsFilter) ValidateMetadata() error { return nil } From fb2bf6e00b92f846a632c57efe7feb1e4634f552 Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Fri, 7 Mar 2025 12:25:51 +0000 Subject: [PATCH 11/11] Fix tests --- tools/cli/internal/cli/split/split.go | 2 +- tools/cli/internal/cli/split/split_test.go | 5 +++++ tools/cli/internal/openapi/filter/filter_test.go | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/tools/cli/internal/cli/split/split.go b/tools/cli/internal/cli/split/split.go index 9cb9424554..5e6b205d93 100644 --- a/tools/cli/internal/cli/split/split.go +++ b/tools/cli/internal/cli/split/split.go @@ -136,7 +136,7 @@ func Builder() *cobra.Command { } cmd.Flags().StringVarP(&opts.basePath, flag.Spec, flag.SpecShort, "-", usage.Spec) - cmd.Flags().StringVar(&opts.env, flag.Environment, "", usage.Environment) + cmd.Flags().StringVar(&opts.env, flag.Environment, "prod", usage.Environment) cmd.Flags().StringVarP(&opts.outputPath, flag.Output, flag.OutputShort, "", usage.Output) cmd.Flags().StringVarP(&opts.format, flag.Format, flag.FormatShort, openapi.ALL, usage.Format) cmd.Flags().StringVar(&opts.gitSha, flag.GitSha, "", usage.GitSha) diff --git a/tools/cli/internal/cli/split/split_test.go b/tools/cli/internal/cli/split/split_test.go index 9ca6371739..ccc9f9d80b 100644 --- a/tools/cli/internal/cli/split/split_test.go +++ b/tools/cli/internal/cli/split/split_test.go @@ -31,6 +31,7 @@ func TestSuccessfulSplit_Run(t *testing.T) { basePath: "../../../test/data/base_spec.json", outputPath: "foas.yaml", fs: fs, + env: "dev", } if err := opts.Run(); err != nil { @@ -44,6 +45,7 @@ func TestSplitPublicPreviewRun(t *testing.T) { basePath: "../../../test/data/base_spec_with_public_preview.json", outputPath: "foas.yaml", fs: fs, + env: "dev", } if err := opts.Run(); err != nil { @@ -64,6 +66,7 @@ func TestSplitPrivatePreviewRun(t *testing.T) { basePath: "../../../test/data/base_spec_with_private_preview.json", outputPath: "foas.yaml", fs: fs, + env: "dev", } if err := opts.Run(); err != nil { @@ -83,6 +86,7 @@ func TestSplitMulitplePreviewsRun(t *testing.T) { opts := &Opts{ basePath: "../../../test/data/base_spec_with_multiple_private_and_public_previews.json", outputPath: "foas.yaml", + env: "dev", fs: fs, } @@ -122,6 +126,7 @@ func TestInjectSha_Run(t *testing.T) { outputPath: "foas.yaml", fs: fs, gitSha: "123456", + env: "dev", } if err := opts.Run(); err != nil { diff --git a/tools/cli/internal/openapi/filter/filter_test.go b/tools/cli/internal/openapi/filter/filter_test.go index d645929ac9..808206f377 100644 --- a/tools/cli/internal/openapi/filter/filter_test.go +++ b/tools/cli/internal/openapi/filter/filter_test.go @@ -33,7 +33,9 @@ func TestNewMetadata(t *testing.T) { func TestValidateMetadata(t *testing.T) { t.Run("Valid metadata", func(t *testing.T) { - metadata := &Metadata{} + metadata := &Metadata{ + targetEnv: "dev", + } err := validateMetadata(metadata) assert.NoError(t, err) })