swagger-routes
Version:
Generate Express or Restify route handlers from a Swagger specification
124 lines (110 loc) • 4.08 kB
JavaScript
const jsonSchema = require('jsonschema')
const parameters = require('./routeParameters')
const swaggerSpec = require('./swaggerSpec')
const PARAM_GROUP = swaggerSpec.PARAM_GROUP
exports.createValidationCheck = createValidationCheck
exports.validateRequest = validateRequest
function createValidationCheck(operation) {
return function validator(req, res, next) {
const result = validateRequest(req, operation)
next(checkValidationResult(result))
}
}
function validateRequest(req, operation) {
const groupSchemas = operation.paramGroupSchemas
const reqData = groupRequestData(req, operation, groupSchemas)
return Object.keys(groupSchemas)
.map(groupId => validateParam(
groupId,
groupSchemas[groupId],
reqData[groupId]
))
.filter(result => !result.valid)
.reduce(reduceFailures, undefined) // must pass init value or reducer doesn't run for single value
}
function groupRequestData(req, operation, groupSchemas) {
return {
header: req.headers ? req.headers : req.headers = {},
path: parameters.getPathParams(req, operation),
query: parameters.castQueryParams(req, groupSchemas),
body: req.body ? req.body : req.body = {},
formData: parameters.getFormData(req)
}
}
function validateParam(groupId, groupSchema, groupData) {
groupData = parameters.formatGroupData(groupSchema, groupData)
groupData = Object.assign({}, groupData)
let result = jsonSchema.validate(groupData, groupSchema, { propertyName: groupId })
result = checkForMissingPathParams(groupId, groupSchema, groupData, result)
result = checkForInvalidPathSegmentName(groupId, groupSchema, groupData, result)
result = removeErrorsForAllowedEmptyValue(groupId, groupSchema, groupData, result)
return result
}
function checkForMissingPathParams(groupId, schema, data, result) {
if (groupId !== 'path' || !schema.properties) return result
data = data || {}
Object.keys(schema.properties).forEach(prop => {
// if the value exists but as an un-replaced Swagger
// path token then assume the path param has not been provided
if (data[prop] && data[prop].match(new RegExp(`^{${prop}}$`))) {
const propPath = result.propertyPath
result.propertyPath += `.${prop}`
result.addError({ message: `is required`, name: prop })
result.propertyPath = propPath
}
})
return result
}
function checkForInvalidPathSegmentName(groupId, schema, data, result) {
if (groupId === PARAM_GROUP.PATH) {
const propKeys = Object.keys(schema.properties)
Object.keys(data).forEach(key => {
if (propKeys.indexOf(key) === -1) {
const path = result.propertyPath
result.propertyPath += `.${key}`
result.addError({ message: 'is an invalid path segment', name: key })
result.propertyPath = path
}
})
}
return result
}
function removeErrorsForAllowedEmptyValue(groupId, schema, data, result) {
if (groupId === PARAM_GROUP.QUERY || groupId === PARAM_GROUP.FORM_DATA) {
result.errors = result.errors.filter(err => {
const prop = err.property.indexOf('.') === -1 ? err.argument : err.property.split('.').pop()
const val = data[prop]
const spec = schema.properties[prop]
return !(spec && spec.allowEmptyValue && !val && val !== 0)
})
}
return result
}
function reduceFailures(master, failure) {
failure = formatFailure(failure)
if (master) {
master.importErrors(failure)
return master
} else {
return failure
}
}
function formatFailure(failure) {
failure.errors.forEach(err => err.message = `${err.property} ${err.message}`)
return failure
}
function checkValidationResult(result) {
if (!result || result.valid) return undefined
const message = result.errors.map(e => e.message).join(', ')
return new ValidationError(message)
}
class ValidationError extends Error {
constructor(message) {
super(message)
this.name = this.constructor.name
this.message = message
this.status = this.statusCode = 400
Error.captureStackTrace(this, this.constructor.name)
}
}