-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
plugin.js
105 lines (90 loc) · 3.24 KB
/
plugin.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
'use strict'
const fp = require('fastify-plugin')
const SCHEMA_FIELDS = ['body', 'querystring', 'params', 'headers']
function schemaConstraint (instace, opts, next) {
const config = Object.assign({
body: {},
querystring: {},
params: {},
headers: {}
}, opts)
let almostOne = false
for (const field of SCHEMA_FIELDS) {
const fieldOptions = config[field]
almostOne = almostOne || fieldOptions.constraint != null
if (fieldOptions.constraint !== undefined && typeof fieldOptions.constraint !== 'function') {
return next(new TypeError(`The "${field}.constraint" option must be a function`))
}
if (fieldOptions.statusCode !== undefined && typeof fieldOptions.statusCode !== 'number') {
return next(new TypeError(`The "${field}.statusCode" option must be a number`))
}
if (fieldOptions.errorMessage !== undefined && typeof fieldOptions.errorMessage !== 'string') {
return next(new TypeError(`The "${field}.errorMessage" option must be a string`))
}
}
if (!almostOne) return next(new Error('Options are required for fastify-schema-constraint'))
const lazyValidators = {}
instace.addHook('preHandler', applyContraints)
next()
function applyContraints (req, reply, next) {
if (!reply.request.routeOptions.schema) {
return next()
}
let field
try {
for (field of SCHEMA_FIELDS) {
contraintValidation(field, req, reply.request.routeOptions.schema[field])
}
next()
} catch (error) {
if (config[field].errorMessage) {
error.message = config[field].errorMessage
}
error.statusCode = config[field].statusCode || 400
next(error)
}
}
function contraintValidation (paramName, req, schema) {
const fn = config[paramName].constraint
if (!fn || !schema) {
// ignore if the constraint function or the route schema are not set
return true
}
let mustBeId
try {
mustBeId = fn(req)
} catch (clientError) {
throw new Error(`Schema constraint function error for ${paramName}: ${clientError.message}`)
}
if (typeof mustBeId !== 'string') {
// ignore the returned value
return true
}
const mandatorySchema = schema.oneOf.find(_ => _.$id === mustBeId)
if (!mandatorySchema) {
throw new Error(`JSON schema $id ${mustBeId} not found in the 'schema.${paramName}.oneOf' route settings`)
}
const lazyKey = `${paramName}-${mandatorySchema.$id}`
let validatorFunction
if (lazyValidators[lazyKey] != null) {
validatorFunction = lazyValidators[lazyKey]
} else {
validatorFunction = instace.validatorCompiler({
schema: mandatorySchema,
method: req.method,
httpPart: paramName
})
lazyValidators[lazyKey] = validatorFunction
}
// NB: querystring field is named as req.query
const validationResult = validatorFunction && validatorFunction(req[paramName.replace('string', '')])
if (validationResult === false || (validationResult && validationResult.error)) {
throw new Error(`Schema constraint failure: the ${paramName} doesn't match the JSON schema ${mustBeId}`)
}
return true
}
}
module.exports = fp(schemaConstraint, {
fastify: '^5.0.0',
name: 'fastify-schema-constraint'
})