-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add support for rendering open-api specification exports within…
… vuepress (#109) * feat(open-api): add open-api typescript types closes INT-407 * feat(open-api): add display of base open-api info and component structure closes INT-407 * feat(open-api): add display of (example) responses and base path information for open-api closes INT-407 * feat(open-api): improve display of open-api operation details improves display of the path/method and adds placeholder security details closes INT-407 * feat(open-api): display API responses using existing components INT-407 * docs(open-api): add a test document for open-api output INT-407 * fix(open-api): fix dark mode not working correctly for the operations block INT-407 * docs(open-api): add json sample for MyParcel webhooks INT-407 * fix(open-api): fix security / auth showing when it was an empty array INT-407 * feat(open-api): add basic $ref resolution support for openapi documents adds support to resolve $ref references to paths within the document INT-407 * feat(open-api): add basic display of open-api schemas INT-407 * feat(open-api): improve rendering of (response) schemas INT-407 * feat(open-api): add display of request parameter and body information INT-407 * feat(open-api): add consistent rendering of examples in request- and response tables INT-407 * feat(open-api): add support for rendering request body description and schema INT-407 * feat(open-api): wrap reponse examples and schemas in details/expand blocks INT-407 * refactor(open-api): resolve type safety issues and create util file for OpenAPI type guards INT-407 * fix(open-api): fix tables not showing correctly in dark mode INT-407 * feat(open-api): add a plugin to render openapi schema files as markdown pages adds a plugin to render json openapi files from a directory into individual pages within vuepress, with each path within as a chapter INT-407 * refactor(open-api): load yaml files from (remote) url rather than json files from a local directory INT-407 * feat(open-api): add and improve display of various schema requirements INT-407 * fix(open-api): add missing global components to the indexed list INT-407 * feat(open-api): add top-level security information INT-407 * fix(open-api): fix example rendering "value" key if summary or description not set * fix(open-api): don't show "Authorization" heading without any security config * feat(open-api): add auth/security requirements for individual operations INT-407 * feat(open-api): support display of webhooks and make paths optional INT-407 * ci(open-api): add nightly (re)deploy to update with changes from remote openapi documents INT-407 * feat(open-api): add servers display if set INT-407 * fix(open-api): render all description fields as markdown INT-407 * docs(open-api): remove all unpublishable schemas * style(open-api): clean up formatting * refactor(open-api): add type definition to method prop for `OpenApiOperation` component * refactor(open-api): don't use indexes for keys * refactor(open-api): process feedback * refactor(open-api): clean up and improve html formatting * Update src/.vuepress/theme/client/components/global/OpenApiSchemaInfo.vue Co-authored-by: Remco Hörters <[email protected]> * refactor(open-api): replace array index with value index * fix(open-api): fix security schemes never rendering * docs(open-api): do not publish address API for now * refactor(open-api): remove redundant checks for schema prop INT-407 --------- Co-authored-by: Remco Hörters <[email protected]>
- Loading branch information
Showing
21 changed files
with
1,065 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
name: 'Nightly rebuild 🌙' | ||
|
||
on: | ||
schedule: | ||
- cron: '0 0 * * *' | ||
|
||
concurrency: | ||
group: ${{ github.workflow }}-${{ github.ref }} | ||
cancel-in-progress: true | ||
|
||
jobs: | ||
upload: | ||
runs-on: ubuntu-22.04 | ||
steps: | ||
- uses: actions/checkout@v3 | ||
|
||
- uses: ./.github/actions/build | ||
|
||
- uses: actions/upload-pages-artifact@v1 | ||
with: | ||
path: src/.vuepress/dist | ||
|
||
deploy-ghp: | ||
needs: upload | ||
runs-on: ubuntu-22.04 | ||
permissions: | ||
pages: write | ||
id-token: write | ||
environment: | ||
name: github-pages | ||
url: ${{ steps.deployment.outputs.page_url }} | ||
steps: | ||
- id: deployment | ||
uses: actions/deploy-pages@v1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -81,6 +81,7 @@ | |
"markdown-it-multimd-table": "^4.1.3", | ||
"mock-fs": "^5.1.2", | ||
"only-allow": "^1.1.1", | ||
"openapi-types": "^12.1.3", | ||
"plop": "^4.0.0", | ||
"postcss-import": "^15.1.0", | ||
"prettier": "^2.8.8", | ||
|
@@ -90,7 +91,8 @@ | |
"ts-node": "^10.9.1", | ||
"typescript": "^5.2.2", | ||
"vite-svg-loader": "^4.0.0", | ||
"vitest": "^0.34.6" | ||
"vitest": "^0.34.6", | ||
"yaml": "^2.4.1" | ||
}, | ||
"packageManager": "[email protected]", | ||
"volta": { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import {parse as parseYaml} from 'yaml'; | ||
import {createPage, type Plugin} from 'vuepress'; | ||
import {type OpenAPIV3_1 as OpenApiType} from 'openapi-types'; | ||
import {kebabCase} from 'lodash-es'; | ||
import {resolveRefs} from '../theme/client/utils/openApiHelpers'; | ||
|
||
interface OpenApiPluginConfig { | ||
yamlUrls: string[]; | ||
} | ||
|
||
export const openApiPlugin = (config: OpenApiPluginConfig): Plugin => ({ | ||
name: '@myparcel/vuepress-openapi', | ||
async onInitialized(app) { | ||
// Find all yaml files in config.yamlUrls and create a page for each of them | ||
await Promise.all( | ||
config.yamlUrls?.map(async (url) => { | ||
const document = (await fetch(url).then(async (res) => parseYaml(await res.text()))) as OpenApiType.Document; | ||
// Use the basename of the file as the slug | ||
const resolvedDocument = resolveRefs(document); | ||
|
||
// Now generate the page contents | ||
app.pages.push(await createPage(app, generateOpenApiPage(resolvedDocument))); | ||
}), | ||
); | ||
}, | ||
}); | ||
|
||
function generateOpenApiPage(document: OpenApiType.Document) { | ||
return { | ||
path: `/api-reference/${kebabCase(document.info.title)}`, | ||
content: renderMarkdown(document), | ||
}; | ||
} | ||
|
||
function renderMarkdown(document: OpenApiType.Document): string { | ||
return `\ | ||
--- | ||
title: ${document.info.title} | ||
description: ${document.info.description} | ||
--- | ||
Version ${document.info.version} | ||
${document.info.description ?? ''} | ||
${document.security ? '## Authorization' : ''} | ||
<OpenApiSecurityRequirements | ||
:security='${JSON.stringify(document.security ?? [])}' | ||
:security-schemes='${JSON.stringify(document.components?.securitySchemes ?? [])}' /> | ||
${document.servers?.length ? '## Servers' : ''} | ||
<OpenApiServers :servers='${JSON.stringify(document.servers ?? [])}' /> | ||
${renderPaths(document, document.paths, 'Endpoints')} | ||
${renderPaths(document, document.webhooks, 'Webhooks')} | ||
`; | ||
} | ||
|
||
function renderPaths( | ||
document: OpenApiType.Document, | ||
paths: OpenApiType.PathsObject | undefined, | ||
heading: string, | ||
): string { | ||
if (!paths || !Object.keys(paths).length) return ''; | ||
|
||
let chapters = `## ${heading}`; | ||
|
||
for (const [path, pathObj] of Object.entries(paths)) { | ||
chapters += ` | ||
### ${path} | ||
\n | ||
<OpenApiPath :path='${JSON.stringify(pathObj)}' :components='${JSON.stringify(document.components)}' title='${path}' /> | ||
`; | ||
} | ||
|
||
return chapters; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<template> | ||
<section class="open-api"> | ||
<OpenApiInfo :info="resolvedDocument.info" /> | ||
|
||
<article | ||
v-for="(item, path) in resolvedDocument.paths" | ||
:key="path"> | ||
<OpenApiPath | ||
v-if="item" | ||
:path="item" | ||
:components="document.components" | ||
:title="path" /> | ||
</article> | ||
</section> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import {computed} from 'vue'; | ||
import {type OpenAPIV3_1 as OpenApiType} from 'openapi-types'; | ||
import OpenApiPath from './OpenApiPath.vue'; | ||
import OpenApiInfo from './OpenApiInfo.vue'; | ||
import { resolveRefs } from '@mptheme/client/utils/openApiHelpers'; | ||
const props = defineProps<{ | ||
document: OpenApiType.Document; | ||
}>(); | ||
const resolvedDocument = computed(() => resolveRefs(props.document)); | ||
// Recursively loop through the entire document and replace any $ref keys with their corresponding values | ||
type RecursiveType = { | ||
[key: string]: unknown | RecursiveType | RecursiveType[]; | ||
}; | ||
</script> |
51 changes: 51 additions & 0 deletions
51
src/.vuepress/theme/client/components/global/OpenApiExample.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<template> | ||
<div v-if="example"> | ||
<strong class="inline-block text-sm">{{ title || 'Example' }}:</strong> | ||
<Markdown | ||
v-if="isExampleObject(example) && example.summary" | ||
class="text-sm" | ||
:content="example.summary" /> | ||
|
||
<Markdown | ||
v-if="isExampleObject(example) && example.description" | ||
class="text-sm" | ||
:content="example.description" /> | ||
|
||
<CodeBlock | ||
v-if="formattedExample && isMultilineString" | ||
:code="formattedExample" /> | ||
|
||
<code | ||
v-else | ||
class="p-1"> | ||
{{ formattedExample }} | ||
</code> | ||
</div> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import {computed} from 'vue'; | ||
import {type OpenAPIV3_1 as OpenApiType} from 'openapi-types'; | ||
import {formatExample} from '@mptheme/client/utils/openApiHelpers'; | ||
import {isExampleObject} from '@mptheme/client/utils/openApiGuards'; | ||
import Markdown from '@mptheme/client/components/global/Markdown.vue'; | ||
import CodeBlock from './CodeBlock.vue'; | ||
const props = defineProps<{ | ||
title?: string; | ||
example?: OpenApiType.ExampleObject | unknown; | ||
}>(); | ||
const formattedExample = computed(() => { | ||
if (!props.example) return undefined; | ||
if (isExampleObject(props.example)) return formatExample(props.example.value); | ||
if (typeof props.example === 'object') return formatExample(props.example); | ||
return props.example.toString(); | ||
}); | ||
// Check the formattedExample for linebreaks, if it has those, it's a multiline string | ||
const isMultilineString = computed(() => formattedExample.value?.includes('\n')); | ||
</script> |
20 changes: 20 additions & 0 deletions
20
src/.vuepress/theme/client/components/global/OpenApiInfo.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<template> | ||
<section class="open-api-info"> | ||
<hgroup> | ||
<h2>{{ info.title }}</h2> | ||
<p>(v{{ info.version }})</p> | ||
<Markdown | ||
v-if="info.description" | ||
:content="info.description" /> | ||
</hgroup> | ||
</section> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import {type OpenAPIV3_1 as OpenApiType} from 'openapi-types'; | ||
import Markdown from './Markdown.vue'; | ||
defineProps<{ | ||
info: OpenApiType.Document['info']; | ||
}>(); | ||
</script> |
54 changes: 54 additions & 0 deletions
54
src/.vuepress/theme/client/components/global/OpenApiOperation.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
<template> | ||
<header class="bg-gray-100 dark:bg-gray-800 mb-4 mt-4 open-api-operation p-3"> | ||
<strong>Endpoint:</strong> | ||
<code | ||
class="dark:text-black inline-block leading-none m-0 ml-3 p-1 rounded-sm text-sm" | ||
:class="methodClass"> | ||
{{ method.toUpperCase() }} | ||
</code> | ||
<pre class="dark:text-gray-100 inline m-0 ml-2 p-0 text-gray-700 text-sm">{{ endpoint }}</pre> | ||
<br /> | ||
|
||
<template v-if="securityRequirements?.length && securitySchemes"> | ||
<strong>Authentication:</strong> | ||
<OpenApiSecurityRequirements | ||
class="text-sm" | ||
:security="securityRequirements" | ||
:security-schemes="securitySchemes" /> | ||
</template> | ||
</header> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import {computed} from 'vue'; | ||
import {type OpenAPIV3_1 as OpenApiType} from 'openapi-types'; | ||
import OpenApiSecurityRequirements from '@mptheme/client/components/global/OpenApiSecurityRequirements.vue'; | ||
const props = defineProps<{ | ||
method: OpenApiType.HttpMethods; | ||
endpoint: string; | ||
securityRequirements?: OpenApiType.SecurityRequirementObject[]; | ||
securitySchemes?: Record<string, OpenApiType.SecuritySchemeObject>; | ||
}>(); | ||
const methodClass = computed(() => { | ||
return { | ||
'bg-green-200': props.method === 'get', | ||
'dark:bg-green-200': props.method === 'get', | ||
'bg-blue-200': props.method === 'post', | ||
'dark:bg-blue-200': props.method === 'post', | ||
'bg-yellow-200': props.method === 'put', | ||
'dark:bg-yellow-200': props.method === 'put', | ||
'bg-red-200': props.method === 'delete', | ||
'dark:bg-red-200': props.method === 'delete', | ||
'bg-purple-200': props.method === 'patch', | ||
'dark:bg-purple-200': props.method === 'patch', | ||
'bg-gray-200': props.method === 'options', | ||
'dark:bg-gray-200': props.method === 'options', | ||
'bg-indigo-200': props.method === 'head', | ||
'dark:bg-indigo-200': props.method === 'head', | ||
'bg-pink-200': props.method === 'trace', | ||
'dark:bg-pink-200': props.method === 'trace', | ||
}; | ||
}); | ||
</script> |
84 changes: 84 additions & 0 deletions
84
src/.vuepress/theme/client/components/global/OpenApiPath.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
<template> | ||
<article class="open-api-path"> | ||
<div | ||
v-for="(operation, method) in path" | ||
:key="method"> | ||
<template v-if="typeof operation === 'object'"> | ||
<h3 v-if="'operationId' in operation"> | ||
{{ operation.operationId }} | ||
</h3> | ||
|
||
<p v-if="'summary' in operation">{{ operation.summary }}</p> | ||
|
||
<OpenApiOperation | ||
:method="method" | ||
:endpoint="title" | ||
:security-schemes="components?.securitySchemes as Record<string, OpenApiType.SecuritySchemeObject>" | ||
:security-requirements="'security' in operation ? operation.security : undefined" /> | ||
|
||
<Markdown | ||
v-if="'description' in operation && operation.description?.length" | ||
:content="operation.description" /> | ||
|
||
<template v-if="'parameters' in operation"> | ||
<h4>Request parameters</h4> | ||
<table> | ||
<thead> | ||
<th>Parameter</th> | ||
<th>Location</th> | ||
<th>Information</th> | ||
</thead> | ||
<tr | ||
v-for="parameter in operation.parameters" | ||
:key="isParameterType(parameter) ? parameter.name : parameter.$ref"> | ||
<OpenApiRequestParam | ||
v-if="isParameterType(parameter)" | ||
:parameter="parameter" /> | ||
</tr> | ||
</table> | ||
</template> | ||
<template v-if="'requestBody' in operation && operation.requestBody"> | ||
<h4>Request body</h4> | ||
|
||
<template v-if="operation.requestBody?.description"> | ||
<Markdown :content="operation.requestBody?.description"></Markdown> | ||
</template> | ||
<DetailsExpand | ||
v-if="'content' in operation.requestBody && operation.requestBody.content" | ||
tag="h5" | ||
title="Schema"> | ||
<div | ||
v-for="(content, key) in operation.requestBody.content" | ||
:key="key"> | ||
<strong>{{ key }}</strong> | ||
<OpenApiSchema | ||
v-if="content.schema && (isSchemaObject(content.schema) || isArraySchemaObject(content.schema))" | ||
:schema="content.schema" /> | ||
</div> | ||
</DetailsExpand> | ||
</template> | ||
|
||
<OpenApiResponses | ||
v-if="'responses' in operation" | ||
:responses="operation.responses" /> | ||
</template> | ||
</div> | ||
</article> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import {type OpenAPIV3_1 as OpenApiType} from 'openapi-types'; | ||
import {isParameterType, isArraySchemaObject, isSchemaObject} from '@mptheme/client/utils/openApiGuards'; | ||
import OpenApiSchema from './OpenApiSchema.vue'; | ||
import OpenApiResponses from './OpenApiResponses.vue'; | ||
import OpenApiRequestParam from './OpenApiRequestParam.vue'; | ||
import OpenApiOperation from './OpenApiOperation.vue'; | ||
import Markdown from './Markdown.vue'; | ||
import DetailsExpand from './DetailsExpand.vue'; | ||
defineProps<{ | ||
title: string; | ||
path: OpenApiType.PathItemObject; | ||
components?: OpenApiType.ComponentsObject; | ||
}>(); | ||
</script> |
Oops, something went wrong.