From 47be5c695662406c220346a741857872115ac0ae Mon Sep 17 00:00:00 2001 From: naporitan Date: Wed, 10 Jul 2024 10:20:52 +0900 Subject: [PATCH] feat(swagger-uri): Add Full Configuration Parameters and Update Dependencies (#618) * chore(swagger-ui): update swagger-ui-dist types * feat(swagger-ui): add swagger-ui parameters * chore(swagger-ui): fix typeerror add url parameter * chore(swagger-ui): refactor test case * chore: add versioning doc * chore(swagger-ui): remove useUnsafeMarkdown * chore(swagger-ui): add requestSnippetsEnabled * chore(swagger-ui): syntaxHighlight: boolean * chore(swagger-ui): fix propertykey * chore(swagger-ui): format code * chore(swagger-ui): add string function property commented --- .changeset/warm-flies-count.md | 5 + packages/swagger-ui/package.json | 2 +- packages/swagger-ui/src/swagger/renderer.ts | 61 ++- .../swagger-ui/test/option-renderer.test.ts | 349 ++++++++++-------- yarn.lock | 10 +- 5 files changed, 256 insertions(+), 171 deletions(-) create mode 100644 .changeset/warm-flies-count.md diff --git a/.changeset/warm-flies-count.md b/.changeset/warm-flies-count.md new file mode 100644 index 000000000..5f31ddca4 --- /dev/null +++ b/.changeset/warm-flies-count.md @@ -0,0 +1,5 @@ +--- +'@hono/swagger-ui': minor +--- + +Add Full Configuration Parameters and Update Dependencies for Swagger UI diff --git a/packages/swagger-ui/package.json b/packages/swagger-ui/package.json index e8123600c..bc992f06f 100644 --- a/packages/swagger-ui/package.json +++ b/packages/swagger-ui/package.json @@ -41,7 +41,7 @@ "hono": "*" }, "devDependencies": { - "@types/swagger-ui-dist": "^3.30.3", + "@types/swagger-ui-dist": "^3.30.5", "hono": "^3.11.7", "publint": "^0.2.2", "tsup": "^7.2.0", diff --git a/packages/swagger-ui/src/swagger/renderer.ts b/packages/swagger-ui/src/swagger/renderer.ts index 1714c4614..ce0fe7a9b 100644 --- a/packages/swagger-ui/src/swagger/renderer.ts +++ b/packages/swagger-ui/src/swagger/renderer.ts @@ -14,17 +14,60 @@ export type DistSwaggerUIOptions = { layout?: SwaggerConfigs['layout'] docExpansion?: SwaggerConfigs['docExpansion'] maxDisplayedTags?: SwaggerConfigs['maxDisplayedTags'] + /** + * accepts function as a string. + * + * @example (a, b) => a.path.localeCompare(b.path) + */ operationsSorter?: string + /** + * accepts function as a string. + * + * @example (req) => req + */ requestInterceptor?: string + /** + * accepts function as a string. + * + * @example (res) => res + */ responseInterceptor?: string persistAuthorization?: boolean defaultModelsExpandDepth?: number defaultModelExpandDepth?: number - defaultModelRendering?: "example" | "model" | undefined + defaultModelRendering?: 'example' | 'model' | undefined displayRequestDuration?: boolean filter?: boolean | string showExtensions?: boolean showCommonExtensions?: boolean + queryConfigEnabled?: boolean + displayOperationId?: boolean + /** + * accepts function as a string. + * swagger-ui accepts alpha in the tagsSorter, but this middleware does not support it. + * + * @example (a, b) => a.name.localeCompare(b.name) + */ + tagsSorter?: string + /** + * accepts function as a string. + * @example () => { console.log('Swagger UI Loaded'); } + */ + onComplete?: string + syntaxHighlight?: boolean | { activated: boolean; theme: string[] } + tryItOutEnabled?: boolean + requestSnippetsEnabled?: boolean + requestSnippets?: object + oauth2RedirectUrl?: string + showMutabledRequest?: boolean + request?: { + curlOptions?: string[] + } + supportedSubmitMethods?: string[] + validatorUrl?: string + withCredentials?: boolean + modelPropertyMacro?: string + parameterMacro?: string } & RequireOne<{ url?: SwaggerConfigs['url'] urls?: SwaggerConfigs['urls'] @@ -59,6 +102,22 @@ const RENDER_TYPE_MAP = { filter: RENDER_TYPE.RAW, showExtensions: RENDER_TYPE.RAW, showCommonExtensions: RENDER_TYPE.RAW, + queryConfigEnabled: RENDER_TYPE.RAW, + displayOperationId: RENDER_TYPE.RAW, + tagsSorter: RENDER_TYPE.RAW, + onComplete: RENDER_TYPE.RAW, + syntaxHighlight: RENDER_TYPE.JSON_STRING, + tryItOutEnabled: RENDER_TYPE.RAW, + requestSnippetsEnabled: RENDER_TYPE.RAW, + requestSnippets: RENDER_TYPE.JSON_STRING, + oauth2RedirectUrl: RENDER_TYPE.STRING, + showMutabledRequest: RENDER_TYPE.RAW, + request: RENDER_TYPE.JSON_STRING, + supportedSubmitMethods: RENDER_TYPE.JSON_STRING, + validatorUrl: RENDER_TYPE.STRING, + withCredentials: RENDER_TYPE.RAW, + modelPropertyMacro: RENDER_TYPE.RAW, + parameterMacro: RENDER_TYPE.RAW, } as const satisfies Record< keyof DistSwaggerUIOptions, (typeof RENDER_TYPE)[keyof typeof RENDER_TYPE] diff --git a/packages/swagger-ui/test/option-renderer.test.ts b/packages/swagger-ui/test/option-renderer.test.ts index d7bd2990a..59702e573 100644 --- a/packages/swagger-ui/test/option-renderer.test.ts +++ b/packages/swagger-ui/test/option-renderer.test.ts @@ -1,174 +1,195 @@ /*eslint quotes: ["off", "single"]*/ +import type { DistSwaggerUIOptions } from '../src/swagger/renderer' import { renderSwaggerUIOptions } from '../src/swagger/renderer' -describe('SwaggerUIOption Rendering', () => { - it('renders correctly with configUrl', () => - expect( - renderSwaggerUIOptions({ - configUrl: 'https://petstore3.swagger.io/api/v3/openapi.json', - }) - ).toEqual("configUrl: 'https://petstore3.swagger.io/api/v3/openapi.json'")) - - it('renders correctly with presets', () => - expect( - renderSwaggerUIOptions({ - presets: ['SwaggerUIBundle.presets.apis', 'SwaggerUIStandalonePreset'], - }) - ).toEqual('presets: [SwaggerUIBundle.presets.apis,SwaggerUIStandalonePreset]')) - - it('renders correctly with plugins', () => - expect( - renderSwaggerUIOptions({ - plugins: ['SwaggerUIBundle.plugins.DownloadUrl'], - }) - ).toEqual('plugins: [SwaggerUIBundle.plugins.DownloadUrl]')) +type TestCase = [description: string, config: DistSwaggerUIOptions, expected: string] - it('renders correctly with deepLinking', () => - expect( - renderSwaggerUIOptions({ - deepLinking: true, - }) - ).toEqual('deepLinking: true')) - - it('renders correctly with spec', () => - expect( - renderSwaggerUIOptions({ +describe('SwaggerUIOption Rendering', () => { + const baseUrl = 'https://petstore3.swagger.io/api/v3/openapi.json' + const commonTests: TestCase[] = [ + [ + 'configUrl', + { configUrl: baseUrl, url: baseUrl }, + `configUrl: '${baseUrl}',url: '${baseUrl}'`, + ], + [ + 'presets', + { presets: ['SwaggerUIBundle.presets.apis', 'SwaggerUIStandalonePreset'], url: baseUrl }, + `presets: [SwaggerUIBundle.presets.apis,SwaggerUIStandalonePreset],url: '${baseUrl}'`, + ], + [ + 'plugins', + { plugins: ['SwaggerUIBundle.plugins.DownloadUrl'], url: baseUrl }, + `plugins: [SwaggerUIBundle.plugins.DownloadUrl],url: '${baseUrl}'`, + ], + ['deepLinking', { deepLinking: true, url: baseUrl }, `deepLinking: true,url: '${baseUrl}'`], + [ + 'spec', + { spec: { openapi: '3.0.0', - info: { - title: 'Swagger Petstore', - version: '1.0.0', - }, - servers: [ - { - url: 'https://petstore3.swagger.io/api/v3', - }, - ], + info: { title: 'Swagger Petstore', version: '1.0.0' }, + servers: [{ url: 'https://petstore3.swagger.io/api/v3' }], }, - }) - ).toEqual( - 'spec: {"openapi":"3.0.0","info":{"title":"Swagger Petstore","version":"1.0.0"},"servers":[{"url":"https://petstore3.swagger.io/api/v3"}]}' - )) - - it('renders correctly with url', () => { - expect( - renderSwaggerUIOptions({ - url: 'https://petstore3.swagger.io/api/v3/openapi.json', - }) - ).toEqual("url: 'https://petstore3.swagger.io/api/v3/openapi.json'") + url: baseUrl, + }, + `spec: {"openapi":"3.0.0","info":{"title":"Swagger Petstore","version":"1.0.0"},"servers":[{"url":"https://petstore3.swagger.io/api/v3"}]},url: '${baseUrl}'`, + ], + [ + 'layout', + { layout: 'StandaloneLayout', url: baseUrl }, + `layout: 'StandaloneLayout',url: '${baseUrl}'`, + ], + [ + 'docExpansion', + { docExpansion: 'list', url: baseUrl }, + `docExpansion: 'list',url: '${baseUrl}'`, + ], + [ + 'maxDisplayedTags', + { maxDisplayedTags: 5, url: baseUrl }, + `maxDisplayedTags: 5,url: '${baseUrl}'`, + ], + [ + 'operationsSorter', + { operationsSorter: '(a, b) => a.path.localeCompare(b.path)', url: baseUrl }, + `operationsSorter: (a, b) => a.path.localeCompare(b.path),url: '${baseUrl}'`, + ], + [ + 'requestInterceptor', + { requestInterceptor: '(req) => req', url: baseUrl }, + `requestInterceptor: (req) => req,url: '${baseUrl}'`, + ], + [ + 'responseInterceptor', + { responseInterceptor: '(res) => res', url: baseUrl }, + `responseInterceptor: (res) => res,url: '${baseUrl}'`, + ], + [ + 'persistAuthorization', + { persistAuthorization: true, url: baseUrl }, + `persistAuthorization: true,url: '${baseUrl}'`, + ], + [ + 'defaultModelsExpandDepth', + { defaultModelsExpandDepth: 1, url: baseUrl }, + `defaultModelsExpandDepth: 1,url: '${baseUrl}'`, + ], + [ + 'defaultModelExpandDepth', + { defaultModelExpandDepth: 2, url: baseUrl }, + `defaultModelExpandDepth: 2,url: '${baseUrl}'`, + ], + [ + 'defaultModelRendering', + { defaultModelRendering: 'model', url: baseUrl }, + `defaultModelRendering: 'model',url: '${baseUrl}'`, + ], + [ + 'displayRequestDuration', + { displayRequestDuration: true, url: baseUrl }, + `displayRequestDuration: true,url: '${baseUrl}'`, + ], + ['filter', { filter: true, url: baseUrl }, `filter: true,url: '${baseUrl}'`], + [ + 'showExtensions', + { showExtensions: true, url: baseUrl }, + `showExtensions: true,url: '${baseUrl}'`, + ], + [ + 'showCommonExtensions', + { showCommonExtensions: true, url: baseUrl }, + `showCommonExtensions: true,url: '${baseUrl}'`, + ], + [ + 'queryConfigEnabled', + { queryConfigEnabled: true, url: baseUrl }, + `queryConfigEnabled: true,url: '${baseUrl}'`, + ], + [ + 'displayOperationId', + { displayOperationId: true, url: baseUrl }, + `displayOperationId: true,url: '${baseUrl}'`, + ], + [ + 'tagsSorter', + { tagsSorter: '(a, b) => a.name.localeCompare(b.name)', url: baseUrl }, + `tagsSorter: (a, b) => a.name.localeCompare(b.name),url: '${baseUrl}'`, + ], + [ + 'onComplete', + { onComplete: '() => console.log("Completed")', url: baseUrl }, + `onComplete: () => console.log("Completed"),url: '${baseUrl}'`, + ], + [ + 'syntaxHighlight as false', + { syntaxHighlight: false, url: baseUrl }, + `syntaxHighlight: false,url: '${baseUrl}'`, + ], + [ + 'syntaxHighlight as object', + { syntaxHighlight: { activated: true, theme: ['agate', 'arta'] }, url: baseUrl }, + `syntaxHighlight: {"activated":true,"theme":["agate","arta"]},url: '${baseUrl}'`, + ], + [ + 'tryItOutEnabled', + { tryItOutEnabled: true, url: baseUrl }, + `tryItOutEnabled: true,url: '${baseUrl}'`, + ], + [ + 'requestSnippetsEnabled', + { requestSnippetsEnabled: true, url: baseUrl }, + `requestSnippetsEnabled: true,url: '${baseUrl}'`, + ], + [ + 'requestSnippets', + { requestSnippets: { generators: { curl_bash: { title: 'cURL (bash)' } } }, url: baseUrl }, + `requestSnippets: {"generators":{"curl_bash":{"title":"cURL (bash)"}}},url: '${baseUrl}'`, + ], + [ + 'oauth2RedirectUrl', + { oauth2RedirectUrl: 'https://example.com/oauth2-redirect.html', url: baseUrl }, + `oauth2RedirectUrl: 'https://example.com/oauth2-redirect.html',url: '${baseUrl}'`, + ], + [ + 'showMutableRequest', + { showMutabledRequest: true, url: baseUrl }, + `showMutabledRequest: true,url: '${baseUrl}'`, + ], + [ + 'request', + { request: { curlOptions: ['--insecure'] }, url: baseUrl }, + `request: {"curlOptions":["--insecure"]},url: '${baseUrl}'`, + ], + [ + 'supportedSubmitMethods', + { supportedSubmitMethods: ['get', 'post', 'put'], url: baseUrl }, + `supportedSubmitMethods: ["get","post","put"],url: '${baseUrl}'`, + ], + [ + 'validatorUrl', + { validatorUrl: 'https://validator.swagger.io', url: baseUrl }, + `validatorUrl: 'https://validator.swagger.io',url: '${baseUrl}'`, + ], + [ + 'withCredentials', + { withCredentials: true, url: baseUrl }, + `withCredentials: true,url: '${baseUrl}'`, + ], + [ + 'modelPropertyMacro', + { modelPropertyMacro: '(property) => property', url: baseUrl }, + `modelPropertyMacro: (property) => property,url: '${baseUrl}'`, + ], + [ + 'parameterMacro', + { parameterMacro: '(parameter) => parameter', url: baseUrl }, + `parameterMacro: (parameter) => parameter,url: '${baseUrl}'`, + ], + ] + + it.each(commonTests)('renders correctly with %s', (_, input, expected) => { + expect(renderSwaggerUIOptions(input)).toEqual(expected) }) - - it('renders correctly with urls', () => { - expect( - renderSwaggerUIOptions({ - urls: [ - { - name: 'Petstore', - url: 'https://petstore3.swagger.io/api/v3/openapi.json', - }, - ], - }) - ).toEqual( - 'urls: [{"name":"Petstore","url":"https://petstore3.swagger.io/api/v3/openapi.json"}]' - ) - }) - - it('renders correctly with layout', () => - expect( - renderSwaggerUIOptions({ - layout: 'StandaloneLayout', - }) - ).toEqual("layout: 'StandaloneLayout'")) - - it('renders correctly with docExpansion', () => - expect( - renderSwaggerUIOptions({ - docExpansion: 'list', - }) - ).toEqual("docExpansion: 'list'")) - - it('renders correctly with maxDisplayedTags', () => - expect( - renderSwaggerUIOptions({ - maxDisplayedTags: 5, - }) - ).toEqual('maxDisplayedTags: 5')) - - it('renders correctly with operationsSorter', () => - expect( - renderSwaggerUIOptions({ - operationsSorter: '(a, b) => a.path.localeCompare(b.path)', - }) - ).toEqual('operationsSorter: (a, b) => a.path.localeCompare(b.path)')) - - it('renders correctly with requestInterceptor', () => - expect( - renderSwaggerUIOptions({ - requestInterceptor: '(req) => req', - }) - ).toEqual('requestInterceptor: (req) => req')) - - it('renders correctly with responseInterceptor', () => - expect( - renderSwaggerUIOptions({ - responseInterceptor: '(res) => res', - }) - ).toEqual('responseInterceptor: (res) => res')) - - it('renders correctly with persistAuthorization', () => - expect( - renderSwaggerUIOptions({ - persistAuthorization: true, - }) - ).toEqual('persistAuthorization: true')) - - it('renders correctly with defaultModelsExpandDepth', () => - expect( - renderSwaggerUIOptions({ - defaultModelsExpandDepth: 1, - }) - ).toEqual('defaultModelsExpandDepth: 1')) - - it('renders correctly with defaultModelExpandDepth', () => - expect( - renderSwaggerUIOptions({ - defaultModelExpandDepth: 2, - }) - ).toEqual('defaultModelExpandDepth: 2')) - - it('renders correctly with defaultModelRendering', () => - expect( - renderSwaggerUIOptions({ - defaultModelRendering: 'model', - }) - ).toEqual("defaultModelRendering: 'model'")) - - it('renders correctly with displayRequestDuration', () => - expect( - renderSwaggerUIOptions({ - displayRequestDuration: true, - }) - ).toEqual('displayRequestDuration: true')) - - it('renders correctly with filter', () => - expect( - renderSwaggerUIOptions({ - filter: true, - }) - ).toEqual('filter: true')) - - it('renders correctly with showExtensions', () => - expect( - renderSwaggerUIOptions({ - showExtensions: true, - }) - ).toEqual('showExtensions: true')) - - it('renders correctly with showCommonExtensions', () => - expect( - renderSwaggerUIOptions({ - showCommonExtensions: true, - }) - ).toEqual('showCommonExtensions: true')) }) diff --git a/yarn.lock b/yarn.lock index 723be5600..c4fa943f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2278,7 +2278,7 @@ __metadata: version: 0.0.0-use.local resolution: "@hono/swagger-ui@workspace:packages/swagger-ui" dependencies: - "@types/swagger-ui-dist": "npm:^3.30.3" + "@types/swagger-ui-dist": "npm:^3.30.5" hono: "npm:^3.11.7" publint: "npm:^0.2.2" tsup: "npm:^7.2.0" @@ -4352,10 +4352,10 @@ __metadata: languageName: node linkType: hard -"@types/swagger-ui-dist@npm:^3.30.3": - version: 3.30.4 - resolution: "@types/swagger-ui-dist@npm:3.30.4" - checksum: 106114fef47a29811a37810e00e52d3fb6f99bafa7ec00b49926827a9c502b27550e2de589af000e472d327c3fc32417f75befa37b2450e8fad81e0a79ad641f +"@types/swagger-ui-dist@npm:^3.30.5": + version: 3.30.5 + resolution: "@types/swagger-ui-dist@npm:3.30.5" + checksum: 0e6ea1b6add4fb9cfd82dcdbd8156b3d0921a6bd2d511b92c05fc6d76e94a78d89eab81ffd6bb34c0c52e97b5e4a61eea77fab502698e9cf170aabf5d2c09510 languageName: node linkType: hard