UNPKG

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