Skip to content

Commit 9d1ce13

Browse files
committed
CLOUDP-297223: Add support for private and public preview spec generation
1 parent 6a9aa82 commit 9d1ce13

File tree

3 files changed

+142
-68
lines changed

3 files changed

+142
-68
lines changed

tools/cli/internal/apiversion/version.go

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package apiversion
1616

1717
import (
18+
"errors"
1819
"fmt"
1920
"log"
2021
"regexp"
@@ -150,6 +151,10 @@ func (v *APIVersion) IsPreview() bool {
150151
return IsPreviewSabilityLevel(v.version)
151152
}
152153

154+
func (v *APIVersion) IsPrivatePreview() bool {
155+
return strings.Contains(v.version, PrivatePreviewStabilityLevel)
156+
}
157+
153158
func IsPreviewSabilityLevel(value string) bool {
154159
// we also need string match given private preview versions like "private-preview-<name>"
155160
return strings.EqualFold(value, PreviewStabilityLevel) || strings.Contains(value, PrivatePreviewStabilityLevel)
@@ -205,7 +210,7 @@ func FindLatestContentVersionMatched(op *openapi3.Operation, requestedVersion *A
205210
continue
206211
}
207212

208-
for contentType := range response.Value.Content {
213+
for contentType, contentValue := range response.Value.Content {
209214
contentVersion, err := New(WithContent(contentType))
210215
if err != nil {
211216
log.Printf("Ignoring invalid content type: %q", contentType)
@@ -217,6 +222,10 @@ func FindLatestContentVersionMatched(op *openapi3.Operation, requestedVersion *A
217222
}
218223

219224
if contentVersion.ExactMatchOnly() || requestedVersion.ExactMatchOnly() {
225+
// for private preview, we will need to match with "preview" and x-xgen-preview name extension
226+
if requestedVersion.IsPrivatePreview() && PrivatePreviewContentMatch(contentValue, requestedVersion) {
227+
return contentVersion
228+
}
220229
continue
221230
}
222231

@@ -237,6 +246,15 @@ func FindLatestContentVersionMatched(op *openapi3.Operation, requestedVersion *A
237246
return latestVersionMatch
238247
}
239248

249+
func PrivatePreviewContentMatch(contentValue *openapi3.MediaType, requestedVersion *APIVersion) bool {
250+
name, err := GetPreviewVersionName(contentValue)
251+
if err != nil {
252+
log.Printf("failed to parse preview version name: %v", err)
253+
}
254+
255+
return strings.EqualFold(name, requestedVersion.version)
256+
}
257+
240258
// Sort versions.
241259
func Sort(versions []*APIVersion) {
242260
for i := 0; i < len(versions); i++ {
@@ -247,3 +265,67 @@ func Sort(versions []*APIVersion) {
247265
}
248266
}
249267
}
268+
269+
func GetPreviewVersionName(contentTypeValue *openapi3.MediaType) (name string, err error) {
270+
public, name, err := parsePreviewExtensionData(contentTypeValue)
271+
if err != nil {
272+
return "", err
273+
}
274+
275+
if public {
276+
return "preview", nil
277+
}
278+
279+
if !public && name != "" {
280+
return "private-preview-" + name, nil
281+
}
282+
283+
return "", errors.New("no preview extension found")
284+
}
285+
286+
func parsePreviewExtensionData(contentTypeValue *openapi3.MediaType) (public bool, name string, err error) {
287+
// Expected formats:
288+
//
289+
// "x-xgen-preview": {
290+
// "name": "api-registry-private-preview"
291+
// }
292+
//
293+
// "x-xgen-preview": {
294+
// "public": "true"
295+
// }
296+
297+
name = ""
298+
public = false
299+
300+
if contentTypeValue.Extensions == nil {
301+
return false, "", errors.New("no preview extension found")
302+
}
303+
304+
previewExtension, ok := contentTypeValue.Extensions["x-xgen-preview"]
305+
if !ok {
306+
return false, "", errors.New("no preview extension found")
307+
}
308+
309+
previewExtensionMap, ok := previewExtension.(map[string]any)
310+
if !ok {
311+
return false, "", errors.New("no preview extension found")
312+
}
313+
314+
// Reading if it's public or not
315+
publicV, ok := previewExtensionMap["public"].(string)
316+
if ok {
317+
public = strings.EqualFold(publicV, "true")
318+
}
319+
320+
// Reading the name
321+
nameV, ok := previewExtensionMap["name"].(string)
322+
if ok {
323+
name = nameV
324+
}
325+
326+
if nameV == "" && publicV != "true" && publicV != "false" {
327+
return false, "", errors.New("invalid value for 'public' field, only 'true' or 'false' are allowed")
328+
}
329+
330+
return public, name, nil
331+
}

tools/cli/internal/cli/split/split_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,14 @@
1515
package split
1616

1717
import (
18+
"net/url"
1819
"testing"
1920

21+
"github.com/getkin/kin-openapi/openapi3"
22+
"github.com/mongodb/openapi/tools/cli/internal/openapi"
2023
"github.com/spf13/afero"
2124
"github.com/stretchr/testify/require"
25+
"github.com/tufin/oasdiff/load"
2226
)
2327

2428
func TestSuccessfulSplit_Run(t *testing.T) {
@@ -34,6 +38,46 @@ func TestSuccessfulSplit_Run(t *testing.T) {
3438
}
3539
}
3640

41+
func TestSplitPublicPreviewRun(t *testing.T) {
42+
fs := afero.NewMemMapFs()
43+
opts := &Opts{
44+
basePath: "../../../test/data/base_spec_with_public_preview.json",
45+
outputPath: "foas.yaml",
46+
fs: fs,
47+
}
48+
49+
if err := opts.Run(); err != nil {
50+
t.Fatalf("Run() unexpected error: %v", err)
51+
}
52+
53+
info, err := loadRunResultOas(fs, "foas-preview.yaml")
54+
require.NoError(t, err)
55+
56+
// check paths has only one
57+
require.Len(t, info.Spec.Paths.Map(), 1)
58+
require.Contains(t, info.Spec.Paths.Map(), "/api/atlas/v2/groups")
59+
}
60+
61+
func TestSplitPrivatePreviewRun(t *testing.T) {
62+
fs := afero.NewMemMapFs()
63+
opts := &Opts{
64+
basePath: "../../../test/data/base_spec_with_private_preview.json",
65+
outputPath: "foas.yaml",
66+
fs: fs,
67+
}
68+
69+
if err := opts.Run(); err != nil {
70+
t.Fatalf("Run() unexpected error: %v", err)
71+
}
72+
73+
info, err := loadRunResultOas(fs, "foas-private-preview-new-feature.yaml")
74+
require.NoError(t, err)
75+
76+
// check paths has only one
77+
require.Len(t, info.Spec.Paths.Map(), 1)
78+
require.Contains(t, info.Spec.Paths.Map(), "/api/atlas/v2/groups")
79+
}
80+
3781
func TestOpts_PreRunE(t *testing.T) {
3882
testCases := []struct {
3983
wantErr require.ErrorAssertionFunc
@@ -84,3 +128,17 @@ func TestInvalidPath_PreRun(t *testing.T) {
84128
require.Error(t, err)
85129
require.EqualError(t, err, "output file must be either a JSON or YAML file, got foas.html")
86130
}
131+
132+
func loadRunResultOas(fs afero.Fs, fileName string) (*load.SpecInfo, error) {
133+
oas := openapi.NewOpenAPI3()
134+
oas.Loader.ReadFromURIFunc = func(loader *openapi3.Loader, uri *url.URL) ([]byte, error) {
135+
f, err := fs.OpenFile(fileName, 0, 0)
136+
if err != nil {
137+
return nil, err
138+
}
139+
defer f.Close()
140+
return afero.ReadAll(f)
141+
}
142+
143+
return oas.CreateOpenAPISpecFromPath(fileName)
144+
}

tools/cli/internal/openapi/versions.go

Lines changed: 1 addition & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@
1515
package openapi
1616

1717
import (
18-
"errors"
1918
"fmt"
2019
"sort"
21-
"strings"
2220

2321
"github.com/getkin/kin-openapi/openapi3"
2422
"github.com/mongodb/openapi/tools/cli/internal/apiversion"
@@ -64,7 +62,7 @@ func extractVersions(oas *openapi3.T) ([]string, error) {
6462

6563
if apiversion.IsPreviewSabilityLevel(version) {
6664
// parse if it is public or not
67-
version, err = getPreviewVersionName(contentTypeValue)
65+
version, err = apiversion.GetPreviewVersionName(contentTypeValue)
6866
if err != nil {
6967
fmt.Printf("failed to parse preview version name: %v\n", err)
7068
return nil, err
@@ -80,70 +78,6 @@ func extractVersions(oas *openapi3.T) ([]string, error) {
8078
return mapKeysToSortedSlice(versions), nil
8179
}
8280

83-
func getPreviewVersionName(contentTypeValue *openapi3.MediaType) (name string, err error) {
84-
public, name, err := parsePreviewExtensionData(contentTypeValue)
85-
if err != nil {
86-
return "", err
87-
}
88-
89-
if public {
90-
return "preview", nil
91-
}
92-
93-
if !public && name != "" {
94-
return "private-preview-" + name, nil
95-
}
96-
97-
return "", errors.New("no preview extension found")
98-
}
99-
100-
func parsePreviewExtensionData(contentTypeValue *openapi3.MediaType) (public bool, name string, err error) {
101-
// Expected formats:
102-
//
103-
// "x-xgen-preview": {
104-
// "name": "api-registry-private-preview"
105-
// }
106-
//
107-
// "x-xgen-preview": {
108-
// "public": "true"
109-
// }
110-
111-
name = ""
112-
public = false
113-
114-
if contentTypeValue.Extensions == nil {
115-
return false, "", errors.New("no preview extension found")
116-
}
117-
118-
previewExtension, ok := contentTypeValue.Extensions["x-xgen-preview"]
119-
if !ok {
120-
return false, "", errors.New("no preview extension found")
121-
}
122-
123-
previewExtensionMap, ok := previewExtension.(map[string]any)
124-
if !ok {
125-
return false, "", errors.New("no preview extension found")
126-
}
127-
128-
// Reading if it's public or not
129-
publicV, ok := previewExtensionMap["public"].(string)
130-
if ok {
131-
public = strings.EqualFold(publicV, "true")
132-
}
133-
134-
// Reading the name
135-
nameV, ok := previewExtensionMap["name"].(string)
136-
if ok {
137-
name = nameV
138-
}
139-
140-
if nameV == "" && publicV != "true" && publicV != "false" {
141-
return false, "", errors.New("invalid value for 'public' field, only 'true' or 'false' are allowed")
142-
}
143-
144-
return public, name, nil
145-
}
146-
14781
// mapKeysToSortedSlice converts map keys to a sorted slice.
14882
func mapKeysToSortedSlice(m map[string]struct{}) []string {
14983
keys := make([]string, 0, len(m))

0 commit comments

Comments
 (0)