swagger-routes
Version:
Generate Express or Restify route handlers from a Swagger specification
174 lines (153 loc) • 4.65 kB
JavaScript
const fs = require('fs')
const path = require('path')
const util = require('./util')
const HTTP_METHODS = [
'get',
'put',
'post',
'delete',
'options',
'head',
'patch'
]
const PARAM_GROUP = {
HEADER: 'header',
PATH: 'path',
QUERY: 'query',
BODY: 'body',
FORM_DATA: 'formData'
}
const PARAM_GROUPS =
Object.keys(PARAM_GROUP)
.map(k => PARAM_GROUP[k])
exports.PARAM_GROUP = PARAM_GROUP
exports.PARAM_GROUPS = PARAM_GROUPS
exports.getSpec = getSpec
exports.getSpecSync = getSpecSync
exports.getAllOperations = getAllOperations
exports.createPathOperation = createPathOperation
function getSpec(spec) {
if (typeof spec === 'string') {
return loadSpec(spec).then(spec => applyDefaults(spec))
} else {
return Promise.resolve(applyDefaults(spec))
}
}
function getSpecSync(spec) {
if (typeof spec === 'string') {
spec = loadSpecSync(spec)
}
return applyDefaults(spec)
}
function loadSpec(specPath) {
return util.readFile(specPath)
.then(contents => util.parseFileContents(contents, specPath))
}
function loadSpecSync(specPath) {
const contents = fs.readFileSync(specPath)
return util.parseFileContents(contents, specPath)
}
function applyDefaults(spec) {
if (!spec.basePath) spec.basePath = '/'
return spec
}
function getAllOperations(spec) {
// we need to resolve refs so a deep copy is needed to avoid modifying the original
spec = JSON.parse(JSON.stringify(spec))
return getPaths(spec)
.reduce((ops, pathInfo) =>
ops.concat(getPathOperations(pathInfo, spec)), [])
}
function getPaths(spec) {
return Object.keys(spec.paths || {})
.map(path => Object.assign({ path }, spec.paths[path]))
}
function getPathOperations(pathInfo, spec) {
const xProps = getXProps(pathInfo)
return Object.keys(pathInfo)
.filter(key => HTTP_METHODS.indexOf(key) !== -1)
.map(method => createPathOperation(method, pathInfo, xProps, spec))
}
function getXProps(data) {
return Object.keys(data)
.filter(prop => prop.startsWith('x-'))
.reduce((xProps, prop) => {
xProps[prop] = data[prop]
return xProps
}, {})
}
function createPathOperation(method, pathInfo, pathsXProps, spec) {
const operationInfo = util.resolveSchemaRefs(pathInfo[method], spec)
if (!operationInfo.parameters) operationInfo.parameters = []
if (!operationInfo.responses) operationInfo.responses = {}
const operation = Object.assign({
id: operationInfo.operationId,
path: pathInfo.path,
fullPath: path.normalize(`/${spec.basePath}/${pathInfo.path}`),
consumes: getOperationProperty('consumes', operationInfo, spec),
produces: getOperationProperty('produces', operationInfo, spec),
paramGroupSchemas: createParamGroupSchemas(operationInfo.parameters, spec),
responseSchemas: createResponseSchemas(operationInfo.responses, spec),
method
}, pathsXProps, operationInfo)
delete operation.operationId
return operation
}
function getOperationProperty(prop, pathInfo, spec) {
return (pathInfo && pathInfo[prop]) ? pathInfo[prop] : spec[prop]
}
function createParamGroupSchemas(parameters) {
return PARAM_GROUPS
.map(loc => {
const params = parameters.filter(param => param.in === loc)
return { 'in': loc, schema: createParamsSchema(params, loc) }
})
.filter(param => Object.keys(param.schema.properties || {}).length)
.reduce((map, param) => {
map[param.in] = param.schema
return map
}, {})
}
function createParamsSchema(params, loc) {
if (loc === PARAM_GROUP.BODY) {
const param = params.shift() // there can only be a single body param
if (param) {
param.name = 'body' // using a consistent name helps with error reporting
if (param.schema) return param.schema
}
}
return {
type: 'object',
properties: params.reduce((props, param) => {
const p = Object.assign({}, param)
delete p.required
props[param.name] = p
return props
}, {}),
required: params
.filter(param => param.required)
.map(param => param.name)
}
}
function createResponseSchemas(responses, spec) {
return Object.keys(responses)
.map(id => ({
id,
bodySchema: responses[id].schema,
headersSchema: createResponseHeadersSchema(responses[id].headers, spec)
}))
.reduce((result, response) => {
result[response.id] = response
return result
}, {})
}
function createResponseHeadersSchema(headers) {
if (!headers) return undefined
return {
type: 'object',
properties: headers,
required: Object.keys(headers)
.filter(name => headers[name].required)
}
}