diff --git a/.changeset/grumpy-countries-flow.md b/.changeset/grumpy-countries-flow.md
new file mode 100644
index 0000000..6d6e879
--- /dev/null
+++ b/.changeset/grumpy-countries-flow.md
@@ -0,0 +1,5 @@
+---
+'@dweber019/backstage-plugin-api-docs-spectral-linter': minor
+---
+
+Add links to external documentation when that is available. Render rule descriptions as markdown.
diff --git a/plugins/api-docs-spectral-linter/dev/index.tsx b/plugins/api-docs-spectral-linter/dev/index.tsx
index da7125f..e9b5aeb 100644
--- a/plugins/api-docs-spectral-linter/dev/index.tsx
+++ b/plugins/api-docs-spectral-linter/dev/index.tsx
@@ -16,6 +16,8 @@ import openapiApiEntity from './openapi-example-api.yaml';
import openapiZalandoApiEntity from './openapi-zalando-example-api.yaml';
// @ts-ignore
import openapiBaloiseApiEntity from './openapi-baloise-example-api.yaml';
+// @ts-ignore
+import openapiOwaspApiEntity from './openapi-owasp-example-api.yaml';
const mockConfig = new MockConfigApi({
spectralLinter: {
@@ -69,4 +71,13 @@ createDevApp()
title: 'Open API - Baloise',
path: '/open-api-baloise',
})
+ .addPage({
+ element: (
+
+
+
+ ),
+ title: 'Open API - OWASP',
+ path: '/open-api-owasp',
+ })
.render();
diff --git a/plugins/api-docs-spectral-linter/dev/openapi-owasp-example-api.yaml b/plugins/api-docs-spectral-linter/dev/openapi-owasp-example-api.yaml
new file mode 100644
index 0000000..d163965
--- /dev/null
+++ b/plugins/api-docs-spectral-linter/dev/openapi-owasp-example-api.yaml
@@ -0,0 +1,126 @@
+apiVersion: backstage.io/v1alpha1
+kind: API
+metadata:
+ name: petstore-owasp-rules
+ description: The petstore API
+ tags:
+ - store
+ - rest
+ annotations:
+ backstage.io/spectral-ruleset-url: https://unpkg.com/@stoplight/spectral-owasp-ruleset@2.0.1/dist/ruleset.mjs
+spec:
+ type: openapi
+ lifecycle: experimental
+ owner: team-c
+ definition: |
+ openapi: "3.0.0"
+ info:
+ version: 1.0.0
+ title: Swagger Petstore
+ license:
+ name: MIT
+ servers:
+ - url: http://petstore.swagger.io/v1
+ paths:
+ /pets:
+ get:
+ summary: List all pets
+ operationId: listPets
+ tags:
+ - pets
+ parameters:
+ - name: limit
+ in: query
+ description: How many items to return at one time (max 100)
+ required: false
+ schema:
+ type: integer
+ format: int32
+ responses:
+ '200':
+ description: A paged array of pets
+ headers:
+ x-next:
+ description: A link to the next page of responses
+ schema:
+ type: string
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Pets"
+ default:
+ description: unexpected error
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Error"
+ post:
+ summary: Create a pet
+ operationId: createPets
+ tags:
+ - pets
+ responses:
+ '201':
+ description: Null response
+ default:
+ description: unexpected error
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Error"
+ /pets/{petId}:
+ get:
+ summary: Info for a specific pet
+ operationId: showPetById
+ tags:
+ - pets
+ parameters:
+ - name: petId
+ in: path
+ required: true
+ description: The id of the pet to retrieve
+ schema:
+ type: string
+ responses:
+ '200':
+ description: Expected response to a valid request
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Pet"
+ default:
+ description: unexpected error
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Error"
+ components:
+ schemas:
+ Pet:
+ type: object
+ required:
+ - id
+ - name
+ properties:
+ id:
+ type: integer
+ format: int64
+ name:
+ type: string
+ tag:
+ type: string
+ Pets:
+ type: array
+ items:
+ $ref: "#/components/schemas/Pet"
+ Error:
+ type: object
+ required:
+ - code
+ - message
+ properties:
+ code:
+ type: integer
+ format: int32
+ message:
+ type: string
diff --git a/plugins/api-docs-spectral-linter/src/api/LinterClient.ts b/plugins/api-docs-spectral-linter/src/api/LinterClient.ts
index 8f19bb1..e158cad 100644
--- a/plugins/api-docs-spectral-linter/src/api/LinterClient.ts
+++ b/plugins/api-docs-spectral-linter/src/api/LinterClient.ts
@@ -103,6 +103,8 @@ export class LinterClient implements LinterApi {
severity: diagnosticItem.severity,
path: diagnosticItem.path.map(item => item.toString()),
code: diagnosticItem.code,
+ ruleDocumentationUrl: ruleDocumentationUrl(spectral, diagnosticItem.code),
+ ruleDescription: ruleDescription(spectral, diagnosticItem.code)
})),
};
}
@@ -124,3 +126,11 @@ export class LinterClient implements LinterApi {
return isApiDocsSpectralLinterAvailable(entity);
}
}
+
+function ruleDocumentationUrl(spectral: Spectral, code: string | number): string | undefined {
+ return spectral.ruleset?.rules[code].documentationUrl || undefined
+}
+
+function ruleDescription(spectral: Spectral, code: string | number): string | undefined {
+ return spectral.ruleset?.rules[code].description || undefined
+}
diff --git a/plugins/api-docs-spectral-linter/src/api/types.ts b/plugins/api-docs-spectral-linter/src/api/types.ts
index c87c212..5287d6e 100644
--- a/plugins/api-docs-spectral-linter/src/api/types.ts
+++ b/plugins/api-docs-spectral-linter/src/api/types.ts
@@ -37,6 +37,10 @@ export type LinterResultData = {
*/
severity: number;
+ ruleDocumentationUrl?: string;
+
+ ruleDescription?: string;
+
/**
* The path in content.
*/
diff --git a/plugins/api-docs-spectral-linter/src/components/EntityApiDocsSpectralLinterContent/EntityApiDocsSpectralLinterContent.tsx b/plugins/api-docs-spectral-linter/src/components/EntityApiDocsSpectralLinterContent/EntityApiDocsSpectralLinterContent.tsx
index 0e7f1be..76aac29 100644
--- a/plugins/api-docs-spectral-linter/src/components/EntityApiDocsSpectralLinterContent/EntityApiDocsSpectralLinterContent.tsx
+++ b/plugins/api-docs-spectral-linter/src/components/EntityApiDocsSpectralLinterContent/EntityApiDocsSpectralLinterContent.tsx
@@ -18,6 +18,7 @@ import React, { useState } from 'react';
import {
CodeSnippet,
InfoCard,
+ MarkdownContent,
Progress,
WarningPanel,
} from '@backstage/core-components';
@@ -113,41 +114,50 @@ export const EntityApiDocsSpectralLinterContent = () => {
value.data.map((ruleResult, idx) => (
-
-
- {ruleResult.message} ({ruleResult.code})
-
- {`alert${idx}` === expanded ? (
-
- ) : (
-
- )}
-
-
- {`alert${idx}` === expanded && (
-
- )}
-
+
+
+ {ruleResult.message} ({ruleResult.code})
+
+ {`alert${idx}` === expanded ? (
+
+ ) : (
+
+ )}
+
+
+ {`alert${idx}` === expanded && (
+
+
+
+
+ )}
+
))