Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: filter lower stabilites from docs and changelog #373

Merged
merged 1 commit into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 37 additions & 10 deletions tools/api-docs-generator/changelog/changelog.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,16 +209,19 @@ func writeOperationChangeDetails(markdown *md.Markdown, changeGroup ChangesByEnd
}
}

func groupChanges(changes checker.Changes) []ChangesByEndpoint {
func groupChanges(changes checker.Changes, gaEndpoints map[Endpoint]bool) []ChangesByEndpoint {
apiChanges := map[Endpoint]checker.Changes{}

for _, change := range changes {
if change, ok := change.(checker.ApiChange); ok {
ep := Endpoint{Path: change.GetPath(), Operation: change.GetOperation()}
if changesForEndpoint, ok := apiChanges[ep]; ok {
apiChanges[ep] = append(changesForEndpoint, change)
} else {
apiChanges[ep] = checker.Changes{change}
if apiChange, ok := change.(checker.ApiChange); ok {
ep := Endpoint{Path: apiChange.GetPath(), Operation: apiChange.GetOperation()}
// Only include changes for GA endpoints
if gaEndpoints[ep] {
if changesForEndpoint, ok := apiChanges[ep]; ok {
apiChanges[ep] = append(changesForEndpoint, apiChange)
} else {
apiChanges[ep] = checker.Changes{apiChange}
}
}
}
}
Expand All @@ -231,6 +234,25 @@ func groupChanges(changes checker.Changes) []ChangesByEndpoint {
return apiChangesSlice
}

func getGAEndpoints(spec *openapi3.T) map[Endpoint]bool {
gaEndpoints := make(map[Endpoint]bool)
for currentPath, pathItem := range spec.Paths.Map() {
for operationName, operation := range pathItem.Operations() {
if ext, ok := operation.Extensions["x-snyk-api-stability"]; ok {
if stabilityStr, ok := ext.(string); ok {
if stabilityStr == "ga" {
ep := Endpoint{Path: currentPath, Operation: operationName}
gaEndpoints[ep] = true
}
} else {
fmt.Printf("Extension 'x-snyk-api-stability' for %s %s is not a string\n", operationName, currentPath)
}
}
}
}
return gaEndpoints
}

func getChangeLog(nextVersionURI, baseVersionURI string, loader *openapi3.Loader) ([]ChangesByEndpoint, error) {
baseVersionURL, err := url.Parse(baseVersionURI)
if err != nil {
Expand All @@ -255,14 +277,19 @@ func getChangeLog(nextVersionURI, baseVersionURI string, loader *openapi3.Loader
return nil, err
}

diffReport, sourcesMap, err := diff.GetWithOperationsSourcesMap(diff.NewConfig(), s1, s2)
changes := checker.CheckBackwardCompatibilityUntilLevel(checker.GetDefaultChecks(), diffReport, sourcesMap, checker.INFO)
// Collect GA endpoints from the next version spec
gaEndpoints := getGAEndpoints(s2.Spec)

groupedChanges := groupChanges(changes)
diffReport, sourcesMap, err := diff.GetWithOperationsSourcesMap(diff.NewConfig(), s1, s2)
if err != nil {
return nil, err
}

changes := checker.CheckBackwardCompatibilityUntilLevel(checker.GetDefaultChecks(), diffReport, sourcesMap, checker.INFO)

// Group and filter changes based on GA endpoints
groupedChanges := groupChanges(changes, gaEndpoints)

return groupedChanges, nil
}

Expand Down
18 changes: 16 additions & 2 deletions tools/api-docs-generator/changelog/changelog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ var tests = []*testConfig{
lastSyncVersion: "2024-05-23",
want: fmt.Sprintf("## %s - Updated %s", "2024-05-23", time.Now().Format("2006-01-02")),
},
{
name: "04_non_ga_changes_not_added",
baseURL: "../testdata/changelog/04_non_ga_changes_not_added/2024-04-25.yaml",
nextURL: "../testdata/changelog/04_non_ga_changes_not_added/2024-05-23.yaml",
latestGAVersion: "2024-05-23",
lastSyncVersion: "2024-04-25",
want: "",
},
}

func testFn(config *testConfig) (string, error) {
Expand Down Expand Up @@ -83,8 +91,14 @@ func Test_delta(t *testing.T) {
if gotErr != nil {
t.Errorf("Expected not to fail %v", gotErr)
}
if !strings.Contains(gotRes, tt.want) {
t.Errorf("Expected markdown to contain %v", tt.want)
if tt.want == "" {
if gotRes != "" {
t.Errorf("Expected output to be empty, but got: %v", gotRes)
}
} else {
if !strings.Contains(gotRes, tt.want) {
t.Errorf("Expected markdown to contain: %v, but got: %v", tt.want, gotRes)
}
}
})
}
Expand Down
108 changes: 81 additions & 27 deletions tools/api-docs-generator/generator/reference_docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,43 +90,97 @@ func clearDir(dirName string) error {
}

func aggregateSpecs(cfg *config.Config, docsBasePath string) (map[string][]operationPath, error) {
aggregatedDocs := map[string][]operationPath{}
aggregatedDocs := make(map[string][]operationPath)

for _, spec := range cfg.Specs {
loader := openapi3.NewLoader()
doc, err := loader.LoadFromFile(path.Join(docsBasePath, spec.Path))
specDocs, err := processSpec(spec, docsBasePath)
if err != nil {
return nil, err
}
for pathURL, pathItem := range doc.Paths.Map() {
for method, operation := range pathItem.Operations() {
for _, tag := range operation.Tags {
if tag == "OpenAPI" {
continue
}
snykDocsExtension := operation.Extensions["x-snyk-documentation"]
if snykDocsExtension != nil {
tag, err = extractCategoryNameFromExtension(snykDocsExtension)
if err != nil {
return nil, err
}
}
tag += spec.Suffix
aggregatedDocs[tag] = append(aggregatedDocs[tag], operationPath{
operation: operation,
pathItem: pathItem,
pathURL: pathURL,
specPath: spec.Path,
method: method,
docsHint: spec.DocsHint,
})
}
}

for tag, ops := range specDocs {
aggregatedDocs[tag] = append(aggregatedDocs[tag], ops...)
}
}

return aggregatedDocs, nil
}

func processSpec(spec config.Spec, docsBasePath string) (map[string][]operationPath, error) {
loader := openapi3.NewLoader()
doc, err := loader.LoadFromFile(path.Join(docsBasePath, spec.Path))
if err != nil {
return nil, err
}

specDocs := make(map[string][]operationPath)
for pathURL, pathItem := range doc.Paths.Map() {
err := processPathItem(pathURL, pathItem, spec, specDocs)
if err != nil {
return nil, err
}
}
return specDocs, nil
}

func processPathItem(pathURL string, pathItem *openapi3.PathItem, spec config.Spec, specDocs map[string][]operationPath) error {
for method, operation := range pathItem.Operations() {
err := processOperation(pathURL, pathItem, method, operation, spec, specDocs)
if err != nil {
return err
}
}
return nil
}

func processOperation(pathURL string,
pathItem *openapi3.PathItem,
method string,
operation *openapi3.Operation,
spec config.Spec,
specDocs map[string][]operationPath) error {
for _, tag := range operation.Tags {
if tag == "OpenAPI" {
continue
}

if snykDocsExtension, ok := operation.Extensions["x-snyk-documentation"]; ok && snykDocsExtension != nil {
var err error
tag, err = extractCategoryNameFromExtension(snykDocsExtension)
if err != nil {
return err
}
}

if !isGAOperation(operation) {
continue
}

tag += spec.Suffix
specDocs[tag] = append(specDocs[tag], operationPath{
operation: operation,
pathItem: pathItem,
pathURL: pathURL,
specPath: spec.Path,
method: method,
docsHint: spec.DocsHint,
})
}
return nil
}

func isGAOperation(operation *openapi3.Operation) bool {
apiStabilityExtension, ok := operation.Extensions["x-snyk-api-stability"]
if !ok || apiStabilityExtension == nil {
return false
}
stabilityStr, ok := apiStabilityExtension.(string)
if !ok {
return false
}
return stabilityStr == "ga"
}

func extractCategoryNameFromExtension(extension interface{}) (string, error) {
extensionMap, worked := extension.(map[string]interface{})
if !worked {
Expand Down
29 changes: 29 additions & 0 deletions tools/api-docs-generator/generator/reference_docs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,29 @@ func Test_aggregateSpecs(t *testing.T) {
},
wantErr: assert.NoError,
},
{
name: "filters out non-stable paths",
args: args{
cfg: &config.Config{
Specs: []config.Spec{
{
Path: "spec_with_stability.yaml",
},
},
},
docsBasePath: "../testdata/reference_docs/",
},
want: map[string][]operationPath{
"stable": {
{
method: "GET",
specPath: "spec_with_stability.yaml",
pathURL: "/stable-path",
},
},
},
wantErr: assert.NoError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand All @@ -302,6 +325,12 @@ func Test_aggregateSpecs(t *testing.T) {
return
}
compareForTest(t, tt.want, got)
for tag := range got {
for _, op := range got[tag] {
assert.NotEqual(t, "/unstable-path", op.pathURL)
assert.NotEqual(t, "/no-stability-path", op.pathURL)
}
}
})
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ tags:
paths:
/test:
post:
x-snyk-api-stability: ga
description: test
operationId: test
tags:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ tags:
paths:
/test:
post:
x-snyk-api-stability: ga
description: test
operationId: test
tags:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ tags:
paths:
/test:
post:
x-snyk-api-stability: ga
description: test
operationId: test
tags:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ tags:
paths:
/test:
post:
x-snyk-api-stability: ga
description: test
operationId: test
tags:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ tags:
paths:
/test:
post:
x-snyk-api-stability: ga
description: test
operationId: test
tags:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ tags:
paths:
/test:
post:
x-snyk-api-stability: ga
description: test
operationId: test
tags:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
openapi: 3.0.3
info:
title: Test
contact: {}
version: 3.0.0
description: Sample API
servers:
- url: /test
description: Test
tags:
- name: Test
description: test
paths:
/test:
post:
x-snyk-api-stability: beta
description: test
operationId: test
tags:
- Test
requestBody:
content:
application/vnd.api+json:
schema:
type: object
properties:
prop1:
type: string
responses:
"201":
description: success
content:
application/vnd.api+json:
schema:
type: object
location:
schema:
type: string
Loading