swagger-generator-koa
Version:
Generate automatic swagger using joi validation.
421 lines (420 loc) • 15.8 kB
JavaScript
;
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
};