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.
@@ -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 [here](https://github.com/epoberezkin/ajv-errors)
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
- logger.debug({ err }, 'client error')
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
 
@@ -121,4 +121,4 @@ function preHandlerCallback (err, request, reply) {
121
121
  }
122
122
 
123
123
  module.exports = handleRequest
124
- 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')
@@ -569,7 +570,19 @@ function notFound (reply) {
569
570
  }
570
571
 
571
572
  reply.context = reply.context[kFourOhFourContext]
572
- reply.context.handler(reply.request, reply)
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.0",
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(8)
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