fastify-swagger
Advanced tools
Comparing version 0.5.0 to 0.6.0
293
index.js
'use strict' | ||
const fp = require('fastify-plugin') | ||
const fs = require('fs') | ||
const path = require('path') | ||
const yaml = require('js-yaml') | ||
const setup = { dynamic: require('./dynamic'), static: require('./static') } | ||
function fastifySwagger (fastify, opts, next) { | ||
fastify.decorate('swagger', swagger) | ||
const routes = [] | ||
fastify.addHook('onRoute', (routeOptions) => { | ||
routes.push(routeOptions) | ||
}) | ||
opts = opts || {} | ||
opts.swagger = opts.swagger || {} | ||
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 basePath = opts.swagger.basePath || null | ||
const securityDefinitions = opts.swagger.securityDefinitions || null | ||
// by default the mode is dynamic, as plugin initially was developed | ||
opts.mode = opts.mode || 'dynamic' | ||
if (opts.exposeRoute === true) { | ||
fastify.register(require('./routes')) | ||
switch (opts.mode) { | ||
case 'static': | ||
setup.static(fastify, opts, next) | ||
break | ||
case 'dynamic': | ||
setup.dynamic(fastify, opts, next) | ||
break | ||
default: | ||
return next(new Error("unsupported mode, should be one of ['static', 'dynamic']")) | ||
} | ||
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 = {} | ||
var 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 (securityDefinitions) { | ||
swaggerObject.securityDefinitions = securityDefinitions | ||
} | ||
swaggerObject.paths = {} | ||
for (var route of routes) { | ||
const url = formatParamUrl(route.url) | ||
const method = route.method.toLowerCase() | ||
const schema = route.schema | ||
const swaggerRoute = swaggerObject.paths[url] || {} | ||
if (schema && schema.hide) { | ||
continue | ||
} | ||
const swaggerMethod = swaggerRoute[method] = {} | ||
const parameters = [] | ||
// 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.summary) { | ||
swaggerMethod.summary = schema.summary | ||
} | ||
if (schema.description) { | ||
swaggerMethod.description = schema.description | ||
} | ||
if (schema.tags) { | ||
swaggerMethod.tags = schema.tags | ||
} | ||
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 | ||
} | ||
} | ||
swaggerMethod.responses = genResponse(schema ? schema.response : null) | ||
if (swaggerRoute) { | ||
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 | ||
} | ||
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') | ||
) | ||
} | ||
function getQueryParams (parameters, query) { | ||
if (query.type && query.properties) { | ||
// for the shorthand querystring declaration | ||
return getQueryParams(parameters, query.properties) | ||
} | ||
Object.keys(query).forEach(prop => { | ||
const obj = query[prop] | ||
const param = obj | ||
param.name = prop | ||
param.in = 'query' | ||
parameters.push(param) | ||
}) | ||
} | ||
function getBodyParams (parameters, body) { | ||
const param = {} | ||
param.name = 'body' | ||
param.in = 'body' | ||
param.schema = body | ||
parameters.push(param) | ||
} | ||
function getFormParams (parameters, body) { | ||
const formParamsSchema = body.properties | ||
if (formParamsSchema) { | ||
Object.keys(formParamsSchema).forEach(name => { | ||
const param = formParamsSchema[name] | ||
delete param.$id | ||
param.in = 'formData' | ||
param.name = name | ||
parameters.push(param) | ||
}) | ||
} | ||
} | ||
function getPathParams (parameters, params) { | ||
if (params.type && params.properties) { | ||
// for the shorthand querystring declaration | ||
return getPathParams(parameters, params.properties) | ||
} | ||
Object.keys(params).forEach(p => { | ||
const param = {} | ||
param.name = p | ||
param.in = 'path' | ||
param.required = true | ||
param.description = params[p].description | ||
param.type = params[p].type | ||
parameters.push(param) | ||
}) | ||
} | ||
function getHeaderParams (parameters, headers) { | ||
if (headers.type && headers.properties) { | ||
// for the shorthand querystring declaration | ||
const headerProperties = Object.keys(headers.properties).reduce((acc, h) => { | ||
const required = (headers.required && headers.required.indexOf(h) >= 0) || false | ||
const newProps = Object.assign({}, headers.properties[h], { required }) | ||
return Object.assign({}, acc, { [h]: newProps }) | ||
}, {}) | ||
return getHeaderParams(parameters, headerProperties) | ||
} | ||
Object.keys(headers).forEach(h => | ||
parameters.push({ | ||
name: h, | ||
in: 'header', | ||
required: headers[h].required, | ||
description: headers[h].description, | ||
type: headers[h].type | ||
}) | ||
) | ||
} | ||
function genResponse (response) { | ||
// if the user does not provided an out schema | ||
if (!response) { | ||
return { 200: { description: 'Default Response' } } | ||
} | ||
// remove previous references | ||
response = Object.assign({}, response) | ||
Object.keys(response).forEach(key => { | ||
if (response[key].type) { | ||
var rsp = response[key] | ||
var description = response[key].description | ||
var headers = response[key].headers | ||
response[key] = { | ||
schema: rsp | ||
} | ||
response[key].description = description || 'Default Response' | ||
if (headers) response[key].headers = headers | ||
} | ||
if (!response[key].description) { | ||
response[key].description = 'Default Response' | ||
} | ||
}) | ||
return response | ||
} | ||
// 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) { | ||
var start = url.indexOf('/:') | ||
if (start === -1) return url | ||
var 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)) | ||
} | ||
} | ||
module.exports = fp(fastifySwagger, { | ||
@@ -293,0 +26,0 @@ fastify: '>=0.39.0', |
{ | ||
"name": "fastify-swagger", | ||
"version": "0.5.0", | ||
"version": "0.6.0", | ||
"description": "Generate Swagger files automatically for Fastify.", | ||
@@ -24,4 +24,4 @@ "main": "index.js", | ||
"devDependencies": { | ||
"fastify": "^0.43.0", | ||
"standard": "^10.0.3", | ||
"fastify": "^1.1.1", | ||
"standard": "^11.0.0", | ||
"swagger-parser": "^4.0.2", | ||
@@ -28,0 +28,0 @@ "tap": "^11.1.0" |
@@ -89,30 +89,50 @@ # fastify-swagger | ||
``` | ||
<a name="api"></a> | ||
## API | ||
#### register options | ||
```js | ||
{ | ||
swagger: { | ||
info: { | ||
title: String, | ||
description: String, | ||
version: String | ||
}, | ||
host: String, | ||
schemes: [ String ], | ||
consumes: [ String ], | ||
produces: [ String ], | ||
securityDefinitions: Object | ||
<a name="register.options"></a> | ||
### register options | ||
<a name="modes"></a> | ||
#### modes | ||
`fastify-swagger` supports two registration modes `dynamic` and `static`: | ||
<a name="mode.dynamic"></a> | ||
##### dynamic | ||
`dynamic` mode is the default one, if you use the plugin this way - swagger specification would be gathered from your routes definitions. | ||
```js | ||
{ | ||
swagger: { | ||
info: { | ||
title: String, | ||
description: String, | ||
version: String | ||
}, | ||
host: String, | ||
schemes: [ String ], | ||
consumes: [ String ], | ||
produces: [ String ], | ||
securityDefinitions: 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! | ||
``` | ||
<a name="options"></a> | ||
#### swagger options | ||
Calling `fastify.swagger` will return to you a JSON object representing your api, if you pass `{ yaml: true }` to `fastify.swagger`, it will return you a yaml string. | ||
*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! | ||
If you pass `{ exposeRoute: true }` the plugin will expose the documentation with the following apis: | ||
Example of the `fastify-swagger` usage in the `dynamic` mode is available [here](examples/dynamic.js). | ||
<a name="mode.static"></a> | ||
##### static | ||
`static` mode should be configured explicitly. In this mode `fastify-swagger` serves given specification, you should craft it yourselfe. | ||
```js | ||
{ | ||
mode: 'static', | ||
specification: { | ||
path: './examples/example-static-specification.yaml' | ||
} | ||
} | ||
``` | ||
Example of the `fastify-swagger` usage in the `static` mode is available [here](examples/static-file.js). | ||
<a name="additional"></a> | ||
#### additional | ||
If you pass `{ exposeRoute: true }` during the registration the plugin will expose the documentation with the following apis: | ||
| url | description | | ||
@@ -124,9 +144,15 @@ |-------|----------------| | ||
<a name="swagger.options"></a> | ||
### swagger options | ||
Calling `fastify.swagger` will return to you a JSON object representing your api, if you pass `{ yaml: true }` to `fastify.swagger`, it will return you a yaml string. | ||
<a name="hide"></a> | ||
#### Hide a route | ||
### Hide a route | ||
Sometimes you may need to hide a certain route from the documentation, just pass `{ hide: true }` to the schema object inside the route declaration. | ||
#### Security | ||
<a name="security"></a> | ||
### Security | ||
Global security definitions and route level security provide documentation only. It does not implement authentication nor route security for you. Once your authentication is implemented, along with your defined security, users will be able to successfully authenticate and interact with your API using the user interfaces of the documentation. | ||
<a name="anknowledgements"></a> | ||
## Acknowledgements | ||
@@ -138,4 +164,5 @@ | ||
<a name="license"></a> | ||
## License | ||
Licensed under [MIT](./LICENSE). |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
17822389
25
10191
166
4