express-swagger-generator
Advanced tools
Comparing version 1.0.4 to 1.1.0
'use strict'; | ||
// Dependencies. | ||
var RecursiveIterator = require('recursive-iterator'); | ||
const RecursiveIterator = require('recursive-iterator'); | ||
@@ -16,16 +16,16 @@ /** | ||
function _tagDuplicated(target, tag) { | ||
// Check input is workable. | ||
if (target && target.length && tag) { | ||
for (var i = 0; i < target.length; i = i + 1) { | ||
var targetTag = target[i]; | ||
// The name of the tag to include already exists in the taget. | ||
// Therefore, it's not necessary to be added again. | ||
if (targetTag.name === tag.name) { | ||
return true; | ||
} | ||
} | ||
} | ||
// Check input is workable. | ||
if (target && target.length && tag) { | ||
for (let i = 0; i < target.length; i = i + 1) { | ||
let targetTag = target[i]; | ||
// The name of the tag to include already exists in the taget. | ||
// Therefore, it's not necessary to be added again. | ||
if (targetTag.name === tag.name) { | ||
return true; | ||
} | ||
} | ||
} | ||
// This will indicate that `tag` is not present in `target`. | ||
return false; | ||
// This will indicate that `tag` is not present in `target`. | ||
return false; | ||
} | ||
@@ -39,22 +39,22 @@ | ||
function _attachTags(conf) { | ||
var tag = conf.tag; | ||
var swaggerObject = conf.swaggerObject; | ||
var propertyName = conf.propertyName; | ||
let tag = conf.tag; | ||
let swaggerObject = conf.swaggerObject; | ||
let propertyName = conf.propertyName; | ||
// Correct deprecated property. | ||
if (propertyName === 'tag') { | ||
propertyName = 'tags'; | ||
} | ||
// Correct deprecated property. | ||
if (propertyName === 'tag') { | ||
propertyName = 'tags'; | ||
} | ||
if (Array.isArray(tag)) { | ||
for (var i = 0; i < tag.length; i = i + 1) { | ||
if (!_tagDuplicated(swaggerObject[propertyName], tag[i])) { | ||
swaggerObject[propertyName].push(tag[i]); | ||
} | ||
} | ||
} else { | ||
if (!_tagDuplicated(swaggerObject[propertyName], tag)) { | ||
swaggerObject[propertyName].push(tag); | ||
} | ||
} | ||
if (Array.isArray(tag)) { | ||
for (let i = 0; i < tag.length; i = i + 1) { | ||
if (!_tagDuplicated(swaggerObject[propertyName], tag[i])) { | ||
swaggerObject[propertyName].push(tag[i]); | ||
} | ||
} | ||
} else { | ||
if (!_tagDuplicated(swaggerObject[propertyName], tag)) { | ||
swaggerObject[propertyName].push(tag); | ||
} | ||
} | ||
} | ||
@@ -70,14 +70,14 @@ | ||
function _objectMerge(obj1, obj2) { | ||
var obj3 = {}; | ||
for (var attr in obj1) { | ||
if (obj1.hasOwnProperty(attr)) { | ||
obj3[attr] = obj1[attr]; | ||
} | ||
} | ||
for (var name in obj2) { | ||
if (obj2.hasOwnProperty(name)) { | ||
obj3[name] = obj2[name]; | ||
} | ||
} | ||
return obj3; | ||
let obj3 = {}; | ||
for (let attr in obj1) { | ||
if (obj1.hasOwnProperty(attr)) { | ||
obj3[attr] = obj1[attr]; | ||
} | ||
} | ||
for (let name in obj2) { | ||
if (obj2.hasOwnProperty(name)) { | ||
obj3[name] = obj2[name]; | ||
} | ||
} | ||
return obj3; | ||
} | ||
@@ -93,10 +93,10 @@ | ||
function swaggerizeObj(swaggerObject) { | ||
swaggerObject.swagger = '2.0'; | ||
swaggerObject.paths = swaggerObject.paths || {}; | ||
swaggerObject.definitions = swaggerObject.definitions || {}; | ||
swaggerObject.responses = swaggerObject.responses || {}; | ||
swaggerObject.parameters = swaggerObject.parameters || {}; | ||
swaggerObject.securityDefinitions = swaggerObject.securityDefinitions || {}; | ||
swaggerObject.tags = swaggerObject.tags || []; | ||
return swaggerObject; | ||
swaggerObject.swagger = '2.0'; | ||
swaggerObject.paths = swaggerObject.paths || {}; | ||
swaggerObject.definitions = swaggerObject.definitions || {}; | ||
swaggerObject.responses = swaggerObject.responses || {}; | ||
swaggerObject.parameters = swaggerObject.parameters || {}; | ||
swaggerObject.securityDefinitions = swaggerObject.securityDefinitions || {}; | ||
swaggerObject.tags = swaggerObject.tags || []; | ||
return swaggerObject; | ||
} | ||
@@ -110,13 +110,13 @@ | ||
function _getSwaggerSchemaWrongProperties() { | ||
return [ | ||
'consume', | ||
'produce', | ||
'path', | ||
'tag', | ||
'definition', | ||
'securityDefinition', | ||
'scheme', | ||
'response', | ||
'parameter', | ||
]; | ||
return [ | ||
'consume', | ||
'produce', | ||
'path', | ||
'tag', | ||
'definition', | ||
'securityDefinition', | ||
'scheme', | ||
'response', | ||
'parameter', | ||
]; | ||
} | ||
@@ -131,8 +131,8 @@ | ||
function _correctSwaggerKey(propertyName) { | ||
var wrong = _getSwaggerSchemaWrongProperties(); | ||
if (wrong.indexOf(propertyName) > 0) { | ||
// Returns the corrected property name. | ||
return propertyName + 's'; | ||
} | ||
return propertyName; | ||
let wrong = _getSwaggerSchemaWrongProperties(); | ||
if (wrong.indexOf(propertyName) > 0) { | ||
// Returns the corrected property name. | ||
return propertyName + 's'; | ||
} | ||
return propertyName; | ||
} | ||
@@ -148,45 +148,54 @@ | ||
function _organizeSwaggerProperties(swaggerObject, pathObject, propertyName) { | ||
var simpleProperties = [ | ||
'consume', | ||
'consumes', | ||
'produce', | ||
'produces', | ||
'path', | ||
'paths', | ||
'schema', | ||
'schemas', | ||
'securityDefinition', | ||
'securityDefinitions', | ||
'response', | ||
'responses', | ||
'parameter', | ||
'parameters', | ||
'definition', | ||
'definitions', | ||
]; | ||
let simpleProperties = [ | ||
'consume', | ||
'consumes', | ||
'produce', | ||
'produces', | ||
// 'path', | ||
// 'paths', | ||
'schema', | ||
'schemas', | ||
'securityDefinition', | ||
'securityDefinitions', | ||
'response', | ||
'responses', | ||
'parameter', | ||
'parameters', | ||
'definition', | ||
'definitions', | ||
]; | ||
// Common properties. | ||
if (simpleProperties.indexOf(propertyName) !== -1) { | ||
var keyName = _correctSwaggerKey(propertyName); | ||
var definitionNames = Object | ||
.getOwnPropertyNames(pathObject[propertyName]); | ||
for (var k = 0; k < definitionNames.length; k = k + 1) { | ||
var definitionName = definitionNames[k]; | ||
swaggerObject[keyName][definitionName] = | ||
pathObject[propertyName][definitionName]; | ||
} | ||
// Tags. | ||
} else if (propertyName === 'tag' || propertyName === 'tags') { | ||
var tag = pathObject[propertyName]; | ||
_attachTags({ | ||
tag: tag, | ||
swaggerObject: swaggerObject, | ||
propertyName: propertyName, | ||
}); | ||
// Paths. | ||
} else { | ||
swaggerObject.paths[propertyName] = _objectMerge( | ||
swaggerObject.paths[propertyName], pathObject[propertyName] | ||
); | ||
} | ||
// Common properties. | ||
if (simpleProperties.indexOf(propertyName) !== -1) { | ||
let keyName = _correctSwaggerKey(propertyName); | ||
let definitionNames = Object | ||
.getOwnPropertyNames(pathObject[propertyName]); | ||
for (let k = 0; k < definitionNames.length; k = k + 1) { | ||
let definitionName = definitionNames[k]; | ||
swaggerObject[keyName][definitionName] = | ||
pathObject[propertyName][definitionName]; | ||
} | ||
// Tags. | ||
} else if (propertyName === 'tag' || propertyName === 'tags') { | ||
let tag = pathObject[propertyName]; | ||
_attachTags({ | ||
tag: tag, | ||
swaggerObject: swaggerObject, | ||
propertyName: propertyName, | ||
}); | ||
// Paths. | ||
} else { | ||
let routes = Object | ||
.getOwnPropertyNames(pathObject[propertyName]); | ||
for (let k = 0; k < routes.length; k = k + 1) { | ||
let route = routes[k]; | ||
if(!swaggerObject.paths){ | ||
swaggerObject.paths = {}; | ||
} | ||
swaggerObject.paths[route] = _objectMerge( | ||
swaggerObject.paths[route], pathObject[propertyName][route] | ||
); | ||
} | ||
} | ||
} | ||
@@ -202,16 +211,16 @@ | ||
function addDataToSwaggerObject(swaggerObject, data) { | ||
if (!swaggerObject || !data) { | ||
throw new Error('swaggerObject and data are required!'); | ||
} | ||
if (!swaggerObject || !data) { | ||
throw new Error('swaggerObject and data are required!'); | ||
} | ||
for (var i = 0; i < data.length; i = i + 1) { | ||
var pathObject = data[i]; | ||
var propertyNames = Object.getOwnPropertyNames(pathObject); | ||
// Iterating the properties of the a given pathObject. | ||
for (var j = 0; j < propertyNames.length; j = j + 1) { | ||
var propertyName = propertyNames[j]; | ||
// Do what's necessary to organize the end specification. | ||
_organizeSwaggerProperties(swaggerObject, pathObject, propertyName); | ||
} | ||
} | ||
for (let i = 0; i < data.length; i = i + 1) { | ||
let pathObject = data[i]; | ||
let propertyNames = Object.getOwnPropertyNames(pathObject); | ||
// Iterating the properties of the a given pathObject. | ||
for (let j = 0; j < propertyNames.length; j = j + 1) { | ||
let propertyName = propertyNames[j]; | ||
// Do what's necessary to organize the end specification. | ||
_organizeSwaggerProperties(swaggerObject, pathObject, propertyName); | ||
} | ||
} | ||
} | ||
@@ -227,11 +236,11 @@ | ||
function seekWrong(list, wrongSet, problems) { | ||
var iterator = new RecursiveIterator(list, 0, false); | ||
for (var item = iterator.next(); !item.done; item = iterator.next()) { | ||
var isDirectChildOfProperties = | ||
item.value.path[item.value.path.length - 2] === 'properties'; | ||
let iterator = new RecursiveIterator(list, 0, false); | ||
for (let item = iterator.next(); !item.done; item = iterator.next()) { | ||
let isDirectChildOfProperties = | ||
item.value.path[item.value.path.length - 2] === 'properties'; | ||
if (wrongSet.indexOf(item.value.key) > 0 && !isDirectChildOfProperties) { | ||
problems.push(item.value.key); | ||
} | ||
} | ||
if (wrongSet.indexOf(item.value.key) > 0 && !isDirectChildOfProperties) { | ||
problems.push(item.value.key); | ||
} | ||
} | ||
} | ||
@@ -246,16 +255,16 @@ | ||
function findDeprecated(sources) { | ||
var wrong = _getSwaggerSchemaWrongProperties(); | ||
// accumulate problems encountered | ||
var problems = []; | ||
sources.forEach(function(source) { | ||
// Iterate through `source`, search for `wrong`, accumulate in `problems`. | ||
seekWrong(source, wrong, problems); | ||
}); | ||
return problems; | ||
let wrong = _getSwaggerSchemaWrongProperties(); | ||
// accumulate problems encountered | ||
let problems = []; | ||
sources.forEach(function(source) { | ||
// Iterate through `source`, search for `wrong`, accumulate in `problems`. | ||
seekWrong(source, wrong, problems); | ||
}); | ||
return problems; | ||
} | ||
module.exports = { | ||
addDataToSwaggerObject: addDataToSwaggerObject, | ||
swaggerizeObj: swaggerizeObj, | ||
findDeprecated: findDeprecated, | ||
}; | ||
addDataToSwaggerObject: addDataToSwaggerObject, | ||
swaggerizeObj: swaggerizeObj, | ||
findDeprecated: findDeprecated, | ||
}; |
@@ -25,88 +25,136 @@ /** | ||
function parseApiFile(file) { | ||
const content = fs.readFileSync(file, 'utf-8'); | ||
const content = fs.readFileSync(file, 'utf-8'); | ||
let comments = doctrineFile.parseFileContent(content, {unwrap: true, sloppy: true}); | ||
return comments; | ||
let comments = doctrineFile.parseFileContent(content, {unwrap: true, sloppy: true, tags: null, recoverable: true}); | ||
return comments; | ||
} | ||
function parseRoute(str) { | ||
let split = str.split(" ") | ||
let split = str.split(" ") | ||
return { | ||
method: split[0].toLowerCase() || 'get', | ||
uri: split[1] || '' | ||
} | ||
return { | ||
method: split[0].toLowerCase() || 'get', | ||
uri: split[1] || '' | ||
} | ||
} | ||
function parseField(str) { | ||
let split = str.split(".") | ||
return { | ||
name: split[0], | ||
parameter_type: split[1] || 'get', | ||
required: split[2] && split[2] === 'required' || false | ||
} | ||
let split = str.split(".") | ||
return { | ||
name: split[0], | ||
parameter_type: split[1] || 'get', | ||
required: split[2] && split[2] === 'required' || false | ||
} | ||
} | ||
function parseType(obj) { | ||
return obj.name || 'string' | ||
if(!obj) return undefined; | ||
if(!obj.name) return 'string'; | ||
const spl = obj.name.split('.'); | ||
if(spl.length > 1 && spl[1] == 'model'){ | ||
return spl[0]; | ||
} | ||
else return obj.name; | ||
} | ||
function parseSchema(obj){ | ||
if(!obj.name) return undefined; | ||
const spl = obj.name.split('.'); | ||
if(spl.length > 1 && spl[1] == 'model'){ | ||
return { "$ref": "#/definitions/" + spl[0] }; | ||
} | ||
else return undefined; | ||
} | ||
function parseReturn(tags) { | ||
let rets = {} | ||
for (let i in tags) { | ||
if (tags[i]['title'] == 'returns' || tags[i]['title'] == 'return') { | ||
let description = tags[i]['description'].split("-") | ||
rets[description[0]] = {description: description[1]} | ||
} | ||
} | ||
return rets | ||
let rets = {} | ||
for (let i in tags) { | ||
if (tags[i]['title'] == 'returns' || tags[i]['title'] == 'return') { | ||
let description = tags[i]['description'].split("-") | ||
rets[description[0]] = {description: description[1]}; | ||
const type = parseType(tags[i].type); | ||
if(type){ | ||
rets[description[0]].type = type; | ||
rets[description[0]].schema = parseSchema(tags[i].type) | ||
} | ||
} | ||
} | ||
return rets | ||
} | ||
function parseDescription(obj) { | ||
return obj.description || '' | ||
return obj.description || '' | ||
} | ||
function parseTag(tags) { | ||
for (var i in tags) { | ||
if (tags[i]['title'] == 'group') { | ||
return tags[i]['description'].split("-") | ||
} | ||
} | ||
return ['default', ''] | ||
for (let i in tags) { | ||
if (tags[i]['title'] == 'group') { | ||
return tags[i]['description'].split("-") | ||
} | ||
} | ||
return ['default', ''] | ||
} | ||
function parseTypedef(tags){ | ||
const typeName = tags[0]['name']; | ||
let details = { | ||
required: [], | ||
properties: {} | ||
}; | ||
for(let i = 1; i < tags.length; i++){ | ||
if(tags[i].title == 'property'){ | ||
let propName = tags[i].name; | ||
const required = propName.split('.')[1]; | ||
if(required && required == 'required'){ | ||
propName = propName.split('.')[0]; | ||
details.required.push(propName); | ||
} | ||
details.properties[propName] = { | ||
type: parseType(tags[i].type), | ||
schema: parseSchema(tags[i].type) | ||
}; | ||
} | ||
} | ||
return {typeName, details}; | ||
} | ||
function fileFormat(comments) { | ||
let route, parameters = {}, params = [], tags = []; | ||
for (let i in comments) { | ||
let desc = parseDescription(comments); | ||
if (i == 'tags') { | ||
for (let j in comments[i]) { | ||
let title = comments[i][j]['title'] | ||
if (title == 'route') { | ||
route = parseRoute(comments[i][j]['description']) | ||
let tag = parseTag(comments[i]) | ||
parameters[route.uri] = {} | ||
parameters[route.uri][route.method] = {} | ||
parameters[route.uri][route.method]['parameters'] = [] | ||
parameters[route.uri][route.method]['description'] = desc | ||
parameters[route.uri][route.method]['tags'] = [tag[0]] | ||
tags.push({ | ||
name: tag[0], | ||
description: tag[1] | ||
}) | ||
} | ||
if (title == 'param') { | ||
let field = parseField(comments[i][j]['name']) | ||
params.push({ | ||
name: field.name, | ||
in: field.parameter_type, | ||
description: comments[i][j]['description'], | ||
required: field.required, | ||
type: parseType(comments[i][j]['type']) | ||
}) | ||
} | ||
} | ||
parameters[route.uri][route.method]['parameters'] = params; | ||
parameters[route.uri][route.method]['responses'] = parseReturn(comments[i]); | ||
} | ||
} | ||
return {parameters: parameters, tags: tags} | ||
let route, parameters = {}, params = [], tags = [], definitions = {}; | ||
for (let i in comments) { | ||
let desc = parseDescription(comments); | ||
if (i == 'tags') { | ||
if(comments[i].length > 0 && comments[i][0]['title'] && comments[i][0]['title'] == 'typedef'){ | ||
const typedefParsed = parseTypedef(comments[i]); | ||
definitions[typedefParsed.typeName] = typedefParsed.details; | ||
continue; | ||
} | ||
for (let j in comments[i]) { | ||
let title = comments[i][j]['title'] | ||
if (title == 'route') { | ||
route = parseRoute(comments[i][j]['description']) | ||
let tag = parseTag(comments[i]) | ||
parameters[route.uri] = parameters[route.uri] || {} | ||
parameters[route.uri][route.method] = parameters[route.uri][route.method] || {} | ||
parameters[route.uri][route.method]['parameters'] = [] | ||
parameters[route.uri][route.method]['description'] = desc | ||
parameters[route.uri][route.method]['tags'] = [tag[0]] | ||
tags.push({ | ||
name: tag[0], | ||
description: tag[1] | ||
}) | ||
} | ||
if (title == 'param') { | ||
let field = parseField(comments[i][j]['name']) | ||
params.push({ | ||
name: field.name, | ||
in: field.parameter_type, | ||
description: comments[i][j]['description'], | ||
required: field.required, | ||
type: parseType(comments[i][j]['type']), | ||
schema: parseSchema(comments[i][j]['type']) | ||
}) | ||
} | ||
parameters[route.uri][route.method]['parameters'] = params; | ||
parameters[route.uri][route.method]['responses'] = parseReturn(comments[i]); | ||
} | ||
} | ||
} | ||
return {parameters: parameters, tags: tags, definitions: definitions} | ||
} | ||
@@ -122,5 +170,5 @@ | ||
function filterJsDocComments(jsDocComments) { | ||
return jsDocComments.filter(function (item) { | ||
return item.tags.length > 0 | ||
}) | ||
return jsDocComments.filter(function (item) { | ||
return item.tags.length > 0 | ||
}) | ||
} | ||
@@ -136,6 +184,6 @@ | ||
function convertGlobPaths(base, globs) { | ||
return globs.reduce(function (acc, globString) { | ||
var globFiles = glob.sync(path.resolve(base, globString)); | ||
return acc.concat(globFiles); | ||
}, []); | ||
return globs.reduce(function (acc, globString) { | ||
let globFiles = glob.sync(path.resolve(base, globString)); | ||
return acc.concat(globFiles); | ||
}, []); | ||
} | ||
@@ -152,40 +200,42 @@ | ||
return function (options) { | ||
/* istanbul ignore if */ | ||
if (!options) { | ||
throw new Error('\'options\' is required.'); | ||
} else /* istanbul ignore if */ if (!options.swaggerDefinition) { | ||
throw new Error('\'swaggerDefinition\' is required.'); | ||
} else /* istanbul ignore if */ if (!options.files) { | ||
throw new Error('\'files\' is required.'); | ||
} | ||
return function (options) { | ||
/* istanbul ignore if */ | ||
if (!options) { | ||
throw new Error('\'options\' is required.'); | ||
} else /* istanbul ignore if */ if (!options.swaggerDefinition) { | ||
throw new Error('\'swaggerDefinition\' is required.'); | ||
} else /* istanbul ignore if */ if (!options.files) { | ||
throw new Error('\'files\' is required.'); | ||
} | ||
// Build basic swagger json | ||
var swaggerObject = swaggerHelpers.swaggerizeObj(options.swaggerDefinition); | ||
var apiFiles = convertGlobPaths(options.basedir, options.files); | ||
// Build basic swagger json | ||
let swaggerObject = swaggerHelpers.swaggerizeObj(options.swaggerDefinition); | ||
let apiFiles = convertGlobPaths(options.basedir, options.files); | ||
// Parse the documentation in the APIs array. | ||
for (var i = 0; i < apiFiles.length; i = i + 1) { | ||
var comments = parseApiFile(apiFiles[i]); | ||
var comments = filterJsDocComments(comments); | ||
// Parse the documentation in the APIs array. | ||
for (let i = 0; i < apiFiles.length; i = i + 1) { | ||
let parsedFile = parseApiFile(apiFiles[i]); | ||
//console.log(JSON.stringify(parsedFile)) | ||
let comments = filterJsDocComments(parsedFile); | ||
for (let i in comments) { | ||
var parsed = fileFormat(comments[i]) | ||
swaggerHelpers.addDataToSwaggerObject(swaggerObject, [{paths: parsed.parameters, tags: parsed.tags}]); | ||
} | ||
} | ||
for (let j in comments) { | ||
parser.parse(swaggerObject, function (err, api) { | ||
if (!err) { | ||
swaggerObject = api; | ||
} | ||
}); | ||
app.use('/api-docs.json', function (req, res) { | ||
res.json(swaggerObject); | ||
}); | ||
app.use('/api-docs', swaggerUi({ | ||
docs: '/api-docs.json' // from the express route above. | ||
})); | ||
return swaggerObject; | ||
} | ||
}; | ||
let parsed = fileFormat(comments[j]) | ||
swaggerHelpers.addDataToSwaggerObject(swaggerObject, [{paths: parsed.parameters, tags: parsed.tags, definitions: parsed.definitions}]); | ||
} | ||
} | ||
parser.parse(swaggerObject, function (err, api) { | ||
if (!err) { | ||
swaggerObject = api; | ||
} | ||
}); | ||
app.use('/api-docs.json', function (req, res) { | ||
res.json(swaggerObject); | ||
}); | ||
app.use('/api-docs', swaggerUi({ | ||
docs: '/api-docs.json' // from the express route above. | ||
})); | ||
return swaggerObject; | ||
} | ||
}; |
{ | ||
"name": "express-swagger-generator", | ||
"version": "1.0.4", | ||
"version": "1.1.0", | ||
"description": "Generates swagger doc & ui based on express existing routes.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -55,4 +55,23 @@ ### Express Swagger Generator | ||
For model definitions: | ||
``` | ||
/** | ||
* @typedef Point | ||
* @property {integer} x.required | ||
* @property {integer} y.required | ||
* @property {string} color | ||
*/ | ||
// Now I can use it as below: | ||
/** | ||
* Insert a point | ||
* @route POST /api/point | ||
* @param {Point.model} point.body.required - the new point | ||
*/ | ||
``` | ||
#### More | ||
This module is based on [express-swaggerize-ui](https://github.com/pgroot/express-swaggerize-ui) and [Doctrine-File](https://github.com/researchgate/doctrine-file) |
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
17334
460
76
5