UNPKG

4.12 kBJavaScriptView Raw
1'use strict'
2
3const jsonSchema = require('jsonschema')
4const parameters = require('./routeParameters')
5const swaggerSpec = require('./swaggerSpec')
6const PARAM_GROUP = swaggerSpec.PARAM_GROUP
7
8exports.createValidationCheck = createValidationCheck
9exports.validateRequest = validateRequest
10
11function createValidationCheck(operation) {
12 return function validator(req, res, next) {
13 const result = validateRequest(req, operation)
14 next(checkValidationResult(result))
15 }
16}
17
18function validateRequest(req, operation) {
19 const groupSchemas = operation.paramGroupSchemas
20 const reqData = groupRequestData(req, operation)
21 return Object.keys(groupSchemas)
22 .map(groupId => {
23 const groupSchema = groupSchemas[groupId]
24 const groupData = parameters.formatGroupData(groupSchema, reqData[groupId], groupId, req)
25 return validateParam(
26 groupId,
27 groupSchema,
28 groupData
29 )
30 })
31 .filter(result => !result.valid)
32 .reduce(reduceFailures, undefined) // must pass init value or reducer doesn't run for single value
33}
34
35function groupRequestData(req, operation) {
36 return {
37 header: req.headers ? req.headers : req.headers = {},
38 path: parameters.getPathParams(req, operation),
39 query: req.query ? req.query : req.query = {},
40 body: req.body ? req.body : req.body = {},
41 formData: parameters.getFormData(req)
42 }
43}
44
45function validateParam(groupId, groupSchema, groupData) {
46 groupData = Object.assign({}, groupData)
47 let result = jsonSchema.validate(groupData, groupSchema, { propertyName: groupId })
48 result = checkForMissingPathParams(groupId, groupSchema, groupData, result)
49 result = checkForInvalidPathSegmentName(groupId, groupSchema, groupData, result)
50 result = removeErrorsForAllowedEmptyValue(groupId, groupSchema, groupData, result)
51 return result
52}
53
54function checkForMissingPathParams(groupId, schema, data, result) {
55 if (groupId !== 'path' || !schema.properties) return result
56 data = data || {}
57 Object.keys(schema.properties).forEach(prop => {
58 // if the value exists but as an un-replaced Swagger
59 // path token then assume the path param has not been provided
60 if (data[prop] && data[prop] === `{${prop}}`) {
61 const propPath = result.propertyPath
62 result.propertyPath += `.${prop}`
63 result.addError({ message: `is required`, name: prop })
64 result.propertyPath = propPath
65 }
66 })
67 return result
68}
69
70function checkForInvalidPathSegmentName(groupId, schema, data, result) {
71 if (groupId === PARAM_GROUP.PATH) {
72 const propKeys = Object.keys(schema.properties)
73 Object.keys(data).forEach(key => {
74 if (propKeys.indexOf(key) === -1) {
75 const path = result.propertyPath
76 result.propertyPath += `.${key}`
77 result.addError({ message: 'is an invalid path segment', name: key })
78 result.propertyPath = path
79 }
80 })
81 }
82 return result
83}
84
85function removeErrorsForAllowedEmptyValue(groupId, schema, data, result) {
86 if (groupId === PARAM_GROUP.QUERY || groupId === PARAM_GROUP.FORM_DATA) {
87 result.errors = result.errors.filter(err => {
88 const prop = err.property.indexOf('.') === -1 ? err.argument : err.property.split('.').pop()
89 const val = data[prop]
90 const spec = schema.properties[prop]
91 return !(spec && spec.allowEmptyValue && !val && val !== 0)
92 })
93 }
94 return result
95}
96
97function reduceFailures(master, failure) {
98 failure = formatFailure(failure)
99 if (master) {
100 master.importErrors(failure)
101 return master
102 } else {
103 return failure
104 }
105}
106
107function formatFailure(failure) {
108 failure.errors.forEach(err => err.message = `${err.property} ${err.message}`)
109 return failure
110}
111
112function checkValidationResult(result) {
113 if (!result || result.valid) return undefined
114 const message = result.errors.map(e => e.message).join(', ')
115 return new ValidationError(message)
116}
117
118class ValidationError extends Error {
119 constructor(message) {
120 super(message)
121 this.name = this.constructor.name
122 this.message = message
123 this.status = this.statusCode = 400
124 Error.captureStackTrace(this, this.constructor.name)
125 }
126}