1 | 'use strict'
|
2 |
|
3 | const jsonSchema = require('jsonschema')
|
4 | const parameters = require('./routeParameters')
|
5 | const swaggerSpec = require('./swaggerSpec')
|
6 | const PARAM_GROUP = swaggerSpec.PARAM_GROUP
|
7 |
|
8 | exports.createValidationCheck = createValidationCheck
|
9 | exports.validateRequest = validateRequest
|
10 |
|
11 | function createValidationCheck(operation) {
|
12 | return function validator(req, res, next) {
|
13 | const result = validateRequest(req, operation)
|
14 | next(checkValidationResult(result))
|
15 | }
|
16 | }
|
17 |
|
18 | function 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)
|
33 | }
|
34 |
|
35 | function 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 |
|
45 | function 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 |
|
54 | function checkForMissingPathParams(groupId, schema, data, result) {
|
55 | if (groupId !== 'path' || !schema.properties) return result
|
56 | data = data || {}
|
57 | Object.keys(schema.properties).forEach(prop => {
|
58 |
|
59 |
|
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 |
|
70 | function 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 |
|
85 | function 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 |
|
97 | function 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 |
|
107 | function formatFailure(failure) {
|
108 | failure.errors.forEach(err => err.message = `${err.property} ${err.message}`)
|
109 | return failure
|
110 | }
|
111 |
|
112 | function 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 |
|
118 | class 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 | }
|