UNPKG

6.07 kBJavaScriptView Raw
1'use strict'
2
3const fastJsonStringify = require('fast-json-stringify')
4const Ajv = require('ajv')
5
6const bodySchema = Symbol('body-schema')
7const querystringSchema = Symbol('querystring-schema')
8const paramsSchema = Symbol('params-schema')
9const responseSchema = Symbol('response-schema')
10const headersSchema = Symbol('headers-schema')
11const kFluentSchema = Symbol.for('fluent-schema-object')
12
13function getValidatorForStatusCodeSchema (statusCodeDefinition, externalSchema) {
14 return fastJsonStringify(statusCodeDefinition, { schema: externalSchema })
15}
16
17function getResponseSchema (responseSchemaDefinition, sharedSchemas) {
18 var statusCodes = Object.keys(responseSchemaDefinition)
19 return statusCodes.reduce(function (r, statusCode) {
20 r[statusCode] = getValidatorForStatusCodeSchema(responseSchemaDefinition[statusCode], sharedSchemas)
21 return r
22 }, {})
23}
24
25function build (context, compile, schemas, schemaResolver) {
26 if (!context.schema) {
27 return
28 }
29
30 generateFluentSchema(context.schema)
31 context.schema = schemas.resolveRefs(context.schema, false, schemaResolver)
32
33 const headers = context.schema.headers
34
35 if (headers && Object.getPrototypeOf(headers) !== Object.prototype) {
36 // do not mess with non-literals, e.g. Joi schemas
37 context[headersSchema] = compile(headers)
38 } else if (headers) {
39 // The header keys are case insensitive
40 // https://tools.ietf.org/html/rfc2616#section-4.2
41 const headersSchemaLowerCase = {}
42 Object.keys(headers).forEach(k => { headersSchemaLowerCase[k] = headers[k] })
43 if (headersSchemaLowerCase.required instanceof Array) {
44 headersSchemaLowerCase.required = headersSchemaLowerCase.required.map(h => h.toLowerCase())
45 }
46 if (headers.properties) {
47 headersSchemaLowerCase.properties = {}
48 Object.keys(headers.properties).forEach(k => {
49 headersSchemaLowerCase.properties[k.toLowerCase()] = headers.properties[k]
50 })
51 }
52 context[headersSchema] = compile(headersSchemaLowerCase)
53 }
54
55 if (context.schema.response) {
56 context[responseSchema] = getResponseSchema(context.schema.response, schemas.getSchemas())
57 }
58
59 if (context.schema.body) {
60 context[bodySchema] = compile(context.schema.body)
61 }
62
63 if (context.schema.querystring) {
64 context[querystringSchema] = compile(context.schema.querystring)
65 }
66
67 if (context.schema.params) {
68 context[paramsSchema] = compile(context.schema.params)
69 }
70}
71
72function generateFluentSchema (schema) {
73 ;['params', 'body', 'querystring', 'query', 'headers'].forEach(key => {
74 if (schema[key] && (schema[key].isFluentSchema || schema[key][kFluentSchema])) {
75 schema[key] = schema[key].valueOf()
76 }
77 })
78
79 if (schema.response) {
80 Object.keys(schema.response).forEach(code => {
81 if (schema.response[code].isFluentSchema || schema.response[code][kFluentSchema]) {
82 schema.response[code] = schema.response[code].valueOf()
83 }
84 })
85 }
86}
87
88function validateParam (validatorFunction, request, paramName) {
89 var ret = validatorFunction && validatorFunction(request[paramName])
90 if (ret === false) return validatorFunction.errors
91 if (ret && ret.error) return ret.error
92 if (ret && ret.value) request[paramName] = ret.value
93 return false
94}
95
96function validate (context, request) {
97 var params = validateParam(context[paramsSchema], request, 'params')
98 if (params) {
99 return wrapValidationError(params, 'params')
100 }
101 var body = validateParam(context[bodySchema], request, 'body')
102 if (body) {
103 return wrapValidationError(body, 'body')
104 }
105 var query = validateParam(context[querystringSchema], request, 'query')
106 if (query) {
107 return wrapValidationError(query, 'querystring')
108 }
109 var headers = validateParam(context[headersSchema], request, 'headers')
110 if (headers) {
111 return wrapValidationError(headers, 'headers')
112 }
113 return null
114}
115
116function wrapValidationError (result, dataVar) {
117 if (result instanceof Error) {
118 return result
119 }
120 var error = new Error(schemaErrorsText(result, dataVar))
121 error.validation = result
122 error.validationContext = dataVar
123 return error
124}
125
126function serialize (context, data, statusCode) {
127 var responseSchemaDef = context[responseSchema]
128 if (!responseSchemaDef) {
129 return JSON.stringify(data)
130 }
131 if (responseSchemaDef[statusCode]) {
132 return responseSchemaDef[statusCode](data)
133 }
134 var fallbackStatusCode = (statusCode + '')[0] + 'xx'
135 if (responseSchemaDef[fallbackStatusCode]) {
136 return responseSchemaDef[fallbackStatusCode](data)
137 }
138 return JSON.stringify(data)
139}
140
141function isValidLogger (logger) {
142 if (!logger) {
143 return false
144 }
145
146 var result = true
147 const methods = ['info', 'error', 'debug', 'fatal', 'warn', 'trace', 'child']
148 for (var i = 0; i < methods.length; i += 1) {
149 if (!logger[methods[i]] || typeof logger[methods[i]] !== 'function') {
150 result = false
151 break
152 }
153 }
154 return result
155}
156
157function schemaErrorsText (errors, dataVar) {
158 var text = ''
159 var separator = ', '
160 for (var i = 0; i < errors.length; i++) {
161 var e = errors[i]
162 text += dataVar + (e.dataPath || '') + ' ' + e.message + separator
163 }
164 return text.slice(0, -separator.length)
165}
166
167function buildSchemaCompiler (externalSchemas, options, cache) {
168 // This instance of Ajv is private
169 // it should not be customized or used
170 const ajv = new Ajv(Object.assign({
171 coerceTypes: true,
172 useDefaults: true,
173 removeAdditional: true,
174 // Explicitly set allErrors to `false`.
175 // When set to `true`, a DoS attack is possible.
176 allErrors: false,
177 nullable: true
178 }, options.customOptions, { cache }))
179
180 if (options.plugins && options.plugins.length > 0) {
181 for (const plugin of options.plugins) {
182 plugin[0](ajv, plugin[1])
183 }
184 }
185
186 if (Array.isArray(externalSchemas)) {
187 externalSchemas.forEach(s => ajv.addSchema(s))
188 }
189
190 return ajv.compile.bind(ajv)
191}
192
193module.exports = { build, validate, serialize, isValidLogger, buildSchemaCompiler }
194module.exports.symbols = { bodySchema, querystringSchema, responseSchema, paramsSchema, headersSchema }