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/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..c8ccb71ecf 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 @@ -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 03a7b8955e..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 } @@ -52,7 +72,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}, @@ -71,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 { @@ -82,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 new file mode 100644 index 0000000000..808206f377 --- /dev/null +++ b/tools/cli/internal/openapi/filter/filter_test.go @@ -0,0 +1,132 @@ +// 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 ( + "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{ + targetEnv: "dev", + } + err := validateMetadata(metadata) + assert.NoError(t, err) + }) + + t.Run("Nil metadata", func(t *testing.T) { + err := validateMetadata(nil) + require.ErrorContains(t, err, "metadata is nil") + }) +} + +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) + require.ErrorContains(t, err, "openapi document is nil") + }) + + t.Run("Nil metadata", func(t *testing.T) { + _, err := ApplyFilters(doc, nil, DefaultFilters) + require.ErrorContains(t, err, "metadata is nil") + }) + + t.Run("Invalid metadata", func(t *testing.T) { + _, err := ApplyFilters(doc, metadata, DefaultFilters) + require.ErrorContains(t, err, "target environment is empty") + }) + + t.Run("Missing versioning metadata", func(t *testing.T) { + metadata := &Metadata{ + targetEnv: "dev", + } + _, err := ApplyFilters(doc, metadata, DefaultFilters) + 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) { + 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 b053bc837e..2eb0a068d7 100644 --- a/tools/cli/internal/openapi/filter/hidden_envs.go +++ b/tools/cli/internal/openapi/filter/hidden_envs.go @@ -25,13 +25,17 @@ 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 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 ef77dc9c7c..872b7df7b2 100644 --- a/tools/cli/internal/openapi/filter/info.go +++ b/tools/cli/internal/openapi/filter/info.go @@ -20,13 +20,17 @@ 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) 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/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..f2278ac37a 100644 --- a/tools/cli/internal/openapi/filter/operations.go +++ b/tools/cli/internal/openapi/filter/operations.go @@ -17,12 +17,16 @@ 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 } +func (*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 bb8678eb4d..e48d27e0e7 100644 --- a/tools/cli/internal/openapi/filter/tags.go +++ b/tools/cli/internal/openapi/filter/tags.go @@ -20,11 +20,15 @@ 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 } +func (*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 8a06b6299c..236c33cbb4 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 @@ -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 d6597afa04..fcc38c2b2c 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 { @@ -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 { 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