fastify 2.14.1 → 2.15.3
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/docs/Hooks.md +26 -2
- package/docs/Server.md +3 -1
- package/docs/Validation-and-Serialization.md +3 -9
- package/fastify.d.ts +1 -1
- package/fastify.js +65 -4
- package/lib/hooks.js +80 -1
- package/lib/reply.js +23 -2
- package/lib/symbols.js +1 -0
- package/lib/validation.js +3 -1
- package/package.json +2 -2
- package/test/hooks.on-ready.async.js +173 -0
- package/test/hooks.on-ready.test.js +236 -0
- package/test/inject.test.js +15 -0
- package/test/internals/reply.test.js +102 -0
- package/test/request-error.test.js +81 -0
- package/test/validation-error-handling.test.js +8 -15
package/docs/Hooks.md
CHANGED
|
@@ -18,6 +18,7 @@ By using hooks you can interact directly with the lifecycle of Fastify. There ar
|
|
|
18
18
|
- [Manage Errors from a hook](#manage-errors-from-a-hook)
|
|
19
19
|
- [Respond to a request from a hook](#respond-to-a-request-from-a-hook)
|
|
20
20
|
- [Application Hooks](#application-hooks)
|
|
21
|
+
- [onReady](#onready)
|
|
21
22
|
- [onClose](#onclose)
|
|
22
23
|
- [onRoute](#onroute)
|
|
23
24
|
- [onRegister](#onregister)
|
|
@@ -107,7 +108,7 @@ If you are using the `preSerialization` hook, you can change (or replace) the pa
|
|
|
107
108
|
|
|
108
109
|
```js
|
|
109
110
|
fastify.addHook('preSerialization', (request, reply, payload, done) => {
|
|
110
|
-
const err = null
|
|
111
|
+
const err = null
|
|
111
112
|
const newPayload = { wrapped: payload }
|
|
112
113
|
done(err, newPayload)
|
|
113
114
|
})
|
|
@@ -282,12 +283,34 @@ fastify.addHook('preHandler', async (request, reply) => {
|
|
|
282
283
|
|
|
283
284
|
You can hook into the application-lifecycle as well. It's important to note that these hooks aren't fully encapsulated. The `this` inside the hooks are encapsulated but the handlers can respond to an event outside the encapsulation boundaries.
|
|
284
285
|
|
|
286
|
+
- [onReady](#onready)
|
|
285
287
|
- [onClose](#onclose)
|
|
286
288
|
- [onRoute](#onroute)
|
|
287
289
|
- [onRegister](#onregister)
|
|
288
290
|
|
|
289
|
-
|
|
291
|
+
### onReady
|
|
292
|
+
Triggered before the server starts listening for requests. It cannot change the routes or add new hooks.
|
|
293
|
+
Registered hook functions are executed serially.
|
|
294
|
+
Only after all `onReady` hook functions have completed will the server start listening for requests.
|
|
295
|
+
Hook functions accept one argument: a callback, `done`, to be invoked after the hook function is complete.
|
|
296
|
+
Hook functions are invoked with `this` bound to the associated Fastify instance.
|
|
297
|
+
|
|
298
|
+
```js
|
|
299
|
+
// callback style
|
|
300
|
+
fastify.addHook('onReady', function (done) {
|
|
301
|
+
// Some code
|
|
302
|
+
const err = null;
|
|
303
|
+
done(err)
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
// or async/await style
|
|
307
|
+
fastify.addHook('onReady', async function () {
|
|
308
|
+
// Some async code
|
|
309
|
+
await loadCacheFromDatabase()
|
|
310
|
+
})
|
|
311
|
+
```
|
|
290
312
|
|
|
313
|
+
<a name="on-close"></a>
|
|
291
314
|
### onClose
|
|
292
315
|
Triggered when `fastify.close()` is invoked to stop the server. It is useful when [plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins.md) need a "shutdown" event, for example to close an open connection to a database.<br>
|
|
293
316
|
The first argument is the Fastify instance, the second one the `done` callback.
|
|
@@ -297,6 +320,7 @@ fastify.addHook('onClose', (instance, done) => {
|
|
|
297
320
|
done()
|
|
298
321
|
})
|
|
299
322
|
```
|
|
323
|
+
|
|
300
324
|
<a name="on-route"></a>
|
|
301
325
|
### onRoute
|
|
302
326
|
Triggered when a new route is registered. Listeners are passed a `routeOptions` object as the sole parameter. The interface is synchronous, and, as such, the listeners do not get passed a callback.
|
package/docs/Server.md
CHANGED
|
@@ -748,13 +748,15 @@ fastify.register(function (instance, options, done) {
|
|
|
748
748
|
<a name="set-error-handler"></a>
|
|
749
749
|
#### setErrorHandler
|
|
750
750
|
|
|
751
|
-
`fastify.setErrorHandler(handler(error, request, reply))`: Set a function that will be called whenever an error happens. The handler is fully encapsulated, so different plugins can set different error handlers. *async-await* is supported as well.<br>
|
|
751
|
+
`fastify.setErrorHandler(handler(error, request, reply))`: Set a function that will be called whenever an error happens. The handler is bound to the Fastify instance, and is fully encapsulated, so different plugins can set different error handlers. *async-await* is supported as well.<br>
|
|
752
752
|
*Note: If the error `statusCode` is less than 400, Fastify will automatically set it at 500 before calling the error handler.*
|
|
753
753
|
|
|
754
754
|
```js
|
|
755
755
|
fastify.setErrorHandler(function (error, request, reply) {
|
|
756
756
|
// Log error
|
|
757
|
+
this.log.error(error)
|
|
757
758
|
// Send error response
|
|
759
|
+
reply.status(409).send({ ok: false })
|
|
758
760
|
})
|
|
759
761
|
```
|
|
760
762
|
|
|
@@ -14,7 +14,7 @@ Fastify uses a schema-based approach, and even if it is not mandatory we recomme
|
|
|
14
14
|
<a name="validation"></a>
|
|
15
15
|
### Validation
|
|
16
16
|
The route validation internally relies upon [Ajv](https://www.npmjs.com/package/ajv), which is a high-performance JSON schema validator. Validating the input is very easy: just add the fields that you need inside the route schema, and you are done! The supported validations are:
|
|
17
|
-
- `body`: validates the body of the request if it is a POST or a PUT.
|
|
17
|
+
- `body`: validates the body of the request if it is a POST, a PATCH or a PUT.
|
|
18
18
|
- `querystring` or `query`: validates the query string. This can be a complete JSON Schema object (with a `type` property of `'object'` and a `'properties'` object containing parameters) or a simpler variation in which the `type` and `properties` attributes are forgone and the query parameters are listed at the top level (see the example below).
|
|
19
19
|
- `params`: validates the route params.
|
|
20
20
|
- `headers`: validates the request headers.
|
|
@@ -338,7 +338,6 @@ Fastify's [baseline ajv configuration](https://github.com/epoberezkin/ajv#option
|
|
|
338
338
|
removeAdditional: true, // remove additional properties
|
|
339
339
|
useDefaults: true, // replace missing properties and items with the values from corresponding default keyword
|
|
340
340
|
coerceTypes: true, // change data type of data to match type keyword
|
|
341
|
-
allErrors: true, // check for all errors
|
|
342
341
|
nullable: true // support keyword "nullable" from Open API 3 specification.
|
|
343
342
|
}
|
|
344
343
|
```
|
|
@@ -355,7 +354,6 @@ const ajv = new Ajv({
|
|
|
355
354
|
removeAdditional: true,
|
|
356
355
|
useDefaults: true,
|
|
357
356
|
coerceTypes: true,
|
|
358
|
-
allErrors: true,
|
|
359
357
|
nullable: true,
|
|
360
358
|
// any other options
|
|
361
359
|
// ...
|
|
@@ -666,7 +664,7 @@ Inline comments in the schema below describe how to configure it to show a diffe
|
|
|
666
664
|
```js
|
|
667
665
|
const fastify = Fastify({
|
|
668
666
|
ajv: {
|
|
669
|
-
customOptions: {
|
|
667
|
+
customOptions: { jsonPointers: true },
|
|
670
668
|
plugins: [
|
|
671
669
|
require('ajv-errors')
|
|
672
670
|
]
|
|
@@ -713,11 +711,7 @@ If you want to return localized error messages, take a look at [ajv-i18n](https:
|
|
|
713
711
|
```js
|
|
714
712
|
const localize = require('ajv-i18n')
|
|
715
713
|
|
|
716
|
-
const fastify = Fastify(
|
|
717
|
-
ajv: {
|
|
718
|
-
customOptions: { allErrors: true }
|
|
719
|
-
}
|
|
720
|
-
})
|
|
714
|
+
const fastify = Fastify()
|
|
721
715
|
|
|
722
716
|
const schema = {
|
|
723
717
|
body: {
|
package/fastify.d.ts
CHANGED
|
@@ -667,7 +667,7 @@ declare namespace fastify {
|
|
|
667
667
|
/**
|
|
668
668
|
* Set a function that will be called whenever an error happens
|
|
669
669
|
*/
|
|
670
|
-
setErrorHandler<E = FastifyError>(handler: (error: E, request: FastifyRequest<HttpRequest>, reply: FastifyReply<HttpResponse>) => void): void
|
|
670
|
+
setErrorHandler<E = FastifyError>(handler: (this: FastifyInstance<HttpServer, HttpRequest, HttpResponse>, error: E, request: FastifyRequest<HttpRequest>, reply: FastifyReply<HttpResponse>) => void): void
|
|
671
671
|
|
|
672
672
|
/**
|
|
673
673
|
* Set a function that will be called whenever an error happens
|
package/fastify.js
CHANGED
|
@@ -6,6 +6,7 @@ const querystring = require('querystring')
|
|
|
6
6
|
let lightMyRequest
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
|
+
kAvvioBoot,
|
|
9
10
|
kChildren,
|
|
10
11
|
kBodyLimit,
|
|
11
12
|
kRoutePrefix,
|
|
@@ -33,7 +34,7 @@ const Request = require('./lib/request')
|
|
|
33
34
|
const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS']
|
|
34
35
|
const decorator = require('./lib/decorate')
|
|
35
36
|
const ContentTypeParser = require('./lib/contentTypeParser')
|
|
36
|
-
const { Hooks, buildHooks } = require('./lib/hooks')
|
|
37
|
+
const { Hooks, buildHooks, hookRunnerApplication } = require('./lib/hooks')
|
|
37
38
|
const { Schemas, buildSchemas } = require('./lib/schemas')
|
|
38
39
|
const { createLogger } = require('./lib/logger')
|
|
39
40
|
const pluginUtils = require('./lib/pluginUtils')
|
|
@@ -166,6 +167,7 @@ function build (options) {
|
|
|
166
167
|
},
|
|
167
168
|
[pluginUtils.registeredPlugins]: [],
|
|
168
169
|
[kPluginNameChain]: [],
|
|
170
|
+
[kAvvioBoot]: null,
|
|
169
171
|
// routes shorthand methods
|
|
170
172
|
delete: function _delete (url, opts, handler) {
|
|
171
173
|
return router.prepareRoute.call(this, 'DELETE', url, opts, handler)
|
|
@@ -285,6 +287,8 @@ function build (options) {
|
|
|
285
287
|
// Override to allow the plugin incapsulation
|
|
286
288
|
avvio.override = override
|
|
287
289
|
avvio.on('start', () => (fastify[kState].started = true))
|
|
290
|
+
fastify[kAvvioBoot] = fastify.ready // the avvio ready function
|
|
291
|
+
fastify.ready = ready // overwrite the avvio ready function
|
|
288
292
|
// cache the closing value, since we are checking it in an hot path
|
|
289
293
|
avvio.once('preReady', () => {
|
|
290
294
|
fastify.onClose((instance, done) => {
|
|
@@ -348,8 +352,15 @@ function build (options) {
|
|
|
348
352
|
else lightMyRequest(httpHandler, opts, cb)
|
|
349
353
|
})
|
|
350
354
|
} else {
|
|
351
|
-
return
|
|
352
|
-
.
|
|
355
|
+
return lightMyRequest((req, res) => {
|
|
356
|
+
this.ready(function (err) {
|
|
357
|
+
if (err) {
|
|
358
|
+
res.emit('error', err)
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
httpHandler(req, res)
|
|
362
|
+
})
|
|
363
|
+
}, opts)
|
|
353
364
|
}
|
|
354
365
|
}
|
|
355
366
|
|
|
@@ -371,6 +382,48 @@ function build (options) {
|
|
|
371
382
|
}
|
|
372
383
|
}
|
|
373
384
|
|
|
385
|
+
function ready (cb) {
|
|
386
|
+
let resolveReady
|
|
387
|
+
let rejectReady
|
|
388
|
+
|
|
389
|
+
// run the hooks after returning the promise
|
|
390
|
+
process.nextTick(runHooks)
|
|
391
|
+
|
|
392
|
+
if (!cb) {
|
|
393
|
+
return new Promise(function (resolve, reject) {
|
|
394
|
+
resolveReady = resolve
|
|
395
|
+
rejectReady = reject
|
|
396
|
+
})
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function runHooks () {
|
|
400
|
+
// start loading
|
|
401
|
+
fastify[kAvvioBoot]((err, done) => {
|
|
402
|
+
if (err || fastify[kState].started) {
|
|
403
|
+
manageErr(err)
|
|
404
|
+
} else {
|
|
405
|
+
hookRunnerApplication('onReady', fastify[kAvvioBoot], fastify, manageErr)
|
|
406
|
+
}
|
|
407
|
+
done()
|
|
408
|
+
})
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function manageErr (err) {
|
|
412
|
+
if (cb) {
|
|
413
|
+
if (err) {
|
|
414
|
+
cb(err)
|
|
415
|
+
} else {
|
|
416
|
+
cb(undefined, fastify)
|
|
417
|
+
}
|
|
418
|
+
} else {
|
|
419
|
+
if (err) {
|
|
420
|
+
return rejectReady(err)
|
|
421
|
+
}
|
|
422
|
+
resolveReady(fastify)
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
374
427
|
// wrapper that we expose to the user for hooks handling
|
|
375
428
|
function addHook (name, fn) {
|
|
376
429
|
throwIfAlreadyStarted('Cannot call "addHook" when fastify instance is already started!')
|
|
@@ -380,6 +433,10 @@ function build (options) {
|
|
|
380
433
|
if (fn.constructor.name === 'AsyncFunction' && fn.length === 4) {
|
|
381
434
|
fastify.log.warn("Async function has too many arguments. Async hooks should not use the 'next' argument.", new Error().stack)
|
|
382
435
|
}
|
|
436
|
+
} else if (name === 'onReady') {
|
|
437
|
+
if (fn.constructor.name === 'AsyncFunction' && fn.length !== 0) {
|
|
438
|
+
throw new Error('Async function has too many arguments. Async hooks should not use the \'done\' argument.')
|
|
439
|
+
}
|
|
383
440
|
} else {
|
|
384
441
|
if (fn.constructor.name === 'AsyncFunction' && fn.length === 3) {
|
|
385
442
|
fastify.log.warn("Async function has too many arguments. Async hooks should not use the 'next' argument.", new Error().stack)
|
|
@@ -395,6 +452,9 @@ function build (options) {
|
|
|
395
452
|
} else if (name === 'onRegister') {
|
|
396
453
|
this[kHooks].validate(name, fn)
|
|
397
454
|
this[kGlobalHooks].onRegister.push(fn)
|
|
455
|
+
} else if (name === 'onReady') {
|
|
456
|
+
this[kHooks].validate(name, fn)
|
|
457
|
+
this[kHooks].add(name, fn)
|
|
398
458
|
} else {
|
|
399
459
|
this.after((err, done) => {
|
|
400
460
|
_addHook.call(this, name, fn)
|
|
@@ -495,7 +555,7 @@ function build (options) {
|
|
|
495
555
|
function setErrorHandler (func) {
|
|
496
556
|
throwIfAlreadyStarted('Cannot call "setErrorHandler" when fastify instance is already started!')
|
|
497
557
|
|
|
498
|
-
this._errorHandler = func
|
|
558
|
+
this._errorHandler = func.bind(this)
|
|
499
559
|
return this
|
|
500
560
|
}
|
|
501
561
|
}
|
|
@@ -513,6 +573,7 @@ function override (old, fn, opts) {
|
|
|
513
573
|
|
|
514
574
|
const instance = Object.create(old)
|
|
515
575
|
old[kChildren].push(instance)
|
|
576
|
+
instance.ready = old[kAvvioBoot].bind(instance)
|
|
516
577
|
instance[kChildren] = []
|
|
517
578
|
instance[kReply] = Reply.buildReply(instance[kReply])
|
|
518
579
|
instance[kRequest] = Request.buildRequest(instance[kRequest])
|
package/lib/hooks.js
CHANGED
|
@@ -12,6 +12,7 @@ const supportedHooks = [
|
|
|
12
12
|
// executed at start/close time
|
|
13
13
|
'onRoute',
|
|
14
14
|
'onRegister',
|
|
15
|
+
'onReady',
|
|
15
16
|
'onClose'
|
|
16
17
|
]
|
|
17
18
|
const {
|
|
@@ -22,6 +23,11 @@ const {
|
|
|
22
23
|
}
|
|
23
24
|
} = require('./errors')
|
|
24
25
|
|
|
26
|
+
const {
|
|
27
|
+
kChildren,
|
|
28
|
+
kHooks
|
|
29
|
+
} = require('./symbols')
|
|
30
|
+
|
|
25
31
|
function Hooks () {
|
|
26
32
|
this.onRequest = []
|
|
27
33
|
this.preParsing = []
|
|
@@ -31,6 +37,7 @@ function Hooks () {
|
|
|
31
37
|
this.onResponse = []
|
|
32
38
|
this.onSend = []
|
|
33
39
|
this.onError = []
|
|
40
|
+
this.onReady = []
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
Hooks.prototype.validate = function (hook, fn) {
|
|
@@ -56,9 +63,80 @@ function buildHooks (h) {
|
|
|
56
63
|
hooks.onSend = h.onSend.slice()
|
|
57
64
|
hooks.onResponse = h.onResponse.slice()
|
|
58
65
|
hooks.onError = h.onError.slice()
|
|
66
|
+
hooks.onReady = []
|
|
59
67
|
return hooks
|
|
60
68
|
}
|
|
61
69
|
|
|
70
|
+
function hookRunnerApplication (hookName, boot, server, cb) {
|
|
71
|
+
const hooks = server[kHooks][hookName]
|
|
72
|
+
var i = 0
|
|
73
|
+
var c = 0
|
|
74
|
+
|
|
75
|
+
next()
|
|
76
|
+
|
|
77
|
+
function exit (err) {
|
|
78
|
+
if (err) {
|
|
79
|
+
cb(err)
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
cb()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function next (err) {
|
|
86
|
+
if (err) {
|
|
87
|
+
exit(err)
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (i === hooks.length && c === server[kChildren].length) {
|
|
92
|
+
if (i === 0 && c === 0) { // speed up start
|
|
93
|
+
exit()
|
|
94
|
+
} else {
|
|
95
|
+
boot(function manageTimeout (err, done) {
|
|
96
|
+
exit(err) // reply to the client's cb
|
|
97
|
+
done(err) // goahead with the avvio line
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (i === hooks.length && c < server[kChildren].length) {
|
|
104
|
+
const child = server[kChildren][c++]
|
|
105
|
+
hookRunnerApplication(hookName, boot, child, next)
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
boot(wrap(hooks[i++], server))
|
|
110
|
+
next()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function wrap (fn, server) {
|
|
114
|
+
return function (err, done) {
|
|
115
|
+
if (err) {
|
|
116
|
+
done(err)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (fn.length === 1) {
|
|
121
|
+
try {
|
|
122
|
+
fn.call(server, done)
|
|
123
|
+
} catch (error) {
|
|
124
|
+
done(error)
|
|
125
|
+
}
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const ret = fn.call(server)
|
|
130
|
+
if (ret && typeof ret.then === 'function') {
|
|
131
|
+
ret.then(done, done)
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
done(err) // auto done
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
62
140
|
function hookRunner (functions, runner, request, reply, cb) {
|
|
63
141
|
var i = 0
|
|
64
142
|
|
|
@@ -131,5 +209,6 @@ module.exports = {
|
|
|
131
209
|
buildHooks,
|
|
132
210
|
hookRunner,
|
|
133
211
|
onSendHookRunner,
|
|
134
|
-
hookIterator
|
|
212
|
+
hookIterator,
|
|
213
|
+
hookRunnerApplication
|
|
135
214
|
}
|
package/lib/reply.js
CHANGED
|
@@ -138,9 +138,30 @@ Reply.prototype.send = function (payload) {
|
|
|
138
138
|
|
|
139
139
|
if (this[kReplySerializer] !== null) {
|
|
140
140
|
payload = this[kReplySerializer](payload)
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
|
|
142
|
+
// The indexOf below also matches custom json mimetypes such as 'application/hal+json' or 'application/ld+json'
|
|
143
|
+
} else if (hasContentType === false || contentType.indexOf('json') > -1) {
|
|
144
|
+
if (hasContentType === false) {
|
|
143
145
|
this[kReplyHeaders]['content-type'] = CONTENT_TYPE.JSON
|
|
146
|
+
} else {
|
|
147
|
+
// If hasContentType === true, we have a JSON mimetype
|
|
148
|
+
if (contentType.indexOf('charset') === -1) {
|
|
149
|
+
// If we have simply application/json instead of a custom json mimetype
|
|
150
|
+
if (contentType.indexOf('/json') > -1) {
|
|
151
|
+
this[kReplyHeaders]['content-type'] = CONTENT_TYPE.JSON
|
|
152
|
+
} else {
|
|
153
|
+
const currContentType = this[kReplyHeaders]['content-type']
|
|
154
|
+
// We extract the custom mimetype part (e.g. 'hal+' from 'application/hal+json')
|
|
155
|
+
const customJsonType = currContentType.substring(
|
|
156
|
+
currContentType.indexOf('/'),
|
|
157
|
+
currContentType.indexOf('json') + 4
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
// We ensure we set the header to the proper JSON content-type if necessary
|
|
161
|
+
// (e.g. 'application/hal+json' instead of 'application/json')
|
|
162
|
+
this[kReplyHeaders]['content-type'] = CONTENT_TYPE.JSON.replace('/json', customJsonType)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
144
165
|
}
|
|
145
166
|
if (typeof payload !== 'string') {
|
|
146
167
|
preserializeHook(this, payload)
|
package/lib/symbols.js
CHANGED
package/lib/validation.js
CHANGED
|
@@ -171,7 +171,9 @@ function buildSchemaCompiler (externalSchemas, options, cache) {
|
|
|
171
171
|
coerceTypes: true,
|
|
172
172
|
useDefaults: true,
|
|
173
173
|
removeAdditional: true,
|
|
174
|
-
allErrors
|
|
174
|
+
// Explicitly set allErrors to `false`.
|
|
175
|
+
// When set to `true`, a DoS attack is possible.
|
|
176
|
+
allErrors: false,
|
|
175
177
|
nullable: true
|
|
176
178
|
}, options.customOptions, { cache }))
|
|
177
179
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastify",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.3",
|
|
4
4
|
"description": "Fast and low overhead web framework, for Node.js",
|
|
5
5
|
"main": "fastify.js",
|
|
6
6
|
"typings": "fastify.d.ts",
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
"dependencies": {
|
|
144
144
|
"abstract-logging": "^2.0.0",
|
|
145
145
|
"ajv": "^6.12.0",
|
|
146
|
-
"avvio": "^6.
|
|
146
|
+
"avvio": "^6.5.0",
|
|
147
147
|
"fast-json-stringify": "^1.18.0",
|
|
148
148
|
"find-my-way": "^2.2.2",
|
|
149
149
|
"flatstr": "^1.0.12",
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Fastify = require('../fastify')
|
|
4
|
+
const immediate = require('util').promisify(setImmediate)
|
|
5
|
+
|
|
6
|
+
module.exports = function asyncTest (t) {
|
|
7
|
+
t.test('async onReady should be called in order', t => {
|
|
8
|
+
t.plan(7)
|
|
9
|
+
const fastify = Fastify()
|
|
10
|
+
|
|
11
|
+
let order = 0
|
|
12
|
+
|
|
13
|
+
fastify.addHook('onReady', async function () {
|
|
14
|
+
await immediate()
|
|
15
|
+
t.equals(order++, 0, 'called in root')
|
|
16
|
+
t.equals(this.pluginName, fastify.pluginName, 'the this binding is the right instance')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
fastify.register(async (childOne, o) => {
|
|
20
|
+
childOne.addHook('onReady', async function () {
|
|
21
|
+
await immediate()
|
|
22
|
+
t.equals(order++, 1, 'called in childOne')
|
|
23
|
+
t.equals(this.pluginName, childOne.pluginName, 'the this binding is the right instance')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
childOne.register(async (childTwo, o) => {
|
|
27
|
+
childTwo.addHook('onReady', async function () {
|
|
28
|
+
await immediate()
|
|
29
|
+
t.equals(order++, 2, 'called in childTwo')
|
|
30
|
+
t.equals(this.pluginName, childTwo.pluginName, 'the this binding is the right instance')
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return fastify.ready().then(() => { t.pass('ready') })
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
t.test('mix ready and onReady', t => {
|
|
39
|
+
t.plan(2)
|
|
40
|
+
const fastify = Fastify()
|
|
41
|
+
let order = 0
|
|
42
|
+
|
|
43
|
+
fastify.addHook('onReady', async function () {
|
|
44
|
+
await immediate()
|
|
45
|
+
order++
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
return fastify.ready()
|
|
49
|
+
.then(() => {
|
|
50
|
+
t.equals(order, 1)
|
|
51
|
+
return fastify.ready()
|
|
52
|
+
})
|
|
53
|
+
.then(() => {
|
|
54
|
+
t.equals(order, 1, 'ready hooks execute once')
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
t.test('listen and onReady order', async t => {
|
|
59
|
+
t.plan(9)
|
|
60
|
+
|
|
61
|
+
const fastify = Fastify()
|
|
62
|
+
let order = 0
|
|
63
|
+
|
|
64
|
+
fastify.register((instance, opts, next) => {
|
|
65
|
+
instance.ready(checkOrder.bind(null, 0))
|
|
66
|
+
instance.addHook('onReady', checkOrder.bind(null, 4))
|
|
67
|
+
|
|
68
|
+
instance.register((subinstance, opts, next) => {
|
|
69
|
+
subinstance.ready(checkOrder.bind(null, 1))
|
|
70
|
+
subinstance.addHook('onReady', checkOrder.bind(null, 5))
|
|
71
|
+
|
|
72
|
+
subinstance.register((realSubInstance, opts, next) => {
|
|
73
|
+
realSubInstance.ready(checkOrder.bind(null, 2))
|
|
74
|
+
realSubInstance.addHook('onReady', checkOrder.bind(null, 6))
|
|
75
|
+
next()
|
|
76
|
+
})
|
|
77
|
+
next()
|
|
78
|
+
})
|
|
79
|
+
next()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
fastify.addHook('onReady', checkOrder.bind(null, 3))
|
|
83
|
+
|
|
84
|
+
await fastify.ready()
|
|
85
|
+
t.pass('trigger the onReady')
|
|
86
|
+
await fastify.listen(0)
|
|
87
|
+
t.pass('do not trigger the onReady')
|
|
88
|
+
|
|
89
|
+
await fastify.close()
|
|
90
|
+
|
|
91
|
+
function checkOrder (shouldbe) {
|
|
92
|
+
t.equals(order, shouldbe)
|
|
93
|
+
order++
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
t.test('multiple ready calls', async t => {
|
|
98
|
+
t.plan(11)
|
|
99
|
+
|
|
100
|
+
const fastify = Fastify()
|
|
101
|
+
let order = 0
|
|
102
|
+
|
|
103
|
+
fastify.register(async (instance, opts) => {
|
|
104
|
+
instance.ready(checkOrder.bind(null, 1))
|
|
105
|
+
instance.addHook('onReady', checkOrder.bind(null, 6))
|
|
106
|
+
|
|
107
|
+
await instance.register(async (subinstance, opts) => {
|
|
108
|
+
subinstance.ready(checkOrder.bind(null, 2))
|
|
109
|
+
subinstance.addHook('onReady', checkOrder.bind(null, 7))
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
t.equals(order, 0, 'ready and hooks not triggered yet')
|
|
113
|
+
order++
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
fastify.addHook('onReady', checkOrder.bind(null, 3))
|
|
117
|
+
fastify.addHook('onReady', checkOrder.bind(null, 4))
|
|
118
|
+
fastify.addHook('onReady', checkOrder.bind(null, 5))
|
|
119
|
+
|
|
120
|
+
await fastify.ready()
|
|
121
|
+
t.pass('trigger the onReady')
|
|
122
|
+
|
|
123
|
+
await fastify.ready()
|
|
124
|
+
t.pass('do not trigger the onReady')
|
|
125
|
+
|
|
126
|
+
await fastify.ready()
|
|
127
|
+
t.pass('do not trigger the onReady')
|
|
128
|
+
|
|
129
|
+
function checkOrder (shouldbe) {
|
|
130
|
+
t.equals(order, shouldbe)
|
|
131
|
+
order++
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
t.test('onReady should manage error in async', t => {
|
|
136
|
+
t.plan(4)
|
|
137
|
+
const fastify = Fastify()
|
|
138
|
+
|
|
139
|
+
fastify.addHook('onReady', function (done) {
|
|
140
|
+
t.pass('called in root')
|
|
141
|
+
done()
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
fastify.register(async (childOne, o) => {
|
|
145
|
+
childOne.addHook('onReady', async function () {
|
|
146
|
+
t.pass('called in childOne')
|
|
147
|
+
throw new Error('FAIL ON READY')
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
childOne.register(async (childTwo, o) => {
|
|
151
|
+
childTwo.addHook('onReady', async function () {
|
|
152
|
+
t.fail('should not be called')
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
fastify.ready(err => {
|
|
158
|
+
t.ok(err)
|
|
159
|
+
t.equals(err.message, 'FAIL ON READY')
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
t.test('onReady throw loading error', t => {
|
|
164
|
+
t.plan(1)
|
|
165
|
+
const fastify = Fastify()
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
fastify.addHook('onReady', async function (done) {})
|
|
169
|
+
} catch (e) {
|
|
170
|
+
t.true(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const semver = require('semver')
|
|
4
|
+
const t = require('tap')
|
|
5
|
+
const Fastify = require('../fastify')
|
|
6
|
+
|
|
7
|
+
if (semver.gt(process.versions.node, '8.0.0')) {
|
|
8
|
+
require('./hooks.on-ready.async')(t)
|
|
9
|
+
} else {
|
|
10
|
+
t.test('async tests', t => {
|
|
11
|
+
t.plan(1)
|
|
12
|
+
t.pass('Skip because Node version < 8')
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
t.test('onReady should be called in order', t => {
|
|
17
|
+
t.plan(7)
|
|
18
|
+
const fastify = Fastify()
|
|
19
|
+
|
|
20
|
+
let order = 0
|
|
21
|
+
|
|
22
|
+
fastify.addHook('onReady', function (done) {
|
|
23
|
+
t.equals(order++, 0, 'called in root')
|
|
24
|
+
t.equals(this.pluginName, fastify.pluginName, 'the this binding is the right instance')
|
|
25
|
+
done()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
fastify.register((childOne, o, next) => {
|
|
29
|
+
childOne.addHook('onReady', function (done) {
|
|
30
|
+
t.equals(order++, 1, 'called in childOne')
|
|
31
|
+
t.equals(this.pluginName, childOne.pluginName, 'the this binding is the right instance')
|
|
32
|
+
done()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
childOne.register((childTwo, o, next) => {
|
|
36
|
+
childTwo.addHook('onReady', function () {
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
setTimeout(resolve, 0)
|
|
39
|
+
})
|
|
40
|
+
.then(() => {
|
|
41
|
+
t.equals(order++, 2, 'called in childTwo')
|
|
42
|
+
t.equals(this.pluginName, childTwo.pluginName, 'the this binding is the right instance')
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
next()
|
|
46
|
+
})
|
|
47
|
+
next()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
fastify.ready(err => t.error(err))
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
t.test('onReady should manage error in sync', t => {
|
|
54
|
+
t.plan(4)
|
|
55
|
+
const fastify = Fastify()
|
|
56
|
+
|
|
57
|
+
fastify.addHook('onReady', function (done) {
|
|
58
|
+
t.pass('called in root')
|
|
59
|
+
done()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
fastify.register((childOne, o, next) => {
|
|
63
|
+
childOne.addHook('onReady', function (done) {
|
|
64
|
+
t.pass('called in childOne')
|
|
65
|
+
done(new Error('FAIL ON READY'))
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
childOne.register((childTwo, o, next) => {
|
|
69
|
+
childTwo.addHook('onReady', function () {
|
|
70
|
+
return Promise().resolve()
|
|
71
|
+
.then(() => {
|
|
72
|
+
t.fail('should not be called')
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
next()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
next()
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
fastify.ready(err => {
|
|
82
|
+
t.ok(err)
|
|
83
|
+
t.equals(err.message, 'FAIL ON READY')
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
t.test('onReady should manage sync error', t => {
|
|
88
|
+
t.plan(4)
|
|
89
|
+
const fastify = Fastify()
|
|
90
|
+
|
|
91
|
+
fastify.addHook('onReady', function (done) {
|
|
92
|
+
t.pass('called in root')
|
|
93
|
+
done()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
fastify.register((childOne, o, next) => {
|
|
97
|
+
childOne.addHook('onReady', function (done) {
|
|
98
|
+
t.pass('called in childOne')
|
|
99
|
+
throw new Error('FAIL UNWANTED SYNC EXCEPTION')
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
childOne.register((childTwo, o, next) => {
|
|
103
|
+
childTwo.addHook('onReady', function () {
|
|
104
|
+
t.fail('should not be called')
|
|
105
|
+
})
|
|
106
|
+
next()
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
next()
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
fastify.ready(err => {
|
|
113
|
+
t.ok(err)
|
|
114
|
+
t.equals(err.message, 'FAIL UNWANTED SYNC EXCEPTION')
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
t.test('onReady can not add decorators or application hooks', t => {
|
|
119
|
+
t.plan(3)
|
|
120
|
+
const fastify = Fastify()
|
|
121
|
+
|
|
122
|
+
fastify.addHook('onReady', function (done) {
|
|
123
|
+
t.pass('called in root')
|
|
124
|
+
fastify.decorate('test', () => {})
|
|
125
|
+
|
|
126
|
+
fastify.addHook('onReady', function () {
|
|
127
|
+
t.fail('it will be not called')
|
|
128
|
+
})
|
|
129
|
+
done()
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
fastify.addHook('onReady', function (done) {
|
|
133
|
+
t.ok(this.hasDecorator('test'))
|
|
134
|
+
done()
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
fastify.ready(err => { t.error(err) })
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
t.test('onReady cannot add lifecycle hooks', t => {
|
|
141
|
+
t.plan(4)
|
|
142
|
+
const fastify = Fastify()
|
|
143
|
+
|
|
144
|
+
fastify.addHook('onReady', function (done) {
|
|
145
|
+
t.pass('called in root')
|
|
146
|
+
try {
|
|
147
|
+
fastify.addHook('onRequest', (request, reply, done) => {})
|
|
148
|
+
} catch (error) {
|
|
149
|
+
t.ok(error)
|
|
150
|
+
t.equals(error.message, 'root plugin has already booted')
|
|
151
|
+
done(error)
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
fastify.addHook('onRequest', (request, reply, done) => {})
|
|
156
|
+
fastify.get('/', (r, reply) => reply.send('hello'))
|
|
157
|
+
|
|
158
|
+
fastify.ready((err) => { t.ok(err) })
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
t.test('onReady does not call done', t => {
|
|
162
|
+
t.plan(3)
|
|
163
|
+
const fastify = Fastify({ pluginTimeout: 500 })
|
|
164
|
+
|
|
165
|
+
fastify.addHook('onReady', function (done) {
|
|
166
|
+
t.pass('called in root')
|
|
167
|
+
// done() // don't call done to test timeout
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
fastify.ready(err => {
|
|
171
|
+
t.ok(err)
|
|
172
|
+
t.equal(err.code, 'ERR_AVVIO_READY_TIMEOUT')
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
t.test('ready chain order when no await', t => {
|
|
177
|
+
t.plan(3)
|
|
178
|
+
const fastify = Fastify({ })
|
|
179
|
+
|
|
180
|
+
let i = 0
|
|
181
|
+
fastify.ready(() => {
|
|
182
|
+
i++
|
|
183
|
+
t.equals(i, 1)
|
|
184
|
+
})
|
|
185
|
+
fastify.ready(() => {
|
|
186
|
+
i++
|
|
187
|
+
t.equals(i, 2)
|
|
188
|
+
})
|
|
189
|
+
fastify.ready(() => {
|
|
190
|
+
i++
|
|
191
|
+
t.equals(i, 3)
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
t.test('ready return the server with callback', t => {
|
|
196
|
+
t.plan(2)
|
|
197
|
+
const fastify = Fastify()
|
|
198
|
+
|
|
199
|
+
fastify.ready((err, instance) => {
|
|
200
|
+
t.error(err)
|
|
201
|
+
t.deepEquals(instance, fastify)
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
t.test('ready return the server with Promise', t => {
|
|
206
|
+
t.plan(1)
|
|
207
|
+
const fastify = Fastify()
|
|
208
|
+
|
|
209
|
+
fastify.ready()
|
|
210
|
+
.then(instance => { t.deepEquals(instance, fastify) })
|
|
211
|
+
.catch(err => { t.fail(err) })
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
t.test('ready return registered', t => {
|
|
215
|
+
t.plan(4)
|
|
216
|
+
const fastify = Fastify()
|
|
217
|
+
|
|
218
|
+
fastify.register((one, opts, next) => {
|
|
219
|
+
one.ready().then(itself => { t.deepEquals(itself, one) })
|
|
220
|
+
next()
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
fastify.register((two, opts, next) => {
|
|
224
|
+
two.ready().then(itself => { t.deepEquals(itself, two) })
|
|
225
|
+
|
|
226
|
+
two.register((twoDotOne, opts, next) => {
|
|
227
|
+
twoDotOne.ready().then(itself => { t.deepEquals(itself, twoDotOne) })
|
|
228
|
+
next()
|
|
229
|
+
})
|
|
230
|
+
next()
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
fastify.ready()
|
|
234
|
+
.then(instance => { t.deepEquals(instance, fastify) })
|
|
235
|
+
.catch(err => { t.fail(err) })
|
|
236
|
+
})
|
package/test/inject.test.js
CHANGED
|
@@ -409,3 +409,18 @@ test('should throw error if callback specified and if ready errors', t => {
|
|
|
409
409
|
t.strictEqual(err, error)
|
|
410
410
|
})
|
|
411
411
|
})
|
|
412
|
+
|
|
413
|
+
test('should support builder-style injection with non-ready app', (t) => {
|
|
414
|
+
t.plan(3)
|
|
415
|
+
const fastify = Fastify()
|
|
416
|
+
const payload = { hello: 'world' }
|
|
417
|
+
|
|
418
|
+
fastify.get('/', (req, reply) => {
|
|
419
|
+
reply.send(payload)
|
|
420
|
+
})
|
|
421
|
+
fastify.inject().get('/').end().then((res) => {
|
|
422
|
+
t.deepEqual(payload, JSON.parse(res.payload))
|
|
423
|
+
t.strictEqual(res.statusCode, 200)
|
|
424
|
+
t.strictEqual(res.headers['content-length'], '17')
|
|
425
|
+
})
|
|
426
|
+
})
|
|
@@ -544,6 +544,57 @@ test('plain string with content type application/json should NOT be serialized a
|
|
|
544
544
|
})
|
|
545
545
|
})
|
|
546
546
|
|
|
547
|
+
test('plain string with custom json content type should NOT be serialized as json', t => {
|
|
548
|
+
t.plan(16)
|
|
549
|
+
|
|
550
|
+
const fastify = require('../..')()
|
|
551
|
+
|
|
552
|
+
const customSamples = {
|
|
553
|
+
collectionjson: {
|
|
554
|
+
mimeType: 'application/vnd.collection+json',
|
|
555
|
+
sample: '{"collection":{"version":"1.0","href":"http://api.example.com/people/"}}'
|
|
556
|
+
},
|
|
557
|
+
hal: {
|
|
558
|
+
mimeType: 'application/hal+json',
|
|
559
|
+
sample: '{"_links":{"self":{"href":"https://api.example.com/people/1"}},"name":"John Doe"}'
|
|
560
|
+
},
|
|
561
|
+
jsonapi: {
|
|
562
|
+
mimeType: 'application/vnd.api+json',
|
|
563
|
+
sample: '{"data":{"type":"people","id":"1"}}'
|
|
564
|
+
},
|
|
565
|
+
jsonld: {
|
|
566
|
+
mimeType: 'application/ld+json',
|
|
567
|
+
sample: '{"@context":"https://json-ld.org/contexts/person.jsonld","name":"John Doe"}'
|
|
568
|
+
},
|
|
569
|
+
siren: {
|
|
570
|
+
mimeType: 'application/vnd.siren+json',
|
|
571
|
+
sample: '{"class":"person","properties":{"name":"John Doe"}}'
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
Object.keys(customSamples).forEach((path) => {
|
|
576
|
+
fastify.get(`/${path}`, function (req, reply) {
|
|
577
|
+
reply.type(customSamples[path].mimeType).send(customSamples[path].sample)
|
|
578
|
+
})
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
fastify.listen(0, err => {
|
|
582
|
+
t.error(err)
|
|
583
|
+
fastify.server.unref()
|
|
584
|
+
|
|
585
|
+
Object.keys(customSamples).forEach((path) => {
|
|
586
|
+
sget({
|
|
587
|
+
method: 'GET',
|
|
588
|
+
url: 'http://localhost:' + fastify.server.address().port + '/' + path
|
|
589
|
+
}, (err, response, body) => {
|
|
590
|
+
t.error(err)
|
|
591
|
+
t.strictEqual(response.headers['content-type'], customSamples[path].mimeType + '; charset=utf-8')
|
|
592
|
+
t.deepEqual(body.toString(), customSamples[path].sample)
|
|
593
|
+
})
|
|
594
|
+
})
|
|
595
|
+
})
|
|
596
|
+
})
|
|
597
|
+
|
|
547
598
|
test('non-string with content type application/json SHOULD be serialized as json', t => {
|
|
548
599
|
t.plan(4)
|
|
549
600
|
|
|
@@ -568,6 +619,57 @@ test('non-string with content type application/json SHOULD be serialized as json
|
|
|
568
619
|
})
|
|
569
620
|
})
|
|
570
621
|
|
|
622
|
+
test('non-string with custom json content type SHOULD be serialized as json', t => {
|
|
623
|
+
t.plan(16)
|
|
624
|
+
|
|
625
|
+
const fastify = require('../..')()
|
|
626
|
+
|
|
627
|
+
const customSamples = {
|
|
628
|
+
collectionjson: {
|
|
629
|
+
mimeType: 'application/vnd.collection+json',
|
|
630
|
+
sample: JSON.parse('{"collection":{"version":"1.0","href":"http://api.example.com/people/"}}')
|
|
631
|
+
},
|
|
632
|
+
hal: {
|
|
633
|
+
mimeType: 'application/hal+json',
|
|
634
|
+
sample: JSON.parse('{"_links":{"self":{"href":"https://api.example.com/people/1"}},"name":"John Doe"}')
|
|
635
|
+
},
|
|
636
|
+
jsonapi: {
|
|
637
|
+
mimeType: 'application/vnd.api+json',
|
|
638
|
+
sample: JSON.parse('{"data":{"type":"people","id":"1"}}')
|
|
639
|
+
},
|
|
640
|
+
jsonld: {
|
|
641
|
+
mimeType: 'application/ld+json',
|
|
642
|
+
sample: JSON.parse('{"@context":"https://json-ld.org/contexts/person.jsonld","name":"John Doe"}')
|
|
643
|
+
},
|
|
644
|
+
siren: {
|
|
645
|
+
mimeType: 'application/vnd.siren+json',
|
|
646
|
+
sample: JSON.parse('{"class":"person","properties":{"name":"John Doe"}}')
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
Object.keys(customSamples).forEach((path) => {
|
|
651
|
+
fastify.get(`/${path}`, function (req, reply) {
|
|
652
|
+
reply.type(customSamples[path].mimeType).send(customSamples[path].sample)
|
|
653
|
+
})
|
|
654
|
+
})
|
|
655
|
+
|
|
656
|
+
fastify.listen(0, err => {
|
|
657
|
+
t.error(err)
|
|
658
|
+
fastify.server.unref()
|
|
659
|
+
|
|
660
|
+
Object.keys(customSamples).forEach((path) => {
|
|
661
|
+
sget({
|
|
662
|
+
method: 'GET',
|
|
663
|
+
url: 'http://localhost:' + fastify.server.address().port + '/' + path
|
|
664
|
+
}, (err, response, body) => {
|
|
665
|
+
t.error(err)
|
|
666
|
+
t.strictEqual(response.headers['content-type'], customSamples[path].mimeType + '; charset=utf-8')
|
|
667
|
+
t.deepEqual(body.toString(), JSON.stringify(customSamples[path].sample))
|
|
668
|
+
})
|
|
669
|
+
})
|
|
670
|
+
})
|
|
671
|
+
})
|
|
672
|
+
|
|
571
673
|
test('error object with a content type that is not application/json should work', t => {
|
|
572
674
|
t.plan(6)
|
|
573
675
|
|
|
@@ -73,3 +73,84 @@ test('default 400 on request error with custom error handler', t => {
|
|
|
73
73
|
})
|
|
74
74
|
})
|
|
75
75
|
})
|
|
76
|
+
|
|
77
|
+
test('error handler binding', t => {
|
|
78
|
+
t.plan(5)
|
|
79
|
+
|
|
80
|
+
const fastify = Fastify()
|
|
81
|
+
|
|
82
|
+
fastify.setErrorHandler(function (err, request, reply) {
|
|
83
|
+
t.strictEqual(this, fastify)
|
|
84
|
+
reply
|
|
85
|
+
.code(err.statusCode)
|
|
86
|
+
.type('application/json; charset=utf-8')
|
|
87
|
+
.send(err)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
fastify.post('/', function (req, reply) {
|
|
91
|
+
reply.send({ hello: 'world' })
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
fastify.inject({
|
|
95
|
+
method: 'POST',
|
|
96
|
+
url: '/',
|
|
97
|
+
simulate: {
|
|
98
|
+
error: true
|
|
99
|
+
},
|
|
100
|
+
body: {
|
|
101
|
+
text: '12345678901234567890123456789012345678901234567890'
|
|
102
|
+
}
|
|
103
|
+
}, (err, res) => {
|
|
104
|
+
t.error(err)
|
|
105
|
+
t.strictEqual(res.statusCode, 400)
|
|
106
|
+
t.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8')
|
|
107
|
+
t.deepEqual(JSON.parse(res.payload), {
|
|
108
|
+
error: 'Bad Request',
|
|
109
|
+
message: 'Simulated',
|
|
110
|
+
statusCode: 400
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('encapsulated error handler binding', t => {
|
|
116
|
+
t.plan(7)
|
|
117
|
+
|
|
118
|
+
const fastify = Fastify()
|
|
119
|
+
|
|
120
|
+
fastify.register(function (app, opts, next) {
|
|
121
|
+
app.decorate('hello', 'world')
|
|
122
|
+
t.strictEqual(app.hello, 'world')
|
|
123
|
+
app.post('/', function (req, reply) {
|
|
124
|
+
reply.send({ hello: 'world' })
|
|
125
|
+
})
|
|
126
|
+
app.setErrorHandler(function (err, request, reply) {
|
|
127
|
+
t.strictEqual(this.hello, 'world')
|
|
128
|
+
reply
|
|
129
|
+
.code(err.statusCode)
|
|
130
|
+
.type('application/json; charset=utf-8')
|
|
131
|
+
.send(err)
|
|
132
|
+
})
|
|
133
|
+
next()
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
fastify.inject({
|
|
137
|
+
method: 'POST',
|
|
138
|
+
url: '/',
|
|
139
|
+
simulate: {
|
|
140
|
+
error: true
|
|
141
|
+
},
|
|
142
|
+
body: {
|
|
143
|
+
text: '12345678901234567890123456789012345678901234567890'
|
|
144
|
+
}
|
|
145
|
+
}, (err, res) => {
|
|
146
|
+
t.error(err)
|
|
147
|
+
t.strictEqual(res.statusCode, 400)
|
|
148
|
+
t.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8')
|
|
149
|
+
t.deepEqual(res.json(), {
|
|
150
|
+
error: 'Bad Request',
|
|
151
|
+
message: 'Simulated',
|
|
152
|
+
statusCode: 400
|
|
153
|
+
})
|
|
154
|
+
t.strictEqual(fastify.hello, undefined)
|
|
155
|
+
})
|
|
156
|
+
})
|
|
@@ -57,10 +57,10 @@ test('should fail immediately with invalid payload', t => {
|
|
|
57
57
|
url: '/'
|
|
58
58
|
}, (err, res) => {
|
|
59
59
|
t.error(err)
|
|
60
|
-
t.deepEqual(
|
|
60
|
+
t.deepEqual(res.json(), {
|
|
61
61
|
statusCode: 400,
|
|
62
62
|
error: 'Bad Request',
|
|
63
|
-
message: "body should have required property 'name'
|
|
63
|
+
message: "body should have required property 'name'"
|
|
64
64
|
})
|
|
65
65
|
t.strictEqual(res.statusCode, 400)
|
|
66
66
|
})
|
|
@@ -216,19 +216,12 @@ test('should be able to attach validation to request', t => {
|
|
|
216
216
|
}, (err, res) => {
|
|
217
217
|
t.error(err)
|
|
218
218
|
|
|
219
|
-
t.deepEqual(
|
|
219
|
+
t.deepEqual(res.json(), [{
|
|
220
220
|
keyword: 'required',
|
|
221
221
|
dataPath: '',
|
|
222
222
|
schemaPath: '#/required',
|
|
223
223
|
params: { missingProperty: 'name' },
|
|
224
224
|
message: 'should have required property \'name\''
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
keyword: 'required',
|
|
228
|
-
dataPath: '',
|
|
229
|
-
schemaPath: '#/required',
|
|
230
|
-
params: { missingProperty: 'work' },
|
|
231
|
-
message: 'should have required property \'work\''
|
|
232
225
|
}])
|
|
233
226
|
t.strictEqual(res.statusCode, 400)
|
|
234
227
|
})
|
|
@@ -255,7 +248,7 @@ test('should respect when attachValidation is explicitly set to false', t => {
|
|
|
255
248
|
t.deepEqual(JSON.parse(res.payload), {
|
|
256
249
|
statusCode: 400,
|
|
257
250
|
error: 'Bad Request',
|
|
258
|
-
message: "body should have required property 'name'
|
|
251
|
+
message: "body should have required property 'name'"
|
|
259
252
|
})
|
|
260
253
|
t.strictEqual(res.statusCode, 400)
|
|
261
254
|
})
|
|
@@ -285,7 +278,7 @@ test('Attached validation error should take precendence over setErrorHandler', t
|
|
|
285
278
|
url: '/'
|
|
286
279
|
}, (err, res) => {
|
|
287
280
|
t.error(err)
|
|
288
|
-
t.deepEqual(res.payload, "Attached: Error: body should have required property 'name'
|
|
281
|
+
t.deepEqual(res.payload, "Attached: Error: body should have required property 'name'")
|
|
289
282
|
t.strictEqual(res.statusCode, 400)
|
|
290
283
|
})
|
|
291
284
|
})
|
|
@@ -320,7 +313,7 @@ test('should handle response validation error', t => {
|
|
|
320
313
|
url: '/'
|
|
321
314
|
}, (err, res) => {
|
|
322
315
|
t.error(err)
|
|
323
|
-
t.strictEqual(res.payload, '{"statusCode":500,"error":"Internal Server Error","message":"name is required!"}')
|
|
316
|
+
t.strictEqual(res.payload, '{"statusCode":500,"error":"Internal Server Error","message":"\\"name\\" is required!"}')
|
|
324
317
|
})
|
|
325
318
|
})
|
|
326
319
|
|
|
@@ -350,7 +343,7 @@ test('should handle response validation error with promises', t => {
|
|
|
350
343
|
url: '/'
|
|
351
344
|
}, (err, res) => {
|
|
352
345
|
t.error(err)
|
|
353
|
-
t.strictEqual(res.payload, '{"statusCode":500,"error":"Internal Server Error","message":"name is required!"}')
|
|
346
|
+
t.strictEqual(res.payload, '{"statusCode":500,"error":"Internal Server Error","message":"\\"name\\" is required!"}')
|
|
354
347
|
})
|
|
355
348
|
})
|
|
356
349
|
|
|
@@ -378,7 +371,7 @@ test('should return a defined output message parsing AJV errors', t => {
|
|
|
378
371
|
url: '/'
|
|
379
372
|
}, (err, res) => {
|
|
380
373
|
t.error(err)
|
|
381
|
-
t.strictEqual(res.payload, '{"statusCode":400,"error":"Bad Request","message":"body should have required property \'name\'
|
|
374
|
+
t.strictEqual(res.payload, '{"statusCode":400,"error":"Bad Request","message":"body should have required property \'name\'"}')
|
|
382
375
|
})
|
|
383
376
|
})
|
|
384
377
|
|