Skip to content

Commit 0a00ee0

Browse files
authored
CLOUDP-297223: Add support for private and public preview spec generation (#432)
1 parent baeb70c commit 0a00ee0

File tree

7 files changed

+69023
-83
lines changed

7 files changed

+69023
-83
lines changed

tools/cli/internal/apiversion/version.go

Lines changed: 128 additions & 7 deletions
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"
@@ -85,8 +86,8 @@ func WithDate(date time.Time) Option {
8586
}
8687
}
8788

88-
// WithContent returns an Option to generate a new APIVersion given the contentType.
89-
func WithContent(contentType string) Option {
89+
// withContent returns an Option to generate a new APIVersion given the contentType.
90+
func withContent(contentType string) Option {
9091
return func(v *APIVersion) error {
9192
version, err := Parse(contentType)
9293
if err != nil {
@@ -103,6 +104,22 @@ func WithContent(contentType string) Option {
103104
}
104105
}
105106

107+
// WithFullContent returns an Option to generate a new APIVersion given the contentType and contentValue.
108+
func WithFullContent(contentType string, contentValue *openapi3.MediaType) Option {
109+
return func(v *APIVersion) error {
110+
if !IsPreviewSabilityLevel(contentType) {
111+
return withContent(contentType)(v)
112+
}
113+
114+
name, err := GetPreviewVersionName(contentValue)
115+
if err != nil {
116+
return err
117+
}
118+
// version will be based on the name, either 'preview' or 'private-preview-<name>'
119+
return WithVersion(name)(v)
120+
}
121+
}
122+
106123
func DateFromVersion(version string) (time.Time, error) {
107124
if IsPreviewSabilityLevel(version) {
108125
return time.Now(), nil
@@ -111,7 +128,7 @@ func DateFromVersion(version string) (time.Time, error) {
111128
}
112129

113130
func (v *APIVersion) Equal(v2 *APIVersion) bool {
114-
return v.version == v2.version
131+
return strings.EqualFold(v.version, v2.version)
115132
}
116133

117134
func (v *APIVersion) GreaterThan(v2 *APIVersion) bool {
@@ -150,9 +167,17 @@ func (v *APIVersion) IsPreview() bool {
150167
return IsPreviewSabilityLevel(v.version)
151168
}
152169

170+
func (v *APIVersion) IsPrivatePreview() bool {
171+
return strings.Contains(v.version, PrivatePreviewStabilityLevel)
172+
}
173+
174+
func (v *APIVersion) IsPublicPreview() bool {
175+
return v.IsPreview() && !v.IsPrivatePreview()
176+
}
177+
153178
func IsPreviewSabilityLevel(value string) bool {
154179
// we also need string match given private preview versions like "private-preview-<name>"
155-
return strings.EqualFold(value, PreviewStabilityLevel) || strings.Contains(value, PrivatePreviewStabilityLevel)
180+
return strings.EqualFold(value, PreviewStabilityLevel) || strings.Contains(value, PreviewStabilityLevel)
156181
}
157182

158183
func IsStableSabilityLevel(value string) bool {
@@ -205,8 +230,8 @@ func FindLatestContentVersionMatched(op *openapi3.Operation, requestedVersion *A
205230
continue
206231
}
207232

208-
for contentType := range response.Value.Content {
209-
contentVersion, err := New(WithContent(contentType))
233+
for contentType, contentValue := range response.Value.Content {
234+
contentVersion, err := New(WithFullContent(contentType, contentValue))
210235
if err != nil {
211236
log.Printf("Ignoring invalid content type: %q", contentType)
212237
continue
@@ -216,7 +241,11 @@ func FindLatestContentVersionMatched(op *openapi3.Operation, requestedVersion *A
216241
return contentVersion
217242
}
218243

219-
if contentVersion.ExactMatchOnly() || requestedVersion.ExactMatchOnly() {
244+
if requestedVersion.ExactMatchOnly() {
245+
// for private preview, we will need to match with "preview" and x-xgen-preview name extension
246+
if privatePreviewContentMatch(contentVersion, contentValue, requestedVersion) {
247+
return contentVersion
248+
}
220249
continue
221250
}
222251

@@ -237,6 +266,21 @@ func FindLatestContentVersionMatched(op *openapi3.Operation, requestedVersion *A
237266
return latestVersionMatch
238267
}
239268

269+
func privatePreviewContentMatch(contentVersion *APIVersion, contentValue *openapi3.MediaType, requestedVersion *APIVersion) bool {
270+
log.Printf("trying to match in case one of the versions is private preview")
271+
if !contentVersion.IsPrivatePreview() && !requestedVersion.IsPrivatePreview() {
272+
return false
273+
}
274+
275+
name, err := GetPreviewVersionName(contentValue)
276+
if err != nil {
277+
log.Printf("failed to parse preview version name for content=%v err=%v", contentVersion.version, err)
278+
return false
279+
}
280+
281+
return strings.EqualFold(name, requestedVersion.version)
282+
}
283+
240284
// Sort versions.
241285
func Sort(versions []*APIVersion) {
242286
for i := 0; i < len(versions); i++ {
@@ -247,3 +291,80 @@ func Sort(versions []*APIVersion) {
247291
}
248292
}
249293
}
294+
295+
// GetPreviewVersionName returns the preview version name.
296+
func GetPreviewVersionName(contentTypeValue *openapi3.MediaType) (name string, err error) {
297+
public, name, err := parsePreviewExtensionData(contentTypeValue)
298+
if err != nil {
299+
return "", err
300+
}
301+
302+
if public {
303+
return "preview", nil
304+
}
305+
306+
if !public && name != "" {
307+
return "private-preview-" + name, nil
308+
}
309+
310+
return "", errors.New("no preview extension found")
311+
}
312+
313+
func parsePreviewExtensionData(contentTypeValue *openapi3.MediaType) (public bool, name string, err error) {
314+
// Expected formats:
315+
//
316+
// "x-xgen-preview": {
317+
// "name": "api-registry-private-preview"
318+
// }
319+
//
320+
// "x-xgen-preview": {
321+
// "public": "true"
322+
// }
323+
324+
name = ""
325+
public = false
326+
327+
if contentTypeValue.Extensions == nil {
328+
return false, "", errors.New("no preview extension found")
329+
}
330+
331+
previewExtension, ok := contentTypeValue.Extensions["x-xgen-preview"]
332+
if !ok {
333+
return false, "", errors.New("no preview extension found")
334+
}
335+
336+
previewExtensionMap, ok := previewExtension.(map[string]any)
337+
if !ok {
338+
return false, "", errors.New("no preview extension found")
339+
}
340+
341+
// Reading if it's public or not
342+
publicV, ok := previewExtensionMap["public"].(string)
343+
if ok {
344+
public = strings.EqualFold(publicV, "true")
345+
}
346+
347+
// Reading the name
348+
nameV, ok := previewExtensionMap["name"].(string)
349+
if ok {
350+
name = nameV
351+
}
352+
353+
if err := validatePreviewExtensionData(name, publicV); err != nil {
354+
return false, "", err
355+
}
356+
357+
return public, name, nil
358+
}
359+
360+
func validatePreviewExtensionData(name, public string) error {
361+
if name != "" && (public == "true") {
362+
return errors.New("both name and public = true fields are set, only one is allowed")
363+
}
364+
365+
if name == "" && public != "true" && public != "false" {
366+
return errors.New("invalid value for 'public' field, only 'true' or 'false' are allowed")
367+
}
368+
369+
return nil
370+
}

tools/cli/internal/apiversion/version_test.go

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ func TestNewAPIVersionFromContentType(t *testing.T) {
153153
for _, tt := range testCases {
154154
t.Run(tt.name, func(t *testing.T) {
155155
t.Parallel()
156-
version, err := New(WithContent(tt.contentType))
156+
version, err := New(withContent(tt.contentType))
157157
if tt.wantErr {
158158
assert.Error(t, err)
159159
} else {
@@ -163,6 +163,136 @@ func TestNewAPIVersionFromContentType(t *testing.T) {
163163
}
164164
}
165165

166+
func TestApiVersion_WithFullContent(t *testing.T) {
167+
testCases := []struct {
168+
name string
169+
contentType string
170+
contentValue *openapi3.MediaType
171+
expectedMatch string
172+
wantErr bool
173+
}{
174+
{
175+
name: "json",
176+
contentType: "application/vnd.atlas.2023-01-01+json",
177+
contentValue: &openapi3.MediaType{},
178+
expectedMatch: "2023-01-01",
179+
wantErr: false,
180+
},
181+
{
182+
name: "csv",
183+
contentType: "application/vnd.atlas.2023-01-02+csv",
184+
contentValue: &openapi3.MediaType{},
185+
186+
expectedMatch: "2023-01-02",
187+
wantErr: false,
188+
},
189+
{
190+
name: "yaml",
191+
contentType: "application/vnd.atlas.2030-02-20+yaml",
192+
contentValue: &openapi3.MediaType{},
193+
expectedMatch: "2030-02-20",
194+
wantErr: false,
195+
},
196+
{
197+
name: "invalid",
198+
199+
contentType: "application/vnd.test.2023-01-01",
200+
contentValue: &openapi3.MediaType{},
201+
expectedMatch: "",
202+
wantErr: true,
203+
},
204+
{
205+
name: "notVersioned",
206+
contentType: "application/json",
207+
contentValue: &openapi3.MediaType{},
208+
expectedMatch: "",
209+
wantErr: true,
210+
},
211+
{
212+
name: "empty",
213+
contentType: "",
214+
contentValue: &openapi3.MediaType{},
215+
expectedMatch: "",
216+
wantErr: true,
217+
},
218+
{
219+
name: "invalidFormat",
220+
contentType: "application/vnd.atlas.2023-01-01",
221+
expectedMatch: "",
222+
contentValue: &openapi3.MediaType{},
223+
wantErr: true,
224+
},
225+
{
226+
name: "invalidDate",
227+
contentType: "application/vnd.atlas.2023111-01-01",
228+
expectedMatch: "",
229+
contentValue: &openapi3.MediaType{},
230+
wantErr: true,
231+
},
232+
233+
{
234+
name: "preview",
235+
contentType: "application/vnd.atlas.preview+json",
236+
contentValue: &openapi3.MediaType{
237+
Extensions: map[string]any{
238+
"x-xgen-preview": map[string]any{
239+
"public": "true",
240+
},
241+
},
242+
},
243+
expectedMatch: "preview",
244+
wantErr: false,
245+
},
246+
{
247+
name: "private-preview",
248+
contentType: "application/vnd.atlas.preview+json",
249+
contentValue: &openapi3.MediaType{
250+
Extensions: map[string]any{
251+
"x-xgen-preview": map[string]any{
252+
"name": "feature",
253+
},
254+
},
255+
},
256+
expectedMatch: "private-preview-feature",
257+
wantErr: false,
258+
},
259+
{
260+
name: "invalid-preview",
261+
contentType: "application/vnd.atlas.preview+json",
262+
contentValue: &openapi3.MediaType{
263+
Extensions: map[string]any{
264+
"x-xgen-preview": map[string]any{
265+
"public": "true",
266+
"name": "feature",
267+
},
268+
},
269+
},
270+
expectedMatch: "private-preview-feature",
271+
wantErr: true,
272+
},
273+
{
274+
name: "invalid-preview",
275+
contentType: "application/vnd.atlas.preview+json",
276+
contentValue: &openapi3.MediaType{},
277+
expectedMatch: "preview",
278+
wantErr: true,
279+
},
280+
}
281+
282+
for _, tt := range testCases {
283+
t.Run(tt.name, func(t *testing.T) {
284+
t.Parallel()
285+
version, err := New(WithFullContent(tt.contentType, tt.contentValue))
286+
if tt.wantErr {
287+
assert.Error(t, err)
288+
} else {
289+
require.NoError(t, err)
290+
assert.Equal(t, tt.expectedMatch, version.String())
291+
}
292+
})
293+
}
294+
}
295+
166296
func TestApiVersion_GreaterThan(t *testing.T) {
167297
testCases := []struct {
168298
name string
@@ -445,6 +575,11 @@ func TestFindLatestContentVersionMatched(t *testing.T) {
445575
targetVersion: "preview",
446576
expectedMatch: "preview",
447577
},
578+
{
579+
name: "exact match private-preview-feature",
580+
targetVersion: "private-preview-feature",
581+
expectedMatch: "private-preview-feature",
582+
},
448583
{
449584
name: "exact match 2023-11-15",
450585
targetVersion: "2023-11-15",

0 commit comments

Comments
 (0)