From 6242b57ccb6fbbd4c8c4f6cac153fd39ea2472b7 Mon Sep 17 00:00:00 2001 From: Jatin Naik Date: Fri, 25 Apr 2025 13:05:30 +0100 Subject: [PATCH] feat: allow for order of rendering be overridden in docs --- .../generator/reference_docs.go | 37 +++++++- .../generator/reference_docs_test.go | 33 +++++++ .../spec_with_order_override.yaml | 90 +++++++++++++++++++ 3 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 tools/api-docs-generator/testdata/reference_docs/spec_with_order_override.yaml diff --git a/tools/api-docs-generator/generator/reference_docs.go b/tools/api-docs-generator/generator/reference_docs.go index edfe6955b75a..e6746e990fef 100644 --- a/tools/api-docs-generator/generator/reference_docs.go +++ b/tools/api-docs-generator/generator/reference_docs.go @@ -1,11 +1,14 @@ package generator import ( + "cmp" "fmt" "os" "path" "path/filepath" + "slices" "sort" + "strconv" "strings" "github.com/getkin/kin-openapi/openapi3" @@ -20,6 +23,7 @@ type operationPath struct { specPath string method string docsHint string + order int } func GenerateReferenceDocs(cfg *config.Config, docsBasePath string) error { @@ -35,6 +39,9 @@ func GenerateReferenceDocs(cfg *config.Config, docsBasePath string) error { } for label, operations := range aggregatedDocs { + slices.SortFunc(operations, func(i, j operationPath) int { + return cmp.Compare(i.order, j.order) + }) destinationPath := path.Join(docsBasePath, cfg.Output.APIReferencePath, labelToFileName(label)) summary = append(summary, fmt.Sprintf("* [%s](%s)\n", label, path.Join(cfg.Output.APIReferencePath, labelToFileName(label)))) @@ -139,6 +146,7 @@ func processOperation(pathURL string, operation *openapi3.Operation, spec config.Spec, specDocs map[string][]operationPath) error { + var order int for _, tag := range operation.Tags { if tag == "OpenAPI" { continue @@ -146,7 +154,15 @@ func processOperation(pathURL string, if snykDocsExtension, ok := operation.Extensions["x-snyk-documentation"]; ok && snykDocsExtension != nil { var err error - tag, err = extractCategoryNameFromExtension(snykDocsExtension) + renamedTag, err := extractCategoryNameFromExtension(snykDocsExtension) + if err != nil { + return err + } + if renamedTag != "" { + tag = renamedTag + } + + order, err = extractOrderFromExtension(snykDocsExtension) if err != nil { return err } @@ -164,6 +180,7 @@ func processOperation(pathURL string, specPath: spec.Path, method: method, docsHint: spec.DocsHint, + order: order, }) } return nil @@ -181,6 +198,22 @@ func isBeta(operation *openapi3.Operation) bool { return stabilityStr == "beta" } +func extractOrderFromExtension(extension interface{}) (int, error) { + extensionMap, worked := extension.(map[string]interface{}) + if !worked { + return 0, fmt.Errorf("failed to parse docs extension as an object") + } + orderValue, worked := extensionMap["order"].(string) + if !worked { + return 0, nil + } + orderAsInt, err := strconv.Atoi(orderValue) + if err != nil { + return 0, fmt.Errorf("x-snyk-documentation extension order field not a int, %w", err) + } + return orderAsInt, nil +} + func extractCategoryNameFromExtension(extension interface{}) (string, error) { extensionMap, worked := extension.(map[string]interface{}) if !worked { @@ -188,7 +221,7 @@ func extractCategoryNameFromExtension(extension interface{}) (string, error) { } categoryValue, worked := extensionMap["category"].(string) if !worked { - return "", fmt.Errorf("x-snyk-documentation extension category field not a string") + return "", nil } return categoryValue, nil } diff --git a/tools/api-docs-generator/generator/reference_docs_test.go b/tools/api-docs-generator/generator/reference_docs_test.go index 9b5613e4e8bd..951f9f7423d8 100644 --- a/tools/api-docs-generator/generator/reference_docs_test.go +++ b/tools/api-docs-generator/generator/reference_docs_test.go @@ -294,6 +294,39 @@ func Test_aggregateSpecs(t *testing.T) { }, wantErr: assert.NoError, }, + { + name: "uses override order if present", + args: args{ + cfg: &config.Config{ + Specs: []config.Spec{ + { + Path: "spec_with_order_override.yaml", + }, + }, + }, + docsBasePath: "../testdata/reference_docs/", + }, + want: map[string][]operationPath{ + "test": { + { + method: "POST", + specPath: "spec_with_order_override.yaml", + pathURL: "/test", + }, + { + method: "POST", + specPath: "spec_with_order_override.yaml", + pathURL: "/test/1", + }, + { + method: "POST", + specPath: "spec_with_order_override.yaml", + pathURL: "/test/2", + }, + }, + }, + wantErr: assert.NoError, + }, { name: "filters out beta paths", args: args{ diff --git a/tools/api-docs-generator/testdata/reference_docs/spec_with_order_override.yaml b/tools/api-docs-generator/testdata/reference_docs/spec_with_order_override.yaml new file mode 100644 index 000000000000..a8722ccfcfcd --- /dev/null +++ b/tools/api-docs-generator/testdata/reference_docs/spec_with_order_override.yaml @@ -0,0 +1,90 @@ +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: ga + x-snyk-documentation: + order: -1 + tags: + - test + description: test + operationId: 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 + /test/1: + post: + x-snyk-api-stability: ga + tags: + - test + description: test + operationId: 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 + /test/2: + post: + x-snyk-api-stability: ga + tags: + - test + description: test + operationId: 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