UNPKG

swagger-generator-koa

Version:
421 lines (420 loc) 15.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; const swagger_spec_express_1 = require("swagger-spec-express"); const joi_to_swagger_1 = __importDefault(require("joi-to-swagger")); const responsesEnum_1 = __importDefault(require("./responsesEnum")); const lodash_1 = require("lodash"); const fs_1 = __importDefault(require("fs")); const path_1 = require("path"); const koa_convert_1 = __importDefault(require("koa-convert")); const koa_mount_1 = __importDefault(require("koa-mount")); const swagger_ui_koa_1 = __importDefault(require("swagger-ui-koa")); const validate_1 = __importDefault(require("./validation/validate")); /** * This will create models for all the provides responses(with joi vlaidations). * @param {object} responseModel */ function createResponseModel({ responseModel, name }) { let isArray = false; if (responseModel && Array.isArray(responseModel) && responseModel.length) { isArray = true; responseModel = responseModel[0]; } for (const property in responseModel) { if (typeof responseModel[property] === 'string') { responseModel[property] = { type: responseModel[property], }; } } const bodyParameter = { type: isArray ? 'array' : 'object', }; if (isArray) { bodyParameter.items = { type: 'object', properties: responseModel, }; } else { bodyParameter.properties = responseModel; } const model = Object.assign({ name, }, bodyParameter); swagger_spec_express_1.common.addModel(model); } /** * Serve swagger. * @param {*} app express object. * @param {*} endPoint swagger end point. * @param {*} options swagger options. */ function serveSwagger(app, endPoint, options, path) { if (path.routePath) { describeSwagger(app, path.routePath, path.requestModelPath, path.responseModelPath); } else { describeSwaggerWithoutPath(app, path.requestModelPath, path.responseModelPath); } swagger_spec_express_1.initialise(app, options); swagger_spec_express_1.compile(); // compile swagger document app.use(swagger_ui_koa_1.default.serve); // serve swagger static files app.use(koa_convert_1.default(koa_mount_1.default(endPoint, swagger_ui_koa_1.default.setup(swagger_spec_express_1.json(), false, null, null, null)))); } /** * This function will generate json for the success response. * @param {object} schema * @param {object} describe */ function createResponses(schema, responseModel, describe) { const responses = { 500: { description: responsesEnum_1.default[500], }, }; try { if (responseModel && !lodash_1.isEmpty(responseModel)) { for (const key in responseModel) { if (responseModel.hasOwnProperty(key)) { createResponseModel({ responseModel: responseModel[key], name: `${schema.model}${key}ResponseModel`, }); responses[key] = { description: responsesEnum_1.default[key] ? responsesEnum_1.default[key] : '', schema: { $ref: `#/definitions/${schema.model}${key}ResponseModel`, }, }; } } } describe.responses = responses; return describe; } catch (error) { console.log('responseModel', responseModel); console.log('Error while generting response model for swagger', error); describe.responses = responses; return describe; } } /** * This function will generate json given header parameter in schema(with joi validation). * @param {object} schema * @param {object} describe */ function getHeader(schema, describe) { const arr = []; for (const key in schema) { if (schema.hasOwnProperty(key)) { arr.push(key); const query = schema[key]; const queryObject = { name: key, type: query.type ? query.type : query, required: query.required === 'undefined' ? false : true, }; if (query._flags && query._flags.presence) { queryObject.required = query._flags.presence === 'required' ? true : false; } swagger_spec_express_1.common.parameters.addHeader(queryObject); } } if (describe.common.parameters) { describe.common.parameters.header = arr; } else { describe.common.parameters = {}; describe.common.parameters.header = arr; } return describe; } /** * This function will create models for given path and query schema and * convert it to json(with joi validation). * @param {object} schema * @param {string} value either query and path * @param {object} describe */ function getQueryAndPathParamObj(schema, value, describe) { const arr = []; for (const key in schema) { if (schema.hasOwnProperty(key)) { arr.push(key); const query = schema[key]; const queryObject = { name: key, type: query.type ? query.type : query, required: query.required === 'undefined' ? false : true, }; if (query._flags && query._flags.presence) { queryObject.required = query._flags.presence === 'required' ? true : false; } value === 'query' ? swagger_spec_express_1.common.parameters.addQuery(queryObject) : swagger_spec_express_1.common.parameters.addPath(queryObject); } } if (describe.common.parameters) { value === 'query' ? (describe.common.parameters.query = arr) : (describe.common.parameters.path = arr); } else { describe.common.parameters = {}; value === 'query' ? (describe.common.parameters.query = arr) : (describe.common.parameters.path = arr); } return describe; } /** * This function will create models for given body schema * and convert it to json(with joi validation). * @param {object} schema * @param {object} describe */ function getBodyParameters(schema, describe) { const bodyParameter = joi_to_swagger_1.default(schema.body).swagger; const model = Object.assign({ name: `${schema.model}Model`, }, bodyParameter); swagger_spec_express_1.common.addModel(model); swagger_spec_express_1.common.parameters.addBody({ name: `${schema.model}Model`, required: true, description: schema.description || undefined, schema: { $ref: `#/definitions/${schema.model}Model`, }, }); describe.common = { parameters: { body: [`${schema.model}Model`], }, }; return describe; } /** * This function will create proper schema based on given body, query, header and path parameter * mentioned in the schema. * @param {object} schema this is schema mentioned for each API in a route. */ function createModel(schema, responseModel) { let describe = { tags: [schema.group], common: {}, }; describe = Object.assign({}, createResponses(schema, responseModel, describe)); if (schema && schema.body) { const bodyParams = getBodyParameters(schema, describe); describe = Object.assign({}, bodyParams); } if (schema && schema.query) { const queryParams = getQueryAndPathParamObj(schema.query, 'query', describe); describe = Object.assign({}, queryParams); } if (schema && schema.path) { const pathParams = getQueryAndPathParamObj(schema.path, 'path', describe); describe = Object.assign({}, pathParams); } if (schema && schema.header) { const headerParams = getHeader(schema.header, describe); describe = Object.assign({}, headerParams); } return describe; } /** * @param app : Koa object * @param routePath : routh folder path. * @param requestModelPath : request model path * @param responseModelPath : responsemodel model path. */ function describeSwagger(app, routePath, requestModelPath, responseModelPath) { try { app._router = { stack: [], }; const rootPath = path_1.resolve(__dirname).split('node_modules')[0]; fs_1.default.readdirSync(path_1.join(rootPath, routePath)).forEach((file) => { if (!file) { console.log('No router file found in given folder'); return; } let responseModel; let requestModel; const route = path_1.join(rootPath, routePath, file); const router = require(route); if (!router || !router.stack) { console.log('Router missing'); return; } if (responseModelPath) { const responseModelFullPath = path_1.join(rootPath, responseModelPath, file); if (fs_1.default.existsSync(responseModelFullPath)) { responseModel = require(responseModelFullPath); } else { console.log('Response model path does not exist responseModelFullPath->', responseModelFullPath); } } if (requestModelPath) { const requestModelFullPath = path_1.join(rootPath, requestModelPath, file); if (fs_1.default.existsSync(requestModelFullPath)) { requestModel = require(requestModelFullPath); } else { console.log('Response model path does not exist requestModelFullPath->', requestModelFullPath); } } processRouter(app, router, requestModel, responseModel, file.split('.')[0]); }); } catch (error) { console.log(`Error in describeSwagger ${error}`); return; } } function processRouter(app, item, requestModel, responseModel, routerName) { try { if (item.stack && item.stack.length > 0) { let count = 0; // tslint:disable-next-line:no-unused-expression lodash_1.map(item.stack, (route) => { let routeRequestModel = lodash_1.get(requestModel, [count]); const routeResposeModel = lodash_1.get(responseModel, lodash_1.get(routeRequestModel, ['model'])); if (routeRequestModel && routeRequestModel.excludeFromSwagger) { count++; return; } if (!routeRequestModel || !lodash_1.has(routeRequestModel, 'group')) { routeRequestModel = { group: routerName, description: routerName, }; } const data = Object.assign({}, createModel(routeRequestModel, routeResposeModel)); describeRouterRoute(route, data); app._router['stack'].push(route); count++; return item; })[0]; } } catch (error) { console.log(`Error in processRouter ${error}`); return; } } function describeRouterRoute(router, metaData) { if (metaData.described) { console.warn('Route already described'); return; } if (!metaData) { throw new Error('Metadata must be set when calling describe'); } if (!router) { throw new Error( // tslint:disable-next-line:max-line-length 'router was null, either the item that swaggerize & describe was called on is not an express app/router or you have called describe before adding at least one route'); } if (!router) { throw new Error( // tslint:disable-next-line:max-line-length 'Unable to add swagger metadata to last route since the last item in the stack was not a route. Route name :' + router.name + '. Metadata :' + JSON.stringify(metaData)); } const verb = router.methods[0] === 'HEAD' ? router.methods[1] : router.methods[0]; if (!verb) { throw new Error( // tslint:disable-next-line:max-line-length "Unable to add swagger metadata to last route since the last route's methods property was empty" + router.name + '. Metadata :' + JSON.stringify(metaData)); } swagger_spec_express_1.ensureValid(metaData); ensureAtLeastOneResponse(metaData); metaData.path = router.path; metaData.verb = verb.toLowerCase(); router.swaggerData = metaData; router.route = { swaggerData: metaData, path: metaData.path, }; metaData.described = true; } /** * @param app : Koa object * @param requestModelPath : request model path * @param responseModelPath : responsemodel model path. */ function describeSwaggerWithoutPath(app, requestModelPath, responseModelPath) { try { app._router = { stack: [], }; const rootPath = path_1.resolve(__dirname).split('node_modules')[0]; app.middleware.forEach((middleware) => { let responseModel; let requestModel; if (middleware.name !== 'dispatch') { return; } if (!middleware.router || !middleware.router.stack) { console.log('Router missing'); return; } const routerPrefix = lodash_1.get(middleware, ['router', 'opts', 'prefix']); let routerBasePath; if (routerPrefix) { routerBasePath = routerPrefix.replace(/\//g, ''); } if (!routerBasePath) { routerBasePath = 'Home'; } if (responseModelPath && routerPrefix) { const responseModelFullPath = path_1.join(rootPath, responseModelPath, `${routerPrefix}.js`); if (fs_1.default.existsSync(responseModelFullPath)) { responseModel = require(responseModelFullPath); } else { console.log('Response model path does not exist responseModelFullPath->', responseModelFullPath); } } if (requestModelPath && routerPrefix) { const requestModelFullPath = path_1.join(rootPath, requestModelPath, `${routerPrefix}.js`); if (fs_1.default.existsSync(requestModelFullPath)) { requestModel = require(requestModelFullPath); } else { console.log('Response model path does not exist requestModelFullPath->', requestModelFullPath); } } processRouter(app, middleware.router, requestModel, responseModel, routerBasePath); }); } catch (error) { console.log(`Error in describeSwagger ${error}`); return; } } function ensureAtLeastOneResponse(metaData) { if (metaData.responses && Object.keys(metaData.responses).length > 0) { return; } if (metaData.common && metaData.common.responses.length > 0) { return; } throw new Error( // tslint:disable-next-line:max-line-length 'Each metadata description for a route must have at least one response, either specified in metaData.responses or metaData.common.responses.'); } module.exports = { swaggerize: swagger_spec_express_1.swaggerize, createModel, serveSwagger, describeSwaggerWithoutPath, validation: validate_1.default };