fastify 2.10.0 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +52 -28
- package/build/build-validation.js +12 -2
- package/docs/Decorators.md +136 -75
- package/docs/Ecosystem.md +13 -2
- package/docs/Errors.md +10 -0
- package/docs/Fluent-Schema.md +1 -3
- package/docs/Hooks.md +75 -37
- package/docs/Plugins-Guide.md +19 -2
- package/docs/Plugins.md +27 -0
- package/docs/Recommendations.md +159 -0
- package/docs/Reply.md +19 -2
- package/docs/Routes.md +96 -6
- package/docs/Server.md +86 -4
- package/docs/Serverless.md +1 -1
- package/docs/Testing.md +18 -3
- package/docs/TypeScript.md +37 -14
- package/docs/Validation-and-Serialization.md +214 -13
- package/fastify.d.ts +33 -40
- package/fastify.js +65 -6
- package/lib/configValidator.js +129 -52
- package/lib/contentTypeParser.js +4 -4
- package/lib/context.js +2 -1
- package/lib/decorate.js +13 -2
- package/lib/errors.js +8 -0
- package/lib/hooks.js +3 -1
- package/lib/logger.js +2 -2
- package/lib/reply.js +10 -4
- package/lib/route.js +37 -27
- package/lib/schemas.js +10 -2
- package/lib/server.js +11 -0
- package/lib/symbols.js +3 -1
- package/lib/validation.js +11 -5
- package/package.json +33 -29
- package/test/404s.test.js +40 -0
- package/test/decorator.test.js +80 -0
- package/test/esm/esm.mjs +13 -0
- package/test/esm/index.test.js +19 -0
- package/test/esm/other.mjs +7 -0
- package/test/esm/plugin.mjs +7 -0
- package/test/fastify-instance.test.js +29 -0
- package/test/genReqId.test.js +1 -1
- package/test/hooks-async.js +19 -0
- package/test/hooks.test.js +139 -7
- package/test/http2/closing.js +53 -0
- package/test/http2/plain.js +15 -0
- package/test/input-validation.js +63 -0
- package/test/input-validation.test.js +161 -0
- package/test/internals/decorator.test.js +37 -2
- package/test/internals/initialConfig.test.js +6 -2
- package/test/internals/reply.test.js +27 -3
- package/test/logger.test.js +310 -1
- package/test/proto-poisoning.test.js +76 -0
- package/test/reply-error.test.js +30 -0
- package/test/route-hooks.test.js +23 -14
- package/test/route.test.js +62 -24
- package/test/router-options.test.js +34 -0
- package/test/schemas.test.js +156 -0
- package/test/shared-schemas.test.js +65 -0
- package/test/types/index.ts +75 -2
package/lib/decorate.js
CHANGED
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
const {
|
|
6
6
|
kReply,
|
|
7
|
-
kRequest
|
|
7
|
+
kRequest,
|
|
8
|
+
kState
|
|
8
9
|
} = require('./symbols.js')
|
|
9
10
|
|
|
10
11
|
const {
|
|
11
12
|
codes: {
|
|
12
13
|
FST_ERR_DEC_ALREADY_PRESENT,
|
|
13
|
-
FST_ERR_DEC_MISSING_DEPENDENCY
|
|
14
|
+
FST_ERR_DEC_MISSING_DEPENDENCY,
|
|
15
|
+
FST_ERR_DEC_AFTER_START
|
|
14
16
|
}
|
|
15
17
|
} = require('./errors')
|
|
16
18
|
|
|
@@ -34,6 +36,7 @@ function decorate (instance, name, fn, dependencies) {
|
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
function decorateFastify (name, fn, dependencies) {
|
|
39
|
+
assertNotStarted(this, name)
|
|
37
40
|
decorate(this, name, fn, dependencies)
|
|
38
41
|
return this
|
|
39
42
|
}
|
|
@@ -63,15 +66,23 @@ function checkDependencies (instance, deps) {
|
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
function decorateReply (name, fn, dependencies) {
|
|
69
|
+
assertNotStarted(this, name)
|
|
66
70
|
decorate(this[kReply].prototype, name, fn, dependencies)
|
|
67
71
|
return this
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
function decorateRequest (name, fn, dependencies) {
|
|
75
|
+
assertNotStarted(this, name)
|
|
71
76
|
decorate(this[kRequest].prototype, name, fn, dependencies)
|
|
72
77
|
return this
|
|
73
78
|
}
|
|
74
79
|
|
|
80
|
+
function assertNotStarted (instance, name) {
|
|
81
|
+
if (instance[kState].started) {
|
|
82
|
+
throw new FST_ERR_DEC_AFTER_START(name)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
75
86
|
module.exports = {
|
|
76
87
|
add: decorateFastify,
|
|
77
88
|
exist: checkExistence,
|
package/lib/errors.js
CHANGED
|
@@ -27,6 +27,7 @@ createError('FST_ERR_CTP_EMPTY_JSON_BODY', "Body cannot be empty when content-ty
|
|
|
27
27
|
*/
|
|
28
28
|
createError('FST_ERR_DEC_ALREADY_PRESENT', "The decorator '%s' has already been added!")
|
|
29
29
|
createError('FST_ERR_DEC_MISSING_DEPENDENCY', "The decorator is missing dependency '%s'.")
|
|
30
|
+
createError('FST_ERR_DEC_AFTER_START', "The decorator '%s' has been added after start!")
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* hooks
|
|
@@ -46,6 +47,8 @@ createError('FST_ERR_REP_INVALID_PAYLOAD_TYPE', "Attempted to send payload of in
|
|
|
46
47
|
createError('FST_ERR_REP_ALREADY_SENT', 'Reply was already sent.')
|
|
47
48
|
createError('FST_ERR_REP_SENT_VALUE', 'The only possible value for reply.sent is true.')
|
|
48
49
|
createError('FST_ERR_SEND_INSIDE_ONERR', 'You cannot use `send` inside the `onError` hook')
|
|
50
|
+
createError('FST_ERR_SEND_UNDEFINED_ERR', 'Undefined error has occured')
|
|
51
|
+
createError('FST_ERR_BAD_STATUS_CODE', 'Called reply with malformed status code')
|
|
49
52
|
|
|
50
53
|
/**
|
|
51
54
|
* schemas
|
|
@@ -72,6 +75,11 @@ createError('FST_ERR_HTTP2_INVALID_VERSION', 'HTTP2 is available only from node
|
|
|
72
75
|
*/
|
|
73
76
|
createError('FST_ERR_INIT_OPTS_INVALID', "Invalid initialization options: '%s'")
|
|
74
77
|
|
|
78
|
+
/**
|
|
79
|
+
* router
|
|
80
|
+
*/
|
|
81
|
+
createError('FST_ERR_BAD_URL', "'%s' is not a valid url component", 400)
|
|
82
|
+
|
|
75
83
|
function createError (code, message, statusCode = 500, Base = Error) {
|
|
76
84
|
if (!code) throw new Error('Fastify error code must not be empty')
|
|
77
85
|
if (!message) throw new Error('Fastify error message must not be empty')
|
package/lib/hooks.js
CHANGED
|
@@ -17,7 +17,8 @@ const supportedHooks = [
|
|
|
17
17
|
const {
|
|
18
18
|
codes: {
|
|
19
19
|
FST_ERR_HOOK_INVALID_TYPE,
|
|
20
|
-
FST_ERR_HOOK_INVALID_HANDLER
|
|
20
|
+
FST_ERR_HOOK_INVALID_HANDLER,
|
|
21
|
+
FST_ERR_SEND_UNDEFINED_ERR
|
|
21
22
|
}
|
|
22
23
|
} = require('./errors')
|
|
23
24
|
|
|
@@ -78,6 +79,7 @@ function hookRunner (functions, runner, request, reply, cb) {
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
function handleReject (err) {
|
|
82
|
+
if (err === undefined) err = new FST_ERR_SEND_UNDEFINED_ERR()
|
|
81
83
|
cb(err, request, reply)
|
|
82
84
|
}
|
|
83
85
|
|
package/lib/logger.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* License: MIT (https://raw.githubusercontent.com/pinojs/pino-http/master/LICENSE)
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
const
|
|
9
|
+
const nullLogger = require('abstract-logging')
|
|
10
10
|
const pino = require('pino')
|
|
11
11
|
const { serializersSym } = pino.symbols
|
|
12
12
|
const { isValidLogger } = require('./validation')
|
|
@@ -82,7 +82,7 @@ function createLogger (options) {
|
|
|
82
82
|
})
|
|
83
83
|
return { logger, hasLogger: true }
|
|
84
84
|
} else if (!options.logger) {
|
|
85
|
-
const logger =
|
|
85
|
+
const logger = nullLogger
|
|
86
86
|
logger.child = () => logger
|
|
87
87
|
return { logger, hasLogger: false }
|
|
88
88
|
} else {
|
package/lib/reply.js
CHANGED
|
@@ -46,7 +46,8 @@ const {
|
|
|
46
46
|
FST_ERR_REP_INVALID_PAYLOAD_TYPE,
|
|
47
47
|
FST_ERR_REP_ALREADY_SENT,
|
|
48
48
|
FST_ERR_REP_SENT_VALUE,
|
|
49
|
-
FST_ERR_SEND_INSIDE_ONERR
|
|
49
|
+
FST_ERR_SEND_INSIDE_ONERR,
|
|
50
|
+
FST_ERR_BAD_STATUS_CODE
|
|
50
51
|
}
|
|
51
52
|
} = require('./errors')
|
|
52
53
|
|
|
@@ -140,9 +141,10 @@ Reply.prototype.send = function (payload) {
|
|
|
140
141
|
if (hasContentType === false || contentType.indexOf('charset') === -1) {
|
|
141
142
|
this[kReplyHeaders]['content-type'] = CONTENT_TYPE.JSON
|
|
142
143
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
if (typeof payload !== 'string') {
|
|
145
|
+
preserializeHook(this, payload)
|
|
146
|
+
return this
|
|
147
|
+
}
|
|
146
148
|
}
|
|
147
149
|
|
|
148
150
|
onSendHook(this, payload)
|
|
@@ -196,6 +198,10 @@ Reply.prototype.headers = function (headers) {
|
|
|
196
198
|
}
|
|
197
199
|
|
|
198
200
|
Reply.prototype.code = function (code) {
|
|
201
|
+
if (statusCodes[code] === undefined) {
|
|
202
|
+
throw new FST_ERR_BAD_STATUS_CODE()
|
|
203
|
+
}
|
|
204
|
+
|
|
199
205
|
this.res.statusCode = code
|
|
200
206
|
this[kReplyHasStatusCode] = true
|
|
201
207
|
return this
|
package/lib/route.js
CHANGED
|
@@ -6,7 +6,7 @@ const Context = require('./context')
|
|
|
6
6
|
const { buildMiddie, onRunMiddlewares } = require('./middleware')
|
|
7
7
|
const { hookRunner, hookIterator } = require('./hooks')
|
|
8
8
|
const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS']
|
|
9
|
-
const supportedHooks = ['preParsing', 'preValidation', 'onRequest', 'preHandler', 'preSerialization', 'onResponse']
|
|
9
|
+
const supportedHooks = ['preParsing', 'preValidation', 'onRequest', 'preHandler', 'preSerialization', 'onResponse', 'onSend']
|
|
10
10
|
const validation = require('./validation')
|
|
11
11
|
const buildSchema = validation.build
|
|
12
12
|
const { buildSchemaCompiler } = validation
|
|
@@ -22,8 +22,10 @@ const {
|
|
|
22
22
|
const {
|
|
23
23
|
kRoutePrefix,
|
|
24
24
|
kLogLevel,
|
|
25
|
+
kLogSerializers,
|
|
25
26
|
kHooks,
|
|
26
27
|
kSchemas,
|
|
28
|
+
kOptions,
|
|
27
29
|
kSchemaCompiler,
|
|
28
30
|
kSchemaResolver,
|
|
29
31
|
kContentTypeParser,
|
|
@@ -180,6 +182,10 @@ function buildRouting (options) {
|
|
|
180
182
|
opts.prefix = prefix
|
|
181
183
|
opts.logLevel = opts.logLevel || this[kLogLevel]
|
|
182
184
|
|
|
185
|
+
if (this[kLogSerializers] || opts.logSerializers) {
|
|
186
|
+
opts.logSerializers = Object.assign(Object.create(this[kLogSerializers]), opts.logSerializers)
|
|
187
|
+
}
|
|
188
|
+
|
|
183
189
|
if (opts.attachValidation == null) {
|
|
184
190
|
opts.attachValidation = false
|
|
185
191
|
}
|
|
@@ -207,33 +213,11 @@ function buildRouting (options) {
|
|
|
207
213
|
this._errorHandler,
|
|
208
214
|
opts.bodyLimit,
|
|
209
215
|
opts.logLevel,
|
|
216
|
+
opts.logSerializers,
|
|
210
217
|
opts.attachValidation,
|
|
211
218
|
this[kReplySerializerDefault]
|
|
212
219
|
)
|
|
213
220
|
|
|
214
|
-
// TODO this needs to be refactored so that buildSchemaCompiler is
|
|
215
|
-
// not called for every single route. Creating a new one for every route
|
|
216
|
-
// is going to be very expensive.
|
|
217
|
-
if (opts.schema) {
|
|
218
|
-
if (this[kSchemaCompiler] == null && this[kSchemaResolver]) {
|
|
219
|
-
done(new FST_ERR_SCH_MISSING_COMPILER(opts.method, url))
|
|
220
|
-
return
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
try {
|
|
224
|
-
if (opts.schemaCompiler == null && this[kSchemaCompiler] == null) {
|
|
225
|
-
const externalSchemas = this[kSchemas].getJsonSchemas({ onlyAbsoluteUri: true })
|
|
226
|
-
this.setSchemaCompiler(buildSchemaCompiler(externalSchemas, schemaCache))
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
buildSchema(context, opts.schemaCompiler || this[kSchemaCompiler], this[kSchemas], this[kSchemaResolver])
|
|
230
|
-
} catch (error) {
|
|
231
|
-
// bubble up the FastifyError instance
|
|
232
|
-
done(error.code ? error : new FST_ERR_SCH_BUILD(opts.method, url, error.message))
|
|
233
|
-
return
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
221
|
for (const hook of supportedHooks) {
|
|
238
222
|
if (opts[hook]) {
|
|
239
223
|
if (Array.isArray(opts[hook])) {
|
|
@@ -245,7 +229,7 @@ function buildRouting (options) {
|
|
|
245
229
|
}
|
|
246
230
|
|
|
247
231
|
try {
|
|
248
|
-
router.on(opts.method, url, { version: opts.version }, routeHandler, context)
|
|
232
|
+
router.on(opts.method, opts.url, { version: opts.version }, routeHandler, context)
|
|
249
233
|
} catch (err) {
|
|
250
234
|
done(err)
|
|
251
235
|
return
|
|
@@ -273,6 +257,24 @@ function buildRouting (options) {
|
|
|
273
257
|
// Must store the 404 Context in 'preReady' because it is only guaranteed to
|
|
274
258
|
// be available after all of the plugins and routes have been loaded.
|
|
275
259
|
fourOhFour.setContext(this, context)
|
|
260
|
+
|
|
261
|
+
if (opts.schema) {
|
|
262
|
+
if (this[kSchemaCompiler] == null && this[kSchemaResolver]) {
|
|
263
|
+
throw new FST_ERR_SCH_MISSING_COMPILER(opts.method, url)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
if (opts.schemaCompiler == null && this[kSchemaCompiler] == null) {
|
|
268
|
+
const externalSchemas = this[kSchemas].getJsonSchemas({ onlyAbsoluteUri: true })
|
|
269
|
+
this.setSchemaCompiler(buildSchemaCompiler(externalSchemas, this[kOptions].ajv, schemaCache))
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
buildSchema(context, opts.schemaCompiler || this[kSchemaCompiler], this[kSchemas], this[kSchemaResolver])
|
|
273
|
+
} catch (error) {
|
|
274
|
+
// bubble up the FastifyError instance
|
|
275
|
+
throw (error.code ? error : new FST_ERR_SCH_BUILD(opts.method, url, error.message))
|
|
276
|
+
}
|
|
277
|
+
}
|
|
276
278
|
})
|
|
277
279
|
|
|
278
280
|
done(notHandledErr)
|
|
@@ -300,7 +302,7 @@ function buildRouting (options) {
|
|
|
300
302
|
|
|
301
303
|
req.id = req.headers[requestIdHeader] || genReqId(req)
|
|
302
304
|
req.originalUrl = req.url
|
|
303
|
-
var hostname = req.headers.host
|
|
305
|
+
var hostname = req.headers.host || req.headers[':authority']
|
|
304
306
|
var ip = req.connection.remoteAddress
|
|
305
307
|
var ips
|
|
306
308
|
|
|
@@ -312,7 +314,15 @@ function buildRouting (options) {
|
|
|
312
314
|
}
|
|
313
315
|
}
|
|
314
316
|
|
|
315
|
-
var
|
|
317
|
+
var loggerOpts = {
|
|
318
|
+
[requestIdLogLabel]: req.id,
|
|
319
|
+
level: context.logLevel
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (context.logSerializers) {
|
|
323
|
+
loggerOpts.serializers = context.logSerializers
|
|
324
|
+
}
|
|
325
|
+
var childLogger = logger.child(loggerOpts)
|
|
316
326
|
childLogger[kDisableRequestLogging] = disableRequestLogging
|
|
317
327
|
|
|
318
328
|
// added hostname, ip, and ips back to the Node req object to maintain backward compatibility
|
package/lib/schemas.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const fastClone = require('rfdc')({ circles: false, proto: true })
|
|
4
|
+
const { kSchemaVisited } = require('./symbols')
|
|
4
5
|
const kFluentSchema = Symbol.for('fluent-schema-object')
|
|
5
6
|
|
|
6
7
|
const {
|
|
@@ -43,6 +44,10 @@ Schemas.prototype.resolve = function (id) {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
Schemas.prototype.resolveRefs = function (routeSchemas, dontClearId, refResolver) {
|
|
47
|
+
if (routeSchemas[kSchemaVisited]) {
|
|
48
|
+
return routeSchemas
|
|
49
|
+
}
|
|
50
|
+
|
|
46
51
|
// alias query to querystring schema
|
|
47
52
|
if (routeSchemas.query) {
|
|
48
53
|
// check if our schema has both querystring and query
|
|
@@ -96,6 +101,7 @@ Schemas.prototype.resolveRefs = function (routeSchemas, dontClearId, refResolver
|
|
|
96
101
|
routeSchemas.params = this.getSchemaAnyway(routeSchemas.params)
|
|
97
102
|
}
|
|
98
103
|
|
|
104
|
+
routeSchemas[kSchemaVisited] = true
|
|
99
105
|
return routeSchemas
|
|
100
106
|
}
|
|
101
107
|
|
|
@@ -117,7 +123,9 @@ Schemas.prototype.traverse = function (schema, refResolver) {
|
|
|
117
123
|
}
|
|
118
124
|
}
|
|
119
125
|
|
|
120
|
-
if (schema[key] !== null && typeof schema[key] === 'object'
|
|
126
|
+
if (schema[key] !== null && typeof schema[key] === 'object' &&
|
|
127
|
+
(key !== 'enum' || (key === 'enum' && schema.type !== 'string'))) {
|
|
128
|
+
// don't traverse non-object values and the `enum` keyword when used for string type
|
|
121
129
|
this.traverse(schema[key], refResolver)
|
|
122
130
|
}
|
|
123
131
|
}
|
|
@@ -135,7 +143,7 @@ Schemas.prototype.cleanId = function (schema) {
|
|
|
135
143
|
}
|
|
136
144
|
|
|
137
145
|
Schemas.prototype.getSchemaAnyway = function (schema) {
|
|
138
|
-
if (schema.oneOf || schema.allOf || schema.anyOf) return schema
|
|
146
|
+
if (schema.oneOf || schema.allOf || schema.anyOf || schema.$merge || schema.$patch) return schema
|
|
139
147
|
if (!schema.type || !schema.properties) {
|
|
140
148
|
return {
|
|
141
149
|
type: 'object',
|
package/lib/server.js
CHANGED
|
@@ -24,6 +24,7 @@ function createServer (options, httpHandler) {
|
|
|
24
24
|
}
|
|
25
25
|
} else if (options.http2) {
|
|
26
26
|
server = http2().createServer(httpHandler)
|
|
27
|
+
server.on('session', sessionTimeout(options.http2SessionTimeout))
|
|
27
28
|
} else {
|
|
28
29
|
server = http.createServer(httpHandler)
|
|
29
30
|
}
|
|
@@ -148,4 +149,14 @@ function http2 () {
|
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
151
|
|
|
152
|
+
function sessionTimeout (timeout) {
|
|
153
|
+
return function (session) {
|
|
154
|
+
session.setTimeout(timeout, close)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function close () {
|
|
159
|
+
this.close()
|
|
160
|
+
}
|
|
161
|
+
|
|
151
162
|
module.exports = { createServer }
|
package/lib/symbols.js
CHANGED
|
@@ -5,6 +5,7 @@ const keys = {
|
|
|
5
5
|
kBodyLimit: Symbol('fastify.bodyLimit'),
|
|
6
6
|
kRoutePrefix: Symbol('fastify.routePrefix'),
|
|
7
7
|
kLogLevel: Symbol('fastify.logLevel'),
|
|
8
|
+
kLogSerializers: Symbol('fastify.logSerializers'),
|
|
8
9
|
kHooks: Symbol('fastify.hooks'),
|
|
9
10
|
kSchemas: Symbol('fastify.schemas'),
|
|
10
11
|
kSchemaCompiler: Symbol('fastify.schemaCompiler'),
|
|
@@ -32,7 +33,8 @@ const keys = {
|
|
|
32
33
|
kOptions: Symbol('fastify.options'),
|
|
33
34
|
kGlobalHooks: Symbol('fastify.globalHooks'),
|
|
34
35
|
kDisableRequestLogging: Symbol('fastify.disableRequestLogging'),
|
|
35
|
-
kPluginNameChain: Symbol('fastify.pluginNameChain')
|
|
36
|
+
kPluginNameChain: Symbol('fastify.pluginNameChain'),
|
|
37
|
+
kSchemaVisited: Symbol('fastify.schemaVisited')
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
module.exports = keys
|
package/lib/validation.js
CHANGED
|
@@ -119,6 +119,7 @@ function wrapValidationError (result, dataVar) {
|
|
|
119
119
|
}
|
|
120
120
|
var error = new Error(schemaErrorsText(result, dataVar))
|
|
121
121
|
error.validation = result
|
|
122
|
+
error.validationContext = dataVar
|
|
122
123
|
return error
|
|
123
124
|
}
|
|
124
125
|
|
|
@@ -163,17 +164,22 @@ function schemaErrorsText (errors, dataVar) {
|
|
|
163
164
|
return text.slice(0, -separator.length)
|
|
164
165
|
}
|
|
165
166
|
|
|
166
|
-
function buildSchemaCompiler (externalSchemas, cache) {
|
|
167
|
+
function buildSchemaCompiler (externalSchemas, options, cache) {
|
|
167
168
|
// This instance of Ajv is private
|
|
168
169
|
// it should not be customized or used
|
|
169
|
-
const ajv = new Ajv({
|
|
170
|
+
const ajv = new Ajv(Object.assign({
|
|
170
171
|
coerceTypes: true,
|
|
171
172
|
useDefaults: true,
|
|
172
173
|
removeAdditional: true,
|
|
173
174
|
allErrors: true,
|
|
174
|
-
nullable: true
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
nullable: true
|
|
176
|
+
}, options.customOptions, { cache }))
|
|
177
|
+
|
|
178
|
+
if (options.plugins && options.plugins.length > 0) {
|
|
179
|
+
for (const plugin of options.plugins) {
|
|
180
|
+
plugin[0](ajv, plugin[1])
|
|
181
|
+
}
|
|
182
|
+
}
|
|
177
183
|
|
|
178
184
|
if (Array.isArray(externalSchemas)) {
|
|
179
185
|
externalSchemas.forEach(s => ajv.addSchema(s))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastify",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"description": "Fast and low overhead web framework, for Node.js",
|
|
5
5
|
"main": "fastify.js",
|
|
6
6
|
"typings": "fastify.d.ts",
|
|
@@ -92,26 +92,28 @@
|
|
|
92
92
|
"node": ">=6"
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
|
-
"@types/node": "^
|
|
96
|
-
"@typescript-eslint/eslint-plugin": "^2.
|
|
97
|
-
"@typescript-eslint/parser": "^2.
|
|
95
|
+
"@types/node": "^12.12.30",
|
|
96
|
+
"@typescript-eslint/eslint-plugin": "^2.24.0",
|
|
97
|
+
"@typescript-eslint/parser": "^2.24.0",
|
|
98
98
|
"JSONStream": "^1.3.5",
|
|
99
|
+
"ajv-merge-patch": "^4.1.0",
|
|
99
100
|
"ajv-pack": "^0.3.1",
|
|
100
|
-
"autocannon": "^3.2.
|
|
101
|
+
"autocannon": "^3.2.2",
|
|
101
102
|
"branch-comparer": "^0.4.0",
|
|
102
|
-
"concurrently": "^5.
|
|
103
|
+
"concurrently": "^5.1.0",
|
|
103
104
|
"cors": "^2.8.5",
|
|
104
|
-
"coveralls": "^3.0.
|
|
105
|
+
"coveralls": "^3.0.11",
|
|
105
106
|
"dns-prefetch-control": "^0.2.0",
|
|
106
|
-
"eslint": "^6.
|
|
107
|
-
"eslint-import-resolver-node": "^0.3.
|
|
107
|
+
"eslint": "^6.7.2",
|
|
108
|
+
"eslint-import-resolver-node": "^0.3.3",
|
|
109
|
+
"events.once": "^2.0.2",
|
|
108
110
|
"fast-json-body": "^1.1.0",
|
|
109
|
-
"fastify-plugin": "^1.
|
|
110
|
-
"fluent-schema": "^0.
|
|
111
|
-
"form-data": "^
|
|
111
|
+
"fastify-plugin": "^1.6.1",
|
|
112
|
+
"fluent-schema": "^0.10.0",
|
|
113
|
+
"form-data": "^3.0.0",
|
|
112
114
|
"frameguard": "^3.0.0",
|
|
113
|
-
"h2url": "^0.
|
|
114
|
-
"helmet": "^3.
|
|
115
|
+
"h2url": "^0.2.0",
|
|
116
|
+
"helmet": "^3.21.3",
|
|
115
117
|
"hide-powered-by": "^1.0.0",
|
|
116
118
|
"hsts": "^2.1.0",
|
|
117
119
|
"http-errors": "^1.7.1",
|
|
@@ -128,28 +130,29 @@
|
|
|
128
130
|
"simple-get": "^3.0.3",
|
|
129
131
|
"snazzy": "^8.0.0",
|
|
130
132
|
"split2": "^3.1.0",
|
|
131
|
-
"standard": "^14.
|
|
133
|
+
"standard": "^14.3.3",
|
|
132
134
|
"tap": "^12.5.2",
|
|
133
135
|
"tap-mocha-reporter": "^3.0.7",
|
|
134
136
|
"then-sleep": "^1.0.1",
|
|
135
|
-
"typescript": "^3.
|
|
136
|
-
"x-xss-protection": "^1.1.0"
|
|
137
|
+
"typescript": "^3.8.3",
|
|
138
|
+
"x-xss-protection": "^1.1.0",
|
|
139
|
+
"yup": "^0.28.3"
|
|
137
140
|
},
|
|
138
141
|
"dependencies": {
|
|
139
|
-
"abstract-logging": "^
|
|
140
|
-
"ajv": "^6.
|
|
141
|
-
"avvio": "^6.
|
|
142
|
-
"fast-json-stringify": "^1.
|
|
143
|
-
"find-my-way": "^2.
|
|
142
|
+
"abstract-logging": "^2.0.0",
|
|
143
|
+
"ajv": "^6.12.0",
|
|
144
|
+
"avvio": "^6.3.1",
|
|
145
|
+
"fast-json-stringify": "^1.18.0",
|
|
146
|
+
"find-my-way": "^2.2.2",
|
|
144
147
|
"flatstr": "^1.0.12",
|
|
145
|
-
"light-my-request": "^3.
|
|
146
|
-
"middie": "^4.0
|
|
147
|
-
"pino": "^5.
|
|
148
|
-
"proxy-addr": "^2.0.
|
|
149
|
-
"readable-stream": "^3.
|
|
148
|
+
"light-my-request": "^3.7.2",
|
|
149
|
+
"middie": "^4.1.0",
|
|
150
|
+
"pino": "^5.17.0",
|
|
151
|
+
"proxy-addr": "^2.0.6",
|
|
152
|
+
"readable-stream": "^3.6.0",
|
|
150
153
|
"rfdc": "^1.1.2",
|
|
151
|
-
"secure-json-parse": "^1.0
|
|
152
|
-
"tiny-lru": "^7.0.
|
|
154
|
+
"secure-json-parse": "^2.1.0",
|
|
155
|
+
"tiny-lru": "^7.0.2"
|
|
153
156
|
},
|
|
154
157
|
"greenkeeper": {
|
|
155
158
|
"ignore": [
|
|
@@ -157,6 +160,7 @@
|
|
|
157
160
|
"boom",
|
|
158
161
|
"joi",
|
|
159
162
|
"@types/node",
|
|
163
|
+
"semver",
|
|
160
164
|
"tap",
|
|
161
165
|
"tap-mocha-reporter",
|
|
162
166
|
"@typescript-eslint/eslint-plugin",
|
package/test/404s.test.js
CHANGED
|
@@ -1733,3 +1733,43 @@ test('Should fail to invoke callNotFound inside a 404 handler', t => {
|
|
|
1733
1733
|
t.is(res.payload, '404 Not Found')
|
|
1734
1734
|
})
|
|
1735
1735
|
})
|
|
1736
|
+
|
|
1737
|
+
test('400 in case of bad url (pre find-my-way v2.2.0 was a 404)', t => {
|
|
1738
|
+
t.test('Dyamic route', t => {
|
|
1739
|
+
t.plan(3)
|
|
1740
|
+
const fastify = Fastify()
|
|
1741
|
+
fastify.get('/hello/:id', () => t.fail('we should not be here'))
|
|
1742
|
+
fastify.inject({
|
|
1743
|
+
url: '/hello/%world',
|
|
1744
|
+
method: 'GET'
|
|
1745
|
+
}, (err, response) => {
|
|
1746
|
+
t.error(err)
|
|
1747
|
+
t.strictEqual(response.statusCode, 400)
|
|
1748
|
+
t.deepEqual(JSON.parse(response.payload), {
|
|
1749
|
+
error: 'Bad Request',
|
|
1750
|
+
message: "'%world' is not a valid url component",
|
|
1751
|
+
statusCode: 400
|
|
1752
|
+
})
|
|
1753
|
+
})
|
|
1754
|
+
})
|
|
1755
|
+
|
|
1756
|
+
t.test('Wildcard', t => {
|
|
1757
|
+
t.plan(3)
|
|
1758
|
+
const fastify = Fastify()
|
|
1759
|
+
fastify.get('*', () => t.fail('we should not be here'))
|
|
1760
|
+
fastify.inject({
|
|
1761
|
+
url: '/hello/%world',
|
|
1762
|
+
method: 'GET'
|
|
1763
|
+
}, (err, response) => {
|
|
1764
|
+
t.error(err)
|
|
1765
|
+
t.strictEqual(response.statusCode, 400)
|
|
1766
|
+
t.deepEqual(JSON.parse(response.payload), {
|
|
1767
|
+
error: 'Bad Request',
|
|
1768
|
+
message: "'/hello/%world' is not a valid url component",
|
|
1769
|
+
statusCode: 400
|
|
1770
|
+
})
|
|
1771
|
+
})
|
|
1772
|
+
})
|
|
1773
|
+
|
|
1774
|
+
t.end()
|
|
1775
|
+
})
|
package/test/decorator.test.js
CHANGED
|
@@ -660,3 +660,83 @@ test('a decorator should addSchema to all the encapsulated tree', t => {
|
|
|
660
660
|
|
|
661
661
|
fastify.ready(t.error)
|
|
662
662
|
})
|
|
663
|
+
|
|
664
|
+
test('after can access to a decorated instance and previous plugin decoration', t => {
|
|
665
|
+
t.plan(11)
|
|
666
|
+
const TEST_VALUE = {}
|
|
667
|
+
const OTHER_TEST_VALUE = {}
|
|
668
|
+
const NEW_TEST_VALUE = {}
|
|
669
|
+
|
|
670
|
+
const fastify = Fastify()
|
|
671
|
+
|
|
672
|
+
fastify.register(fp(function (instance, options, next) {
|
|
673
|
+
instance.decorate('test', TEST_VALUE)
|
|
674
|
+
|
|
675
|
+
next()
|
|
676
|
+
})).after(function (err, instance, done) {
|
|
677
|
+
t.error(err)
|
|
678
|
+
t.equal(instance.test, TEST_VALUE)
|
|
679
|
+
|
|
680
|
+
instance.decorate('test2', OTHER_TEST_VALUE)
|
|
681
|
+
done()
|
|
682
|
+
})
|
|
683
|
+
|
|
684
|
+
fastify.register(fp(function (instance, options, next) {
|
|
685
|
+
t.equal(instance.test, TEST_VALUE)
|
|
686
|
+
t.equal(instance.test2, OTHER_TEST_VALUE)
|
|
687
|
+
|
|
688
|
+
instance.decorate('test3', NEW_TEST_VALUE)
|
|
689
|
+
|
|
690
|
+
next()
|
|
691
|
+
})).after(function (err, instance, done) {
|
|
692
|
+
t.error(err)
|
|
693
|
+
t.equal(instance.test, TEST_VALUE)
|
|
694
|
+
t.equal(instance.test2, OTHER_TEST_VALUE)
|
|
695
|
+
t.equal(instance.test3, NEW_TEST_VALUE)
|
|
696
|
+
|
|
697
|
+
done()
|
|
698
|
+
})
|
|
699
|
+
|
|
700
|
+
fastify.get('/', function (req, res) {
|
|
701
|
+
t.equal(this.test, TEST_VALUE)
|
|
702
|
+
t.equal(this.test2, OTHER_TEST_VALUE)
|
|
703
|
+
res.send({})
|
|
704
|
+
})
|
|
705
|
+
|
|
706
|
+
fastify.inject('/')
|
|
707
|
+
.then(response => {
|
|
708
|
+
t.equal(response.statusCode, 200)
|
|
709
|
+
})
|
|
710
|
+
})
|
|
711
|
+
|
|
712
|
+
test('decorate* should throw if called after ready', t => {
|
|
713
|
+
t.plan(3)
|
|
714
|
+
const fastify = Fastify()
|
|
715
|
+
|
|
716
|
+
fastify.get('/', (request, reply) => {
|
|
717
|
+
reply.send({
|
|
718
|
+
hello: 'world'
|
|
719
|
+
})
|
|
720
|
+
})
|
|
721
|
+
|
|
722
|
+
fastify.listen(0)
|
|
723
|
+
.then(() => {
|
|
724
|
+
try {
|
|
725
|
+
fastify.decorate('test', true)
|
|
726
|
+
} catch (e) {
|
|
727
|
+
t.is(e.message, "FST_ERR_DEC_AFTER_START: The decorator 'test' has been added after start!")
|
|
728
|
+
}
|
|
729
|
+
try {
|
|
730
|
+
fastify.decorateRequest('test', true)
|
|
731
|
+
} catch (e) {
|
|
732
|
+
t.is(e.message, "FST_ERR_DEC_AFTER_START: The decorator 'test' has been added after start!")
|
|
733
|
+
}
|
|
734
|
+
try {
|
|
735
|
+
fastify.decorateReply('test', true)
|
|
736
|
+
} catch (e) {
|
|
737
|
+
t.is(e.message, "FST_ERR_DEC_AFTER_START: The decorator 'test' has been added after start!")
|
|
738
|
+
}
|
|
739
|
+
return fastify.close()
|
|
740
|
+
})
|
|
741
|
+
.catch(err => t.fail(err))
|
|
742
|
+
})
|
package/test/esm/esm.mjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import t from 'tap'
|
|
2
|
+
import Fastify from '../../fastify.js'
|
|
3
|
+
|
|
4
|
+
t.test('esm support', async t => {
|
|
5
|
+
const fastify = Fastify()
|
|
6
|
+
|
|
7
|
+
fastify.register(import('./plugin.mjs'), { foo: 'bar' })
|
|
8
|
+
fastify.register(import('./other.mjs'))
|
|
9
|
+
|
|
10
|
+
await fastify.ready()
|
|
11
|
+
|
|
12
|
+
t.strictEqual(fastify.foo, 'bar')
|
|
13
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const t = require('tap')
|
|
4
|
+
const semver = require('semver')
|
|
5
|
+
|
|
6
|
+
if (semver.lt(process.versions.node, '13.3.0')) {
|
|
7
|
+
t.skip('Skip because Node version <= 13.3.0')
|
|
8
|
+
t.end()
|
|
9
|
+
} else {
|
|
10
|
+
// Node v8 throw a `SyntaxError: Unexpected token import`
|
|
11
|
+
// even if this branch is never touch in the code,
|
|
12
|
+
// by using `eval` we can avoid this issue.
|
|
13
|
+
// eslint-disable-next-line
|
|
14
|
+
new Function('module', 'return import(module)')('./esm.mjs').catch((err) => {
|
|
15
|
+
process.nextTick(() => {
|
|
16
|
+
throw err
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
}
|