diff --git a/README.md b/README.md index bc85d90..8ac8aeb 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ await fastify.ready() | transformStaticCSP | undefined | Synchronous function to transform CSP header for static resources if the header has been previously set. | | transformSpecification | undefined | Synchronous function to transform the swagger document. | | transformSpecificationClone| true | Provide a deepcloned swaggerObject to transformSpecification | - | uiConfig | {} | Configuration options for [Swagger UI](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md). Must be literal values, see [#5710](https://github.com/swagger-api/swagger-ui/issues/5710). | + | uiConfig | {} | Configuration options for [Swagger UI](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md). | | uiHooks | {} | Additional hooks for the documentation's routes. You can provide the `onRequest` and `preHandler` hooks with the same [route's options](https://www.fastify.io/docs/latest/Routes/#options) interface.| | logLevel | info | Allow to define route log level. | @@ -121,6 +121,32 @@ The plugin will expose the documentation with the following APIs: | `'/documentation/'` | The swagger UI | | `'/documentation/*'` | External files that you may use in `$ref` | +#### uiConfig + +To configure Swagger UI, you need to modify the `uiConfig` option. +It's important to ensure that functions are self-contained. Keep in mind that +you cannot modify the backend code within the `uiConfig` functions, as these +functions are processed only by the browser. You can reference the Swagger UI +element using `ui`, which is assigned to `window.ui`. + +##### Example +```js +const fastify = require('fastify')() + +await fastify.register(require('@fastify/swagger')) + +await fastify.register(require('@fastify/swagger-ui'), { + uiConfig: { + onComplete: function () { + alert('ui has type of ' + typeof ui) // 'ui has type of object' + alert('fastify has type of ' + typeof fastify) // 'fastify has type of undefined' + alert('window has type of ' + typeof window) // 'window has type of object' + alert('global has type of ' + typeof global) // 'global has type of undefined' + } + } +}) +``` + #### transformSpecification There can be use cases, where you want to modify the swagger definition on request. E.g. you want to modify the server diff --git a/lib/routes.js b/lib/routes.js index 62f8fd2..668b76b 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -4,6 +4,7 @@ const path = require('path') const yaml = require('yaml') const fastifyStatic = require('@fastify/static') const rfdc = require('rfdc')() +const swaggerInitializer = require('./swagger-initializer') // URI prefix to separate static assets for swagger UI const staticPrefix = '/static' @@ -74,23 +75,17 @@ function fastifySwagger (fastify, opts, done) { } }) - fastify.route({ - url: '/uiConfig', - method: 'GET', - schema: { hide: true }, - ...hooks, - handler: (req, reply) => { - reply.send(opts.uiConfig) - } - }) + const swaggerInitializerContent = swaggerInitializer(opts) fastify.route({ - url: '/initOAuth', + url: `${staticPrefix}/swagger-initializer.js`, method: 'GET', schema: { hide: true }, ...hooks, handler: (req, reply) => { - reply.send(opts.initOAuth) + reply + .header('content-type', 'application/javascript; charset=utf-8') + .send(swaggerInitializerContent) } }) diff --git a/lib/serialize.js b/lib/serialize.js new file mode 100644 index 0000000..84ee2d2 --- /dev/null +++ b/lib/serialize.js @@ -0,0 +1,69 @@ +'use strict' + +function serialize (value) { + switch (typeof value) { + case 'bigint': + return value.toString() + 'n' + case 'boolean': + return value ? 'true' : 'false' + case 'function': + return value.toString() + case 'number': + return '' + value + case 'object': + if (value === null) { + return 'null' + } else if (Array.isArray(value)) { + return serializeArray(value) + } else if (value instanceof RegExp) { + return `/${value.source}/${value.flags}` + } else if (value instanceof Date) { + return `new Date(${value.getTime()})` + } else if (value instanceof Set) { + return `new Set(${serializeArray(Array.from(value))})` + } else if (value instanceof Map) { + return `new Map(${serializeArray(Array.from(value))})` + } else { + return serializeObject(value) + } + case 'string': + return JSON.stringify(value) + case 'symbol': + return serializeSymbol(value) + case 'undefined': + return 'undefined' + } +} +const symbolRE = /Symbol\((.+)\)/ +function serializeSymbol (value) { + return symbolRE.test(value.toString()) + ? `Symbol("${value.toString().match(symbolRE)[1]}")` + : 'Symbol()' +} + +function serializeArray (value) { + let result = '[' + const il = value.length + const last = il - 1 + for (let i = 0; i < il; ++i) { + result += serialize(value[i]) + i !== last && (result += ',') + } + return result + ']' +} + +function serializeObject (value) { + let result = '{' + const keys = Object.keys(value) + let i = 0 + const il = keys.length + const last = il - 1 + for (; i < il; ++i) { + const key = keys[i] + result += `"${key}":${serialize(value[key])}` + i !== last && (result += ',') + } + return result + '}' +} + +module.exports = serialize diff --git a/lib/swagger-initializer.js b/lib/swagger-initializer.js new file mode 100644 index 0000000..81d89a8 --- /dev/null +++ b/lib/swagger-initializer.js @@ -0,0 +1,36 @@ +'use strict' + +const serialize = require('./serialize') + +function swaggerInitializer (opts) { + return `window.onload = function () { + function resolveUrl(url) { + const anchor = document.createElement('a') + anchor.href = url + return anchor.href + } + + const config = ${serialize(opts.uiConfig)} + const resConfig = Object.assign({}, { + dom_id: '#swagger-ui', + deepLinking: true, + presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIStandalonePreset + ], + plugins: [ + SwaggerUIBundle.plugins.DownloadUrl + ], + layout: "StandaloneLayout" + }, config, { + url: resolveUrl('./json').replace('static/json', 'json'), + oauth2RedirectUrl: resolveUrl('./oauth2-redirect.html') + }); + + const ui = SwaggerUIBundle(resConfig) + window.ui = ui + ui.initOAuth(${serialize(opts.initOAuth)}) + }` +} + +module.exports = swaggerInitializer diff --git a/scripts/prepare-swagger-ui.js b/scripts/prepare-swagger-ui.js index 714ae8f..e22bb4e 100644 --- a/scripts/prepare-swagger-ui.js +++ b/scripts/prepare-swagger-ui.js @@ -15,7 +15,6 @@ const filesToCopy = [ 'index.html', 'index.css', 'oauth2-redirect.html', - 'swagger-initializer.js', 'swagger-ui-bundle.js', 'swagger-ui-bundle.js.map', 'swagger-ui-standalone-preset.js', @@ -29,55 +28,6 @@ filesToCopy.forEach(filename => { fse.copySync(`${swaggerUiAssetPath}/${filename}`, resolve(`./static/${filename}`)) }) -fse.writeFileSync(resolve(`./${folderName}/swagger-initializer.js`), `window.onload = function () { - function resolveUrl (url) { - const anchor = document.createElement('a') - anchor.href = url - return anchor.href - } - - function resolveConfig (cb) { - return fetch( - resolveUrl('./uiConfig').replace('${folderName}/uiConfig', 'uiConfig') - ) - .then(res => res.json()) - .then((config) => { - const resConfig = Object.assign({}, { - dom_id: '#swagger-ui', - deepLinking: true, - presets: [ - SwaggerUIBundle.presets.apis, - SwaggerUIStandalonePreset - ], - plugins: [ - SwaggerUIBundle.plugins.DownloadUrl - ], - layout: "StandaloneLayout" - }, config, { - url: resolveUrl('./json').replace('${folderName}/json', 'json'), - oauth2RedirectUrl: resolveUrl('./oauth2-redirect.html') - }); - return cb(resConfig); - }) - } - - // Begin Swagger UI call region - const buildUi = function (config) { - const ui = SwaggerUIBundle(config) - window.ui = ui - - fetch(resolveUrl('./initOAuth').replace('${folderName}/initOAuth', 'initOAuth')) - .then(res => res.json()) - .then((config) => { - ui.initOAuth(config); - }); - - } - // End Swagger UI call region - - resolveConfig(buildUi); -}`) - const sha = { script: [], style: [] diff --git a/test/route.test.js b/test/route.test.js index 794cf3c..d21578a 100644 --- a/test/route.test.js +++ b/test/route.test.js @@ -74,62 +74,6 @@ test('/documentation/json route', async (t) => { t.pass('valid swagger object') }) -test('/documentation/uiConfig route', async (t) => { - t.plan(1) - const fastify = Fastify() - - const uiConfig = { - docExpansion: 'full' - } - - await fastify.register(fastifySwagger, swaggerOption) - await fastify.register(fastifySwaggerUi, { uiConfig }) - - fastify.get('/', () => {}) - fastify.post('/', () => {}) - fastify.get('/example', schemaQuerystring, () => {}) - fastify.post('/example', schemaBody, () => {}) - fastify.get('/parameters/:id', schemaParams, () => {}) - fastify.get('/example1', schemaSecurity, () => {}) - - const res = await fastify.inject({ - method: 'GET', - url: '/documentation/uiConfig' - }) - - const payload = JSON.parse(res.payload) - - t.match(payload, uiConfig, 'uiConfig should be valid') -}) - -test('/documentation/initOAuth route', async (t) => { - t.plan(1) - const fastify = Fastify() - - const initOAuth = { - scopes: ['openid', 'profile', 'email', 'offline_access'] - } - - await fastify.register(fastifySwagger, swaggerOption) - await fastify.register(fastifySwaggerUi, { initOAuth }) - - fastify.get('/', () => {}) - fastify.post('/', () => {}) - fastify.get('/example', schemaQuerystring, () => {}) - fastify.post('/example', schemaBody, () => {}) - fastify.get('/parameters/:id', schemaParams, () => {}) - fastify.get('/example1', schemaSecurity, () => {}) - - const res = await fastify.inject({ - method: 'GET', - url: '/documentation/initOAuth' - }) - - const payload = JSON.parse(res.payload) - - t.match(payload, initOAuth, 'initOAuth should be valid') -}) - test('fastify.swagger should return a valid swagger yaml', async (t) => { t.plan(3) const fastify = Fastify() @@ -313,7 +257,7 @@ test('with routePrefix: \'/\' should redirect to ./static/index.html', async (t) }) test('/documentation/static/:file should send back the correct file', async (t) => { - t.plan(22) + t.plan(21) const fastify = Fastify() await fastify.register(fastifySwagger, swaggerOption) @@ -360,14 +304,7 @@ test('/documentation/static/:file should send back the correct file', async (t) url: '/documentation/static/swagger-initializer.js' }) t.equal(typeof res.payload, 'string') - t.equal(res.headers['content-type'], 'application/javascript; charset=UTF-8') - t.equal( - readFileSync( - resolve(__dirname, '..', 'static', 'swagger-initializer.js'), - 'utf8' - ), - res.payload - ) + t.equal(res.headers['content-type'], 'application/javascript; charset=utf-8') t.ok(res.payload.indexOf('resolveUrl') !== -1) } diff --git a/test/serialize.test.js b/test/serialize.test.js new file mode 100644 index 0000000..65c431e --- /dev/null +++ b/test/serialize.test.js @@ -0,0 +1,128 @@ +'use strict' + +const { test } = require('tap') +const serialize = require('../lib/serialize') + +test('serialize', async (t) => { + t.plan(8) + + t.test('boolean', t => { + t.plan(2) + + t.equal(serialize(true), 'true') + t.equal(serialize(false), 'false') + }) + + t.test('number', t => { + t.plan(7) + + t.equal(serialize(0), '0') + t.equal(serialize(1), '1') + t.equal(serialize(1.0), '1') + t.equal(serialize(1.01), '1.01') + t.equal(serialize(Infinity), 'Infinity') + t.equal(serialize(-Infinity), '-Infinity') + t.equal(serialize(NaN), 'NaN') + }) + + t.test('string', t => { + t.plan(3) + + t.equal(serialize('0'), '"0"') + t.equal(serialize('abc'), '"abc"') + t.equal(serialize('"a'), '"\\\"a"') // eslint-disable-line no-useless-escape + }) + + t.test('bigint', t => { + t.plan(3) + + t.equal(serialize(0n), '0n') + t.equal(serialize(1000000000n), '1000000000n') + t.equal(serialize(-9999n), '-9999n') + }) + + t.test('function', t => { + t.plan(7) + + t.equal(serialize(function a () {}), 'function a () {}') + t.equal(serialize(async function a () {}), 'async function a () {}') + t.equal(serialize(() => {}), '() => {}') + t.equal(serialize(async () => {}), 'async () => {}') + t.equal(serialize(() => Date.now), '() => Date.now') + + t.equal(serialize(function () {}), 'function () {}') + t.equal(serialize(async function () {}), 'async function () {}') + }) + + t.test('undefined', t => { + t.plan(1) + + t.equal(serialize(undefined), 'undefined') + }) + + t.test('symbol', t => { + t.plan(2) + + t.equal(serialize(Symbol('a')), 'Symbol("a")') + t.equal(serialize(Symbol()), 'Symbol()') // eslint-disable-line symbol-description + }) + + t.test('object', t => { + t.plan(7) + + t.test('null', t => { + t.plan(1) + + t.equal(serialize(null), 'null') + }) + + t.test('RegExp', t => { + t.plan(1) + + t.equal(serialize(/0-9/gi), '/0-9/gi') + }) + + t.test('Date', t => { + t.plan(1) + + t.equal(serialize(new Date(0)), 'new Date(0)') + }) + + t.test('Array', t => { + t.plan(5) + + t.equal(serialize([]), '[]') + t.equal(serialize(['a']), '["a"]') + t.equal(serialize([1, 1n, 'a', true]), '[1,1n,"a",true]') + t.equal(serialize([{}]), '[{}]') + t.equal(serialize([{ a: [{}] }]), '[{"a":[{}]}]') + }) + + t.test('POJO', t => { + t.plan(3) + + t.equal(serialize({}), '{}') + t.equal(serialize({ key: 'value' }), '{"key":"value"}') + t.equal(serialize({ null: null, undefined }), '{"null":null,"undefined":undefined}') + }) + + t.test('Set', t => { + t.plan(3) + + t.equal(serialize(new Set()), 'new Set([])') + t.equal(serialize(new Set(['a'])), 'new Set(["a"])') + t.equal(serialize(new Set(['a', {}])), 'new Set(["a",{}])') + }) + + t.test('Map', t => { + t.plan(3) + + t.equal(serialize(new Map()), 'new Map([])') + t.equal(serialize(new Map([['a', 1]])), 'new Map([["a",1]])') + const map = new Map() + map.set('b', 1) + + t.equal(serialize(map), 'new Map([["b",1]])') + }) + }) +}) diff --git a/test/static.test.js b/test/static.test.js index d5afdf2..56c9cdc 100644 --- a/test/static.test.js +++ b/test/static.test.js @@ -400,104 +400,3 @@ test('/documentation/json returns cache.swaggerObject on second request in dynam t.pass('valid swagger json') } }) - -test('/documentation/uiConfig should have default', async (t) => { - const config = { - mode: 'static', - specification: { - path: './examples/example-static-specification.yaml', - baseDir: resolve(__dirname, '..', '..', 'static') - } - } - - t.plan(2) - const fastify = new Fastify() - await fastify.register(fastifySwagger, config) - await fastify.register(fastifySwaggerUi) - - const res = await fastify.inject({ - method: 'GET', - url: '/documentation/uiConfig' - }) - - t.equal(res.statusCode, 200) - t.equal(res.payload, '{}') -}) - -test('/documentation/uiConfig can be customize', async (t) => { - t.plan(2) - const config = { - mode: 'static', - specification: { - path: './examples/example-static-specification.yaml', - baseDir: resolve(__dirname, '..', '..', 'static') - } - } - - const fastify = new Fastify() - await fastify.register(fastifySwagger, config) - - await fastify.register(fastifySwaggerUi, { - uiConfig: { - docExpansion: 'full' - } - }) - - const res = await fastify.inject({ - method: 'GET', - url: '/documentation/uiConfig' - }) - - t.equal(res.statusCode, 200) - t.equal(res.payload, '{"docExpansion":"full"}') -}) - -test('/documentation/initOAuth should have default', async (t) => { - const config = { - mode: 'static', - specification: { - path: './examples/example-static-specification.yaml', - baseDir: resolve(__dirname, '..', '..', 'static') - } - } - - t.plan(2) - const fastify = new Fastify() - await fastify.register(fastifySwagger, config) - await fastify.register(fastifySwaggerUi) - - const res = await fastify.inject({ - method: 'GET', - url: '/documentation/initOAuth' - }) - - t.equal(res.statusCode, 200) - t.equal(res.payload, '{}') -}) - -test('/documentation/initOAuth can be customize', async (t) => { - const config = { - mode: 'static', - specification: { - path: './examples/example-static-specification.yaml', - baseDir: resolve(__dirname, '..', '..', 'static') - } - } - - t.plan(2) - const fastify = new Fastify() - await fastify.register(fastifySwagger, config) - await fastify.register(fastifySwaggerUi, { - initOAuth: { - scopes: ['openid', 'profile', 'email', 'offline_access'] - } - }) - - const res = await fastify.inject({ - method: 'GET', - url: '/documentation/initOAuth' - }) - - t.equal(res.statusCode, 200) - t.equal(res.payload, '{"scopes":["openid","profile","email","offline_access"]}') -}) diff --git a/test/swagger-initializer.test.js b/test/swagger-initializer.test.js new file mode 100644 index 0000000..6918195 --- /dev/null +++ b/test/swagger-initializer.test.js @@ -0,0 +1,78 @@ +'use strict' + +const { test } = require('tap') +const Fastify = require('fastify') +const fastifySwagger = require('@fastify/swagger') +const fastifySwaggerUi = require('../index') + +test('/documentation/static/swagger-initializer.js should have default uiConfig', async (t) => { + t.plan(2) + + const fastify = new Fastify() + await fastify.register(fastifySwagger) + await fastify.register(fastifySwaggerUi) + + const res = await fastify.inject({ + method: 'GET', + url: '/documentation/static/swagger-initializer.js' + }) + + t.equal(res.statusCode, 200) + t.ok(res.payload.includes('const config = {}')) +}) + +test('/documentation/static/swagger-initializer.js should have configurable uiConfig', async (t) => { + t.plan(2) + + const fastify = new Fastify() + await fastify.register(fastifySwagger) + + await fastify.register(fastifySwaggerUi, { + // eslint-disable-next-line no-undef + uiConfig: { onComplete: () => { alert('test') } } + }) + + const res = await fastify.inject({ + method: 'GET', + url: '/documentation/static/swagger-initializer.js' + }) + + t.equal(res.statusCode, 200) + t.ok(res.payload.includes("const config = {\"onComplete\":() => { alert('test') }}")) +}) + +test('/documentation/static/swagger-initializer.js should have default initOAuth', async (t) => { + t.plan(2) + + const fastify = new Fastify() + await fastify.register(fastifySwagger) + await fastify.register(fastifySwaggerUi) + + const res = await fastify.inject({ + method: 'GET', + url: '/documentation/static/swagger-initializer.js' + }) + + t.equal(res.statusCode, 200) + t.ok(res.payload.includes('ui.initOAuth({})')) +}) + +test('/documentation/static/swagger-initializer.js should have configurable initOAuth', async (t) => { + t.plan(2) + + const fastify = new Fastify() + await fastify.register(fastifySwagger) + await fastify.register(fastifySwaggerUi, { + initOAuth: { + clientId: 'someId' + } + }) + + const res = await fastify.inject({ + method: 'GET', + url: '/documentation/static/swagger-initializer.js' + }) + + t.equal(res.statusCode, 200) + t.ok(res.payload.includes('ui.initOAuth({"clientId":"someId"})')) +}) diff --git a/types/index.d.ts b/types/index.d.ts index 7e7ab7b..5843b24 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -55,41 +55,402 @@ declare namespace fastifySwaggerUi { transformSpecificationClone?: boolean } - export type FastifySwaggerUiConfigOptions = Partial<{ - deepLinking: boolean - displayOperationId: boolean - defaultModelsExpandDepth: number - defaultModelExpandDepth: number - defaultModelRendering: string - displayRequestDuration: boolean - docExpansion: string - filter: boolean | string - layout: string - maxDisplayedTags: number - showExtensions: boolean - showCommonExtensions: boolean - useUnsafeMarkdown: boolean - syntaxHighlight: { - activate?: boolean - theme?: string - } | false - tryItOutEnabled: boolean - validatorUrl: string | null - supportedSubmitMethods: Array<'get' | 'post' | 'put' | 'delete' | 'patch' | 'options'> - persistAuthorization: boolean - }> + type SupportedHTTPMethods = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace"; - export type FastifySwaggerInitOAuthOptions = Partial<{ - clientId: string, - clientSecret: string, - realm: string, - appName: string, - scopeSeparator: string, - scopes: string | string[], - additionalQueryStringParams: { [key: string]: any }, - useBasicAuthenticationWithAccessCodeGrant: boolean, - usePkceWithAuthorizationCodeGrant: boolean - }> + interface PluginsOptions { + /** + * Control behavior of plugins when targeting the same component with wrapComponent.
+ * - `legacy` (default) : last plugin takes precedence over the others
+ * - `chain` : chain wrapComponents when targeting the same core component, + * allowing multiple plugins to wrap the same component + * @default 'legacy' + */ + pluginLoadType?: PluginLoadType; + } + + type PluginLoadType = 'legacy' | 'chain'; + + type SorterLike = + | "alpha" + | "method" + | { + (name1: string, name2: string): number; + }; + + interface Request { + [prop: string]: any; + } + + interface Response { + [prop: string]: any; + } + + /** + * See https://swagger.io/docs/open-source-tools/swagger-ui/customization/plugin-api/ + */ + interface SwaggerUIPlugin { + (system: any): { + statePlugins?: { + [stateKey: string]: { + actions?: Indexable | undefined; + reducers?: Indexable | undefined; + selectors?: Indexable | undefined; + wrapActions?: Indexable | undefined; + wrapSelectors?: Indexable | undefined; + }; + } | undefined; + components?: Indexable | undefined; + wrapComponents?: Indexable | undefined; + rootInjects?: Indexable | undefined; + afterLoad?: ((system: any) => any) | undefined; + fn?: Indexable | undefined; + }; + } + + interface Indexable { + [index: string]: any; + } + + export type FastifySwaggerUiConfigOptions = { + // Core + + /** + * URL to fetch external configuration document from. + */ + configUrl?: string | undefined; + + /** + * REQUIRED if domNode is not provided. The ID of a DOM element inside which SwaggerUI will put its user interface. + */ + dom_id?: string | undefined; + + /** + * REQUIRED if dom_id is not provided. The HTML DOM element inside which SwaggerUI will put its user interface. Overrides dom_id. + */ + domNode?: HTMLElement | null | undefined; + + /** + * A JavaScript object describing the OpenAPI definition. When used, the url parameter will not be parsed. This is useful for testing manually-generated definitions without hosting them + */ + spec?: { [propName: string]: any } | undefined; + + /** + * The URL pointing to API definition (normally swagger.json or swagger.yaml). Will be ignored if urls or spec is used. + */ + url?: string | undefined; + + /** + * An array of API definition objects ([{url: "", name: ""},{url: "", name: ""}]) + * used by Topbar plugin. When used and Topbar plugin is enabled, the url parameter will not be parsed. + * Names and URLs must be unique among all items in this array, since they're used as identifiers. + */ + urls?: Array<{ + url: string; + name: string; + }> | undefined; + + // Plugin system + + /** + * The name of a component available via the plugin system to use as the top-level layout + * for Swagger UI. + */ + layout?: string | undefined; + + /** + * A Javascript object to configure plugin integration and behaviors + */ + pluginsOptions?: PluginsOptions; + + /** + * An array of plugin functions to use in Swagger UI. + */ + plugins?: SwaggerUIPlugin[] | undefined; + + /** + * An array of presets to use in Swagger UI. + * Usually, you'll want to include ApisPreset if you use this option. + */ + presets?: SwaggerUIPlugin[] | undefined; + + // Display + + /** + * If set to true, enables deep linking for tags and operations. + * See the Deep Linking documentation for more information. + */ + deepLinking?: boolean | undefined; + + /** + * Controls the display of operationId in operations list. The default is false. + */ + displayOperationId?: boolean | undefined; + + /** + * The default expansion depth for models (set to -1 completely hide the models). + */ + defaultModelsExpandDepth?: number | undefined; + + /** + * The default expansion depth for the model on the model-example section. + */ + defaultModelExpandDepth?: number | undefined; + + /** + * Controls how the model is shown when the API is first rendered. + * (The user can always switch the rendering for a given model by clicking the + * 'Model' and 'Example Value' links.) + */ + defaultModelRendering?: "example" | "model" | undefined; + + /** + * Controls the display of the request duration (in milliseconds) for "Try it out" requests. + */ + displayRequestDuration?: boolean | undefined; + + /** + * Controls the default expansion setting for the operations and tags. + * It can be 'list' (expands only the tags), 'full' (expands the tags and operations) + * or 'none' (expands nothing). + */ + docExpansion?: "list" | "full" | "none" | undefined; + + /** + * If set, enables filtering. + * The top bar will show an edit box that you can use to filter the tagged operations that are shown. + * Can be Boolean to enable or disable, or a string, in which case filtering will be enabled + * using that string as the filter expression. + * Filtering is case sensitive matching the filter expression anywhere inside the tag. + */ + filter?: boolean | string | undefined; + + /** + * If set, limits the number of tagged operations displayed to at most this many. + * The default is to show all operations. + */ + maxDisplayedTags?: number | undefined; + + /** + * Apply a sort to the operation list of each API. + * It can be 'alpha' (sort by paths alphanumerically), + * 'method' (sort by HTTP method) or a function (see Array.prototype.sort() to know how sort function works). + * Default is the order returned by the server unchanged. + */ + operationsSorter?: SorterLike | undefined; + + /** + * Controls the display of vendor extension (x-) fields and values for Operations, + * Parameters, Responses, and Schema. + */ + showExtensions?: boolean | undefined; + + /** + * Controls the display of extensions (pattern, maxLength, minLength, maximum, minimum) fields + * and values for Parameters. + */ + showCommonExtensions?: boolean | undefined; + + /** + * Apply a sort to the tag list of each API. + * It can be 'alpha' (sort by paths alphanumerically) + * or a function (see Array.prototype.sort() to learn how to write a sort function). + * Two tag name strings are passed to the sorter for each pass. + * Default is the order determined by Swagger UI. + */ + tagsSorter?: SorterLike | undefined; + + /** + * When enabled, sanitizer will leave style, class and data-* attributes untouched + * on all HTML Elements declared inside markdown strings. + * This parameter is Deprecated and will be removed in 4.0.0. + * @deprecated + */ + useUnsafeMarkdown?: boolean | undefined; + + /** + * Provides a mechanism to be notified when Swagger UI has finished rendering a newly provided definition. + */ + onComplete?: (() => any) | undefined; + + /** + * Set to false to deactivate syntax highlighting of payloads and cURL command, + * can be otherwise an object with the activate and theme properties. + */ + syntaxHighlight?: + | false + | { + /** + * Whether syntax highlighting should be activated or not. + */ + activate?: boolean | undefined; + /** + * Highlight.js syntax coloring theme to use. (Only these 6 styles are available.) + */ + theme?: "agate" | "arta" | "monokai" | "nord" | "obsidian" | "tomorrow-night" | undefined; + } | undefined; + /** + * Controls whether the "Try it out" section should be enabled by default. + */ + tryItOutEnabled?: boolean | undefined; + + /** + * This is the default configuration section for the the requestSnippets plugin. + */ + requestSnippets?: { + generators?: { + [genName: string]: { + title: string; + syntax: string; + }; + } | undefined; + defaultExpanded?: boolean | undefined; + /** + * e.g. only show curl bash = ["curl_bash"] + */ + languagesMask?: string[] | undefined; + } | undefined; + + // Network + + /** + * OAuth redirect URL. + */ + oauth2RedirectUrl?: string | undefined; + + /** + * MUST be a function. Function to intercept remote definition, + * "Try it out", and OAuth 2.0 requests. + * Accepts one argument requestInterceptor(request) and must return the modified request, + * or a Promise that resolves to the modified request. + */ + requestInterceptor?: ((a: Request) => Request | Promise) | undefined; + + /** + * MUST be a function. Function to intercept remote definition, + * "Try it out", and OAuth 2.0 responses. + * Accepts one argument responseInterceptor(response) and must return the modified response, + * or a Promise that resolves to the modified response. + */ + responseInterceptor?: ((a: Response) => Response | Promise) | undefined; + + /** + * If set to true, uses the mutated request returned from a requestInterceptor + * to produce the curl command in the UI, otherwise the request + * beforethe requestInterceptor was applied is used. + */ + showMutatedRequest?: boolean | undefined; + + /** + * List of HTTP methods that have the "Try it out" feature enabled. + * An empty array disables "Try it out" for all operations. + * This does not filter the operations from the display. + */ + supportedSubmitMethods?: SupportedHTTPMethods[] | undefined; + + /** + * By default, Swagger UI attempts to validate specs against swagger.io's online validator. + * You can use this parameter to set a different validator URL, + * for example for locally deployed validators (Validator Badge). + * Setting it to either none, 127.0.0.1 or localhost will disable validation. + */ + validatorUrl?: string | undefined | null; + + /** + * If set to true, enables passing credentials, as defined in the Fetch standard, + * in CORS requests that are sent by the browser. + * Note that Swagger UI cannot currently set cookies cross-domain (see swagger-js#1163) + * - as a result, you will have to rely on browser-supplied + * cookies (which this setting enables sending) that Swagger UI cannot control. + */ + withCredentials?: boolean | undefined; + + // Macros + + /** + * Function to set default values to each property in model. + * Accepts one argument modelPropertyMacro(property), property is immutable + */ + modelPropertyMacro?: ((propName: Readonly) => any) | undefined; + + /** + * Function to set default value to parameters. + * Accepts two arguments parameterMacro(operation, parameter). + * Operation and parameter are objects passed for context, both remain immutable + */ + parameterMacro?: ((operation: Readonly, parameter: Readonly) => any) | undefined; + + // Authorization + + /** + * If set to true, it persists authorization data and it would not be lost on browser close/refresh + */ + persistAuthorization?: boolean | undefined; + } + + export type FastifySwaggerInitOAuthOptions = { + /** + * Default clientId. + */ + clientId?: string; + + /** + * Never use this parameter in your production environment. + * It exposes crucial security information. This feature is intended for + * dev/test environments only. + * Default clientSecret. + */ + clientSecret?: string, + + /** + * realm query parameter (for oauth1) added to authorizationUrl and tokenUrl. + */ + realm?: string; + + /** + * application name, displayed in authorization popup. + */ + appName?: string; + + /** + * scope separator for passing scopes, encoded before calling, default + * value is a space (encoded value %20). + * + * @default ' ' + */ + scopeSeparator?: string; + + /** + * string array or scope separator (i.e. space) separated string of + * initially selected oauth scopes + * + * @default [] + */ + scopes?: string | string[]; + + /** + * Additional query parameters added to authorizationUrl and tokenUrl. + * MUST be an object + */ + additionalQueryStringParams?: { [key: string]: any }; + + /** + * Only activated for the accessCode flow. During the authorization_code + * request to the tokenUrl, pass the Client Password using the HTTP Basic + * Authentication scheme (Authorization header with Basic + * base64encode(client_id + client_secret)). + * + * @default false + */ + useBasicAuthenticationWithAccessCodeGrant?: boolean; + + /** + * Only applies to Authorization Code flows. Proof Key for Code Exchange + * brings enhanced security for OAuth public clients. + * + * @default false + */ + usePkceWithAuthorizationCodeGrant?: boolean + } export type FastifySwaggerUiHooksOptions = Partial<{ onRequest?: onRequestHookHandler,