UNPKG

fastify-oas

Version:

Fastify OpenAPI specification generator plugin

309 lines (276 loc) 8.47 kB
const fs = require('fs'); const path = require('path'); const jsyaml = require('js-yaml'); const root = require('app-root-path'); const helpers = require('../helpers'); const DEFAULTS = { name: 'fastify-oas', description: 'Fastify OpenAPI', version: '1.0.0', openapi: '3.0.0', host: '127.0.0.1', schemes: ['http'], basePath: '/', consumes: ['*/*'], produces: ['*/*'], }; const flattenObject = (ob, prefix) => { const toReturn = {}; prefix = prefix ? prefix + '.' : ''; for (let i in ob) { if (!Object.prototype.hasOwnProperty.call(ob, i)) continue; if (typeof ob[i] === 'object' && ob[i] !== null) { // Recursion on deeper objects Object.assign(toReturn, flattenObject(ob[i], prefix + i)); } else { toReturn[prefix + i] = ob[i]; } } return toReturn; }; const get = (value, path, defaultValue) => String(path) .split('.') .reduce((acc, v) => { try { return acc[v] === undefined ? defaultValue : acc[v]; } catch (e) { return defaultValue; } }, value); const set = (obj, path, value) => { if (Object(obj) !== obj) return obj; if (!path) { // Object is a reference, substitute with resolved schema Reflect.deleteProperty(obj, '$ref'); return Object.assign(obj, value); } if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || []; path .slice(0, -1) .reduce( (a, c, i) => Object(a[c]) === a[c] ? a[c] : (a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {}), obj )[path[path.length - 1]] = value; return obj; }; module.exports = ({ options = {}, getSchemas, getSchema, routes = [], } = {}) => { try { const data = fs.readFileSync(path.join(root.toString(), 'package.json'), { encoding: 'utf8', }); const { name, description, version } = JSON.parse(data); if (name) DEFAULTS.name = name; if (description) DEFAULTS.description = description; if (version) DEFAULTS.version = version; } catch (_) { // do nothing } const cache = { swaggerObject: null, swaggerString: null, }; const resolveReferences = (schema, cache = {}) => { if (schema) { const flat = flattenObject(schema); const refs = Object.keys(flat).filter((k) => k.indexOf('$ref') > -1); refs.forEach((ref) => { const refValue = get(schema, ref); if (refValue.indexOf('#') === refValue.length - 1) { const schemaId = refValue.substring(0, refValue.indexOf('#')); const resolved = cache[schemaId] || (cache[schemaId] = getSchema(schemaId)); set( schema, ref.substring(0, ref.indexOf('$ref')), resolveReferences(resolved, cache) ); } }); } return schema; }; const handler = (opts = {}) => { // opts = Object.assign({}, options, opts); const { yaml, swagger, openapi, addModels, hideUntagged } = Object.assign( { yaml: false, swagger: {}, openapi: DEFAULTS.openapi, addModels: false, hideUntagged: false, }, options, opts ); if (yaml) { if (cache.swaggerString) return cache.swaggerString; } else { if (cache.swaggerObject) return cache.swaggerObject; } const swaggerObject = {}; swaggerObject.openapi = openapi; if (swagger.info) { swaggerObject.info = swagger.info; } else { swaggerObject.info = { title: DEFAULTS.name, description: DEFAULTS.description, version: DEFAULTS.version, }; } swaggerObject.components = swagger.components || {}; if (swagger.tags) { swaggerObject.tags = swagger.tags; } if (swagger['x-tagGroups']) { // redoc extension swaggerObject['x-tagGroups'] = swagger['x-tagGroups']; } if (swagger.externalDocs) { swaggerObject.externalDocs = swagger.externalDocs; } if (swagger.security) { swaggerObject.security = swagger.security; } if (swagger.servers) { // openapi 3 swaggerObject.servers = swagger.servers; } else { const host = swagger.host || DEFAULTS.host; const schemes = swagger.schemes || DEFAULTS.schemes; const basePath = swagger.basePath || DEFAULTS.basePath; // port from swagger 2 swaggerObject.servers = schemes.map((sch) => { return { url: `${sch}://${host}${basePath}`, }; }); } if (swagger.securityDefinitions) { // swagger 2 securityDefinitions swaggerObject.components.securitySchemes = Object.assign( {}, swaggerObject.components.securitySchemes, swagger.securityDefinitions ); } const defaultConsumes = swagger.consumes || DEFAULTS.consumes; const defaultProduces = swagger.produces || DEFAULTS.produces; if (addModels && typeof getSchemas === 'function') { const schemas = getSchemas(); const schemaKeys = Object.keys(schemas); swaggerObject.components.schemas = swaggerObject.components.schemas || {}; const schemaDst = swaggerObject.components.schemas; for (const schemaKey of schemaKeys) { const schema = helpers.clone(schemas[schemaKey]); const id = schema.$id || schemaKey; delete schema.$id; schemaDst[id] = schema; } } swaggerObject.paths = {}; for (const routeOpts of routes) { const route = helpers.clone(routeOpts); if ( (route.schema && route.schema.hide) || (hideUntagged && (!route.schema || !route.schema.tags)) ) { continue; } const schema = route.schema; const url = helpers.formatParamUrl(route.url); const swaggerRoute = swaggerObject.paths[url] || {}; const swaggerMethod = { responses: {}, }; const parameters = []; const methods = typeof route.method === 'string' ? [route.method] : route.method; for (const method of methods) { swaggerRoute[method.toLowerCase()] = swaggerMethod; } if (schema) { schema.consumes = schema.consumes || defaultConsumes; schema.produces = schema.produces || defaultProduces; if (schema.summary) { swaggerMethod.summary = schema.summary; } if (schema.description) { swaggerMethod.description = schema.description; } if (schema.tags) { swaggerMethod.tags = schema.tags; } if (schema.deprecated) { swaggerMethod.deprecated = schema.deprecated; } if (schema.security) { swaggerMethod.security = schema.security; } if (schema.operationId) { swaggerMethod.operationId = schema.operationId; } if (schema.querystring) { resolveReferences(schema.querystring); helpers.genQuery(parameters, schema.querystring); } if (schema.body) { swaggerMethod.requestBody = {}; resolveReferences(schema.body); helpers.genBody( swaggerMethod.requestBody, schema.body, schema.consumes ); } if (schema.params) { resolveReferences(schema.params); helpers.genPath(parameters, schema.params); } if (schema.headers) { resolveReferences(schema.headers); helpers.genHeaders(parameters, schema.headers); } if (schema.cookies) { resolveReferences(schema.cookies); helpers.genCookies(parameters, schema.cookies); } resolveReferences(schema.response); helpers.genResponse( swaggerMethod.responses, schema.response || { 200: { description: 'Default Response' } }, schema.produces ); if (parameters.length) { swaggerMethod.parameters = parameters; } } else { swaggerMethod.responses = { 200: { description: 'default response', }, }; } swaggerObject.paths[url] = swaggerRoute; } if (yaml) { const swaggerString = jsyaml.safeDump(swaggerObject, { skipInvalid: true, }); cache.swaggerString = swaggerString; return swaggerString; } cache.swaggerObject = swaggerObject; return swaggerObject; }; return handler; };