Skip to content

Commit c86a5cf

Browse files
CLOUDP-294587: Update FOASCLI version to list also the preview api version (#355)
1 parent 89ce6d0 commit c86a5cf

File tree

9 files changed

+68799
-47
lines changed

9 files changed

+68799
-47
lines changed

tools/cli/internal/apiversion/version.go

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,18 @@ import (
2424
)
2525

2626
type APIVersion struct {
27-
version string
28-
versionDate time.Time
27+
version string
28+
stabilityVersion string
29+
versionDate time.Time
2930
}
3031

3132
const (
32-
dateFormat = "2006-01-02"
33+
dateFormat = "2006-01-02"
34+
StableStabilityLevel = "STABLE"
35+
PreviewStabilityLevel = "PREVIEW"
3336
)
3437

35-
var ContentPattern = regexp.MustCompile(`application/vnd\.atlas\.(\d{4})-(\d{2})-(\d{2})\+(.+)`)
38+
var contentPattern = regexp.MustCompile(`application/vnd\.atlas\.((\d{4})-(\d{2})-(\d{2})|preview)\+(.+)`)
3639

3740
// Option is a function that sets a value on the APIVersion.
3841
type Option func(v *APIVersion) error
@@ -67,10 +70,12 @@ func WithDate(date time.Time) Option {
6770
return func(v *APIVersion) error {
6871
v.version = date.Format(dateFormat)
6972
v.versionDate = date
73+
v.stabilityVersion = StableStabilityLevel
7074
return nil
7175
}
7276
}
7377

78+
// WithContent returns an Option to generate a new APIVersion given the contentType.
7479
func WithContent(contentType string) Option {
7580
return func(v *APIVersion) error {
7681
version, err := Parse(contentType)
@@ -79,6 +84,12 @@ func WithContent(contentType string) Option {
7984
}
8085

8186
v.version = version
87+
v.stabilityVersion = StableStabilityLevel
88+
if version == PreviewStabilityLevel {
89+
v.stabilityVersion = PreviewStabilityLevel
90+
return nil
91+
}
92+
8293
v.versionDate, err = DateFromVersion(version)
8394
if err != nil {
8495
return err
@@ -119,13 +130,26 @@ func (v *APIVersion) Date() time.Time {
119130
return v.versionDate
120131
}
121132

133+
func FindMatchesFromContentType(contentType string) []string {
134+
return contentPattern.FindStringSubmatch(contentType)
135+
}
136+
137+
func ReplaceContentType(contentType, replacement string) string {
138+
return contentPattern.ReplaceAllString(contentType, replacement)
139+
}
140+
122141
// Parse extracts the version date from the content type.
123142
func Parse(contentType string) (string, error) {
124-
matches := ContentPattern.FindStringSubmatch(contentType)
143+
matches := contentPattern.FindStringSubmatch(contentType)
125144
if matches == nil {
126145
return "", fmt.Errorf("invalid content type: %s", contentType)
127146
}
128-
return fmt.Sprintf("%s-%s-%s", matches[1], matches[2], matches[3]), nil
147+
148+
if len(matches) == 3 {
149+
return fmt.Sprintf("%s-%s-%s", matches[2], matches[3], matches[4]), nil
150+
}
151+
152+
return matches[1], nil
129153
}
130154

131155
// FindLatestContentVersionMatched finds the latest content version that matches the requested version.

tools/cli/internal/cli/flag/flag.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,6 @@ const (
4747
ChannelIDShort = "c"
4848
From = "from"
4949
To = "to"
50+
StabilityLevel = "stability-level"
51+
StabilityLevelShort = "l"
5052
)

tools/cli/internal/cli/usage/usage.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,5 @@ const (
3737
SlackChannelID = "Slack Channel ID."
3838
From = "Date in the format YYYY-MM-DD that indicates the start of a date range"
3939
To = "Date in the format YYYY-MM-DD that indicates the end of a date range"
40+
StabilityLevel = "Stability level related to the API Version. Valid values: [STABLE, PREVIEW]"
4041
)

tools/cli/internal/cli/versions/versions.go

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"fmt"
2121
"strings"
2222

23+
"github.com/mongodb/openapi/tools/cli/internal/apiversion"
2324
"github.com/mongodb/openapi/tools/cli/internal/cli/flag"
2425
"github.com/mongodb/openapi/tools/cli/internal/cli/usage"
2526
"github.com/mongodb/openapi/tools/cli/internal/openapi"
@@ -29,11 +30,12 @@ import (
2930
)
3031

3132
type Opts struct {
32-
fs afero.Fs
33-
basePath string
34-
outputPath string
35-
format string
36-
env string
33+
fs afero.Fs
34+
basePath string
35+
outputPath string
36+
format string
37+
env string
38+
stabilityLevel string
3739
}
3840

3941
func (o *Opts) Run() error {
@@ -44,12 +46,7 @@ func (o *Opts) Run() error {
4446
}
4547

4648
var versions []string
47-
if o.env == "" {
48-
versions, err = openapi.ExtractVersions(specInfo.Spec)
49-
} else {
50-
versions, err = openapi.ExtractVersionsWithEnv(specInfo.Spec, o.env)
51-
}
52-
49+
versions, err = openapi.ExtractVersionsWithEnv(specInfo.Spec, o.env)
5350
if err != nil {
5451
return err
5552
}
@@ -58,7 +55,8 @@ func (o *Opts) Run() error {
5855
return errors.New("no versions found in the OpenAPI specification")
5956
}
6057

61-
bytes, err := o.getVersionBytes(versions)
58+
versions = o.filterStabilityLevelVersions(versions)
59+
bytes, err := o.versionsAsBytes(versions)
6260
if err != nil {
6361
return err
6462
}
@@ -71,7 +69,26 @@ func (o *Opts) Run() error {
7169
return nil
7270
}
7371

74-
func (o *Opts) getVersionBytes(versions []string) ([]byte, error) {
72+
func (o *Opts) filterStabilityLevelVersions(apiVersions []string) []string {
73+
if o.stabilityLevel == "" || apiVersions == nil {
74+
return apiVersions
75+
}
76+
77+
var out []string
78+
for _, v := range apiVersions {
79+
if o.stabilityLevel == apiversion.PreviewStabilityLevel && strings.Contains(v, "preview") {
80+
out = append(out, v)
81+
}
82+
83+
if o.stabilityLevel == apiversion.StableStabilityLevel && !strings.Contains(v, "preview") {
84+
out = append(out, v)
85+
}
86+
}
87+
88+
return out
89+
}
90+
91+
func (o *Opts) versionsAsBytes(versions []string) ([]byte, error) {
7592
data, err := json.MarshalIndent(versions, "", " ")
7693
if err != nil {
7794
return nil, err
@@ -95,32 +112,38 @@ func (o *Opts) getVersionBytes(versions []string) ([]byte, error) {
95112
}
96113

97114
func (o *Opts) PreRunE(_ []string) error {
115+
o.stabilityLevel = strings.ToUpper(o.stabilityLevel)
116+
if o.stabilityLevel != "" && o.stabilityLevel != apiversion.PreviewStabilityLevel && o.stabilityLevel != apiversion.StableStabilityLevel {
117+
return fmt.Errorf("stability level must be %q or %q, got %q", apiversion.PreviewStabilityLevel, apiversion.StableStabilityLevel, o.stabilityLevel)
118+
}
119+
98120
if o.basePath == "" {
99-
return fmt.Errorf("no OAS detected. Please, use the flag %s to include the base OAS", flag.Base)
121+
return fmt.Errorf("no OAS detected. Please, use the flag %q to include the base OAS", flag.Base)
100122
}
101123

102124
if o.outputPath != "" && !strings.Contains(o.outputPath, ".json") && !strings.Contains(o.outputPath, ".yaml") {
103-
return fmt.Errorf("output file must be either a JSON or YAML file, got %s", o.outputPath)
125+
return fmt.Errorf("output file must be either a JSON or YAML file, got %q", o.outputPath)
104126
}
105127

106128
if o.format != "json" && o.format != "yaml" {
107-
return fmt.Errorf("output format must be either 'json' or 'yaml', got %s", o.format)
129+
return fmt.Errorf("output format must be either 'json' or 'yaml', got %q", o.format)
108130
}
109131

110132
return nil
111133
}
112134

113135
// Builder builds the versions command with the following signature:
114-
// versions -s oas.
136+
// versions -s oas --env dev|qa|staging|prod -stability-level STABLE|PREVIEW.
115137
func Builder() *cobra.Command {
116138
opts := &Opts{
117139
fs: afero.NewOsFs(),
118140
}
119141

120142
cmd := &cobra.Command{
121-
Use: "versions -s spec ",
122-
Short: "Get a list of versions from an OpenAPI specification.",
123-
Args: cobra.NoArgs,
143+
Use: "versions -s spec ",
144+
Aliases: []string{"versions list", "versions ls"},
145+
Short: "Get a list of versions from an OpenAPI specification.",
146+
Args: cobra.NoArgs,
124147
PreRunE: func(_ *cobra.Command, args []string) error {
125148
return opts.PreRunE(args)
126149
},
@@ -131,6 +154,7 @@ func Builder() *cobra.Command {
131154

132155
cmd.Flags().StringVarP(&opts.basePath, flag.Spec, flag.SpecShort, "", usage.Spec)
133156
cmd.Flags().StringVar(&opts.env, flag.Environment, "", usage.Environment)
157+
cmd.Flags().StringVarP(&opts.stabilityLevel, flag.StabilityLevel, flag.StabilityLevelShort, "", usage.StabilityLevel)
134158
cmd.Flags().StringVarP(&opts.outputPath, flag.Output, flag.OutputShort, "", usage.Output)
135159
cmd.Flags().StringVarP(&opts.format, flag.Format, flag.FormatShort, "json", usage.Format)
136160
return cmd

tools/cli/internal/cli/versions/versions_test.go

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,49 +19,98 @@ import (
1919

2020
"github.com/spf13/afero"
2121
"github.com/stretchr/testify/assert"
22+
"github.com/stretchr/testify/require"
2223
)
2324

24-
func TestVersions(t *testing.T) {
25+
func TestVersions_Run(t *testing.T) {
2526
fs := afero.NewMemMapFs()
2627
opts := &Opts{
2728
basePath: "../../../test/data/base_spec.json",
2829
outputPath: "foas.json",
2930
fs: fs,
3031
}
3132

32-
if err := opts.Run(); err != nil {
33-
t.Fatalf("Run() unexpected error: %v", err)
33+
require.NoError(t, opts.Run())
34+
b, err := afero.ReadFile(fs, opts.outputPath)
35+
require.NoError(t, err)
36+
// Check initial versions
37+
assert.NotEmpty(t, b)
38+
assert.Contains(t, string(b), "2023-02-01")
39+
}
40+
41+
func TestVersion_RunWithEnv(t *testing.T) {
42+
fs := afero.NewMemMapFs()
43+
opts := &Opts{
44+
basePath: "../../../test/data/base_spec.json",
45+
outputPath: "foas.json",
46+
fs: fs,
47+
env: "staging",
3448
}
3549

50+
require.NoError(t, opts.Run())
3651
b, err := afero.ReadFile(fs, opts.outputPath)
37-
if err != nil {
38-
t.Fatalf("ReadFile() unexpected error: %v", err)
39-
}
52+
require.NoError(t, err)
4053

4154
// Check initial versions
4255
assert.NotEmpty(t, b)
4356
assert.Contains(t, string(b), "2023-02-01")
4457
}
4558

46-
func TestVersionWithEnv(t *testing.T) {
59+
func TestVersion_RunWithPreview(t *testing.T) {
4760
fs := afero.NewMemMapFs()
4861
opts := &Opts{
49-
basePath: "../../../test/data/base_spec.json",
62+
basePath: "../../../test/data/base_spec_with_preview.json",
5063
outputPath: "foas.json",
5164
fs: fs,
5265
env: "staging",
5366
}
5467

55-
if err := opts.Run(); err != nil {
56-
t.Fatalf("Run() unexpected error: %v", err)
68+
require.NoError(t, opts.Run())
69+
b, err := afero.ReadFile(fs, opts.outputPath)
70+
require.NoError(t, err)
71+
72+
// Check initial versions
73+
assert.NotEmpty(t, b)
74+
assert.Contains(t, string(b), "2023-02-01")
75+
assert.Contains(t, string(b), "preview")
76+
}
77+
78+
func TestVersion_RunStabilityLevelPreview(t *testing.T) {
79+
fs := afero.NewMemMapFs()
80+
opts := &Opts{
81+
basePath: "../../../test/data/base_spec_with_preview.json",
82+
outputPath: "foas.json",
83+
fs: fs,
84+
env: "staging",
85+
stabilityLevel: "PREVIEW",
5786
}
5887

88+
require.NoError(t, opts.Run())
5989
b, err := afero.ReadFile(fs, opts.outputPath)
60-
if err != nil {
61-
t.Fatalf("ReadFile() unexpected error: %v", err)
90+
require.NoError(t, err)
91+
92+
// Check initial versions
93+
assert.NotEmpty(t, b)
94+
assert.NotContains(t, string(b), "2023-02-01")
95+
assert.Contains(t, string(b), "preview")
96+
}
97+
98+
func TestVersion_RunStabilityLevelStable(t *testing.T) {
99+
fs := afero.NewMemMapFs()
100+
opts := &Opts{
101+
basePath: "../../../test/data/base_spec_with_preview.json",
102+
outputPath: "foas.json",
103+
fs: fs,
104+
env: "staging",
105+
stabilityLevel: "STABLE",
62106
}
63107

108+
require.NoError(t, opts.Run())
109+
b, err := afero.ReadFile(fs, opts.outputPath)
110+
require.NoError(t, err)
111+
64112
// Check initial versions
65113
assert.NotEmpty(t, b)
66114
assert.Contains(t, string(b), "2023-02-01")
115+
assert.NotContains(t, string(b), "preview")
67116
}

tools/cli/internal/openapi/filter/info.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
"github.com/mongodb/openapi/tools/cli/internal/apiversion"
2121
)
2222

23-
// Filter: InfoFilter is a filter that modifies the Info object in the OpenAPI spec.
23+
// InfoFilter is a filter that modifies the Info object in the OpenAPI spec.
2424
type InfoFilter struct {
2525
oas *openapi3.T
2626
metadata *Metadata
@@ -39,11 +39,11 @@ func (f *InfoFilter) Apply() error {
3939
}
4040

4141
func replaceVersion(input string, v *apiversion.APIVersion) string {
42-
matches := apiversion.ContentPattern.FindStringSubmatch(input)
42+
matches := apiversion.FindMatchesFromContentType(input)
4343
if matches == nil {
4444
return input // No match found, return the original string
4545
}
4646

47-
replacement := fmt.Sprintf("application/vnd.atlas.%s+%s", v.String(), matches[4])
48-
return apiversion.ContentPattern.ReplaceAllString(input, replacement)
47+
replacement := fmt.Sprintf("application/vnd.atlas.%s+%s", v.String(), matches[5])
48+
return apiversion.ReplaceContentType(input, replacement)
4949
}

tools/cli/internal/openapi/filter/tags.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
14+
1415
package filter
1516

1617
import (
@@ -19,7 +20,7 @@ import (
1920
"github.com/getkin/kin-openapi/openapi3"
2021
)
2122

22-
// Filter: TagsFilter removes tags that are not used in the operations.
23+
// TagsFilter removes tags that are not used in the operations.
2324
type TagsFilter struct {
2425
oas *openapi3.T
2526
}

0 commit comments

Comments
 (0)