String.prototype.substr() is deprecated (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substr) so we replace it with slice() which works similarily but isn't deprecated. Signed-off-by: Tobias Speicher <rootcommander@gmail.com>
320 lines
8.3 KiB
JavaScript
320 lines
8.3 KiB
JavaScript
let _ = require("lodash")
|
|
let { pathToRegexp } = require("path-to-regexp")
|
|
|
|
/********************************************************
|
|
* Based on: https://github.com/fsbahman/apidoc-swagger *
|
|
********************************************************/
|
|
|
|
let swagger = {
|
|
swagger: "2.0",
|
|
info: {},
|
|
paths: {},
|
|
definitions: {},
|
|
}
|
|
|
|
function toSwagger(apidocJson, projectJson) {
|
|
swagger.info = addInfo(projectJson)
|
|
swagger.paths = extractPaths(apidocJson)
|
|
return swagger
|
|
}
|
|
|
|
let tagsRegex = /(<([^>]+)>)/gi
|
|
// Removes <p> </p> tags from text
|
|
function removeTags(text) {
|
|
return text ? text.replace(tagsRegex, "") : text
|
|
}
|
|
|
|
function addInfo(projectJson) {
|
|
let info = {}
|
|
info["title"] = projectJson.title || projectJson.name
|
|
info["version"] = projectJson.version
|
|
info["description"] = projectJson.description
|
|
return info
|
|
}
|
|
|
|
/**
|
|
* Extracts paths provided in json format
|
|
* post, patch, put request parameters are extracted in body
|
|
* get and delete are extracted to path parameters
|
|
* @param apidocJson
|
|
* @returns {{}}
|
|
*/
|
|
function extractPaths(apidocJson) {
|
|
let apiPaths = groupByUrl(apidocJson)
|
|
let paths = {}
|
|
for (let i = 0; i < apiPaths.length; i++) {
|
|
let verbs = apiPaths[i].verbs
|
|
let url = verbs[0].url
|
|
let pattern = pathToRegexp(url, null)
|
|
let matches = pattern.exec(url)
|
|
|
|
// Surrounds URL parameters with curly brackets -> :email with {email}
|
|
let pathKeys = []
|
|
for (let j = 1; j < matches.length; j++) {
|
|
let key = matches[j].slice(1)
|
|
url = url.replace(matches[j], "{" + key + "}")
|
|
pathKeys.push(key)
|
|
}
|
|
|
|
for (let j = 0; j < verbs.length; j++) {
|
|
let verb = verbs[j]
|
|
let type = verb.type
|
|
|
|
let obj = (paths[url] = paths[url] || {})
|
|
|
|
if (type === "post" || type === "patch" || type === "put") {
|
|
_.extend(
|
|
obj,
|
|
createPostPushPutOutput(verb, swagger.definitions, pathKeys)
|
|
)
|
|
} else {
|
|
_.extend(obj, createGetDeleteOutput(verb, swagger.definitions))
|
|
}
|
|
}
|
|
}
|
|
return paths
|
|
}
|
|
|
|
function createPostPushPutOutput(verbs, definitions, pathKeys) {
|
|
let pathItemObject = {}
|
|
let verbDefinitionResult = createVerbDefinitions(verbs, definitions)
|
|
|
|
let params = []
|
|
let pathParams = createPathParameters(verbs, pathKeys)
|
|
pathParams = _.filter(pathParams, function (param) {
|
|
let hasKey = pathKeys.indexOf(param.name) !== -1
|
|
return !(param.in === "path" && !hasKey)
|
|
})
|
|
|
|
params = params.concat(pathParams)
|
|
let required =
|
|
verbs.parameter &&
|
|
verbs.parameter.fields &&
|
|
verbs.parameter.fields.Parameter &&
|
|
verbs.parameter.fields.Parameter.length > 0
|
|
|
|
params.push({
|
|
in: "body",
|
|
name: "body",
|
|
description: removeTags(verbs.description),
|
|
required: required,
|
|
schema: {
|
|
$ref: "#/definitions/" + verbDefinitionResult.topLevelParametersRef,
|
|
},
|
|
})
|
|
|
|
pathItemObject[verbs.type] = {
|
|
tags: [verbs.group],
|
|
summary: removeTags(verbs.description),
|
|
consumes: ["application/json"],
|
|
produces: ["application/json"],
|
|
parameters: params,
|
|
}
|
|
|
|
if (verbDefinitionResult.topLevelSuccessRef) {
|
|
pathItemObject[verbs.type].responses = {
|
|
200: {
|
|
description: "successful operation",
|
|
schema: {
|
|
type: verbDefinitionResult.topLevelSuccessRefType,
|
|
items: {
|
|
$ref: "#/definitions/" + verbDefinitionResult.topLevelSuccessRef,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
return pathItemObject
|
|
}
|
|
|
|
function createVerbDefinitions(verbs, definitions) {
|
|
let result = {
|
|
topLevelParametersRef: null,
|
|
topLevelSuccessRef: null,
|
|
topLevelSuccessRefType: null,
|
|
}
|
|
let defaultObjectName = verbs.name
|
|
|
|
let fieldArrayResult = {}
|
|
if (verbs && verbs.parameter && verbs.parameter.fields) {
|
|
fieldArrayResult = createFieldArrayDefinitions(
|
|
verbs.parameter.fields.Parameter,
|
|
definitions,
|
|
verbs.name,
|
|
defaultObjectName
|
|
)
|
|
result.topLevelParametersRef = fieldArrayResult.topLevelRef
|
|
}
|
|
|
|
if (verbs && verbs.success && verbs.success.fields) {
|
|
fieldArrayResult = createFieldArrayDefinitions(
|
|
verbs.success.fields["Success 200"],
|
|
definitions,
|
|
verbs.name,
|
|
defaultObjectName
|
|
)
|
|
result.topLevelSuccessRef = fieldArrayResult.topLevelRef
|
|
result.topLevelSuccessRefType = fieldArrayResult.topLevelRefType
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
function createFieldArrayDefinitions(
|
|
fieldArray,
|
|
definitions,
|
|
topLevelRef,
|
|
defaultObjectName
|
|
) {
|
|
let result = {
|
|
topLevelRef: topLevelRef,
|
|
topLevelRefType: null,
|
|
}
|
|
|
|
if (!fieldArray) {
|
|
return result
|
|
}
|
|
|
|
for (let i = 0; i < fieldArray.length; i++) {
|
|
let parameter = fieldArray[i]
|
|
|
|
let nestedName = createNestedName(parameter.field)
|
|
let objectName = nestedName.objectName
|
|
if (!objectName) {
|
|
objectName = defaultObjectName
|
|
}
|
|
let type = parameter.type
|
|
if (i === 0) {
|
|
result.topLevelRefType = type
|
|
if (parameter.type === "Object") {
|
|
objectName = nestedName.propertyName
|
|
nestedName.propertyName = null
|
|
} else if (parameter.type === "Array") {
|
|
objectName = nestedName.propertyName
|
|
nestedName.propertyName = null
|
|
result.topLevelRefType = "array"
|
|
}
|
|
result.topLevelRef = objectName
|
|
}
|
|
|
|
definitions[objectName] = definitions[objectName] || {
|
|
properties: {},
|
|
required: [],
|
|
}
|
|
|
|
if (nestedName.propertyName) {
|
|
let prop = {
|
|
type: (parameter.type || "").toLowerCase(),
|
|
description: removeTags(parameter.description),
|
|
}
|
|
if (parameter.type === "Object") {
|
|
prop.$ref = "#/definitions/" + parameter.field
|
|
}
|
|
|
|
let typeIndex = type.indexOf("[]")
|
|
if (typeIndex !== -1 && typeIndex === type.length - 2) {
|
|
prop.type = "array"
|
|
prop.items = {
|
|
type: type.slice(0, type.length - 2),
|
|
}
|
|
}
|
|
|
|
definitions[objectName]["properties"][nestedName.propertyName] = prop
|
|
if (!parameter.optional) {
|
|
let arr = definitions[objectName]["required"]
|
|
if (arr.indexOf(nestedName.propertyName) === -1) {
|
|
arr.push(nestedName.propertyName)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
function createNestedName(field) {
|
|
let propertyName = field
|
|
let objectName
|
|
let propertyNames = field.split(".")
|
|
if (propertyNames && propertyNames.length > 1) {
|
|
propertyName = propertyNames[propertyNames.length - 1]
|
|
propertyNames.pop()
|
|
objectName = propertyNames.join(".")
|
|
}
|
|
|
|
return {
|
|
propertyName: propertyName,
|
|
objectName: objectName,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate get, delete method output
|
|
* @param verbs
|
|
* @param definitions
|
|
* @returns {{}}
|
|
*/
|
|
function createGetDeleteOutput(verbs, definitions) {
|
|
let pathItemObject = {}
|
|
verbs.type = verbs.type === "del" ? "delete" : verbs.type
|
|
|
|
let verbDefinitionResult = createVerbDefinitions(verbs, definitions)
|
|
pathItemObject[verbs.type] = {
|
|
tags: [verbs.group],
|
|
summary: removeTags(verbs.description),
|
|
consumes: ["application/json"],
|
|
produces: ["application/json"],
|
|
parameters: createPathParameters(verbs),
|
|
}
|
|
if (verbDefinitionResult.topLevelSuccessRef) {
|
|
pathItemObject[verbs.type].responses = {
|
|
200: {
|
|
description: "successful operation",
|
|
schema: {
|
|
type: verbDefinitionResult.topLevelSuccessRefType,
|
|
items: {
|
|
$ref: "#/definitions/" + verbDefinitionResult.topLevelSuccessRef,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
return pathItemObject
|
|
}
|
|
|
|
/**
|
|
* Iterate through all method parameters and create array of parameter objects which are stored as path parameters
|
|
* @param verbs
|
|
* @returns {Array}
|
|
*/
|
|
function createPathParameters(verbs) {
|
|
let pathItemObject = []
|
|
if (verbs.parameter && verbs.parameter.fields.Parameter) {
|
|
for (let i = 0; i < verbs.parameter.fields.Parameter.length; i++) {
|
|
let param = verbs.parameter.fields.Parameter[i]
|
|
let field = param.field
|
|
let type = param.type
|
|
pathItemObject.push({
|
|
name: field,
|
|
in: type === "file" ? "formData" : "path",
|
|
required: !param.optional,
|
|
type: param.type.toLowerCase(),
|
|
description: removeTags(param.description),
|
|
})
|
|
}
|
|
}
|
|
return pathItemObject
|
|
}
|
|
|
|
function groupByUrl(apidocJson) {
|
|
return _.chain(apidocJson)
|
|
.groupBy("url")
|
|
.toPairs()
|
|
.map(function (element) {
|
|
return _.zipObject(["url", "verbs"], element)
|
|
})
|
|
.value()
|
|
}
|
|
|
|
module.exports = toSwagger
|