From 50105fcd9252522b2055ac30fde8026a68c8995e Mon Sep 17 00:00:00 2001 From: Radu Tarean Date: Wed, 9 Oct 2024 13:51:03 +0300 Subject: [PATCH] feat: filter lower stabilites from docs and changelog --- .../api-docs-generator/changelog/changelog.go | 47 ++++++-- .../changelog/changelog_test.go | 18 ++- .../generator/reference_docs.go | 108 +++++++++++++----- .../generator/reference_docs_test.go | 29 +++++ .../01_new_version_added/2024-04-25.yaml | 1 + .../01_new_version_added/2024-05-23.yaml | 1 + .../02_no_changes_detected/2024-04-25.yaml | 1 + .../02_no_changes_detected/2024-05-23.yaml | 1 + .../2024-05-23.yaml | 1 + .../2024-05-23_update.yaml | 1 + .../2024-04-25.yaml | 38 ++++++ .../2024-05-23.yaml | 40 +++++++ .../reference_docs/spec_with_only_tagged.yaml | 1 + .../spec_with_only_tagged_2.yaml | 1 + .../reference_docs/spec_with_override.yaml | 1 + .../reference_docs/spec_with_stability.yaml | 33 ++++++ 16 files changed, 283 insertions(+), 39 deletions(-) create mode 100644 tools/api-docs-generator/testdata/changelog/04_non_ga_changes_not_added/2024-04-25.yaml create mode 100644 tools/api-docs-generator/testdata/changelog/04_non_ga_changes_not_added/2024-05-23.yaml create mode 100644 tools/api-docs-generator/testdata/reference_docs/spec_with_stability.yaml diff --git a/tools/api-docs-generator/changelog/changelog.go b/tools/api-docs-generator/changelog/changelog.go index fb88dab887f9..af75448ec12a 100644 --- a/tools/api-docs-generator/changelog/changelog.go +++ b/tools/api-docs-generator/changelog/changelog.go @@ -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} + } } } } @@ -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 { @@ -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 } diff --git a/tools/api-docs-generator/changelog/changelog_test.go b/tools/api-docs-generator/changelog/changelog_test.go index ee5445265889..a032ac20515e 100644 --- a/tools/api-docs-generator/changelog/changelog_test.go +++ b/tools/api-docs-generator/changelog/changelog_test.go @@ -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) { @@ -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) + } } }) } diff --git a/tools/api-docs-generator/generator/reference_docs.go b/tools/api-docs-generator/generator/reference_docs.go index bcda832189d5..99d3bce953ac 100644 --- a/tools/api-docs-generator/generator/reference_docs.go +++ b/tools/api-docs-generator/generator/reference_docs.go @@ -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 { diff --git a/tools/api-docs-generator/generator/reference_docs_test.go b/tools/api-docs-generator/generator/reference_docs_test.go index c5f7c67008fe..ce4634ea7688 100644 --- a/tools/api-docs-generator/generator/reference_docs_test.go +++ b/tools/api-docs-generator/generator/reference_docs_test.go @@ -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) { @@ -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) + } + } }) } } diff --git a/tools/api-docs-generator/testdata/changelog/01_new_version_added/2024-04-25.yaml b/tools/api-docs-generator/testdata/changelog/01_new_version_added/2024-04-25.yaml index 37f65dcc0d52..3c304d988a7a 100644 --- a/tools/api-docs-generator/testdata/changelog/01_new_version_added/2024-04-25.yaml +++ b/tools/api-docs-generator/testdata/changelog/01_new_version_added/2024-04-25.yaml @@ -13,6 +13,7 @@ tags: paths: /test: post: + x-snyk-api-stability: ga description: test operationId: test tags: diff --git a/tools/api-docs-generator/testdata/changelog/01_new_version_added/2024-05-23.yaml b/tools/api-docs-generator/testdata/changelog/01_new_version_added/2024-05-23.yaml index 6d7f47290230..79b688a8927d 100644 --- a/tools/api-docs-generator/testdata/changelog/01_new_version_added/2024-05-23.yaml +++ b/tools/api-docs-generator/testdata/changelog/01_new_version_added/2024-05-23.yaml @@ -13,6 +13,7 @@ tags: paths: /test: post: + x-snyk-api-stability: ga description: test operationId: test tags: diff --git a/tools/api-docs-generator/testdata/changelog/02_no_changes_detected/2024-04-25.yaml b/tools/api-docs-generator/testdata/changelog/02_no_changes_detected/2024-04-25.yaml index 6d7f47290230..79b688a8927d 100644 --- a/tools/api-docs-generator/testdata/changelog/02_no_changes_detected/2024-04-25.yaml +++ b/tools/api-docs-generator/testdata/changelog/02_no_changes_detected/2024-04-25.yaml @@ -13,6 +13,7 @@ tags: paths: /test: post: + x-snyk-api-stability: ga description: test operationId: test tags: diff --git a/tools/api-docs-generator/testdata/changelog/02_no_changes_detected/2024-05-23.yaml b/tools/api-docs-generator/testdata/changelog/02_no_changes_detected/2024-05-23.yaml index 6d7f47290230..79b688a8927d 100644 --- a/tools/api-docs-generator/testdata/changelog/02_no_changes_detected/2024-05-23.yaml +++ b/tools/api-docs-generator/testdata/changelog/02_no_changes_detected/2024-05-23.yaml @@ -13,6 +13,7 @@ tags: paths: /test: post: + x-snyk-api-stability: ga description: test operationId: test tags: diff --git a/tools/api-docs-generator/testdata/changelog/03_current_version_updated_in_place/2024-05-23.yaml b/tools/api-docs-generator/testdata/changelog/03_current_version_updated_in_place/2024-05-23.yaml index 6d7f47290230..79b688a8927d 100644 --- a/tools/api-docs-generator/testdata/changelog/03_current_version_updated_in_place/2024-05-23.yaml +++ b/tools/api-docs-generator/testdata/changelog/03_current_version_updated_in_place/2024-05-23.yaml @@ -13,6 +13,7 @@ tags: paths: /test: post: + x-snyk-api-stability: ga description: test operationId: test tags: diff --git a/tools/api-docs-generator/testdata/changelog/03_current_version_updated_in_place/2024-05-23_update.yaml b/tools/api-docs-generator/testdata/changelog/03_current_version_updated_in_place/2024-05-23_update.yaml index 353cbe8c884d..397b86628334 100644 --- a/tools/api-docs-generator/testdata/changelog/03_current_version_updated_in_place/2024-05-23_update.yaml +++ b/tools/api-docs-generator/testdata/changelog/03_current_version_updated_in_place/2024-05-23_update.yaml @@ -13,6 +13,7 @@ tags: paths: /test: post: + x-snyk-api-stability: ga description: test operationId: test tags: diff --git a/tools/api-docs-generator/testdata/changelog/04_non_ga_changes_not_added/2024-04-25.yaml b/tools/api-docs-generator/testdata/changelog/04_non_ga_changes_not_added/2024-04-25.yaml new file mode 100644 index 000000000000..f324e3190243 --- /dev/null +++ b/tools/api-docs-generator/testdata/changelog/04_non_ga_changes_not_added/2024-04-25.yaml @@ -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 diff --git a/tools/api-docs-generator/testdata/changelog/04_non_ga_changes_not_added/2024-05-23.yaml b/tools/api-docs-generator/testdata/changelog/04_non_ga_changes_not_added/2024-05-23.yaml new file mode 100644 index 000000000000..6b7a6e2b74b7 --- /dev/null +++ b/tools/api-docs-generator/testdata/changelog/04_non_ga_changes_not_added/2024-05-23.yaml @@ -0,0 +1,40 @@ +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 + prop2: + type: string + responses: + "201": + description: success + content: + application/vnd.api+json: + schema: + type: object + location: + schema: + type: string diff --git a/tools/api-docs-generator/testdata/reference_docs/spec_with_only_tagged.yaml b/tools/api-docs-generator/testdata/reference_docs/spec_with_only_tagged.yaml index 203cb1459642..75aec746099e 100644 --- a/tools/api-docs-generator/testdata/reference_docs/spec_with_only_tagged.yaml +++ b/tools/api-docs-generator/testdata/reference_docs/spec_with_only_tagged.yaml @@ -25,6 +25,7 @@ paths: properties: prop1: type: string + x-snyk-api-stability: ga responses: "201": description: success diff --git a/tools/api-docs-generator/testdata/reference_docs/spec_with_only_tagged_2.yaml b/tools/api-docs-generator/testdata/reference_docs/spec_with_only_tagged_2.yaml index 5a251194df48..0c35d1f80d13 100644 --- a/tools/api-docs-generator/testdata/reference_docs/spec_with_only_tagged_2.yaml +++ b/tools/api-docs-generator/testdata/reference_docs/spec_with_only_tagged_2.yaml @@ -13,6 +13,7 @@ tags: paths: /another_test: post: + x-snyk-api-stability: ga description: test operationId: test tags: diff --git a/tools/api-docs-generator/testdata/reference_docs/spec_with_override.yaml b/tools/api-docs-generator/testdata/reference_docs/spec_with_override.yaml index 6298e1867154..32f4d5397e87 100644 --- a/tools/api-docs-generator/testdata/reference_docs/spec_with_override.yaml +++ b/tools/api-docs-generator/testdata/reference_docs/spec_with_override.yaml @@ -13,6 +13,7 @@ tags: paths: /test: post: + x-snyk-api-stability: ga x-snyk-documentation: category: overridden-test tags: diff --git a/tools/api-docs-generator/testdata/reference_docs/spec_with_stability.yaml b/tools/api-docs-generator/testdata/reference_docs/spec_with_stability.yaml new file mode 100644 index 000000000000..f099bc5740e5 --- /dev/null +++ b/tools/api-docs-generator/testdata/reference_docs/spec_with_stability.yaml @@ -0,0 +1,33 @@ +openapi: 3.0.0 +info: + title: Test + contact: {} + version: 3.0.0 + description: Sample API +paths: + /stable-path: + get: + tags: + - stable + x-snyk-api-stability: ga + responses: + '200': + description: OK + summary: Stable path + /unstable-path: + get: + tags: + - unstable + x-snyk-api-stability: beta + responses: + '200': + description: OK + summary: Unstable path + /no-stability-path: + get: + tags: + - no-stability + responses: + '200': + description: OK + summary: No stability path