fastify 2.12.1 → 2.14.1
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/.dependabot/config.yml +28 -0
- package/README.md +43 -26
- package/docs/Decorators.md +136 -75
- package/docs/Ecosystem.md +3 -0
- package/docs/Errors.md +5 -0
- package/docs/Hooks.md +9 -1
- package/docs/LTS.md +13 -10
- package/docs/Logging.md +1 -1
- package/docs/Middleware.md +1 -1
- package/docs/Recommendations.md +4 -0
- package/docs/Reply.md +3 -2
- package/docs/Routes.md +6 -6
- package/docs/Server.md +26 -0
- package/docs/Validation-and-Serialization.md +87 -3
- package/fastify.d.ts +10 -36
- package/fastify.js +25 -1
- package/lib/decorate.js +13 -2
- package/lib/errors.js +7 -1
- package/lib/handleRequest.js +9 -3
- package/lib/reply.js +20 -6
- package/lib/route.js +18 -23
- package/package.json +23 -34
- package/test/404s.test.js +64 -1
- package/test/content-length.js +26 -0
- package/test/content-length.test.js +5 -0
- package/test/decorator.test.js +32 -0
- package/test/http2/head.js +35 -0
- package/test/http2/http2.test.js +1 -0
- package/test/internals/decorator.test.js +37 -2
- package/test/internals/handleRequest.test.js +8 -1
- package/test/reply-error.test.js +1 -1
- package/test/route.test.js +24 -24
- package/test/router-options.test.js +34 -0
- package/test/schemas.test.js +44 -14
- package/test/types/index.ts +20 -0
- package/test/validation-error-handling.test.js +100 -0
|
@@ -552,9 +552,9 @@ fastify.route({
|
|
|
552
552
|
method: 'POST',
|
|
553
553
|
url: '/',
|
|
554
554
|
schema: {
|
|
555
|
-
body:
|
|
555
|
+
body: { $ref: 'urn:schema:request#' },
|
|
556
556
|
response: {
|
|
557
|
-
'2xx':
|
|
557
|
+
'2xx': { $ref: 'urn:schema:response#' }
|
|
558
558
|
}
|
|
559
559
|
},
|
|
560
560
|
handler (req, reply) {
|
|
@@ -658,7 +658,91 @@ fastify.setErrorHandler(function (error, request, reply) {
|
|
|
658
658
|
})
|
|
659
659
|
```
|
|
660
660
|
|
|
661
|
-
If you want custom error response in schema without headaches and quickly, you can take a look at [
|
|
661
|
+
If you want custom error response in schema without headaches and quickly, you can take a look at [`ajv-errors`](https://github.com/epoberezkin/ajv-errors). Checkout the [example](https://github.com/fastify/example/blob/master/validation-messages/custom-errors-messages.js) usage.
|
|
662
|
+
|
|
663
|
+
Below is an example showing how to add **custom error messages for each property** of a schema by supplying custom AJV options.
|
|
664
|
+
Inline comments in the schema below describe how to configure it to show a different error message for each case:
|
|
665
|
+
|
|
666
|
+
```js
|
|
667
|
+
const fastify = Fastify({
|
|
668
|
+
ajv: {
|
|
669
|
+
customOptions: { allErrors: true, jsonPointers: true },
|
|
670
|
+
plugins: [
|
|
671
|
+
require('ajv-errors')
|
|
672
|
+
]
|
|
673
|
+
}
|
|
674
|
+
})
|
|
675
|
+
|
|
676
|
+
const schema = {
|
|
677
|
+
body: {
|
|
678
|
+
type: 'object',
|
|
679
|
+
properties: {
|
|
680
|
+
name: {
|
|
681
|
+
type: 'string',
|
|
682
|
+
errorMessage: {
|
|
683
|
+
type: 'Bad name'
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
age: {
|
|
687
|
+
type: 'number',
|
|
688
|
+
errorMessage: {
|
|
689
|
+
type: 'Bad age', // specify custom message for
|
|
690
|
+
min: 'Too young' // all constraints except required
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
},
|
|
694
|
+
required: ['name', 'age'],
|
|
695
|
+
errorMessage: {
|
|
696
|
+
required: {
|
|
697
|
+
name: 'Why no name!', // specify error message for when the
|
|
698
|
+
age: 'Why no age!' // property is missing from input
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
fastify.post('/', { schema, }, (request, reply) => {
|
|
705
|
+
reply.send({
|
|
706
|
+
hello: 'world'
|
|
707
|
+
})
|
|
708
|
+
})
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
If you want to return localized error messages, take a look at [ajv-i18n](https://github.com/epoberezkin/ajv-i18n)
|
|
712
|
+
|
|
713
|
+
```js
|
|
714
|
+
const localize = require('ajv-i18n')
|
|
715
|
+
|
|
716
|
+
const fastify = Fastify({
|
|
717
|
+
ajv: {
|
|
718
|
+
customOptions: { allErrors: true }
|
|
719
|
+
}
|
|
720
|
+
})
|
|
721
|
+
|
|
722
|
+
const schema = {
|
|
723
|
+
body: {
|
|
724
|
+
type: 'object',
|
|
725
|
+
properties: {
|
|
726
|
+
name: {
|
|
727
|
+
type: 'string',
|
|
728
|
+
},
|
|
729
|
+
age: {
|
|
730
|
+
type: 'number',
|
|
731
|
+
}
|
|
732
|
+
},
|
|
733
|
+
required: ['name', 'age'],
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
fastify.setErrorHandler(function (error, request, reply) {
|
|
738
|
+
if (error.validation) {
|
|
739
|
+
localize.ru(error.validation)
|
|
740
|
+
reply.status(400).send(error.validation)
|
|
741
|
+
return
|
|
742
|
+
}
|
|
743
|
+
reply.send(error)
|
|
744
|
+
})
|
|
745
|
+
```
|
|
662
746
|
|
|
663
747
|
### JSON Schema and Shared Schema support
|
|
664
748
|
|
package/fastify.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import * as ajv from 'ajv'
|
|
|
8
8
|
import * as http from 'http'
|
|
9
9
|
import * as http2 from 'http2'
|
|
10
10
|
import * as https from 'https'
|
|
11
|
+
import * as LightMyRequest from 'light-my-request'
|
|
11
12
|
|
|
12
13
|
declare function fastify<
|
|
13
14
|
HttpServer extends (http.Server | http2.Http2Server) = http.Server,
|
|
@@ -36,6 +37,10 @@ declare namespace fastify {
|
|
|
36
37
|
|
|
37
38
|
type HTTPMethod = 'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT' | 'OPTIONS'
|
|
38
39
|
|
|
40
|
+
// // Keep the original name of the interfaces to avoid braking change
|
|
41
|
+
interface HTTPInjectOptions extends LightMyRequest.InjectOptions {}
|
|
42
|
+
interface HTTPInjectResponse extends LightMyRequest.Response {}
|
|
43
|
+
|
|
39
44
|
interface ValidationResult {
|
|
40
45
|
keyword: string;
|
|
41
46
|
dataPath: string;
|
|
@@ -317,42 +322,6 @@ declare namespace fastify {
|
|
|
317
322
|
logSerializers?: Object
|
|
318
323
|
}
|
|
319
324
|
|
|
320
|
-
/**
|
|
321
|
-
* Fake http inject options
|
|
322
|
-
*/
|
|
323
|
-
interface HTTPInjectOptions {
|
|
324
|
-
url: string,
|
|
325
|
-
method?: HTTPMethod,
|
|
326
|
-
authority?: string,
|
|
327
|
-
headers?: DefaultHeaders,
|
|
328
|
-
query?: DefaultQuery,
|
|
329
|
-
remoteAddress?: string,
|
|
330
|
-
payload?: string | object | Buffer | NodeJS.ReadableStream
|
|
331
|
-
simulate?: {
|
|
332
|
-
end?: boolean,
|
|
333
|
-
split?: boolean,
|
|
334
|
-
error?: boolean,
|
|
335
|
-
close?: boolean
|
|
336
|
-
},
|
|
337
|
-
validate?: boolean
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Fake http inject response
|
|
342
|
-
*/
|
|
343
|
-
interface HTTPInjectResponse {
|
|
344
|
-
raw: {
|
|
345
|
-
req: NodeJS.ReadableStream,
|
|
346
|
-
res: http.ServerResponse
|
|
347
|
-
},
|
|
348
|
-
headers: Record<string, string>,
|
|
349
|
-
statusCode: number,
|
|
350
|
-
statusMessage: string,
|
|
351
|
-
payload: string,
|
|
352
|
-
rawPayload: Buffer,
|
|
353
|
-
trailers: object
|
|
354
|
-
}
|
|
355
|
-
|
|
356
325
|
/**
|
|
357
326
|
* Server listen options
|
|
358
327
|
*/
|
|
@@ -685,6 +654,11 @@ declare namespace fastify {
|
|
|
685
654
|
*/
|
|
686
655
|
inject(opts: HTTPInjectOptions | string): Promise<HTTPInjectResponse>
|
|
687
656
|
|
|
657
|
+
/**
|
|
658
|
+
* Useful for testing http requests without running a sever
|
|
659
|
+
*/
|
|
660
|
+
inject(): LightMyRequest.Chain;
|
|
661
|
+
|
|
688
662
|
/**
|
|
689
663
|
* Set the 404 handler
|
|
690
664
|
*/
|
package/fastify.js
CHANGED
|
@@ -42,6 +42,11 @@ const { buildRouting, validateBodyLimitOption } = require('./lib/route')
|
|
|
42
42
|
const build404 = require('./lib/fourOhFour')
|
|
43
43
|
const getSecuredInitialConfig = require('./lib/initialConfigValidation')
|
|
44
44
|
const { defaultInitOptions } = getSecuredInitialConfig
|
|
45
|
+
const {
|
|
46
|
+
codes: {
|
|
47
|
+
FST_ERR_BAD_URL
|
|
48
|
+
}
|
|
49
|
+
} = require('./lib/errors')
|
|
45
50
|
|
|
46
51
|
function build (options) {
|
|
47
52
|
// Options validations
|
|
@@ -73,6 +78,7 @@ function build (options) {
|
|
|
73
78
|
customOptions: {},
|
|
74
79
|
plugins: []
|
|
75
80
|
}, options.ajv)
|
|
81
|
+
const frameworkErrors = options.frameworkErrors
|
|
76
82
|
|
|
77
83
|
// Ajv options
|
|
78
84
|
if (!ajvOptions.customOptions || Object.prototype.toString.call(ajvOptions.customOptions) !== '[object Object]') {
|
|
@@ -417,7 +423,11 @@ function build (options) {
|
|
|
417
423
|
message: 'Client Error',
|
|
418
424
|
statusCode: 400
|
|
419
425
|
})
|
|
420
|
-
|
|
426
|
+
|
|
427
|
+
// Most devs do not know what to do with this error.
|
|
428
|
+
// In the vast majority of cases, it's a network error and/or some
|
|
429
|
+
// config issue on the the load balancer side.
|
|
430
|
+
logger.trace({ err }, 'client error')
|
|
421
431
|
socket.end(`HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`)
|
|
422
432
|
}
|
|
423
433
|
|
|
@@ -431,6 +441,20 @@ function build (options) {
|
|
|
431
441
|
}
|
|
432
442
|
|
|
433
443
|
function onBadUrl (path, req, res) {
|
|
444
|
+
if (frameworkErrors) {
|
|
445
|
+
req.id = genReqId(req)
|
|
446
|
+
req.originalUrl = req.url
|
|
447
|
+
var childLogger = logger.child({ reqId: req.id })
|
|
448
|
+
if (modifyCoreObjects) {
|
|
449
|
+
req.log = res.log = childLogger
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
childLogger.info({ req }, 'incoming request')
|
|
453
|
+
|
|
454
|
+
const request = new Request(null, req, null, req.headers, childLogger)
|
|
455
|
+
const reply = new Reply(res, { onSend: [], onError: [] }, request, childLogger)
|
|
456
|
+
return frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)
|
|
457
|
+
}
|
|
434
458
|
const body = `{"error":"Bad Request","message":"'${path}' is not a valid url component","statusCode":400}`
|
|
435
459
|
res.writeHead(400, {
|
|
436
460
|
'Content-Type': 'application/json',
|
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
|
|
@@ -47,7 +48,7 @@ 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')
|
|
49
50
|
createError('FST_ERR_SEND_UNDEFINED_ERR', 'Undefined error has occured')
|
|
50
|
-
createError('FST_ERR_BAD_STATUS_CODE', 'Called reply with
|
|
51
|
+
createError('FST_ERR_BAD_STATUS_CODE', 'Called reply with an invalid status code: %s')
|
|
51
52
|
|
|
52
53
|
/**
|
|
53
54
|
* schemas
|
|
@@ -74,6 +75,11 @@ createError('FST_ERR_HTTP2_INVALID_VERSION', 'HTTP2 is available only from node
|
|
|
74
75
|
*/
|
|
75
76
|
createError('FST_ERR_INIT_OPTS_INVALID', "Invalid initialization options: '%s'")
|
|
76
77
|
|
|
78
|
+
/**
|
|
79
|
+
* router
|
|
80
|
+
*/
|
|
81
|
+
createError('FST_ERR_BAD_URL', "'%s' is not a valid url component", 400)
|
|
82
|
+
|
|
77
83
|
function createError (code, message, statusCode = 500, Base = Error) {
|
|
78
84
|
if (!code) throw new Error('Fastify error code must not be empty')
|
|
79
85
|
if (!message) throw new Error('Fastify error message must not be empty')
|
package/lib/handleRequest.js
CHANGED
|
@@ -71,7 +71,10 @@ function handler (request, reply) {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
function preValidationCallback (err, request, reply) {
|
|
74
|
-
if (reply.sent === true ||
|
|
74
|
+
if (reply.sent === true ||
|
|
75
|
+
reply.res.writableEnded === true ||
|
|
76
|
+
(reply.res.writable === false && reply.res.finished === true)) return
|
|
77
|
+
|
|
75
78
|
if (err != null) {
|
|
76
79
|
reply.send(err)
|
|
77
80
|
return
|
|
@@ -102,7 +105,10 @@ function preValidationCallback (err, request, reply) {
|
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
function preHandlerCallback (err, request, reply) {
|
|
105
|
-
if (reply.sent ||
|
|
108
|
+
if (reply.sent ||
|
|
109
|
+
reply.res.writableEnded === true ||
|
|
110
|
+
(reply.res.writable === false && reply.res.finished === true)) return
|
|
111
|
+
|
|
106
112
|
if (err != null) {
|
|
107
113
|
reply.send(err)
|
|
108
114
|
return
|
|
@@ -115,4 +121,4 @@ function preHandlerCallback (err, request, reply) {
|
|
|
115
121
|
}
|
|
116
122
|
|
|
117
123
|
module.exports = handleRequest
|
|
118
|
-
module.exports[Symbol.for('internals')] = { handler }
|
|
124
|
+
module.exports[Symbol.for('internals')] = { handler, preHandlerCallback }
|
package/lib/reply.js
CHANGED
|
@@ -18,10 +18,11 @@ const {
|
|
|
18
18
|
kReplyIsRunningOnErrorHook,
|
|
19
19
|
kDisableRequestLogging
|
|
20
20
|
} = require('./symbols.js')
|
|
21
|
-
const { hookRunner, onSendHookRunner } = require('./hooks')
|
|
21
|
+
const { hookRunner, hookIterator, onSendHookRunner } = require('./hooks')
|
|
22
22
|
const validation = require('./validation')
|
|
23
23
|
const serialize = validation.serialize
|
|
24
24
|
|
|
25
|
+
const internals = require('./handleRequest')[Symbol.for('internals')]
|
|
25
26
|
const loggerUtils = require('./logger')
|
|
26
27
|
const now = loggerUtils.now
|
|
27
28
|
const wrapThenable = require('./wrapThenable')
|
|
@@ -198,11 +199,12 @@ Reply.prototype.headers = function (headers) {
|
|
|
198
199
|
}
|
|
199
200
|
|
|
200
201
|
Reply.prototype.code = function (code) {
|
|
201
|
-
|
|
202
|
-
|
|
202
|
+
const intValue = parseInt(code)
|
|
203
|
+
if (isNaN(intValue) || intValue < 100 || intValue > 600) {
|
|
204
|
+
throw new FST_ERR_BAD_STATUS_CODE(String(code))
|
|
203
205
|
}
|
|
204
206
|
|
|
205
|
-
this.res.statusCode =
|
|
207
|
+
this.res.statusCode = intValue
|
|
206
208
|
this[kReplyHasStatusCode] = true
|
|
207
209
|
return this
|
|
208
210
|
}
|
|
@@ -475,13 +477,13 @@ function handleError (reply, error, cb) {
|
|
|
475
477
|
})
|
|
476
478
|
flatstr(payload)
|
|
477
479
|
reply[kReplyHeaders]['content-type'] = CONTENT_TYPE.JSON
|
|
480
|
+
reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
|
|
478
481
|
|
|
479
482
|
if (cb) {
|
|
480
483
|
cb(reply, payload)
|
|
481
484
|
return
|
|
482
485
|
}
|
|
483
486
|
|
|
484
|
-
reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
|
|
485
487
|
reply[kReplySent] = true
|
|
486
488
|
res.writeHead(res.statusCode, reply[kReplyHeaders])
|
|
487
489
|
res.end(payload)
|
|
@@ -568,7 +570,19 @@ function notFound (reply) {
|
|
|
568
570
|
}
|
|
569
571
|
|
|
570
572
|
reply.context = reply.context[kFourOhFourContext]
|
|
571
|
-
|
|
573
|
+
|
|
574
|
+
// preHandler hook
|
|
575
|
+
if (reply.context.preHandler !== null) {
|
|
576
|
+
hookRunner(
|
|
577
|
+
reply.context.preHandler,
|
|
578
|
+
hookIterator,
|
|
579
|
+
reply.request,
|
|
580
|
+
reply,
|
|
581
|
+
internals.preHandlerCallback
|
|
582
|
+
)
|
|
583
|
+
} else {
|
|
584
|
+
internals.preHandlerCallback(null, reply.request, reply)
|
|
585
|
+
}
|
|
572
586
|
}
|
|
573
587
|
|
|
574
588
|
function noop () {}
|
package/lib/route.js
CHANGED
|
@@ -218,29 +218,6 @@ function buildRouting (options) {
|
|
|
218
218
|
this[kReplySerializerDefault]
|
|
219
219
|
)
|
|
220
220
|
|
|
221
|
-
// TODO this needs to be refactored so that buildSchemaCompiler is
|
|
222
|
-
// not called for every single route. Creating a new one for every route
|
|
223
|
-
// is going to be very expensive.
|
|
224
|
-
if (opts.schema) {
|
|
225
|
-
if (this[kSchemaCompiler] == null && this[kSchemaResolver]) {
|
|
226
|
-
done(new FST_ERR_SCH_MISSING_COMPILER(opts.method, url))
|
|
227
|
-
return
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
try {
|
|
231
|
-
if (opts.schemaCompiler == null && this[kSchemaCompiler] == null) {
|
|
232
|
-
const externalSchemas = this[kSchemas].getJsonSchemas({ onlyAbsoluteUri: true })
|
|
233
|
-
this.setSchemaCompiler(buildSchemaCompiler(externalSchemas, this[kOptions].ajv, schemaCache))
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
buildSchema(context, opts.schemaCompiler || this[kSchemaCompiler], this[kSchemas], this[kSchemaResolver])
|
|
237
|
-
} catch (error) {
|
|
238
|
-
// bubble up the FastifyError instance
|
|
239
|
-
done(error.code ? error : new FST_ERR_SCH_BUILD(opts.method, url, error.message))
|
|
240
|
-
return
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
221
|
for (const hook of supportedHooks) {
|
|
245
222
|
if (opts[hook]) {
|
|
246
223
|
if (Array.isArray(opts[hook])) {
|
|
@@ -280,6 +257,24 @@ function buildRouting (options) {
|
|
|
280
257
|
// Must store the 404 Context in 'preReady' because it is only guaranteed to
|
|
281
258
|
// be available after all of the plugins and routes have been loaded.
|
|
282
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
|
+
}
|
|
283
278
|
})
|
|
284
279
|
|
|
285
280
|
done(notHandledErr)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastify",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.1",
|
|
4
4
|
"description": "Fast and low overhead web framework, for Node.js",
|
|
5
5
|
"main": "fastify.js",
|
|
6
6
|
"typings": "fastify.d.ts",
|
|
@@ -92,28 +92,30 @@
|
|
|
92
92
|
"node": ">=6"
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
|
-
"@types/node": "^12.12.
|
|
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-errors": "^1.0.1",
|
|
100
|
+
"ajv-i18n": "^3.5.0",
|
|
99
101
|
"ajv-merge-patch": "^4.1.0",
|
|
100
102
|
"ajv-pack": "^0.3.1",
|
|
101
103
|
"autocannon": "^3.2.2",
|
|
102
104
|
"branch-comparer": "^0.4.0",
|
|
103
|
-
"concurrently": "^5.0
|
|
105
|
+
"concurrently": "^5.1.0",
|
|
104
106
|
"cors": "^2.8.5",
|
|
105
|
-
"coveralls": "^3.0.
|
|
107
|
+
"coveralls": "^3.0.11",
|
|
106
108
|
"dns-prefetch-control": "^0.2.0",
|
|
107
109
|
"eslint": "^6.7.2",
|
|
108
|
-
"eslint-import-resolver-node": "^0.3.
|
|
110
|
+
"eslint-import-resolver-node": "^0.3.3",
|
|
109
111
|
"events.once": "^2.0.2",
|
|
110
112
|
"fast-json-body": "^1.1.0",
|
|
111
|
-
"fastify-plugin": "^1.
|
|
113
|
+
"fastify-plugin": "^1.6.1",
|
|
112
114
|
"fluent-schema": "^0.10.0",
|
|
113
115
|
"form-data": "^3.0.0",
|
|
114
116
|
"frameguard": "^3.0.0",
|
|
115
117
|
"h2url": "^0.2.0",
|
|
116
|
-
"helmet": "^3.
|
|
118
|
+
"helmet": "^3.21.3",
|
|
117
119
|
"hide-powered-by": "^1.0.0",
|
|
118
120
|
"hsts": "^2.1.0",
|
|
119
121
|
"http-errors": "^1.7.1",
|
|
@@ -130,43 +132,30 @@
|
|
|
130
132
|
"simple-get": "^3.0.3",
|
|
131
133
|
"snazzy": "^8.0.0",
|
|
132
134
|
"split2": "^3.1.0",
|
|
133
|
-
"standard": "^14.
|
|
135
|
+
"standard": "^14.3.3",
|
|
134
136
|
"tap": "^12.5.2",
|
|
135
137
|
"tap-mocha-reporter": "^3.0.7",
|
|
136
138
|
"then-sleep": "^1.0.1",
|
|
137
|
-
"typescript": "^3.
|
|
139
|
+
"typescript": "^3.8.3",
|
|
138
140
|
"x-xss-protection": "^1.1.0",
|
|
139
|
-
"yup": "^0.28.
|
|
141
|
+
"yup": "^0.28.3"
|
|
140
142
|
},
|
|
141
143
|
"dependencies": {
|
|
142
144
|
"abstract-logging": "^2.0.0",
|
|
143
|
-
"ajv": "^6.
|
|
144
|
-
"avvio": "^6.3.
|
|
145
|
-
"fast-json-stringify": "^1.
|
|
146
|
-
"find-my-way": "^2.2.
|
|
145
|
+
"ajv": "^6.12.0",
|
|
146
|
+
"avvio": "^6.3.1",
|
|
147
|
+
"fast-json-stringify": "^1.18.0",
|
|
148
|
+
"find-my-way": "^2.2.2",
|
|
147
149
|
"flatstr": "^1.0.12",
|
|
148
|
-
"light-my-request": "^3.7.
|
|
150
|
+
"light-my-request": "^3.7.3",
|
|
149
151
|
"middie": "^4.1.0",
|
|
150
|
-
"pino": "^5.
|
|
151
|
-
"proxy-addr": "^2.0.
|
|
152
|
-
"readable-stream": "^3.
|
|
152
|
+
"pino": "^5.17.0",
|
|
153
|
+
"proxy-addr": "^2.0.6",
|
|
154
|
+
"readable-stream": "^3.6.0",
|
|
153
155
|
"rfdc": "^1.1.2",
|
|
154
|
-
"secure-json-parse": "^2.
|
|
156
|
+
"secure-json-parse": "^2.1.0",
|
|
155
157
|
"tiny-lru": "^7.0.2"
|
|
156
158
|
},
|
|
157
|
-
"greenkeeper": {
|
|
158
|
-
"ignore": [
|
|
159
|
-
"autocannon",
|
|
160
|
-
"boom",
|
|
161
|
-
"joi",
|
|
162
|
-
"@types/node",
|
|
163
|
-
"semver",
|
|
164
|
-
"tap",
|
|
165
|
-
"tap-mocha-reporter",
|
|
166
|
-
"@typescript-eslint/eslint-plugin",
|
|
167
|
-
"lolex"
|
|
168
|
-
]
|
|
169
|
-
},
|
|
170
159
|
"standard": {
|
|
171
160
|
"ignore": [
|
|
172
161
|
"lib/configValidator.js"
|
package/test/404s.test.js
CHANGED
|
@@ -1290,7 +1290,7 @@ test('onSend hooks run when an encapsulated route invokes the notFound handler',
|
|
|
1290
1290
|
|
|
1291
1291
|
// https://github.com/fastify/fastify/issues/713
|
|
1292
1292
|
test('preHandler option for setNotFoundHandler', t => {
|
|
1293
|
-
t.plan(
|
|
1293
|
+
t.plan(10)
|
|
1294
1294
|
|
|
1295
1295
|
t.test('preHandler option', t => {
|
|
1296
1296
|
t.plan(2)
|
|
@@ -1316,6 +1316,69 @@ test('preHandler option for setNotFoundHandler', t => {
|
|
|
1316
1316
|
})
|
|
1317
1317
|
})
|
|
1318
1318
|
|
|
1319
|
+
// https://github.com/fastify/fastify/issues/2229
|
|
1320
|
+
t.test('preHandler hook in setNotFoundHandler should be called when callNotFound', t => {
|
|
1321
|
+
t.plan(2)
|
|
1322
|
+
const fastify = Fastify()
|
|
1323
|
+
|
|
1324
|
+
fastify.setNotFoundHandler({
|
|
1325
|
+
preHandler: (req, reply, done) => {
|
|
1326
|
+
req.body.preHandler = true
|
|
1327
|
+
done()
|
|
1328
|
+
}
|
|
1329
|
+
}, function (req, reply) {
|
|
1330
|
+
reply.code(404).send(req.body)
|
|
1331
|
+
})
|
|
1332
|
+
|
|
1333
|
+
fastify.post('/', function (req, reply) {
|
|
1334
|
+
reply.callNotFound()
|
|
1335
|
+
})
|
|
1336
|
+
|
|
1337
|
+
fastify.inject({
|
|
1338
|
+
method: 'POST',
|
|
1339
|
+
url: '/',
|
|
1340
|
+
payload: { hello: 'world' }
|
|
1341
|
+
}, (err, res) => {
|
|
1342
|
+
t.error(err)
|
|
1343
|
+
var payload = JSON.parse(res.payload)
|
|
1344
|
+
t.deepEqual(payload, { preHandler: true, hello: 'world' })
|
|
1345
|
+
})
|
|
1346
|
+
})
|
|
1347
|
+
|
|
1348
|
+
t.test('preHandler hook in setNotFoundHandler should accept an array of functions and be called when callNotFound', t => {
|
|
1349
|
+
t.plan(2)
|
|
1350
|
+
const fastify = Fastify()
|
|
1351
|
+
|
|
1352
|
+
fastify.setNotFoundHandler({
|
|
1353
|
+
preHandler: [
|
|
1354
|
+
(req, reply, done) => {
|
|
1355
|
+
req.body.preHandler1 = true
|
|
1356
|
+
done()
|
|
1357
|
+
},
|
|
1358
|
+
(req, reply, done) => {
|
|
1359
|
+
req.body.preHandler2 = true
|
|
1360
|
+
done()
|
|
1361
|
+
}
|
|
1362
|
+
]
|
|
1363
|
+
}, function (req, reply) {
|
|
1364
|
+
reply.code(404).send(req.body)
|
|
1365
|
+
})
|
|
1366
|
+
|
|
1367
|
+
fastify.post('/', function (req, reply) {
|
|
1368
|
+
reply.callNotFound()
|
|
1369
|
+
})
|
|
1370
|
+
|
|
1371
|
+
fastify.inject({
|
|
1372
|
+
method: 'POST',
|
|
1373
|
+
url: '/',
|
|
1374
|
+
payload: { hello: 'world' }
|
|
1375
|
+
}, (err, res) => {
|
|
1376
|
+
t.error(err)
|
|
1377
|
+
var payload = JSON.parse(res.payload)
|
|
1378
|
+
t.deepEqual(payload, { preHandler1: true, preHandler2: true, hello: 'world' })
|
|
1379
|
+
})
|
|
1380
|
+
})
|
|
1381
|
+
|
|
1319
1382
|
t.test('preHandler option should be called after preHandler hook', t => {
|
|
1320
1383
|
t.plan(2)
|
|
1321
1384
|
const fastify = Fastify()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const t = require('tap')
|
|
4
|
+
const test = t.test
|
|
5
|
+
const Fastify = require('../fastify')
|
|
6
|
+
|
|
7
|
+
test('#2214 - wrong content-length', t => {
|
|
8
|
+
const fastify = Fastify()
|
|
9
|
+
|
|
10
|
+
fastify.get('/', async () => {
|
|
11
|
+
const error = new Error('MY_ERROR_MESSAGE')
|
|
12
|
+
error.headers = {
|
|
13
|
+
'content-length': 2
|
|
14
|
+
}
|
|
15
|
+
throw error
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
fastify.inject({
|
|
19
|
+
method: 'GET',
|
|
20
|
+
path: '/'
|
|
21
|
+
})
|
|
22
|
+
.then(response => {
|
|
23
|
+
t.strictEqual(response.headers['content-length'], '' + response.rawPayload.length)
|
|
24
|
+
t.end()
|
|
25
|
+
})
|
|
26
|
+
})
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
const t = require('tap')
|
|
4
4
|
const test = t.test
|
|
5
5
|
const Fastify = require('..')
|
|
6
|
+
const semver = require('semver')
|
|
7
|
+
|
|
8
|
+
if (semver.gt(process.versions.node, '8.0.0')) {
|
|
9
|
+
require('./content-length')
|
|
10
|
+
}
|
|
6
11
|
|
|
7
12
|
test('default 413 with bodyLimit option', t => {
|
|
8
13
|
t.plan(4)
|