fastify 4.16.3 → 4.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/GOVERNANCE.md +2 -2
  2. package/README.md +13 -10
  3. package/docs/Guides/Ecosystem.md +6 -0
  4. package/docs/Guides/Serverless.md +12 -1
  5. package/docs/Reference/Errors.md +5 -0
  6. package/docs/Reference/LTS.md +5 -5
  7. package/docs/Reference/Lifecycle.md +3 -0
  8. package/docs/Reference/Logging.md +34 -1
  9. package/docs/Reference/Reply.md +19 -0
  10. package/docs/Reference/Request.md +1 -1
  11. package/docs/Reference/Server.md +20 -2
  12. package/docs/Reference/Validation-and-Serialization.md +3 -2
  13. package/examples/benchmark/body.json +3 -0
  14. package/fastify.d.ts +6 -1
  15. package/fastify.js +18 -13
  16. package/lib/contentTypeParser.js +3 -1
  17. package/lib/errors.js +5 -0
  18. package/lib/handleRequest.js +15 -4
  19. package/lib/reply.js +3 -2
  20. package/lib/route.js +14 -3
  21. package/lib/schemas.js +18 -21
  22. package/lib/server.js +9 -4
  23. package/lib/validation.js +100 -16
  24. package/package.json +33 -33
  25. package/test/404s.test.js +4 -2
  26. package/test/custom-parser.0.test.js +777 -0
  27. package/test/{custom-parser.test.js → custom-parser.1.test.js} +1 -742
  28. package/test/delete.test.js +3 -0
  29. package/test/fastify-instance.test.js +39 -0
  30. package/test/fluent-schema.test.js +4 -4
  31. package/test/get.test.js +3 -0
  32. package/test/hooks-async.test.js +154 -0
  33. package/test/hooks.test.js +29 -29
  34. package/test/input-validation.js +10 -5
  35. package/test/internals/reply.test.js +114 -45
  36. package/test/internals/validation.test.js +58 -0
  37. package/test/reply-error.test.js +7 -7
  38. package/test/request-error.test.js +3 -0
  39. package/test/route-hooks.test.js +38 -0
  40. package/test/route.test.js +192 -74
  41. package/test/schema-examples.test.js +2 -0
  42. package/test/schema-serialization.test.js +7 -5
  43. package/test/schema-special-usage.test.js +569 -0
  44. package/test/schema-validation.test.js +8 -4
  45. package/test/search.test.js +3 -0
  46. package/test/types/fastify.test-d.ts +4 -1
  47. package/test/types/hooks.test-d.ts +42 -0
  48. package/test/types/instance.test-d.ts +1 -0
  49. package/test/types/logger.test-d.ts +10 -4
  50. package/test/types/reply.test-d.ts +4 -1
  51. package/test/types/route.test-d.ts +12 -0
  52. package/test/url-rewriting.test.js +3 -0
  53. package/test/validation-error-handling.test.js +18 -3
  54. package/types/.eslintrc.json +1 -1
  55. package/types/instance.d.ts +1 -1
  56. package/types/logger.d.ts +10 -1
  57. package/types/reply.d.ts +7 -1
  58. package/types/route.d.ts +11 -31
  59. package/types/type-provider.d.ts +0 -1
  60. package/types/utils.d.ts +3 -1
  61. /package/types/{tsconfig.json → tsconfig.eslint.json} +0 -0
@@ -8,7 +8,11 @@ const sget = require('simple-get').concat
8
8
  const joi = require('joi')
9
9
  const Fastify = require('..')
10
10
  const proxyquire = require('proxyquire')
11
- const { FST_ERR_INVALID_URL } = require('../lib/errors')
11
+ const {
12
+ FST_ERR_INVALID_URL,
13
+ FST_ERR_INSTANCE_ALREADY_LISTENING,
14
+ FST_ERR_ROUTE_METHOD_INVALID
15
+ } = require('../lib/errors')
12
16
 
13
17
  function getUrl (app) {
14
18
  const { address, port } = app.server.address()
@@ -20,13 +24,14 @@ function getUrl (app) {
20
24
  }
21
25
 
22
26
  test('route', t => {
23
- t.plan(9)
27
+ t.plan(10)
24
28
  const test = t.test
25
- const fastify = Fastify()
26
29
 
27
30
  test('route - get', t => {
28
- t.plan(1)
29
- try {
31
+ t.plan(4)
32
+
33
+ const fastify = Fastify()
34
+ t.doesNotThrow(() =>
30
35
  fastify.route({
31
36
  method: 'GET',
32
37
  url: '/',
@@ -46,15 +51,27 @@ test('route', t => {
46
51
  reply.send({ hello: 'world' })
47
52
  }
48
53
  })
49
- t.pass()
50
- } catch (e) {
51
- t.fail()
52
- }
54
+ )
55
+
56
+ fastify.listen({ port: 0 }, function (err) {
57
+ if (err) t.error(err)
58
+ t.teardown(() => { fastify.close() })
59
+ sget({
60
+ method: 'GET',
61
+ url: getUrl(fastify) + '/'
62
+ }, (err, response, body) => {
63
+ t.error(err)
64
+ t.equal(response.statusCode, 200)
65
+ t.same(JSON.parse(body), { hello: 'world' })
66
+ })
67
+ })
53
68
  })
54
69
 
55
70
  test('missing schema - route', t => {
56
- t.plan(1)
57
- try {
71
+ t.plan(4)
72
+
73
+ const fastify = Fastify()
74
+ t.doesNotThrow(() =>
58
75
  fastify.route({
59
76
  method: 'GET',
60
77
  url: '/missing',
@@ -62,106 +79,188 @@ test('route', t => {
62
79
  reply.send({ hello: 'world' })
63
80
  }
64
81
  })
65
- t.pass()
66
- } catch (e) {
67
- t.fail()
68
- }
82
+ )
83
+
84
+ fastify.listen({ port: 0 }, function (err) {
85
+ if (err) t.error(err)
86
+ t.teardown(() => { fastify.close() })
87
+ sget({
88
+ method: 'GET',
89
+ url: getUrl(fastify) + '/missing'
90
+ }, (err, response, body) => {
91
+ t.error(err)
92
+ t.equal(response.statusCode, 200)
93
+ t.same(JSON.parse(body), { hello: 'world' })
94
+ })
95
+ })
69
96
  })
70
97
 
71
98
  test('invalid handler attribute - route', t => {
72
99
  t.plan(1)
73
- try {
74
- fastify.get('/', { handler: 'not a function' }, () => {})
75
- t.fail()
76
- } catch (e) {
77
- t.pass()
78
- }
100
+
101
+ const fastify = Fastify()
102
+ t.throws(() => fastify.get('/', { handler: 'not a function' }, () => { }))
79
103
  })
80
104
 
81
- test('Multiple methods', t => {
82
- t.plan(1)
83
- try {
105
+ test('Add Multiple methods per route all uppercase', t => {
106
+ t.plan(7)
107
+
108
+ const fastify = Fastify()
109
+ t.doesNotThrow(() =>
84
110
  fastify.route({
85
111
  method: ['GET', 'DELETE'],
86
112
  url: '/multiple',
87
113
  handler: function (req, reply) {
88
114
  reply.send({ hello: 'world' })
89
115
  }
116
+ }))
117
+
118
+ fastify.listen({ port: 0 }, function (err) {
119
+ if (err) t.error(err)
120
+ t.teardown(() => { fastify.close() })
121
+ sget({
122
+ method: 'GET',
123
+ url: getUrl(fastify) + '/multiple'
124
+ }, (err, response, body) => {
125
+ t.error(err)
126
+ t.equal(response.statusCode, 200)
127
+ t.same(JSON.parse(body), { hello: 'world' })
90
128
  })
91
- t.pass()
92
- } catch (e) {
93
- t.fail()
94
- }
95
- })
96
129
 
97
- test('Add multiple methods', t => {
98
- t.plan(1)
99
- try {
100
- fastify.get('/add-multiple', function (req, reply) {
101
- reply.send({ hello: 'Bob!' })
130
+ sget({
131
+ method: 'DELETE',
132
+ url: getUrl(fastify) + '/multiple'
133
+ }, (err, response, body) => {
134
+ t.error(err)
135
+ t.equal(response.statusCode, 200)
136
+ t.same(JSON.parse(body), { hello: 'world' })
102
137
  })
138
+ })
139
+ })
140
+
141
+ test('Add Multiple methods per route all lowercase', t => {
142
+ t.plan(7)
143
+
144
+ const fastify = Fastify()
145
+ t.doesNotThrow(() =>
103
146
  fastify.route({
104
- method: ['PUT', 'DELETE'],
105
- url: '/add-multiple',
147
+ method: ['get', 'delete'],
148
+ url: '/multiple',
106
149
  handler: function (req, reply) {
107
150
  reply.send({ hello: 'world' })
108
151
  }
152
+ }))
153
+
154
+ fastify.listen({ port: 0 }, function (err) {
155
+ if (err) t.error(err)
156
+ t.teardown(() => { fastify.close() })
157
+ sget({
158
+ method: 'GET',
159
+ url: getUrl(fastify) + '/multiple'
160
+ }, (err, response, body) => {
161
+ t.error(err)
162
+ t.equal(response.statusCode, 200)
163
+ t.same(JSON.parse(body), { hello: 'world' })
109
164
  })
110
- t.pass()
111
- } catch (e) {
112
- t.fail()
113
- }
165
+
166
+ sget({
167
+ method: 'DELETE',
168
+ url: getUrl(fastify) + '/multiple'
169
+ }, (err, response, body) => {
170
+ t.error(err)
171
+ t.equal(response.statusCode, 200)
172
+ t.same(JSON.parse(body), { hello: 'world' })
173
+ })
174
+ })
114
175
  })
115
176
 
116
- fastify.listen({ port: 0 }, function (err) {
117
- if (err) t.error(err)
118
- t.teardown(() => { fastify.close() })
177
+ test('Add Multiple methods per route mixed uppercase and lowercase', t => {
178
+ t.plan(7)
119
179
 
120
- test('cannot add another route after binding', t => {
121
- t.plan(1)
122
- try {
123
- fastify.route({
124
- method: 'GET',
125
- url: '/another-get-route',
126
- handler: function (req, reply) {
127
- reply.send({ hello: 'world' })
128
- }
129
- })
130
- t.fail()
131
- } catch (e) {
132
- t.pass()
133
- }
134
- })
180
+ const fastify = Fastify()
181
+ t.doesNotThrow(() =>
182
+ fastify.route({
183
+ method: ['GET', 'delete'],
184
+ url: '/multiple',
185
+ handler: function (req, reply) {
186
+ reply.send({ hello: 'world' })
187
+ }
188
+ }))
135
189
 
136
- test('route - get', t => {
137
- t.plan(3)
190
+ fastify.listen({ port: 0 }, function (err) {
191
+ if (err) t.error(err)
192
+ t.teardown(() => { fastify.close() })
138
193
  sget({
139
194
  method: 'GET',
140
- url: 'http://localhost:' + fastify.server.address().port
195
+ url: getUrl(fastify) + '/multiple'
141
196
  }, (err, response, body) => {
142
197
  t.error(err)
143
198
  t.equal(response.statusCode, 200)
144
199
  t.same(JSON.parse(body), { hello: 'world' })
145
200
  })
146
- })
147
201
 
148
- test('route - missing schema', t => {
149
- t.plan(3)
150
202
  sget({
151
- method: 'GET',
152
- url: 'http://localhost:' + fastify.server.address().port + '/missing'
203
+ method: 'DELETE',
204
+ url: getUrl(fastify) + '/multiple'
153
205
  }, (err, response, body) => {
154
206
  t.error(err)
155
207
  t.equal(response.statusCode, 200)
156
208
  t.same(JSON.parse(body), { hello: 'world' })
157
209
  })
158
210
  })
211
+ })
212
+
213
+ test('Add invalid Multiple methods per route', t => {
214
+ t.plan(1)
215
+
216
+ const fastify = Fastify()
217
+ t.throws(() =>
218
+ fastify.route({
219
+ method: ['GET', 1],
220
+ url: '/invalid-method',
221
+ handler: function (req, reply) {
222
+ reply.send({ hello: 'world' })
223
+ }
224
+ }), new FST_ERR_ROUTE_METHOD_INVALID())
225
+ })
226
+
227
+ test('Add method', t => {
228
+ t.plan(1)
159
229
 
160
- test('route - multiple methods', t => {
161
- t.plan(6)
230
+ const fastify = Fastify()
231
+ t.throws(() =>
232
+ fastify.route({
233
+ method: 1,
234
+ url: '/invalid-method',
235
+ handler: function (req, reply) {
236
+ reply.send({ hello: 'world' })
237
+ }
238
+ }), new FST_ERR_ROUTE_METHOD_INVALID())
239
+ })
240
+
241
+ test('Add additional multiple methods to existing route', t => {
242
+ t.plan(7)
243
+
244
+ const fastify = Fastify()
245
+ t.doesNotThrow(() => {
246
+ fastify.get('/add-multiple', function (req, reply) {
247
+ reply.send({ hello: 'Bob!' })
248
+ })
249
+ fastify.route({
250
+ method: ['PUT', 'DELETE'],
251
+ url: '/add-multiple',
252
+ handler: function (req, reply) {
253
+ reply.send({ hello: 'world' })
254
+ }
255
+ })
256
+ })
257
+
258
+ fastify.listen({ port: 0 }, function (err) {
259
+ if (err) t.error(err)
260
+ t.teardown(() => { fastify.close() })
162
261
  sget({
163
- method: 'GET',
164
- url: 'http://localhost:' + fastify.server.address().port + '/multiple'
262
+ method: 'PUT',
263
+ url: getUrl(fastify) + '/add-multiple'
165
264
  }, (err, response, body) => {
166
265
  t.error(err)
167
266
  t.equal(response.statusCode, 200)
@@ -170,7 +269,7 @@ test('route', t => {
170
269
 
171
270
  sget({
172
271
  method: 'DELETE',
173
- url: 'http://localhost:' + fastify.server.address().port + '/multiple'
272
+ url: getUrl(fastify) + '/add-multiple'
174
273
  }, (err, response, body) => {
175
274
  t.error(err)
176
275
  t.equal(response.statusCode, 200)
@@ -178,6 +277,25 @@ test('route', t => {
178
277
  })
179
278
  })
180
279
  })
280
+
281
+ test('cannot add another route after binding', t => {
282
+ t.plan(1)
283
+
284
+ const fastify = Fastify()
285
+
286
+ fastify.listen({ port: 0 }, function (err) {
287
+ if (err) t.error(err)
288
+ t.teardown(() => { fastify.close() })
289
+
290
+ t.throws(() => fastify.route({
291
+ method: 'GET',
292
+ url: '/another-get-route',
293
+ handler: function (req, reply) {
294
+ reply.send({ hello: 'world' })
295
+ }
296
+ }), new FST_ERR_INSTANCE_ALREADY_LISTENING('Cannot add route!'))
297
+ })
298
+ })
181
299
  })
182
300
 
183
301
  test('invalid schema - route', t => {
@@ -185,7 +303,7 @@ test('invalid schema - route', t => {
185
303
 
186
304
  const fastify = Fastify()
187
305
  fastify.route({
188
- handler: () => {},
306
+ handler: () => { },
189
307
  method: 'GET',
190
308
  url: '/invalid',
191
309
  schema: {
@@ -207,7 +325,7 @@ test('same route definition object on multiple prefixes', async t => {
207
325
  t.plan(2)
208
326
 
209
327
  const routeObject = {
210
- handler: () => {},
328
+ handler: () => { },
211
329
  method: 'GET',
212
330
  url: '/simple'
213
331
  }
@@ -1459,7 +1577,7 @@ test('invalid url attribute - non string URL', t => {
1459
1577
  const fastify = Fastify()
1460
1578
 
1461
1579
  try {
1462
- fastify.get(/^\/(donations|skills|blogs)/, () => {})
1580
+ fastify.get(/^\/(donations|skills|blogs)/, () => { })
1463
1581
  } catch (error) {
1464
1582
  t.equal(error.code, FST_ERR_INVALID_URL().code)
1465
1583
  }
@@ -502,6 +502,7 @@ test('should return custom error messages with ajv-errors', t => {
502
502
  t.error(err)
503
503
  t.same(JSON.parse(res.payload), {
504
504
  statusCode: 400,
505
+ code: 'FST_ERR_VALIDATION',
505
506
  error: 'Bad Request',
506
507
  message: 'body/age bad age - should be num, body name please, body work please'
507
508
  })
@@ -557,6 +558,7 @@ test('should be able to handle formats of ajv-formats when added by plugins opti
557
558
  }, (_err, res) => {
558
559
  t.same(JSON.parse(res.payload), {
559
560
  statusCode: 400,
561
+ code: 'FST_ERR_VALIDATION',
560
562
  error: 'Bad Request',
561
563
  message: 'body/id must match format "uuid"'
562
564
  })
@@ -394,7 +394,8 @@ test('Use shared schema and $ref with $id in response ($ref to $id)', t => {
394
394
  t.same(res.json(), {
395
395
  error: 'Bad Request',
396
396
  message: "body must have required property 'address'",
397
- statusCode: 400
397
+ statusCode: 400,
398
+ code: 'FST_ERR_VALIDATION'
398
399
  })
399
400
  })
400
401
  })
@@ -503,7 +504,8 @@ test('Shared schema should be pass to serializer and validator ($ref to shared s
503
504
  t.same(res.json(), {
504
505
  error: 'Bad Request',
505
506
  message: 'body/0/location/email must match format "email"',
506
- statusCode: 400
507
+ statusCode: 400,
508
+ code: 'FST_ERR_VALIDATION'
507
509
  })
508
510
  })
509
511
  })
@@ -806,9 +808,9 @@ test('do not crash if status code serializer errors', async t => {
806
808
  const someUserErrorType2 = {
807
809
  type: 'object',
808
810
  properties: {
809
- code: { type: 'number' }
811
+ customCode: { type: 'number' }
810
812
  },
811
- required: ['code']
813
+ required: ['customCode']
812
814
  }
813
815
 
814
816
  fastify.get(
@@ -834,7 +836,7 @@ test('do not crash if status code serializer errors', async t => {
834
836
  t.same(res.json(), {
835
837
  statusCode: 500,
836
838
  code: 'FST_ERR_FAILED_ERROR_SERIALIZATION',
837
- message: 'Failed to serialize an error. Error: "code" is required!. ' +
839
+ message: 'Failed to serialize an error. Error: "customCode" is required!. ' +
838
840
  'Original error: querystring must have required property \'foo\''
839
841
  })
840
842
  })