fastify 2.14.0 → 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/docs/Recommendations.md +4 -0
- package/docs/Reply.md +2 -1
- package/docs/Server.md +2 -0
- package/docs/Validation-and-Serialization.md +85 -1
- package/fastify.js +5 -1
- package/lib/handleRequest.js +1 -1
- package/lib/reply.js +15 -2
- package/package.json +3 -1
- package/test/404s.test.js +64 -1
- package/test/validation-error-handling.test.js +100 -0
package/docs/Recommendations.md
CHANGED
|
@@ -67,6 +67,10 @@ defaults
|
|
|
67
67
|
option dontlognull
|
|
68
68
|
retries 3
|
|
69
69
|
option redispatch
|
|
70
|
+
# The following option make haproxy close connections to backend servers
|
|
71
|
+
# instead of keeping them open. This can alleviate unexpected connection
|
|
72
|
+
# reset errors in the Node process.
|
|
73
|
+
option http-server-close
|
|
70
74
|
maxconn 2000
|
|
71
75
|
timeout connect 5000
|
|
72
76
|
timeout client 50000
|
package/docs/Reply.md
CHANGED
|
@@ -147,7 +147,8 @@ reply.code(303).redirect(302, '/home')
|
|
|
147
147
|
|
|
148
148
|
<a name="call-not-found"></a>
|
|
149
149
|
### .callNotFound()
|
|
150
|
-
Invokes the custom not found handler.
|
|
150
|
+
Invokes the custom not found handler. Note that it will only call `preHandler` hook specified in [`setNotFoundHandler`](https://github.com/fastify/fastify/blob/master/docs/Server.md#set-not-found-handler).
|
|
151
|
+
|
|
151
152
|
```js
|
|
152
153
|
reply.callNotFound()
|
|
153
154
|
```
|
package/docs/Server.md
CHANGED
|
@@ -720,6 +720,8 @@ This property can be used to set the schema compiler, it is a shortcut for the `
|
|
|
720
720
|
|
|
721
721
|
You can also register a [`preValidation`](https://www.fastify.io/docs/latest/Hooks/#route-hooks) and [preHandler](https://www.fastify.io/docs/latest/Hooks/#route-hooks) hook for the 404 handler.
|
|
722
722
|
|
|
723
|
+
_Note: The `preValidation` hook registered using this method will run for a route that Fastify does not recognize and **not** when a route handler manually calls [`reply.callNotFound`](https://github.com/fastify/fastify/blob/master/docs/Reply.md#call-not-found)_. In which case only preHandler will be run.
|
|
724
|
+
|
|
723
725
|
```js
|
|
724
726
|
fastify.setNotFoundHandler({
|
|
725
727
|
preValidation: (req, reply, done) => {
|
|
@@ -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.js
CHANGED
|
@@ -423,7 +423,11 @@ function build (options) {
|
|
|
423
423
|
message: 'Client Error',
|
|
424
424
|
statusCode: 400
|
|
425
425
|
})
|
|
426
|
-
|
|
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')
|
|
427
431
|
socket.end(`HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`)
|
|
428
432
|
}
|
|
429
433
|
|
package/lib/handleRequest.js
CHANGED
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')
|
|
@@ -569,7 +570,19 @@ function notFound (reply) {
|
|
|
569
570
|
}
|
|
570
571
|
|
|
571
572
|
reply.context = reply.context[kFourOhFourContext]
|
|
572
|
-
|
|
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
|
+
}
|
|
573
586
|
}
|
|
574
587
|
|
|
575
588
|
function noop () {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastify",
|
|
3
|
-
"version": "2.14.
|
|
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",
|
|
@@ -96,6 +96,8 @@
|
|
|
96
96
|
"@typescript-eslint/eslint-plugin": "^2.24.0",
|
|
97
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",
|
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()
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const t = require('tap')
|
|
4
4
|
const Joi = require('joi')
|
|
5
|
+
const localize = require('ajv-i18n')
|
|
5
6
|
const Fastify = require('..')
|
|
6
7
|
const test = t.test
|
|
7
8
|
|
|
@@ -65,6 +66,105 @@ test('should fail immediately with invalid payload', t => {
|
|
|
65
66
|
})
|
|
66
67
|
})
|
|
67
68
|
|
|
69
|
+
test('should return custom error messages with ajv-errors', t => {
|
|
70
|
+
t.plan(3)
|
|
71
|
+
|
|
72
|
+
const fastify = Fastify({
|
|
73
|
+
ajv: {
|
|
74
|
+
customOptions: { allErrors: true, jsonPointers: true },
|
|
75
|
+
plugins: [
|
|
76
|
+
require('ajv-errors')
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const schema = {
|
|
82
|
+
body: {
|
|
83
|
+
type: 'object',
|
|
84
|
+
properties: {
|
|
85
|
+
name: { type: 'string' },
|
|
86
|
+
work: { type: 'string' },
|
|
87
|
+
age: {
|
|
88
|
+
type: 'number',
|
|
89
|
+
errorMessage: {
|
|
90
|
+
type: 'bad age - should be num'
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
required: ['name', 'work'],
|
|
95
|
+
errorMessage: {
|
|
96
|
+
required: {
|
|
97
|
+
name: 'name please',
|
|
98
|
+
work: 'work please',
|
|
99
|
+
age: 'age please'
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fastify.post('/', { schema }, function (req, reply) {
|
|
106
|
+
reply.code(200).send(req.body.name)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
fastify.inject({
|
|
110
|
+
method: 'POST',
|
|
111
|
+
payload: {
|
|
112
|
+
hello: 'salman',
|
|
113
|
+
age: 'bad'
|
|
114
|
+
},
|
|
115
|
+
url: '/'
|
|
116
|
+
}, (err, res) => {
|
|
117
|
+
t.error(err)
|
|
118
|
+
t.deepEqual(JSON.parse(res.payload), {
|
|
119
|
+
statusCode: 400,
|
|
120
|
+
error: 'Bad Request',
|
|
121
|
+
message: 'body/age bad age - should be num, body name please, body work please'
|
|
122
|
+
})
|
|
123
|
+
t.strictEqual(res.statusCode, 400)
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('should return localized error messages with ajv-i18n', t => {
|
|
128
|
+
t.plan(3)
|
|
129
|
+
|
|
130
|
+
const fastify = Fastify({
|
|
131
|
+
ajv: {
|
|
132
|
+
customOptions: { allErrors: true }
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
fastify.setErrorHandler(function (error, request, reply) {
|
|
137
|
+
if (error.validation) {
|
|
138
|
+
localize.ru(error.validation)
|
|
139
|
+
reply.status(400).send(error.validation)
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
reply.send(error)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
fastify.post('/', { schema }, function (req, reply) {
|
|
146
|
+
reply.code(200).send(req.body.name)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
fastify.inject({
|
|
150
|
+
method: 'POST',
|
|
151
|
+
payload: {
|
|
152
|
+
name: 'salman'
|
|
153
|
+
},
|
|
154
|
+
url: '/'
|
|
155
|
+
}, (err, res) => {
|
|
156
|
+
t.error(err)
|
|
157
|
+
t.deepEqual(JSON.parse(res.payload), [{
|
|
158
|
+
dataPath: '',
|
|
159
|
+
keyword: 'required',
|
|
160
|
+
message: 'должно иметь обязательное поле work',
|
|
161
|
+
params: { missingProperty: 'work' },
|
|
162
|
+
schemaPath: '#/required'
|
|
163
|
+
}])
|
|
164
|
+
t.strictEqual(res.statusCode, 400)
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
|
|
68
168
|
test('should be able to use setErrorHandler specify custom validation error', t => {
|
|
69
169
|
t.plan(3)
|
|
70
170
|
|