swagger-routes
Version:
Generate Express or Restify route handlers from a Swagger specification
179 lines (154 loc) • 5.97 kB
JavaScript
const path = require('path')
const fs = require('fs')
const Mustache = require('mustache')
const swaggerSpec = require('./swaggerSpec')
const util = require('./util')
exports.enableFile = enableFile
exports.disableFile = disableFile
exports.getFilePath = getFilePath
exports.getTemplate = getTemplate
exports.renderTemplate = renderTemplate
exports.updateHeader = updateHeader
exports.getDefaultTemplateView = getDefaultTemplateView
exports.getDisabledFilePath = getDisabledFilePath
exports.disableOldOperationFiles = disableOldOperationFiles
exports.fileInfo = fileInfo
function enableFile(id, data, type, headerLineRegex, options) {
const fileInfo = _enableFile(id, data, type, options)
if (options.maintainHeaders && !fileInfo.gen && !!headerLineRegex) {
updateHeader(fileInfo, data, type, headerLineRegex, options)
}
return fileInfo
}
function _enableFile(id, data, type, options) {
const filePath = getFilePath(id, type, options)
if (!util.existsSync(filePath)) {
const disabledPath = getDisabledFilePath(id, type, options)
if (util.existsSync(disabledPath)) {
return renameFile(disabledPath, filePath, options)
} else if (options[type].generate) {
return writeNewHandler(data, type, filePath, options)
}
}
return fileInfo(filePath, false, false, options)
}
function renameFile(oldPath, filePath, options) {
fs.renameSync(oldPath, filePath)
return fileInfo(filePath, false, false, options)
}
function writeNewHandler(data, type, filePath, options) {
const contents = renderTemplate(data, type, options)
util.writeFileSync(filePath, contents)
return fileInfo(filePath, true, false, options)
}
function renderTemplate(data, type, options) {
const view = options[type].getTemplateView(data)
return Mustache.render(options[type].template, view)
}
function disableFile(id, type, options) {
const filePath = getFilePath(id, type, options)
const disabledPath = getDisabledFilePath(id, type, options)
if (util.existsSync(filePath)) {
fs.renameSync(filePath, disabledPath)
}
return fileInfo(disabledPath, false, true, options)
}
function getTemplate(type, options) {
const template = options[type].template
if (template && typeof template === 'string') {
return fs.readFileSync(template, 'utf8')
} else {
return template
}
}
function getFilePath(id, type, options) {
return path.join(options[type].path, `${id}${options.fileType || '.js'}`)
}
function getDisabledFilePath(id, type, options) {
return path.join(options[type].path, `${options.unusedFilePrefix}${id}${options.fileType || '.js'}`)
}
function fileInfo(filePath, gen, old, options) {
return {
id: path.basename(filePath, options.fileType || '.js'),
path: path.resolve(filePath),
gen,
old
}
}
function disableOldOperationFiles(operations, type, options) {
if (!options[type].generate || options[type].create) return []
const fileType = options.fileType || '.js'
const filenames = fs.readdirSync(options[type].path)
const unknown = filenames.filter(name =>
name.endsWith(fileType) &&
!name.startsWith(options.unusedFilePrefix) &&
!operations.find(op => `${op.id}${fileType}` === name))
return unknown.map(name => {
name = path.basename(name, fileType)
const filePath = getFilePath(name, type, options)
const disabledPath = getDisabledFilePath(name, type, options)
fs.renameSync(filePath, disabledPath)
return fileInfo(disabledPath, false, true, options)
})
}
function getDefaultTemplateView(operation, commentMarker) {
const view = Object.assign({}, operation)
const groupsMap = operation.paramGroupSchemas
view.method = view.method.toUpperCase()
view.summary = (view.summary || view.id).split('\n').join(`\n${commentMarker}`)
view.params = Object.keys(groupsMap)
.map(groupId => {
const propMap = groupsMap[groupId].properties
return {
groupId,
order: swaggerSpec.PARAM_GROUPS.indexOf(groupId),
properties: Object.keys(propMap).map(name => ({
details: formatParamPropertyDoc(name, propMap[name])
}))
}
})
.sort((a, b) => a.order - b.order)
return view
}
function formatParamPropertyDoc(name, info) {
const MAX_CHARS = 100
const type = info.format || info.type || ''
const desc = (info.description || '').split('\n')[0]
const match = desc.match(/(^.*?[a-z]{2,}[.!?])\s+\W*[A-Z]/)
const i = match ? Math.min(match.index, MAX_CHARS) : Math.min(desc.length, MAX_CHARS)
const shortDesc = desc.substr(0, i)
let dot = '.'
if (shortDesc.endsWith('.')) dot = ''
else if (shortDesc.length === MAX_CHARS) dot = '...'
let s = name
if (type) s += ` {${type}}`
if (shortDesc) s += ` ${shortDesc}${dot}`
return s
}
/**
* Keeps the header block of your templated file in sync with your Swagger api.
*
* @param {object} fileInfo info on the file to target
* @param {object} data the operation which to gen the header from
* @param {string} type the type of file being updated
* @param {regex} headerLineRegex regex to find if a line is part of the header
* @param {object} options options containing details on rendering the header
* @return {void}
*/
function updateHeader(fileInfo, data, type, headerLineRegex, options) {
const contents = fs.readFileSync(fileInfo.path, 'utf8') || ''
const body = contents.split('\n')
// assumes all comments at top of template are the header block
const preheader = []
while (body.length && !body[0].trim().match(headerLineRegex)) preheader.push(body.shift())
while (body.length && body[0].trim().match(headerLineRegex)) body.shift()
const template = renderTemplate(data, type, options)
const header = []
for (let hline of template.split('\n')) {
if (!hline.trim().match(headerLineRegex)) break
else header.push(hline)
}
const newContents = preheader.concat(header).concat(body).join('\n')
fs.writeFileSync(fileInfo.path, newContents)
}