diff --git a/README.md b/README.md
index 831008b7..f83ca999 100644
--- a/README.md
+++ b/README.md
@@ -123,6 +123,7 @@ fastify.ready(err => {
`dynamic` mode is the default one, if you use the plugin this way - swagger specification would be gathered from your routes definitions.
```js
{
+ // swagger 2.0 options
swagger: {
info: {
title: String,
@@ -136,14 +137,29 @@ fastify.ready(err => {
produces: [ String ],
tags: [ Object ],
securityDefinitions: Object
- }
+ },
+ // openapi 3.0.3 options
+ // openapi: {
+ // info: {
+ // title: String,
+ // description: String,
+ // version: String,
+ // },
+ // externalDocs: Object,
+ // servers: [ Object ],
+ // components: Object,
+ // security: [ Object ],
+ // tags: [ Object ]
+ // }
}
```
*All the above parameters are optional.*
- You can use all the properties of the [swagger specification](https://swagger.io/specification/), if you find anything missing, please open an issue or a pr!
+ You can use all the properties of the [swagger specification](https://swagger.io/specification/v2/) and [openapi specification](https://swagger.io/specification/), if you find anything missing, please open an issue or a pr!
+
+ fastify-swagger will generate Swagger v2 by default. If you pass the `opeanapi` option it will generate OpenAPI instead.
- Example of the `fastify-swagger` usage in the `dynamic` mode is available [here](examples/dynamic.js).
+ Example of the `fastify-swagger` usage in the `dynamic` mode, `swagger` option is available [here](examples/dynamic-swagger.js) and `openapi` option is avaiable [here](examples/dynamic-openapi.js).
##### options
@@ -154,6 +170,7 @@ fastify.ready(err => {
| hiddenTag | X-HIDDEN | Tag to control hiding of routes. |
| stripBasePath | true | Strips base path from routes in docs. |
| swagger | {} | Swagger configuration. |
+ | openapi | {} | Openapi configuration. |
| transform | null | Transform method for schema. |
##### static
diff --git a/dynamic.js b/dynamic.js
deleted file mode 100644
index 53c310b4..00000000
--- a/dynamic.js
+++ /dev/null
@@ -1,413 +0,0 @@
-'use strict'
-
-const fs = require('fs')
-const path = require('path')
-const yaml = require('js-yaml')
-const Ref = require('json-schema-resolver')
-
-module.exports = function (fastify, opts, next) {
- fastify.decorate('swagger', swagger)
-
- const routes = []
- const sharedSchemasMap = new Map()
- let ref
-
- fastify.addHook('onRoute', (routeOptions) => {
- routes.push(routeOptions)
- })
-
- fastify.addHook('onRegister', async (instance) => {
- // we need to wait the ready event to get all the .getSchemas()
- // otherwise it will be empty
- instance.addHook('onReady', (done) => {
- const allSchemas = instance.getSchemas()
- for (const schemaId of Object.keys(allSchemas)) {
- if (!sharedSchemasMap.has(schemaId)) {
- sharedSchemasMap.set(schemaId, allSchemas[schemaId])
- }
- }
- done()
- })
- })
-
- opts = Object.assign({}, {
- exposeRoute: false,
- hiddenTag: 'X-HIDDEN',
- stripBasePath: true,
- swagger: {},
- transform: null
- }, opts || {})
-
- const info = opts.swagger.info || null
- const host = opts.swagger.host || null
- const schemes = opts.swagger.schemes || null
- const consumes = opts.swagger.consumes || null
- const produces = opts.swagger.produces || null
- const definitions = opts.swagger.definitions || null
- const basePath = opts.swagger.basePath || null
- const securityDefinitions = opts.swagger.securityDefinitions || null
- const security = opts.swagger.security || null
- const tags = opts.swagger.tags || null
- const externalDocs = opts.swagger.externalDocs || null
- const stripBasePath = opts.stripBasePath
- const transform = opts.transform
- const hiddenTag = opts.hiddenTag
- const extensions = []
-
- for (const [key, value] of Object.entries(opts.swagger)) {
- if (key.startsWith('x-')) {
- extensions.push([key, value])
- }
- }
-
- if (opts.exposeRoute === true) {
- const prefix = opts.routePrefix || '/documentation'
- fastify.register(require('./routes'), { prefix })
- }
-
- const cache = {
- swaggerObject: null,
- swaggerString: null
- }
-
- function swagger (opts) {
- if (opts && opts.yaml) {
- if (cache.swaggerString) return cache.swaggerString
- } else {
- if (cache.swaggerObject) return cache.swaggerObject
- }
-
- const swaggerObject = {}
- let pkg
-
- try {
- pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json')))
- } catch (err) {
- return next(err)
- }
-
- // Base swagger info
- // this info is displayed in the swagger file
- // in the same order as here
- swaggerObject.swagger = '2.0'
- if (info) {
- swaggerObject.info = info
- } else {
- swaggerObject.info = {
- version: '1.0.0',
- title: pkg.name || ''
- }
- }
- if (host) swaggerObject.host = host
- if (schemes) swaggerObject.schemes = schemes
- if (basePath) swaggerObject.basePath = basePath
- if (consumes) swaggerObject.consumes = consumes
- if (produces) swaggerObject.produces = produces
- if (definitions) swaggerObject.definitions = definitions
- else swaggerObject.definitions = {}
-
- if (securityDefinitions) {
- swaggerObject.securityDefinitions = securityDefinitions
- }
- if (security) {
- swaggerObject.security = security
- }
- if (tags) {
- swaggerObject.tags = tags
- }
- if (externalDocs) {
- swaggerObject.externalDocs = externalDocs
- }
-
- for (const [key, value] of extensions) {
- swaggerObject[key] = value
- }
-
- const externalSchemas = Array.from(sharedSchemasMap.values())
-
- ref = Ref({ clone: true, applicationUri: 'todo.com', externalSchemas })
- swaggerObject.definitions = {
- ...swaggerObject.definitions,
- ...(ref.definitions().definitions)
- }
-
- // Swagger doesn't accept $id on /definitions schemas.
- // The $ids are needed by Ref() to check the URI so we need
- // to remove them at the end of the process
- Object.values(swaggerObject.definitions)
- .forEach(_ => { delete _.$id })
-
- swaggerObject.paths = {}
- for (const route of routes) {
- const schema = transform
- ? transform(route.schema)
- : route.schema
-
- if (schema && schema.hide) {
- continue
- }
-
- if (schema && schema.tags && schema.tags.includes(hiddenTag)) {
- continue
- }
-
- let path = stripBasePath && route.url.startsWith(basePath)
- ? route.url.replace(basePath, '')
- : route.url
- if (!path.startsWith('/')) {
- path = '/' + path
- }
- const url = formatParamUrl(path)
-
- const swaggerRoute = swaggerObject.paths[url] || {}
-
- const swaggerMethod = {}
- const parameters = []
-
- // route.method should be either a String, like 'POST', or an Array of Strings, like ['POST','PUT','PATCH']
- const methods = typeof route.method === 'string' ? [route.method] : route.method
-
- for (const method of methods) {
- swaggerRoute[method.toLowerCase()] = swaggerMethod
- }
-
- // All the data the user can give us, is via the schema object
- if (schema) {
- // the resulting schema will be in this order
- if (schema.operationId) {
- swaggerMethod.operationId = schema.operationId
- }
-
- if (schema.summary) {
- swaggerMethod.summary = schema.summary
- }
-
- if (schema.description) {
- swaggerMethod.description = schema.description
- }
-
- if (schema.tags) {
- swaggerMethod.tags = schema.tags
- }
-
- if (schema.produces) {
- swaggerMethod.produces = schema.produces
- }
-
- if (schema.consumes) {
- swaggerMethod.consumes = schema.consumes
- }
-
- if (schema.querystring) {
- getQueryParams(parameters, schema.querystring)
- }
-
- if (schema.body) {
- const consumesAllFormOnly =
- consumesFormOnly(schema) || consumesFormOnly(swaggerObject)
- consumesAllFormOnly
- ? getFormParams(parameters, schema.body)
- : getBodyParams(parameters, schema.body)
- }
-
- if (schema.params) {
- getPathParams(parameters, schema.params)
- }
-
- if (schema.headers) {
- getHeaderParams(parameters, schema.headers)
- }
-
- if (parameters.length) {
- swaggerMethod.parameters = parameters
- }
-
- if (schema.deprecated) {
- swaggerMethod.deprecated = schema.deprecated
- }
-
- if (schema.security) {
- swaggerMethod.security = schema.security
- }
-
- for (const key of Object.keys(schema)) {
- if (key.startsWith('x-')) {
- swaggerMethod[key] = schema[key]
- }
- }
- }
-
- swaggerMethod.responses = genResponse(schema ? schema.response : null)
-
- swaggerObject.paths[url] = swaggerRoute
- }
-
- if (opts && opts.yaml) {
- const swaggerString = yaml.safeDump(swaggerObject, { skipInvalid: true })
- cache.swaggerString = swaggerString
- return swaggerString
- }
-
- cache.swaggerObject = swaggerObject
- return swaggerObject
-
- function getBodyParams (parameters, body) {
- const bodyResolved = ref.resolve(body)
-
- const param = {}
- param.name = 'body'
- param.in = 'body'
- param.schema = bodyResolved
- parameters.push(param)
- }
-
- function getFormParams (parameters, form) {
- const resolved = ref.resolve(form)
- const add = plainJsonObjectToSwagger2('formData', resolved, swaggerObject.definitions)
- add.forEach(_ => parameters.push(_))
- }
-
- function getQueryParams (parameters, query) {
- const resolved = ref.resolve(query)
- const add = plainJsonObjectToSwagger2('query', resolved, swaggerObject.definitions)
- add.forEach(_ => parameters.push(_))
- }
-
- function getPathParams (parameters, path) {
- const resolved = ref.resolve(path)
- const add = plainJsonObjectToSwagger2('path', resolved, swaggerObject.definitions)
- add.forEach(_ => parameters.push(_))
- }
-
- function getHeaderParams (parameters, headers) {
- const resolved = ref.resolve(headers)
- const add = plainJsonObjectToSwagger2('header', resolved, swaggerObject.definitions)
- add.forEach(_ => parameters.push(_))
- }
-
- // https://swagger.io/docs/specification/2-0/describing-responses/
- function genResponse (fastifyResponseJson) {
- // if the user does not provided an out schema
- if (!fastifyResponseJson) {
- return { 200: { description: 'Default Response' } }
- }
-
- const responsesContainer = {}
-
- Object.keys(fastifyResponseJson).forEach(key => {
- // 2xx is not supported by swagger
-
- const rawJsonSchema = fastifyResponseJson[key]
- const resolved = ref.resolve(rawJsonSchema)
-
- responsesContainer[key] = {
- schema: resolved,
- description: rawJsonSchema.description || 'Default Response'
- }
- })
-
- return responsesContainer
- }
- }
-
- next()
-}
-
-function consumesFormOnly (schema) {
- const consumes = schema.consumes
- return (
- consumes &&
- consumes.length === 1 &&
- (consumes[0] === 'application/x-www-form-urlencoded' ||
- consumes[0] === 'multipart/form-data')
- )
-}
-
-// The swagger standard does not accept the url param with ':'
-// so '/user/:id' is not valid.
-// This function converts the url in a swagger compliant url string
-// => '/user/{id}'
-function formatParamUrl (url) {
- let start = url.indexOf('/:')
- if (start === -1) return url
-
- const end = url.indexOf('/', ++start)
-
- if (end === -1) {
- return url.slice(0, start) + '{' + url.slice(++start) + '}'
- } else {
- return formatParamUrl(url.slice(0, start) + '{' + url.slice(++start, end) + '}' + url.slice(end))
- }
-}
-
-// For supported keys read:
-// https://swagger.io/docs/specification/2-0/describing-parameters/
-function plainJsonObjectToSwagger2 (container, jsonSchema, externalSchemas) {
- const obj = localRefResolve(jsonSchema, externalSchemas)
- let toSwaggerProp
- switch (container) {
- case 'query':
- toSwaggerProp = function (properyName, jsonSchemaElement) {
- jsonSchemaElement.in = container
- jsonSchemaElement.name = properyName
- return jsonSchemaElement
- }
- break
- case 'formData':
- toSwaggerProp = function (properyName, jsonSchemaElement) {
- delete jsonSchemaElement.$id
- jsonSchemaElement.in = container
- jsonSchemaElement.name = properyName
-
- // https://json-schema.org/understanding-json-schema/reference/non_json_data.html#contentencoding
- if (jsonSchemaElement.contentEncoding === 'binary') {
- delete jsonSchemaElement.contentEncoding // Must be removed
- jsonSchemaElement.type = 'file'
- }
-
- return jsonSchemaElement
- }
- break
- case 'path':
- toSwaggerProp = function (properyName, jsonSchemaElement) {
- jsonSchemaElement.in = container
- jsonSchemaElement.name = properyName
- jsonSchemaElement.required = true
- return jsonSchemaElement
- }
- break
- case 'header':
- toSwaggerProp = function (properyName, jsonSchemaElement) {
- return {
- in: 'header',
- name: properyName,
- required: jsonSchemaElement.required,
- description: jsonSchemaElement.description,
- type: jsonSchemaElement.type
- }
- }
- break
- }
-
- return Object.keys(obj).reduce((acc, propKey) => {
- acc.push(toSwaggerProp(propKey, obj[propKey]))
- return acc
- }, [])
-}
-
-function localRefResolve (jsonSchema, externalSchemas) {
- if (jsonSchema.type && jsonSchema.properties) {
- // for the shorthand querystring/params/headers declaration
- const propertiesMap = Object.keys(jsonSchema.properties).reduce((acc, h) => {
- const required = (jsonSchema.required && jsonSchema.required.indexOf(h) >= 0) || false
- const newProps = Object.assign({}, jsonSchema.properties[h], { required })
- return Object.assign({}, acc, { [h]: newProps })
- }, {})
-
- return propertiesMap
- }
-
- // $ref is in the format: #/definitions//
- const localReference = jsonSchema.$ref.split('/')[2]
- return localRefResolve(externalSchemas[localReference], externalSchemas)
-}
diff --git a/examples/dynamic-openapi.js b/examples/dynamic-openapi.js
new file mode 100644
index 00000000..a1a9f9ee
--- /dev/null
+++ b/examples/dynamic-openapi.js
@@ -0,0 +1,70 @@
+'use strict'
+
+const fastify = require('fastify')()
+
+fastify.register(require('../index'), {
+ openapi: {
+ info: {
+ title: 'Test swagger',
+ description: 'testing the fastify swagger api',
+ version: '0.1.0'
+ },
+ servers: [{
+ url: 'http://localhost'
+ }],
+ components: {
+ securitySchemes: {
+ apiKey: {
+ type: 'apiKey',
+ name: 'apiKey',
+ in: 'header'
+ }
+ }
+ }
+ },
+ exposeRoute: true
+})
+
+fastify.put('/some-route/:id', {
+ schema: {
+ description: 'post some data',
+ tags: ['user', 'code'],
+ summary: 'qwerty',
+ security: [{ apiKey: [] }],
+ params: {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ description: 'user id'
+ }
+ }
+ },
+ body: {
+ type: 'object',
+ properties: {
+ hello: { type: 'string' },
+ obj: {
+ type: 'object',
+ properties: {
+ some: { type: 'string' }
+ }
+ }
+ }
+ },
+ response: {
+ 201: {
+ description: 'Succesful response',
+ type: 'object',
+ properties: {
+ hello: { type: 'string' }
+ }
+ }
+ }
+ }
+}, (req, reply) => { reply.send({ hello: `Hello ${req.body.hello}` }) })
+
+fastify.listen(3000, err => {
+ if (err) throw err
+ console.log('listening')
+})
diff --git a/examples/dynamic.js b/examples/dynamic-swagger.js
similarity index 100%
rename from examples/dynamic.js
rename to examples/dynamic-swagger.js
diff --git a/examples/test-package.json b/examples/test-package.json
index 05e6f1d1..0967ef42 100644
--- a/examples/test-package.json
+++ b/examples/test-package.json
@@ -1,3 +1 @@
-{
- "version": "3.1.0"
-}
+{}
diff --git a/index.d.ts b/index.d.ts
index 34f9eb1c..c1908a0c 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -1,4 +1,5 @@
-import { FastifyPlugin } from 'fastify';
+import { FastifyPluginCallback } from 'fastify';
+import { OpenAPI, OpenAPIV2, OpenAPIV3 } from 'openapi-types';
import * as SwaggerSchema from 'swagger-schema-official';
declare module 'fastify' {
@@ -29,7 +30,7 @@ declare module 'fastify' {
}
}
-export const fastifySwagger: FastifyPlugin;
+export const fastifySwagger: FastifyPluginCallback;
export type SwaggerOptions = (FastifyStaticSwaggerOptions | FastifyDynamicSwaggerOptions);
export interface FastifySwaggerOptions {
@@ -48,7 +49,8 @@ export interface FastifySwaggerOptions {
export interface FastifyDynamicSwaggerOptions extends FastifySwaggerOptions {
mode?: 'dynamic';
- swagger?: Partial;
+ swagger?: Partial;
+ openapi?: Partial
hiddenTag?: string;
/**
* Strips matching base path from routes in documentation
@@ -63,12 +65,12 @@ export interface FastifyDynamicSwaggerOptions extends FastifySwaggerOptions {
export interface StaticPathSpec {
path: string;
- postProcessor?: (spec: SwaggerSchema.Spec) => SwaggerSchema.Spec;
+ postProcessor?: (spec: OpenAPI.Document) => OpenAPI.Document;
baseDir: string;
}
export interface StaticDocumentSpec {
- document: string;
+ document: OpenAPIV2.Document | OpenAPIV3.Document;
}
export interface FastifyStaticSwaggerOptions extends FastifySwaggerOptions {
@@ -76,4 +78,4 @@ export interface FastifyStaticSwaggerOptions extends FastifySwaggerOptions {
specification: StaticPathSpec | StaticDocumentSpec;
}
-export default fastifySwagger;
\ No newline at end of file
+export default fastifySwagger;
diff --git a/index.js b/index.js
index ca388af2..ef67c663 100644
--- a/index.js
+++ b/index.js
@@ -2,7 +2,7 @@
const fp = require('fastify-plugin')
-const setup = { dynamic: require('./dynamic'), static: require('./static') }
+const setup = { dynamic: require('./lib/dynamic'), static: require('./lib/static') }
function fastifySwagger (fastify, opts, next) {
opts = opts || {}
diff --git a/lib/dynamic.js b/lib/dynamic.js
new file mode 100644
index 00000000..e393cedf
--- /dev/null
+++ b/lib/dynamic.js
@@ -0,0 +1,31 @@
+'use strict'
+
+const { addHook, resolveSwaggerFunction } = require('./dynamicUtil')
+
+module.exports = function (fastify, opts, done) {
+ const { routes, Ref } = addHook(fastify)
+
+ opts = Object.assign({}, {
+ exposeRoute: false,
+ hiddenTag: 'X-HIDDEN',
+ stripBasePath: true,
+ openapi: {},
+ swagger: {},
+ transform: null
+ }, opts || {})
+
+ if (opts.exposeRoute === true) {
+ const prefix = opts.routePrefix || '/documentation'
+ fastify.register(require('./routes'), { prefix })
+ }
+
+ const cache = {
+ object: null,
+ string: null
+ }
+
+ const swagger = resolveSwaggerFunction(opts, routes, Ref, cache, done)
+ fastify.decorate('swagger', swagger)
+
+ done()
+}
diff --git a/lib/dynamicUtil.js b/lib/dynamicUtil.js
new file mode 100644
index 00000000..e2ca40f5
--- /dev/null
+++ b/lib/dynamicUtil.js
@@ -0,0 +1,82 @@
+'use strict'
+
+const fs = require('fs')
+const path = require('path')
+const Ref = require('json-schema-resolver')
+
+function addHook (fastify) {
+ const routes = []
+ const sharedSchemasMap = new Map()
+
+ fastify.addHook('onRoute', (routeOptions) => {
+ routes.push(routeOptions)
+ })
+
+ fastify.addHook('onRegister', async (instance) => {
+ // we need to wait the ready event to get all the .getSchemas()
+ // otherwise it will be empty
+ // TODO: better handle for schemaId
+ // when schemaId is the same in difference instance
+ // the latter will lost
+ instance.addHook('onReady', (done) => {
+ const allSchemas = instance.getSchemas()
+ for (const schemaId of Object.keys(allSchemas)) {
+ if (!sharedSchemasMap.has(schemaId)) {
+ sharedSchemasMap.set(schemaId, allSchemas[schemaId])
+ }
+ }
+ done()
+ })
+ })
+
+ return {
+ routes,
+ Ref () {
+ const externalSchemas = Array.from(sharedSchemasMap.values())
+ // TODO: hardcoded applicationUri is not a ideal solution
+ return Ref({ clone: true, applicationUri: 'todo.com', externalSchemas })
+ }
+ }
+}
+
+function resolveSwaggerFunction (opts, routes, Ref, cache, done) {
+ let build
+ if (Object.keys(opts.openapi).length > 0 && opts.openapi.constructor === Object) {
+ build = require('./openapi')
+ } else {
+ build = require('./swagger')
+ }
+ return build(opts, routes, Ref, cache, done)
+}
+
+// The swagger standard does not accept the url param with ':'
+// so '/user/:id' is not valid.
+// This function converts the url in a swagger compliant url string
+// => '/user/{id}'
+function formatParamUrl (url) {
+ let start = url.indexOf('/:')
+ if (start === -1) return url
+
+ const end = url.indexOf('/', ++start)
+
+ if (end === -1) {
+ return url.slice(0, start) + '{' + url.slice(++start) + '}'
+ } else {
+ return formatParamUrl(url.slice(0, start) + '{' + url.slice(++start, end) + '}' + url.slice(end))
+ }
+}
+
+function readPackageJson (done) {
+ try {
+ return JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')))
+ } catch (err) {
+ return done(err)
+ }
+}
+
+module.exports = {
+ addHook,
+ resolveSwaggerFunction,
+ formatParamUrl,
+ readPackageJson
+}
diff --git a/lib/openapi.js b/lib/openapi.js
new file mode 100644
index 00000000..3a4d1bed
--- /dev/null
+++ b/lib/openapi.js
@@ -0,0 +1,200 @@
+'use strict'
+
+const yaml = require('js-yaml')
+const { formatParamUrl, readPackageJson } = require('./dynamicUtil')
+const { getBodyParams, getCommonParams, generateResponse, stripBasePathByServers } = require('./openapiUtil')
+
+// TODO: refactor for readability, reference in below
+// https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two
+// https://www.informit.com/articles/article.aspx?p=1398607
+module.exports = function (opts, routes, Ref, cache, done) {
+ let ref
+
+ const info = opts.openapi.info || null
+ const servers = opts.openapi.servers || null
+ const components = opts.openapi.components || null
+ const tags = opts.openapi.tags || null
+ const externalDocs = opts.openapi.externalDocs || null
+
+ const stripBasePath = opts.stripBasePath
+ const transform = opts.transform
+ const hiddenTag = opts.hiddenTag
+ const extensions = []
+
+ for (const [key, value] of Object.entries(opts.openapi)) {
+ if (key.startsWith('x-')) {
+ extensions.push([key, value])
+ }
+ }
+
+ return function (opts) {
+ if (opts && opts.yaml) {
+ if (cache.string) return cache.string
+ } else {
+ if (cache.object) return cache.object
+ }
+
+ const openapiObject = {}
+ const pkg = readPackageJson(done)
+
+ // Base Openapi info
+ // this info is displayed in the swagger file
+ // in the same order as here
+ openapiObject.openapi = '3.0.3'
+ if (info) {
+ openapiObject.info = info
+ } else {
+ openapiObject.info = {
+ version: pkg.version || '1.0.0',
+ title: pkg.name || ''
+ }
+ }
+ if (servers) {
+ openapiObject.servers = servers
+ }
+ if (components) {
+ openapiObject.components = Object.assign({}, components, { schemas: Object.assign({}, components.schemas) })
+ } else {
+ openapiObject.components = { schemas: {} }
+ }
+ if (tags) {
+ openapiObject.tags = tags
+ }
+ if (externalDocs) {
+ openapiObject.externalDocs = externalDocs
+ }
+
+ for (const [key, value] of extensions) {
+ openapiObject[key] = value
+ }
+
+ ref = Ref()
+ openapiObject.components.schemas = {
+ ...openapiObject.components.schemas,
+ ...(ref.definitions().definitions)
+ }
+
+ // Swagger doesn't accept $id on /definitions schemas.
+ // The $ids are needed by Ref() to check the URI so we need
+ // to remove them at the end of the process
+ Object.values(openapiObject.components.schemas)
+ .forEach(_ => { delete _.$id })
+
+ openapiObject.paths = {}
+
+ for (const route of routes) {
+ const schema = transform
+ ? transform(route.schema)
+ : route.schema
+
+ if (schema && schema.hide) {
+ continue
+ }
+
+ if (schema && schema.tags && schema.tags.includes(hiddenTag)) {
+ continue
+ }
+
+ const path = stripBasePath
+ ? stripBasePathByServers(route.url, openapiObject.servers)
+ : route.url
+ const url = formatParamUrl(path)
+
+ const swaggerRoute = openapiObject.paths[url] || {}
+
+ const swaggerMethod = {}
+ const parameters = []
+
+ // route.method should be either a String, like 'POST', or an Array of Strings, like ['POST','PUT','PATCH']
+ const methods = typeof route.method === 'string' ? [route.method] : route.method
+
+ for (const method of methods) {
+ swaggerRoute[method.toLowerCase()] = swaggerMethod
+ }
+
+ // All the data the user can give us, is via the schema object
+ if (schema) {
+ // the resulting schema will be in this order
+ if (schema.operationId) {
+ swaggerMethod.operationId = schema.operationId
+ }
+
+ if (schema.tags) {
+ swaggerMethod.tags = schema.tags
+ }
+
+ if (schema.summary) {
+ swaggerMethod.summary = schema.summary
+ }
+
+ if (schema.description) {
+ swaggerMethod.description = schema.description
+ }
+
+ if (schema.externalDocs) {
+ swaggerMethod.externalDocs = schema.externalDocs
+ }
+
+ if (schema.querystring) {
+ getCommonParams('query', parameters, schema.querystring, ref, openapiObject.components.schemas)
+ }
+
+ if (schema.body) {
+ swaggerMethod.requestBody = {
+ content: {}
+ }
+ getBodyParams(swaggerMethod.requestBody.content, schema.body, schema.consumes, ref)
+ }
+
+ if (schema.params) {
+ getCommonParams('path', parameters, schema.params, ref, openapiObject.components.schemas)
+ }
+
+ if (schema.headers) {
+ getCommonParams('header', parameters, schema.headers, ref, openapiObject.components.schemas)
+ }
+
+ // TODO: need to documentation, we treat it same as the querystring
+ // fastify do not support cookies schema in first place
+ if (schema.cookies) {
+ getCommonParams('cookie', parameters, schema.cookies, ref, openapiObject.components.schemas)
+ }
+
+ if (parameters.length) {
+ swaggerMethod.parameters = parameters
+ }
+
+ if (schema.deprecated) {
+ swaggerMethod.deprecated = schema.deprecated
+ }
+
+ if (schema.security) {
+ swaggerMethod.security = schema.security
+ }
+
+ if (schema.servers) {
+ swaggerMethod.servers = schema.servers
+ }
+
+ for (const key of Object.keys(schema)) {
+ if (key.startsWith('x-')) {
+ swaggerMethod[key] = schema[key]
+ }
+ }
+ }
+
+ swaggerMethod.responses = generateResponse(schema ? schema.response : null, schema ? schema.produces : null, ref)
+
+ openapiObject.paths[url] = swaggerRoute
+ }
+
+ if (opts && opts.yaml) {
+ const openapiString = yaml.safeDump(openapiObject, { skipInvalid: true })
+ cache.string = openapiString
+ return openapiString
+ }
+
+ cache.object = openapiObject
+ return openapiObject
+ }
+}
diff --git a/lib/openapiUtil.js b/lib/openapiUtil.js
new file mode 100644
index 00000000..b5ef1310
--- /dev/null
+++ b/lib/openapiUtil.js
@@ -0,0 +1,139 @@
+'use strict'
+
+const { URL } = require('url')
+const { localRefResolve } = require('./swaggerUtil')
+
+// TODO: improvement needed, maybe remove the depend of json-schema-resolver
+function defToComponent (jsonSchema) {
+ if (typeof jsonSchema === 'object') {
+ Object.keys(jsonSchema).forEach(function (key) {
+ if (key === '$ref') {
+ jsonSchema[key] = jsonSchema[key].replace('definitions', 'components/schemas')
+ } else {
+ jsonSchema[key] = defToComponent(jsonSchema[key])
+ }
+ })
+ }
+ return jsonSchema
+}
+
+function plainJsonObjectToOpenapi3 (container, jsonSchema, externalSchemas) {
+ const obj = defToComponent(localRefResolve(jsonSchema, externalSchemas))
+ let toSwaggerProp
+ switch (container) {
+ case 'cookie':
+ case 'query':
+ toSwaggerProp = function (propertyName, jsonSchemaElement) {
+ jsonSchemaElement.in = container
+ jsonSchemaElement.name = propertyName
+ return jsonSchemaElement
+ }
+ break
+ case 'path':
+ toSwaggerProp = function (propertyName, jsonSchemaElement) {
+ jsonSchemaElement.in = container
+ jsonSchemaElement.name = propertyName
+ jsonSchemaElement.required = true
+ return jsonSchemaElement
+ }
+ break
+ case 'header':
+ toSwaggerProp = function (propertyName, jsonSchemaElement) {
+ return {
+ in: 'header',
+ name: propertyName,
+ required: jsonSchemaElement.required,
+ description: jsonSchemaElement.description,
+ type: jsonSchemaElement.type
+ }
+ }
+ break
+ }
+
+ return Object.keys(obj).map((propKey) => {
+ const jsonSchema = toSwaggerProp(propKey, obj[propKey])
+ jsonSchema.schema = {}
+ jsonSchema.schema.type = jsonSchema.type
+ if (jsonSchema.type === 'object') {
+ jsonSchema.schema.properties = jsonSchema.properties
+ }
+ if (jsonSchema.type === 'array') {
+ jsonSchema.schema.items = jsonSchema.items
+ }
+ delete jsonSchema.type
+ delete jsonSchema.properties
+ delete jsonSchema.items
+ return jsonSchema
+ })
+}
+
+function getBodyParams (parameters, body, consumes, ref) {
+ const bodyResolved = defToComponent(ref.resolve(body))
+
+ if ((Array.isArray(consumes) && consumes.length === 0) || typeof consumes === 'undefined') {
+ consumes = ['application/json']
+ }
+
+ consumes.forEach((consume) => {
+ parameters[consume] = {
+ schema: bodyResolved
+ }
+ })
+}
+
+function getCommonParams (container, parameters, schema, ref, sharedSchema) {
+ const resolved = defToComponent(ref.resolve(schema))
+ const add = plainJsonObjectToOpenapi3(container, resolved, sharedSchema)
+ add.forEach(openapiSchema => parameters.push(openapiSchema))
+}
+
+function generateResponse (fastifyResponseJson, produces, ref) {
+ // if the user does not provided an out schema
+ if (!fastifyResponseJson) {
+ return { 200: { description: 'Default Response' } }
+ }
+
+ const responsesContainer = {}
+
+ Object.keys(fastifyResponseJson).forEach(key => {
+ const rawJsonSchema = fastifyResponseJson[key]
+ const resolved = defToComponent(ref.resolve(rawJsonSchema))
+
+ const content = {}
+
+ if ((Array.isArray(produces) && produces.length === 0) || typeof produces === 'undefined') {
+ produces = ['application/json']
+ }
+
+ produces.forEach((produce) => {
+ content[produce] = {
+ schema: resolved
+ }
+ })
+
+ responsesContainer[key] = {
+ content,
+ description: rawJsonSchema.description || 'Default Response'
+ }
+ })
+
+ return responsesContainer
+}
+
+function stripBasePathByServers (path, servers) {
+ servers = Array.isArray(servers) ? servers : []
+ servers.forEach(function (server) {
+ const basePath = new URL(server.url).pathname
+ if (path.startsWith(basePath) && basePath !== '/') {
+ path = path.replace(basePath, '')
+ }
+ })
+ return path
+}
+
+module.exports = {
+ getBodyParams,
+ getCommonParams,
+ generateResponse,
+ stripBasePathByServers
+}
diff --git a/routes.js b/lib/routes.js
similarity index 90%
rename from routes.js
rename to lib/routes.js
index 8e8c7a95..011055d3 100644
--- a/routes.js
+++ b/lib/routes.js
@@ -19,7 +19,7 @@ function getRedirectPathForTheRootRoute (url) {
return redirectPath
}
-function fastifySwagger (fastify, opts, next) {
+function fastifySwagger (fastify, opts, done) {
fastify.route({
url: '/',
method: 'GET',
@@ -51,13 +51,13 @@ function fastifySwagger (fastify, opts, next) {
// serve swagger-ui with the help of fastify-static
fastify.register(fastifyStatic, {
- root: path.join(__dirname, 'static'),
+ root: path.join(__dirname, '..', 'static'),
prefix: staticPrefix,
decorateReply: false
})
fastify.register(fastifyStatic, {
- root: opts.baseDir || __dirname,
+ root: opts.baseDir || path.join(__dirname, '..'),
serve: false
})
@@ -72,7 +72,7 @@ function fastifySwagger (fastify, opts, next) {
}
})
- next()
+ done()
}
module.exports = fastifySwagger
diff --git a/static.js b/lib/static.js
similarity index 80%
rename from static.js
rename to lib/static.js
index 23adbdb1..a2530f4c 100644
--- a/static.js
+++ b/lib/static.js
@@ -4,25 +4,25 @@ const path = require('path')
const fs = require('fs')
const yaml = require('js-yaml')
-module.exports = function (fastify, opts, next) {
- if (!opts.specification) return next(new Error('specification is missing in the module options'))
- if (typeof opts.specification !== 'object') return next(new Error('specification is not an object'))
+module.exports = function (fastify, opts, done) {
+ if (!opts.specification) return done(new Error('specification is missing in the module options'))
+ if (typeof opts.specification !== 'object') return done(new Error('specification is not an object'))
let swaggerObject = {}
if (!opts.specification.path && !opts.specification.document) {
- return next(new Error('both specification.path and specification.document are missing, should be path to the file or swagger document spec'))
+ return done(new Error('both specification.path and specification.document are missing, should be path to the file or swagger document spec'))
} else if (opts.specification.path) {
- if (typeof opts.specification.path !== 'string') return next(new Error('specification.path is not a string'))
+ if (typeof opts.specification.path !== 'string') return done(new Error('specification.path is not a string'))
- if (!fs.existsSync(path.resolve(opts.specification.path))) return next(new Error(`${opts.specification.path} does not exist`))
+ if (!fs.existsSync(path.resolve(opts.specification.path))) return done(new Error(`${opts.specification.path} does not exist`))
const extName = path.extname(opts.specification.path).toLowerCase()
- if (['.yaml', '.json'].indexOf(extName) === -1) return next(new Error("specification.path extension name is not supported, should be one from ['.yaml', '.json']"))
+ if (['.yaml', '.json'].indexOf(extName) === -1) return done(new Error("specification.path extension name is not supported, should be one from ['.yaml', '.json']"))
- if (opts.specification.postProcessor && typeof opts.specification.postProcessor !== 'function') return next(new Error('specification.postProcessor should be a function'))
+ if (opts.specification.postProcessor && typeof opts.specification.postProcessor !== 'function') return done(new Error('specification.postProcessor should be a function'))
- if (opts.specification.baseDir && typeof opts.specification.baseDir !== 'string') return next(new Error('specification.baseDir should be string'))
+ if (opts.specification.baseDir && typeof opts.specification.baseDir !== 'string') return done(new Error('specification.baseDir should be string'))
if (!opts.specification.baseDir) {
opts.specification.baseDir = path.resolve(path.dirname(opts.specification.path))
@@ -51,7 +51,7 @@ module.exports = function (fastify, opts, next) {
swaggerObject = opts.specification.postProcessor(swaggerObject)
}
} else {
- if (typeof opts.specification.document !== 'object') return next(new Error('specification.document is not an object'))
+ if (typeof opts.specification.document !== 'object') return done(new Error('specification.document is not an object'))
swaggerObject = opts.specification.document
}
@@ -89,5 +89,5 @@ module.exports = function (fastify, opts, next) {
return swaggerObject
}
- next()
+ done()
}
diff --git a/lib/swagger.js b/lib/swagger.js
new file mode 100644
index 00000000..b7f6c423
--- /dev/null
+++ b/lib/swagger.js
@@ -0,0 +1,208 @@
+'use strict'
+
+const yaml = require('js-yaml')
+const { formatParamUrl, readPackageJson } = require('./dynamicUtil')
+const { consumesFormOnly, getBodyParams, getCommonParams, generateResponse } = require('./swaggerUtil')
+
+// TODO: refactor for readability, reference in below
+// https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two
+// https://www.informit.com/articles/article.aspx?p=1398607
+module.exports = function (opts, routes, Ref, cache, done) {
+ let ref
+
+ const info = opts.swagger.info || null
+ const host = opts.swagger.host || null
+ const schemes = opts.swagger.schemes || null
+ const consumes = opts.swagger.consumes || null
+ const produces = opts.swagger.produces || null
+ const definitions = opts.swagger.definitions || null
+ const basePath = opts.swagger.basePath || null
+ const securityDefinitions = opts.swagger.securityDefinitions || null
+ const security = opts.swagger.security || null
+ const tags = opts.swagger.tags || null
+ const externalDocs = opts.swagger.externalDocs || null
+ const stripBasePath = opts.stripBasePath
+ const transform = opts.transform
+ const hiddenTag = opts.hiddenTag
+ const extensions = []
+
+ for (const [key, value] of Object.entries(opts.swagger)) {
+ if (key.startsWith('x-')) {
+ extensions.push([key, value])
+ }
+ }
+
+ return function (opts) {
+ if (opts && opts.yaml) {
+ if (cache.string) return cache.string
+ } else {
+ if (cache.object) return cache.object
+ }
+
+ const swaggerObject = {}
+ const pkg = readPackageJson(done)
+
+ // Base swagger info
+ // this info is displayed in the swagger file
+ // in the same order as here
+ swaggerObject.swagger = '2.0'
+ if (info) {
+ swaggerObject.info = info
+ } else {
+ swaggerObject.info = {
+ version: pkg.version || '1.0.0',
+ title: pkg.name || ''
+ }
+ }
+ if (host) swaggerObject.host = host
+ if (schemes) swaggerObject.schemes = schemes
+ if (basePath) swaggerObject.basePath = basePath
+ if (consumes) swaggerObject.consumes = consumes
+ if (produces) swaggerObject.produces = produces
+ if (definitions) swaggerObject.definitions = definitions
+ else swaggerObject.definitions = {}
+
+ if (securityDefinitions) {
+ swaggerObject.securityDefinitions = securityDefinitions
+ }
+ if (security) {
+ swaggerObject.security = security
+ }
+ if (tags) {
+ swaggerObject.tags = tags
+ }
+ if (externalDocs) {
+ swaggerObject.externalDocs = externalDocs
+ }
+
+ for (const [key, value] of extensions) {
+ swaggerObject[key] = value
+ }
+
+ ref = Ref()
+ swaggerObject.definitions = {
+ ...swaggerObject.definitions,
+ ...(ref.definitions().definitions)
+ }
+
+ // Swagger doesn't accept $id on /definitions schemas.
+ // The $ids are needed by Ref() to check the URI so we need
+ // to remove them at the end of the process
+ Object.values(swaggerObject.definitions)
+ .forEach(_ => { delete _.$id })
+
+ swaggerObject.paths = {}
+ for (const route of routes) {
+ const schema = transform
+ ? transform(route.schema)
+ : route.schema
+
+ if (schema && schema.hide) {
+ continue
+ }
+
+ if (schema && schema.tags && schema.tags.includes(hiddenTag)) {
+ continue
+ }
+
+ let path = stripBasePath && route.url.startsWith(basePath)
+ ? route.url.replace(basePath, '')
+ : route.url
+ if (!path.startsWith('/')) {
+ path = '/' + path
+ }
+ const url = formatParamUrl(path)
+
+ const swaggerRoute = swaggerObject.paths[url] || {}
+
+ const swaggerMethod = {}
+ const parameters = []
+
+ // route.method should be either a String, like 'POST', or an Array of Strings, like ['POST','PUT','PATCH']
+ const methods = typeof route.method === 'string' ? [route.method] : route.method
+
+ for (const method of methods) {
+ swaggerRoute[method.toLowerCase()] = swaggerMethod
+ }
+
+ // All the data the user can give us, is via the schema object
+ if (schema) {
+ // the resulting schema will be in this order
+ if (schema.operationId) {
+ swaggerMethod.operationId = schema.operationId
+ }
+
+ if (schema.summary) {
+ swaggerMethod.summary = schema.summary
+ }
+
+ if (schema.description) {
+ swaggerMethod.description = schema.description
+ }
+
+ if (schema.tags) {
+ swaggerMethod.tags = schema.tags
+ }
+
+ if (schema.produces) {
+ swaggerMethod.produces = schema.produces
+ }
+
+ if (schema.consumes) {
+ swaggerMethod.consumes = schema.consumes
+ }
+
+ if (schema.querystring) {
+ getCommonParams('query', parameters, schema.querystring, ref, swaggerObject.definitions)
+ }
+
+ if (schema.body) {
+ const consumesAllFormOnly =
+ consumesFormOnly(schema) || consumesFormOnly(swaggerObject)
+ consumesAllFormOnly
+ ? getCommonParams('formData', parameters, schema.body, ref, swaggerObject.definitions)
+ : getBodyParams(parameters, schema.body, ref)
+ }
+
+ if (schema.params) {
+ getCommonParams('path', parameters, schema.params, ref, swaggerObject.definitions)
+ }
+
+ if (schema.headers) {
+ getCommonParams('header', parameters, schema.headers, ref, swaggerObject.definitions)
+ }
+
+ if (parameters.length) {
+ swaggerMethod.parameters = parameters
+ }
+
+ if (schema.deprecated) {
+ swaggerMethod.deprecated = schema.deprecated
+ }
+
+ if (schema.security) {
+ swaggerMethod.security = schema.security
+ }
+
+ for (const key of Object.keys(schema)) {
+ if (key.startsWith('x-')) {
+ swaggerMethod[key] = schema[key]
+ }
+ }
+ }
+
+ swaggerMethod.responses = generateResponse(schema ? schema.response : null, ref)
+
+ swaggerObject.paths[url] = swaggerRoute
+ }
+
+ if (opts && opts.yaml) {
+ const swaggerString = yaml.safeDump(swaggerObject, { skipInvalid: true })
+ cache.string = swaggerString
+ return swaggerString
+ }
+
+ cache.object = swaggerObject
+ return swaggerObject
+ }
+}
diff --git a/lib/swaggerUtil.js b/lib/swaggerUtil.js
new file mode 100644
index 00000000..b861e4b7
--- /dev/null
+++ b/lib/swaggerUtil.js
@@ -0,0 +1,140 @@
+'use strict'
+
+function localRefResolve (jsonSchema, externalSchemas) {
+ if (jsonSchema.type && jsonSchema.properties) {
+ // for the shorthand querystring/params/headers declaration
+ const propertiesMap = Object.keys(jsonSchema.properties).reduce((acc, headers) => {
+ const required = (jsonSchema.required && jsonSchema.required.indexOf(headers) >= 0) || false
+ const newProps = Object.assign({}, jsonSchema.properties[headers], { required })
+ return Object.assign({}, acc, { [headers]: newProps })
+ }, {})
+
+ return propertiesMap
+ }
+
+ // for oneOf, anyOf, allOf support in querystring/params/headers
+ if (jsonSchema.oneOf || jsonSchema.anyOf || jsonSchema.allOf) {
+ const schemas = jsonSchema.oneOf || jsonSchema.anyOf || jsonSchema.allOf
+ return schemas.reduce(function (acc, schema) {
+ const json = localRefResolve(schema, externalSchemas)
+ return { ...acc, ...json }
+ }, {})
+ }
+
+ // $ref is in the format: #/definitions//
+ const localReference = jsonSchema.$ref.split('/')[2]
+ return localRefResolve(externalSchemas[localReference], externalSchemas)
+}
+
+// For supported keys read:
+// https://swagger.io/docs/specification/2-0/describing-parameters/
+function plainJsonObjectToSwagger2 (container, jsonSchema, externalSchemas) {
+ const obj = localRefResolve(jsonSchema, externalSchemas)
+ let toSwaggerProp
+ switch (container) {
+ case 'query':
+ toSwaggerProp = function (propertyName, jsonSchemaElement) {
+ jsonSchemaElement.in = container
+ jsonSchemaElement.name = propertyName
+ return jsonSchemaElement
+ }
+ break
+ case 'formData':
+ toSwaggerProp = function (propertyName, jsonSchemaElement) {
+ delete jsonSchemaElement.$id
+ jsonSchemaElement.in = container
+ jsonSchemaElement.name = propertyName
+
+ // https://json-schema.org/understanding-json-schema/reference/non_json_data.html#contentencoding
+ if (jsonSchemaElement.contentEncoding === 'binary') {
+ delete jsonSchemaElement.contentEncoding // Must be removed
+ jsonSchemaElement.type = 'file'
+ }
+
+ return jsonSchemaElement
+ }
+ break
+ case 'path':
+ toSwaggerProp = function (propertyName, jsonSchemaElement) {
+ jsonSchemaElement.in = container
+ jsonSchemaElement.name = propertyName
+ jsonSchemaElement.required = true
+ return jsonSchemaElement
+ }
+ break
+ case 'header':
+ toSwaggerProp = function (propertyName, jsonSchemaElement) {
+ return {
+ in: 'header',
+ name: propertyName,
+ required: jsonSchemaElement.required,
+ description: jsonSchemaElement.description,
+ type: jsonSchemaElement.type
+ }
+ }
+ break
+ }
+
+ return Object.keys(obj).map((propKey) => {
+ return toSwaggerProp(propKey, obj[propKey])
+ })
+}
+
+function consumesFormOnly (schema) {
+ const consumes = schema.consumes
+ return (
+ consumes &&
+ consumes.length === 1 &&
+ (consumes[0] === 'application/x-www-form-urlencoded' ||
+ consumes[0] === 'multipart/form-data')
+ )
+}
+
+function getBodyParams (parameters, body, ref) {
+ const bodyResolved = ref.resolve(body)
+
+ const param = {}
+ param.name = 'body'
+ param.in = 'body'
+ param.schema = bodyResolved
+ parameters.push(param)
+}
+
+function getCommonParams (container, parameters, schema, ref, sharedSchemas) {
+ const resolved = ref.resolve(schema)
+ const add = plainJsonObjectToSwagger2(container, resolved, sharedSchemas)
+ add.forEach(swaggerSchema => parameters.push(swaggerSchema))
+}
+
+// https://swagger.io/docs/specification/2-0/describing-responses/
+function generateResponse (fastifyResponseJson, ref) {
+ // if the user does not provided an out schema
+ if (!fastifyResponseJson) {
+ return { 200: { description: 'Default Response' } }
+ }
+
+ const responsesContainer = {}
+
+ Object.keys(fastifyResponseJson).forEach(key => {
+ // 2xx is not supported by swagger
+
+ const rawJsonSchema = fastifyResponseJson[key]
+ const resolved = ref.resolve(rawJsonSchema)
+
+ responsesContainer[key] = {
+ schema: resolved,
+ description: rawJsonSchema.description || 'Default Response'
+ }
+ })
+
+ return responsesContainer
+}
+
+module.exports = {
+ consumesFormOnly,
+ localRefResolve,
+ plainJsonObjectToSwagger2,
+ getBodyParams,
+ getCommonParams,
+ generateResponse
+}
diff --git a/package.json b/package.json
index 0254ff79..b753742d 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,7 @@
"fs-extra": "^9.0.0",
"joi": "^14.3.1",
"joi-to-json-schema": "^5.1.0",
+ "openapi-types": "^7.0.1",
"pre-commit": "^1.2.2",
"standard": "^16.0.1",
"swagger-parser": "^10.0.2",
diff --git a/test/openapi.js b/test/openapi.js
new file mode 100644
index 00000000..7e162ee8
--- /dev/null
+++ b/test/openapi.js
@@ -0,0 +1,805 @@
+'use strict'
+
+const t = require('tap')
+const test = t.test
+const Fastify = require('fastify')
+const Swagger = require('swagger-parser')
+const yaml = require('js-yaml')
+const fastifySwagger = require('../index')
+
+const swaggerInfo = {
+ openapi: {
+ info: {
+ title: 'Test swagger',
+ description: 'testing the fastify swagger api',
+ version: '0.1.0'
+ },
+ servers: [
+ {
+ url: 'http://localhost'
+ }
+ ],
+ tags: [
+ { name: 'tag' }
+ ],
+ externalDocs: {
+ description: 'Find more info here',
+ url: 'https://swagger.io'
+ }
+ }
+}
+
+const opts1 = {
+ schema: {
+ response: {
+ 200: {
+ type: 'object',
+ properties: {
+ hello: { type: 'string' }
+ }
+ }
+ },
+ querystring: {
+ hello: { type: 'string' },
+ world: { type: 'string' },
+ foo: { type: 'array', items: { type: 'string' } },
+ bar: { type: 'object', properties: { baz: { type: 'string' } } }
+ }
+ }
+}
+
+const opts2 = {
+ schema: {
+ body: {
+ type: 'object',
+ properties: {
+ hello: { type: 'string' },
+ obj: {
+ type: 'object',
+ properties: {
+ some: { type: 'string' }
+ }
+ }
+ },
+ required: ['hello']
+ }
+ }
+}
+
+const opts3 = {
+ schema: {
+ params: {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ description: 'user id'
+ }
+ }
+ }
+ }
+}
+
+const opts4 = {
+ schema: {
+ headers: {
+ type: 'object',
+ properties: {
+ authorization: {
+ type: 'string',
+ description: 'api token'
+ }
+ },
+ required: ['authorization']
+ }
+ }
+}
+
+const opts5 = {
+ schema: {
+ headers: {
+ type: 'object',
+ properties: {
+ 'x-api-token': {
+ type: 'string',
+ description: 'optional api token'
+ },
+ 'x-api-version': {
+ type: 'string',
+ description: 'optional api version'
+ }
+ }
+ },
+ params: {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ description: 'user id'
+ }
+ }
+ }
+ }
+}
+
+const opts6 = {
+ schema: {
+ security: [
+ {
+ apiKey: []
+ }
+ ]
+ }
+}
+
+const opts7 = {
+ schema: {
+ consumes: ['application/x-www-form-urlencoded'],
+ body: {
+ type: 'object',
+ properties: {
+ hello: {
+ description: 'hello',
+ type: 'string'
+ }
+ },
+ required: ['hello']
+ }
+ }
+}
+
+const opts8 = {
+ schema: {
+ 'x-tension': true
+ }
+}
+
+const opts9 = {
+ schema: {
+ produces: ['*/*'],
+ response: {
+ 200: {
+ type: 'object',
+ properties: {
+ hello: {
+ description: 'hello',
+ type: 'string'
+ }
+ },
+ required: ['hello']
+ }
+ }
+ }
+}
+
+const opts10 = {
+ schema: {
+ querystring: {
+ allOf: [
+ {
+ type: 'object',
+ properties: {
+ foo: { type: 'string' }
+ }
+ }
+ ]
+ }
+ }
+}
+
+const opts11 = {
+ schema: {
+ cookies: {
+ type: 'object',
+ properties: {
+ bar: { type: 'string' }
+ }
+ }
+ }
+}
+
+test('fastify.swagger should return a valid swagger object', t => {
+ t.plan(3)
+ const fastify = Fastify()
+
+ fastify.register(fastifySwagger, swaggerInfo)
+
+ fastify.get('/', () => {})
+ fastify.post('/', () => {})
+ fastify.get('/example', opts1, () => {})
+ fastify.post('/example', opts2, () => {})
+ fastify.get('/parameters/:id', opts3, () => {})
+ fastify.get('/headers', opts4, () => {})
+ fastify.get('/headers/:id', opts5, () => {})
+ fastify.get('/security', opts6, () => {})
+
+ fastify.ready(err => {
+ t.error(err)
+
+ const swaggerObject = fastify.swagger()
+ t.is(typeof swaggerObject, 'object')
+
+ Swagger.validate(swaggerObject)
+ .then(function (api) {
+ t.pass('valid swagger object')
+ })
+ .catch(function (err) {
+ t.fail(err)
+ })
+ })
+})
+
+test('fastify.swagger should return a valid swagger yaml', t => {
+ t.plan(3)
+ const fastify = Fastify()
+
+ fastify.register(fastifySwagger, swaggerInfo)
+
+ fastify.get('/', () => {})
+ fastify.post('/', () => {})
+ fastify.get('/example', opts1, () => {})
+ fastify.post('/example', opts2, () => {})
+ fastify.get('/parameters/:id', opts3, () => {})
+ fastify.get('/headers', opts4, () => {})
+ fastify.get('/headers/:id', opts5, () => {})
+ fastify.get('/security', opts6, () => {})
+
+ fastify.ready(err => {
+ t.error(err)
+
+ const swaggerYaml = fastify.swagger({ yaml: true })
+ t.is(typeof swaggerYaml, 'string')
+
+ try {
+ yaml.safeLoad(swaggerYaml)
+ t.pass('valid swagger yaml')
+ } catch (err) {
+ t.fail(err)
+ }
+ })
+})
+
+test('hide support when property set in transform() - property', t => {
+ t.plan(2)
+ const fastify = Fastify()
+
+ fastify.register(fastifySwagger, {
+ ...swaggerInfo,
+ transform: schema => {
+ return { ...schema, hide: true }
+ }
+ })
+
+ const opts = {
+ schema: {
+ body: {
+ type: 'object',
+ properties: {
+ hello: { type: 'string' },
+ obj: {
+ type: 'object',
+ properties: {
+ some: { type: 'string' }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fastify.get('/', opts, () => {})
+
+ fastify.ready(err => {
+ t.error(err)
+
+ const swaggerObject = fastify.swagger()
+ t.notOk(swaggerObject.paths['/'])
+ })
+})
+
+test('fastify.swagger components', t => {
+ t.plan(2)
+ const fastify = Fastify()
+
+ swaggerInfo.openapi.components = {
+ schemas: {
+ ExampleModel: {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'integer',
+ description: 'Some id'
+ },
+ name: {
+ type: 'string',
+ description: 'Name of smthng'
+ }
+ }
+ }
+ }
+ }
+
+ fastify.register(fastifySwagger, swaggerInfo)
+
+ fastify.get('/', () => {})
+
+ fastify.ready(err => {
+ t.error(err)
+
+ const swaggerObject = fastify.swagger()
+ t.deepEquals(swaggerObject.components, swaggerInfo.openapi.components)
+ delete swaggerInfo.openapi.components // remove what we just added
+ })
+})
+
+test('hide support - tags Default', t => {
+ t.plan(2)
+ const fastify = Fastify()
+
+ fastify.register(fastifySwagger, swaggerInfo)
+
+ const opts = {
+ schema: {
+ tags: ['X-HIDDEN'],
+ body: {
+ type: 'object',
+ properties: {
+ hello: { type: 'string' },
+ obj: {
+ type: 'object',
+ properties: {
+ some: { type: 'string' }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fastify.get('/', opts, () => {})
+
+ fastify.ready(err => {
+ t.error(err)
+
+ const swaggerObject = fastify.swagger()
+ t.notOk(swaggerObject.paths['/'])
+ })
+})
+
+test('hide support - tags Custom', t => {
+ t.plan(2)
+ const fastify = Fastify()
+
+ fastify.register(fastifySwagger, { ...swaggerInfo, hiddenTag: 'NOP' })
+
+ const opts = {
+ schema: {
+ tags: ['NOP'],
+ body: {
+ type: 'object',
+ properties: {
+ hello: { type: 'string' },
+ obj: {
+ type: 'object',
+ properties: {
+ some: { type: 'string' }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fastify.get('/', opts, () => {})
+
+ fastify.ready(err => {
+ t.error(err)
+
+ const swaggerObject = fastify.swagger()
+ t.notOk(swaggerObject.paths['/'])
+ })
+})
+
+test('deprecated route', t => {
+ t.plan(3)
+ const fastify = Fastify()
+
+ fastify.register(fastifySwagger, swaggerInfo)
+
+ const opts = {
+ schema: {
+ deprecated: true,
+ body: {
+ type: 'object',
+ properties: {
+ hello: { type: 'string' },
+ obj: {
+ type: 'object',
+ properties: {
+ some: { type: 'string' }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fastify.get('/', opts, () => {})
+
+ fastify.ready(err => {
+ t.error(err)
+ const swaggerObject = fastify.swagger()
+
+ Swagger.validate(swaggerObject)
+ .then(function (api) {
+ t.pass('valid swagger object')
+ t.ok(swaggerObject.paths['/'])
+ })
+ .catch(function (err) {
+ t.fail(err)
+ })
+ })
+})
+
+test('route meta info', t => {
+ t.plan(8)
+ const fastify = Fastify()
+
+ fastify.register(fastifySwagger, swaggerInfo)
+
+ const opts = {
+ schema: {
+ operationId: 'doSomething',
+ summary: 'Route summary',
+ tags: ['tag'],
+ description: 'Route description',
+ servers: [
+ {
+ url: 'https://localhost'
+ }
+ ],
+ externalDocs: {
+ description: 'Find more info here',
+ url: 'https://swagger.io'
+ }
+ }
+ }
+
+ fastify.get('/', opts, () => {})
+
+ fastify.ready(err => {
+ t.error(err)
+ const swaggerObject = fastify.swagger()
+
+ Swagger.validate(swaggerObject)
+ .then(function (api) {
+ const definedPath = api.paths['/'].get
+ t.ok(definedPath)
+ t.equal(opts.schema.operationId, definedPath.operationId)
+ t.equal(opts.schema.summary, definedPath.summary)
+ t.same(opts.schema.tags, definedPath.tags)
+ t.equal(opts.schema.description, definedPath.description)
+ t.equal(opts.schema.servers, definedPath.servers)
+ t.equal(opts.schema.externalDocs, definedPath.externalDocs)
+ })
+ .catch(function (err) {
+ t.fail(err)
+ })
+ })
+})
+
+test('route with produces', t => {
+ t.plan(3)
+ const fastify = Fastify()
+
+ fastify.register(fastifySwagger, swaggerInfo)
+
+ fastify.get('/', opts9, () => {})
+
+ fastify.ready(err => {
+ t.error(err)
+ const swaggerObject = fastify.swagger()
+
+ Swagger.validate(swaggerObject)
+ .then(function (api) {
+ const definedPath = api.paths['/'].get
+ t.ok(definedPath)
+ t.same(definedPath.responses[200].content, {
+ '*/*': {
+ schema: {
+ type: 'object',
+ properties: {
+ hello: {
+ description: 'hello',
+ type: 'string'
+ }
+ },
+ required: ['hello']
+ }
+ }
+ })
+ })
+ .catch(function (err) {
+ t.fail(err)
+ })
+ })
+})
+
+test('route oneOf, anyOf, allOf', t => {
+ t.plan(3)
+ const fastify = Fastify()
+
+ fastify.register(fastifySwagger, swaggerInfo)
+
+ fastify.get('/', opts10, () => {})
+
+ fastify.ready(err => {
+ t.error(err)
+ const swaggerObject = fastify.swagger()
+ Swagger.validate(swaggerObject)
+ .then(function (api) {
+ const definedPath = api.paths['/'].get
+ t.ok(definedPath)
+ t.same(definedPath.parameters, [
+ {
+ required: false,
+ in: 'query',
+ name: 'foo',
+ schema: {
+ type: 'string'
+ }
+ }
+ ])
+ })
+ .catch(function (err) {
+ t.fail(err)
+ })
+ })
+})
+
+test('route cookies schema', t => {
+ t.plan(3)
+ const fastify = Fastify()
+
+ fastify.register(fastifySwagger, swaggerInfo)
+
+ fastify.get('/', opts11, () => {})
+
+ fastify.ready(err => {
+ t.error(err)
+ const swaggerObject = fastify.swagger()
+ Swagger.validate(swaggerObject)
+ .then(function (api) {
+ const definedPath = api.paths['/'].get
+ t.ok(definedPath)
+ t.same(definedPath.parameters, [
+ {
+ required: false,
+ in: 'cookie',
+ name: 'bar',
+ schema: {
+ type: 'string'
+ }
+ }
+ ])
+ })
+ .catch(function (err) {
+ t.fail(err)
+ })
+ })
+})
+
+test('parses form parameters when all api consumes application/x-www-form-urlencoded', t => {
+ t.plan(3)
+ const fastify = Fastify()
+ fastify.register(fastifySwagger, swaggerInfo)
+ fastify.get('/', opts7, () => {})
+
+ fastify.ready(err => {
+ t.error(err)
+ const swaggerObject = fastify.swagger()
+
+ Swagger.validate(swaggerObject)
+ .then(function (api) {
+ const definedPath = api.paths['/'].get
+ t.ok(definedPath)
+ t.same(definedPath.requestBody.content, {
+ 'application/x-www-form-urlencoded': {
+ schema: {
+ type: 'object',
+ properties: {
+ hello: {
+ description: 'hello',
+ type: 'string'
+ }
+ },
+ required: ['hello']
+ }
+ }
+ })
+ })
+ .catch(function (err) {
+ t.fail(err)
+ })
+ })
+})
+
+test('includes swagger extensions', t => {
+ t.plan(5)
+ const fastify = Fastify()
+ fastify.register(fastifySwagger, { openapi: { 'x-ternal': true } })
+ fastify.get('/', opts8, () => {})
+
+ fastify.ready(err => {
+ t.error(err)
+ const swaggerObject = fastify.swagger()
+
+ Swagger.validate(swaggerObject)
+ .then(function (api) {
+ t.ok(api['x-ternal'])
+ t.same(api['x-ternal'], true)
+
+ const definedPath = api.paths['/'].get
+ t.ok(definedPath)
+ t.same(definedPath['x-tension'], true)
+ })
+ .catch(function (err) {
+ t.fail(err)
+ })
+ })
+})
+
+test('basePath support', t => {
+ t.plan(3)
+ const fastify = Fastify()
+
+ fastify.register(fastifySwagger, {
+ openapi: Object.assign({}, swaggerInfo.openapi, {
+ servers: [
+ {
+ url: 'http://localhost/prefix'
+ }
+ ]
+ })
+ })
+
+ fastify.get('/prefix/endpoint', {}, () => {})
+
+ fastify.ready(err => {
+ t.error(err)
+
+ const swaggerObject = fastify.swagger()
+ t.notOk(swaggerObject.paths['/prefix/endpoint'])
+ t.ok(swaggerObject.paths['/endpoint'])
+ })
+})
+
+test('basePath maintained when stripBasePath is set to false', t => {
+ t.plan(4)
+
+ const fastify = Fastify()
+
+ fastify.register(fastifySwagger, {
+ stripBasePath: false,
+ openapi: Object.assign({}, swaggerInfo.openapi, {
+ servers: [
+ {
+ url: 'http://localhost/foo'
+ }
+ ]
+ })
+ })
+
+ fastify.get('/foo/endpoint', {}, () => {})
+
+ fastify.ready(err => {
+ t.error(err)
+
+ const swaggerObject = fastify.swagger()
+ t.notOk(swaggerObject.paths.endpoint)
+ t.notOk(swaggerObject.paths['/endpoint'])
+ t.ok(swaggerObject.paths['/foo/endpoint'])
+ })
+})
+
+test('cache - json', t => {
+ t.plan(3)
+ const fastify = Fastify()
+
+ fastify.register(fastifySwagger, swaggerInfo)
+
+ fastify.ready(err => {
+ t.error(err)
+
+ fastify.swagger()
+ const swaggerObject = fastify.swagger()
+ t.is(typeof swaggerObject, 'object')
+
+ Swagger.validate(swaggerObject)
+ .then(function (api) {
+ t.pass('valid swagger object')
+ })
+ .catch(function (err) {
+ t.fail(err)
+ })
+ })
+})
+
+test('cache - yaml', t => {
+ t.plan(3)
+ const fastify = Fastify()
+
+ fastify.register(fastifySwagger, swaggerInfo)
+
+ fastify.ready(err => {
+ t.error(err)
+
+ fastify.swagger({ yaml: true })
+ const swaggerYaml = fastify.swagger({ yaml: true })
+ t.is(typeof swaggerYaml, 'string')
+
+ try {
+ yaml.safeLoad(swaggerYaml)
+ t.pass('valid swagger yaml')
+ } catch (err) {
+ t.fail(err)
+ }
+ })
+})
+
+test('route with multiple method', t => {
+ t.plan(3)
+ const fastify = Fastify()
+
+ fastify.register(fastifySwagger, swaggerInfo)
+
+ fastify.route({
+ method: ['GET', 'POST'],
+ url: '/',
+ handler: function (request, reply) {
+ reply.send({ hello: 'world' })
+ }
+ })
+
+ fastify.ready(err => {
+ t.error(err)
+
+ const swaggerObject = fastify.swagger()
+ t.is(typeof swaggerObject, 'object')
+
+ Swagger.validate(swaggerObject)
+ .then(function (api) {
+ t.pass('valid swagger object')
+ })
+ .catch(function (err) {
+ t.fail(err)
+ })
+ })
+})
+
+test('openapi $ref', t => {
+ t.plan(3)
+ const fastify = Fastify()
+
+ fastify.register(fastifySwagger, swaggerInfo)
+ fastify.register(function (instance, _, done) {
+ instance.addSchema({ $id: 'Order', type: 'object', properties: { id: { type: 'integer' } } })
+ instance.post('/', { schema: { body: { $ref: 'Order#' }, response: { 200: { $ref: 'Order#' } } } }, () => {})
+ done()
+ })
+
+ fastify.ready(err => {
+ t.error(err)
+
+ const swaggerObject = fastify.swagger()
+ t.is(typeof swaggerObject, 'object')
+
+ Swagger.validate(swaggerObject)
+ .then(function (api) {
+ t.pass('valid swagger object')
+ })
+ .catch(function (err) {
+ t.fail(err)
+ })
+ })
+})
diff --git a/test/static.js b/test/static.js
index db108093..efe13b22 100644
--- a/test/static.js
+++ b/test/static.js
@@ -5,7 +5,7 @@ const t = require('tap')
const test = t.test
const Fastify = require('fastify')
const fastifySwagger = require('../index')
-const fastifySwaggerDynamic = require('../dynamic')
+const fastifySwaggerDynamic = require('../lib/dynamic')
const yaml = require('js-yaml')
const resolve = require('path').resolve
@@ -298,7 +298,7 @@ test('/documentation/:file should serve static file from the location of main sp
fastify.inject({
method: 'GET',
- url: '/documentation/dynamic.js'
+ url: '/documentation/dynamic-swagger.js'
}, (err, res) => {
t.error(err)
t.strictEqual(res.statusCode, 200)
@@ -615,7 +615,45 @@ test('inserts default package name', t => {
const testPackageJSON = path.join(__dirname, '../examples/test-package.json')
path.join = (...args) => {
- if (args[1] === 'package.json') {
+ if (args[2] === 'package.json') {
+ return testPackageJSON
+ }
+ return originalPathJoin(...args)
+ }
+
+ fastify.inject(
+ {
+ method: 'GET',
+ url: '/documentation/json'
+ },
+ (err, res) => {
+ t.error(err)
+ t.pass('Inserted default package name.')
+ }
+ )
+})
+
+test('inserts default package name - openapi', t => {
+ const config = {
+ mode: 'dynamic',
+ openapi: {
+ servers: []
+ },
+ specification: {
+ path: './examples/example-static-specification.json'
+ },
+ exposeRoute: true
+ }
+
+ t.plan(2)
+ const fastify = Fastify()
+ fastify.register(fastifySwagger, config)
+
+ const originalPathJoin = path.join
+ const testPackageJSON = path.join(__dirname, '../examples/test-package.json')
+
+ path.join = (...args) => {
+ if (args[2] === 'package.json') {
return testPackageJSON
}
return originalPathJoin(...args)
@@ -650,7 +688,45 @@ test('throws an error if cannot parse package\'s JSON', t => {
const testPackageJSON = path.join(__dirname, '')
path.join = (...args) => {
- if (args[1] === 'package.json') {
+ if (args[2] === 'package.json') {
+ return testPackageJSON
+ }
+ return originalPathJoin(...args)
+ }
+
+ fastify.inject(
+ {
+ method: 'GET',
+ url: '/documentation/json'
+ },
+ (err, res) => {
+ t.error(err)
+ t.equal(err, null)
+ }
+ )
+})
+
+test('throws an error if cannot parse package\'s JSON - openapi', t => {
+ const config = {
+ mode: 'dynamic',
+ openapi: {
+ servers: []
+ },
+ specification: {
+ path: './examples/example-static-specification.json'
+ },
+ exposeRoute: true
+ }
+
+ t.plan(2)
+ const fastify = Fastify()
+ fastify.register(fastifySwagger, config)
+
+ const originalPathJoin = path.join
+ const testPackageJSON = path.join(__dirname, '')
+
+ path.join = (...args) => {
+ if (args[2] === 'package.json') {
return testPackageJSON
}
return originalPathJoin(...args)
diff --git a/test/swagger.js b/test/swagger.js
index 72fde412..486768bb 100644
--- a/test/swagger.js
+++ b/test/swagger.js
@@ -6,6 +6,7 @@ const Fastify = require('fastify')
const Swagger = require('swagger-parser')
const yaml = require('js-yaml')
const fastifySwagger = require('../index')
+const { readPackageJson } = require('../lib/dynamicUtil')
const swaggerInfo = {
swagger: {
@@ -209,8 +210,9 @@ test('fastify.swagger should default info properties', t => {
t.error(err)
const swaggerObject = fastify.swagger()
- t.equal(swaggerObject.info.title, 'fastify-swagger')
- t.equal(swaggerObject.info.version, '1.0.0')
+ const pkg = readPackageJson(function () {})
+ t.equal(swaggerObject.info.title, pkg.name)
+ t.equal(swaggerObject.info.version, pkg.version)
})
})
diff --git a/test/types/http2-types.test.ts b/test/types/http2-types.test.ts
index 813676f2..e6462d4e 100644
--- a/test/types/http2-types.test.ts
+++ b/test/types/http2-types.test.ts
@@ -1,5 +1,6 @@
import fastify from 'fastify';
import fastifySwagger from '../..';
+import { minimalOpenApiV3Document } from './minimal-openapiV3-document';
const app = fastify({
http2: true
@@ -11,7 +12,7 @@ app.register(fastifySwagger, { transform: (schema : any) => schema });
app.register(fastifySwagger, {
mode: 'static',
specification: {
- document: 'path'
+ document: minimalOpenApiV3Document
},
routePrefix: '/documentation',
exposeRoute: true,
diff --git a/test/types/imports.test.ts b/test/types/imports.test.ts
index 37b22491..28ee3c8f 100644
--- a/test/types/imports.test.ts
+++ b/test/types/imports.test.ts
@@ -2,12 +2,13 @@ import fastify from "fastify";
import swaggerDefault, { fastifySwagger, SwaggerOptions } from "../..";
import * as fastifySwaggerStar from "../..";
+import { minimalOpenApiV3Document } from './minimal-openapiV3-document';
const app = fastify();
const fastifySwaggerOptions: SwaggerOptions = {
mode: "static",
specification: {
- document: "path",
+ document: minimalOpenApiV3Document,
},
routePrefix: "/documentation",
exposeRoute: true,
diff --git a/test/types/minimal-openapiV3-document.ts b/test/types/minimal-openapiV3-document.ts
new file mode 100644
index 00000000..dd94e296
--- /dev/null
+++ b/test/types/minimal-openapiV3-document.ts
@@ -0,0 +1,11 @@
+import { OpenAPIV3 } from 'openapi-types'
+
+export const minimalOpenApiV3Document: OpenAPIV3.Document = {
+ openapi: '3.0.0',
+ info: {
+ "version": "1.0.0",
+ "title": "Test OpenApiv3 specification",
+ },
+ "paths": {
+ }
+}
diff --git a/test/types/types.test.ts b/test/types/types.test.ts
index 4af34522..72eb27cf 100644
--- a/test/types/types.test.ts
+++ b/test/types/types.test.ts
@@ -1,5 +1,6 @@
import fastify from 'fastify';
import fastifySwagger, { SwaggerOptions } from '../..';
+import { minimalOpenApiV3Document } from './minimal-openapiV3-document';
const app = fastify();
@@ -9,7 +10,7 @@ app.register(fastifySwagger, { transform: (schema : any) => schema });
app.register(fastifySwagger, {
mode: 'static',
specification: {
- document: 'path'
+ document: minimalOpenApiV3Document
},
routePrefix: '/documentation',
exposeRoute: true,
@@ -18,7 +19,7 @@ app.register(fastifySwagger, {
const fastifySwaggerOptions: SwaggerOptions = {
mode: 'static',
specification: {
- document: 'path'
+ document: minimalOpenApiV3Document
},
routePrefix: '/documentation',
exposeRoute: true,
@@ -86,3 +87,32 @@ app
.ready(err => {
app.swagger();
});
+
+app
+ .register(fastifySwagger, {
+ openapi: {
+ info: {
+ title: "Test openapi",
+ description: "testing the fastify swagger api",
+ version: "0.1.0",
+ },
+ servers: [{ url: "http://localhost" }],
+ externalDocs: {
+ url: "https://swagger.io",
+ description: "Find more info here",
+ },
+ components: {
+ schemas: {},
+ securitySchemes: {
+ apiKey: {
+ type: "apiKey",
+ name: "apiKey",
+ in: "header",
+ },
+ },
+ },
+ },
+ })
+ .ready((err) => {
+ app.swagger();
+ });
\ No newline at end of file