Skip to content

Commit

Permalink
feat: add the ability to override the category name
Browse files Browse the repository at this point in the history
  • Loading branch information
tinygrasshopper committed Jun 3, 2024
1 parent 98000d2 commit d9a2fde
Show file tree
Hide file tree
Showing 6 changed files with 275 additions and 0 deletions.
15 changes: 15 additions & 0 deletions tools/api-docs-generator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ To generate the API documentation locally, run:
make run
```

## Override the category names in the generated documentation

To override the category names for an operation in the generated documentation, apply the `x-snyk-documentation` extension to the operation in the OpenAPI specification. This extension is provided so that the category name can be overridden without changing the tags, as tag updates are considered breaking changes. For example:

```yaml
paths:
/tomatoes:
post:
x-snyk-documentation:
category: vegetables
tags:
- fruits
```
In the above example, the generated docs will use the `vegetables` category for the `POST /tomatoes` operation.

## Config

Generator is configured with `config.yml`. The following options are available:
Expand Down
19 changes: 19 additions & 0 deletions tools/api-docs-generator/generator/reference_docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ func aggregateSpecs(cfg *config.Config, docsBasePath string) (map[string][]opera
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,
Expand All @@ -120,6 +127,18 @@ func aggregateSpecs(cfg *config.Config, docsBasePath string) (map[string][]opera
return aggregatedDocs, nil
}

func extractCategoryNameFromExtension(extension interface{}) (string, error) {
extensionMap, worked := extension.(map[string]interface{})
if !worked {
return "", fmt.Errorf("failed to parse docs extension as an object")
}
categoryValue, worked := extensionMap["category"].(string)
if !worked {
return "", fmt.Errorf("x-snyk-documentation extension category field not a string")
}
return categoryValue, nil
}

func renderReferenceDocsPage(filePath, label, docsPath string, operation []operationPath, categoryContext config.CategoryContexts) error {
docsFile, err := os.Create(filePath)
if err != nil {
Expand Down
128 changes: 128 additions & 0 deletions tools/api-docs-generator/generator/reference_docs_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package generator

import (
"fmt"
"os"
"path"
"testing"
Expand Down Expand Up @@ -192,3 +193,130 @@ func createTempFile(t *testing.T, baseDir, content string) string {
assert.NoError(t, err)
return file.Name()
}

func Test_aggregateSpecs(t *testing.T) {
type args struct {
cfg *config.Config
docsBasePath string
}
tests := []struct {
name string
args args
want map[string][]operationPath
wantErr assert.ErrorAssertionFunc
}{
{
name: "aggregates single specs",
args: args{
cfg: &config.Config{
Specs: []config.Spec{
{
Path: "spec_with_only_tagged.yaml",
},
},
},
docsBasePath: "../testdata/reference_docs/",
},
want: map[string][]operationPath{
"test": {
{
method: "POST",
specPath: "spec_with_only_tagged.yaml",
pathURL: "/test",
},
},
},
wantErr: assert.NoError,
},
{
name: "aggregates multiple specs",
args: args{
cfg: &config.Config{
Specs: []config.Spec{
{
Path: "spec_with_only_tagged.yaml",
},
{
Path: "spec_with_only_tagged_2.yaml",
},
},
},
docsBasePath: "../testdata/reference_docs/",
},
want: map[string][]operationPath{
"test": {
{
method: "POST",
specPath: "spec_with_only_tagged.yaml",
pathURL: "/test",
},
},
"another": {
{
method: "POST",
specPath: "spec_with_only_tagged_2.yaml",
pathURL: "/another_test",
},
},
},
wantErr: assert.NoError,
},
{
name: "uses override category name if present",
args: args{
cfg: &config.Config{
Specs: []config.Spec{
{
Path: "spec_with_only_tagged.yaml",
},
{
Path: "spec_with_override.yaml",
},
},
},
docsBasePath: "../testdata/reference_docs/",
},
want: map[string][]operationPath{
"test": {
{
method: "POST",
specPath: "spec_with_only_tagged.yaml",
pathURL: "/test",
},
},
"overridden-test": {
{
method: "POST",
specPath: "spec_with_override.yaml",
pathURL: "/test",
},
},
},
wantErr: assert.NoError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := aggregateSpecs(tt.args.cfg, tt.args.docsBasePath)
if !tt.wantErr(t, err, fmt.Sprintf("aggregateSpecs(%v, %v)", tt.args.cfg, tt.args.docsBasePath)) {
return
}
compareForTest(t, tt.want, got)
})
}
}

func compareForTest(t *testing.T, want, got map[string][]operationPath) {
t.Helper()
assert.Equal(t, len(want), len(got), "length of maps do not match")
for key, wantValue := range want {
gotValue, ok := got[key]
assert.Truef(t, ok, "key %s not found in got map", key)
assert.Equal(t, len(wantValue), len(gotValue), "length of values do not match")
for i := range wantValue {
assert.Equal(t, wantValue[i].method, gotValue[i].method, "method does not match")
assert.Equal(t, wantValue[i].pathURL, gotValue[i].pathURL, "pathURL does not match")
assert.Equal(t, wantValue[i].specPath, gotValue[i].specPath, "pathURL does not match")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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:
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
openapi: 3.0.3
info:
title: Test
contact: {}
version: 3.0.0
description: Sample API
servers:
- url: /test
description: Test
tags:
- name: another
description: another
paths:
/another_test:
post:
description: test
operationId: test
tags:
- another
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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-documentation:
category: overridden-test
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

0 comments on commit d9a2fde

Please sign in to comment.