fastify 5.4.0 → 5.6.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 (98) hide show
  1. package/LICENSE +1 -1
  2. package/SECURITY.md +158 -2
  3. package/build/build-validation.js +19 -1
  4. package/docs/Guides/Delay-Accepting-Requests.md +8 -5
  5. package/docs/Guides/Ecosystem.md +11 -0
  6. package/docs/Guides/Migration-Guide-V5.md +6 -10
  7. package/docs/Guides/Recommendations.md +1 -1
  8. package/docs/Reference/Errors.md +3 -1
  9. package/docs/Reference/Hooks.md +2 -6
  10. package/docs/Reference/Lifecycle.md +2 -2
  11. package/docs/Reference/Request.md +1 -1
  12. package/docs/Reference/Routes.md +5 -5
  13. package/docs/Reference/Server.md +306 -179
  14. package/docs/Reference/TypeScript.md +3 -5
  15. package/docs/Reference/Validation-and-Serialization.md +55 -3
  16. package/docs/Reference/Warnings.md +2 -1
  17. package/fastify.d.ts +19 -2
  18. package/fastify.js +34 -33
  19. package/lib/configValidator.js +196 -28
  20. package/lib/contentTypeParser.js +41 -48
  21. package/lib/error-handler.js +3 -3
  22. package/lib/errors.js +5 -0
  23. package/lib/handleRequest.js +13 -17
  24. package/lib/promise.js +23 -0
  25. package/lib/reply.js +17 -19
  26. package/lib/route.js +37 -3
  27. package/lib/server.js +36 -35
  28. package/lib/warnings.js +11 -1
  29. package/package.json +7 -7
  30. package/test/async-await.test.js +81 -134
  31. package/test/async_hooks.test.js +18 -37
  32. package/test/body-limit.test.js +51 -0
  33. package/test/buffer.test.js +22 -0
  34. package/test/case-insensitive.test.js +44 -65
  35. package/test/check.test.js +17 -21
  36. package/test/close-pipelining.test.js +24 -15
  37. package/test/constrained-routes.test.js +231 -0
  38. package/test/custom-http-server.test.js +7 -15
  39. package/test/custom-parser.0.test.js +267 -348
  40. package/test/custom-parser.1.test.js +141 -191
  41. package/test/custom-parser.2.test.js +34 -44
  42. package/test/custom-parser.3.test.js +56 -104
  43. package/test/custom-parser.4.test.js +106 -144
  44. package/test/custom-parser.5.test.js +56 -75
  45. package/test/custom-querystring-parser.test.js +51 -77
  46. package/test/decorator.test.js +76 -259
  47. package/test/delete.test.js +101 -110
  48. package/test/diagnostics-channel/404.test.js +7 -15
  49. package/test/diagnostics-channel/async-request.test.js +8 -16
  50. package/test/diagnostics-channel/error-request.test.js +7 -15
  51. package/test/diagnostics-channel/sync-request-reply.test.js +9 -16
  52. package/test/diagnostics-channel/sync-request.test.js +9 -16
  53. package/test/fastify-instance.test.js +1 -1
  54. package/test/header-overflow.test.js +18 -29
  55. package/test/helper.js +138 -134
  56. package/test/hooks-async.test.js +26 -32
  57. package/test/hooks.test.js +261 -447
  58. package/test/http-methods/copy.test.js +14 -19
  59. package/test/http-methods/get.test.js +131 -143
  60. package/test/http-methods/head.test.js +53 -84
  61. package/test/http-methods/mkcalendar.test.js +45 -72
  62. package/test/http-methods/move.test.js +6 -10
  63. package/test/http-methods/propfind.test.js +34 -44
  64. package/test/http-methods/unlock.test.js +5 -9
  65. package/test/http2/secure-with-fallback.test.js +3 -1
  66. package/test/https/custom-https-server.test.js +9 -13
  67. package/test/input-validation.js +139 -150
  68. package/test/internals/errors.test.js +50 -1
  69. package/test/internals/handle-request.test.js +29 -5
  70. package/test/internals/promise.test.js +63 -0
  71. package/test/internals/reply.test.js +277 -496
  72. package/test/plugin.1.test.js +40 -68
  73. package/test/plugin.2.test.js +40 -70
  74. package/test/plugin.3.test.js +25 -68
  75. package/test/promises.test.js +42 -63
  76. package/test/register.test.js +8 -18
  77. package/test/request-error.test.js +57 -100
  78. package/test/request-id.test.js +30 -49
  79. package/test/route-hooks.test.js +12 -16
  80. package/test/route-shorthand.test.js +9 -27
  81. package/test/route.1.test.js +74 -131
  82. package/test/route.8.test.js +9 -17
  83. package/test/router-options.test.js +450 -0
  84. package/test/schema-validation.test.js +30 -31
  85. package/test/server.test.js +143 -5
  86. package/test/stream.1.test.js +33 -50
  87. package/test/stream.4.test.js +18 -28
  88. package/test/stream.5.test.js +11 -19
  89. package/test/types/errors.test-d.ts +13 -1
  90. package/test/types/instance.test-d.ts +18 -1
  91. package/test/types/type-provider.test-d.ts +55 -0
  92. package/test/use-semicolon-delimiter.test.js +117 -59
  93. package/test/versioned-routes.test.js +39 -56
  94. package/types/errors.d.ts +11 -1
  95. package/types/hooks.d.ts +1 -1
  96. package/types/instance.d.ts +3 -1
  97. package/types/logger.d.ts +16 -14
  98. package/types/reply.d.ts +2 -2
@@ -1,72 +1,61 @@
1
1
  'use strict'
2
2
 
3
3
  const { test } = require('node:test')
4
- const sget = require('simple-get').concat
5
4
  const Fastify = require('../fastify')
6
5
  const jsonParser = require('fast-json-body')
7
- const { getServerUrl } = require('./helper')
8
- const { waitForCb } = require('./toolkit')
9
6
 
10
7
  process.removeAllListeners('warning')
11
8
 
12
- test('Should have typeof body object with no custom parser defined, null body and content type = \'text/plain\'', (t, testDone) => {
13
- t.plan(4)
9
+ test('Should have typeof body object with no custom parser defined, null body and content type = \'text/plain\'', async (t) => {
10
+ t.plan(3)
14
11
  const fastify = Fastify()
15
12
 
16
13
  fastify.post('/', (req, reply) => {
17
14
  reply.send(req.body)
18
15
  })
19
16
 
20
- fastify.listen({ port: 0 }, err => {
21
- t.assert.ifError(err)
22
-
23
- sget({
24
- method: 'POST',
25
- url: getServerUrl(fastify),
26
- body: null,
27
- headers: {
28
- 'Content-Type': 'text/plain'
29
- }
30
- }, (err, response, body) => {
31
- t.assert.ifError(err)
32
- t.assert.strictEqual(response.statusCode, 200)
33
- t.assert.strictEqual(typeof body, 'object')
34
- fastify.close()
35
- testDone()
36
- })
17
+ const fastifyServer = await fastify.listen({ port: 0 })
18
+ t.after(() => fastify.close())
19
+
20
+ const result = await fetch(fastifyServer, {
21
+ method: 'POST',
22
+ body: null,
23
+ headers: {
24
+ 'Content-Type': 'text/plain'
25
+ }
37
26
  })
27
+
28
+ t.assert.ok(result.ok)
29
+ t.assert.strictEqual(result.status, 200)
30
+ t.assert.strictEqual(await result.text(), '')
38
31
  })
39
32
 
40
- test('Should have typeof body object with no custom parser defined, undefined body and content type = \'text/plain\'', (t, testDone) => {
41
- t.plan(4)
33
+ test('Should have typeof body object with no custom parser defined, undefined body and content type = \'text/plain\'', async (t) => {
34
+ t.plan(3)
42
35
  const fastify = Fastify()
43
36
 
44
37
  fastify.post('/', (req, reply) => {
45
38
  reply.send(req.body)
46
39
  })
47
40
 
48
- fastify.listen({ port: 0 }, err => {
49
- t.assert.ifError(err)
50
-
51
- sget({
52
- method: 'POST',
53
- url: getServerUrl(fastify),
54
- body: undefined,
55
- headers: {
56
- 'Content-Type': 'text/plain'
57
- }
58
- }, (err, response, body) => {
59
- t.assert.ifError(err)
60
- t.assert.strictEqual(response.statusCode, 200)
61
- t.assert.strictEqual(typeof body, 'object')
62
- fastify.close()
63
- testDone()
64
- })
41
+ const fastifyServer = await fastify.listen({ port: 0 })
42
+ t.after(() => fastify.close())
43
+
44
+ const result = await fetch(fastifyServer, {
45
+ method: 'POST',
46
+ body: undefined,
47
+ headers: {
48
+ 'Content-Type': 'text/plain'
49
+ }
65
50
  })
51
+
52
+ t.assert.ok(result.ok)
53
+ t.assert.strictEqual(result.status, 200)
54
+ t.assert.strictEqual(await result.text(), '')
66
55
  })
67
56
 
68
- test('Should get the body as string /1', (t, testDone) => {
69
- t.plan(6)
57
+ test('Should get the body as string /1', async (t) => {
58
+ t.plan(4)
70
59
  const fastify = Fastify()
71
60
 
72
61
  fastify.post('/', (req, reply) => {
@@ -85,28 +74,23 @@ test('Should get the body as string /1', (t, testDone) => {
85
74
  }
86
75
  })
87
76
 
88
- fastify.listen({ port: 0 }, err => {
89
- t.assert.ifError(err)
90
-
91
- sget({
92
- method: 'POST',
93
- url: getServerUrl(fastify),
94
- body: 'hello world',
95
- headers: {
96
- 'Content-Type': 'text/plain'
97
- }
98
- }, (err, response, body) => {
99
- t.assert.ifError(err)
100
- t.assert.strictEqual(response.statusCode, 200)
101
- t.assert.strictEqual(body.toString(), 'hello world')
102
- fastify.close()
103
- testDone()
104
- })
77
+ const fastifyServer = await fastify.listen({ port: 0 })
78
+ t.after(() => fastify.close())
79
+
80
+ const result = await fetch(fastifyServer, {
81
+ method: 'POST',
82
+ body: 'hello world',
83
+ headers: {
84
+ 'Content-Type': 'text/plain'
85
+ }
105
86
  })
87
+
88
+ t.assert.strictEqual(result.status, 200)
89
+ t.assert.strictEqual(await result.text(), 'hello world')
106
90
  })
107
91
 
108
- test('Should get the body as string /2', (t, testDone) => {
109
- t.plan(6)
92
+ test('Should get the body as string /2', async (t) => {
93
+ t.plan(4)
110
94
  const fastify = Fastify()
111
95
 
112
96
  fastify.post('/', (req, reply) => {
@@ -125,28 +109,23 @@ test('Should get the body as string /2', (t, testDone) => {
125
109
  }
126
110
  })
127
111
 
128
- fastify.listen({ port: 0 }, err => {
129
- t.assert.ifError(err)
130
-
131
- sget({
132
- method: 'POST',
133
- url: getServerUrl(fastify),
134
- body: 'hello world',
135
- headers: {
136
- 'Content-Type': ' text/plain/test '
137
- }
138
- }, (err, response, body) => {
139
- t.assert.ifError(err)
140
- t.assert.strictEqual(response.statusCode, 200)
141
- t.assert.strictEqual(body.toString(), 'hello world')
142
- fastify.close()
143
- testDone()
144
- })
112
+ const fastifyServer = await fastify.listen({ port: 0 })
113
+ t.after(() => fastify.close())
114
+
115
+ const result = await fetch(fastifyServer, {
116
+ method: 'POST',
117
+ body: 'hello world',
118
+ headers: {
119
+ 'Content-Type': ' text/plain/test '
120
+ }
145
121
  })
122
+
123
+ t.assert.strictEqual(result.status, 200)
124
+ t.assert.strictEqual(await result.text(), 'hello world')
146
125
  })
147
126
 
148
- test('Should get the body as buffer', (t, testDone) => {
149
- t.plan(6)
127
+ test('Should get the body as buffer', async (t) => {
128
+ t.plan(4)
150
129
  const fastify = Fastify()
151
130
 
152
131
  fastify.post('/', (req, reply) => {
@@ -165,28 +144,23 @@ test('Should get the body as buffer', (t, testDone) => {
165
144
  }
166
145
  })
167
146
 
168
- fastify.listen({ port: 0 }, err => {
169
- t.assert.ifError(err)
170
-
171
- sget({
172
- method: 'POST',
173
- url: getServerUrl(fastify),
174
- body: '{"hello":"world"}',
175
- headers: {
176
- 'Content-Type': 'application/json'
177
- }
178
- }, (err, response, body) => {
179
- t.assert.ifError(err)
180
- t.assert.strictEqual(response.statusCode, 200)
181
- t.assert.strictEqual(body.toString(), '{"hello":"world"}')
182
- fastify.close()
183
- testDone()
184
- })
147
+ const fastifyServer = await fastify.listen({ port: 0 })
148
+ t.after(() => fastify.close())
149
+
150
+ const result = await fetch(fastifyServer, {
151
+ method: 'POST',
152
+ body: '{"hello":"world"}',
153
+ headers: {
154
+ 'Content-Type': 'application/json'
155
+ }
185
156
  })
157
+
158
+ t.assert.strictEqual(result.status, 200)
159
+ t.assert.strictEqual(await result.text(), '{"hello":"world"}')
186
160
  })
187
161
 
188
- test('Should get the body as buffer', (t, testDone) => {
189
- t.plan(6)
162
+ test('Should get the body as buffer', async (t) => {
163
+ t.plan(4)
190
164
  const fastify = Fastify()
191
165
 
192
166
  fastify.post('/', (req, reply) => {
@@ -205,28 +179,23 @@ test('Should get the body as buffer', (t, testDone) => {
205
179
  }
206
180
  })
207
181
 
208
- fastify.listen({ port: 0 }, err => {
209
- t.assert.ifError(err)
210
-
211
- sget({
212
- method: 'POST',
213
- url: getServerUrl(fastify),
214
- body: 'hello world',
215
- headers: {
216
- 'Content-Type': 'text/plain'
217
- }
218
- }, (err, response, body) => {
219
- t.assert.ifError(err)
220
- t.assert.strictEqual(response.statusCode, 200)
221
- t.assert.strictEqual(body.toString(), 'hello world')
222
- fastify.close()
223
- testDone()
224
- })
182
+ const fastifyServer = await fastify.listen({ port: 0 })
183
+ t.after(() => fastify.close())
184
+
185
+ const result = await fetch(fastifyServer, {
186
+ method: 'POST',
187
+ body: 'hello world',
188
+ headers: {
189
+ 'Content-Type': 'text/plain'
190
+ }
225
191
  })
192
+
193
+ t.assert.strictEqual(result.status, 200)
194
+ t.assert.strictEqual(await result.text(), 'hello world')
226
195
  })
227
196
 
228
- test('Should parse empty bodies as a string', (t) => {
229
- t.plan(9)
197
+ test('Should parse empty bodies as a string', async (t) => {
198
+ t.plan(8)
230
199
  const fastify = Fastify()
231
200
 
232
201
  fastify.addContentTypeParser('text/plain', { parseAs: 'string' }, (req, body, done) => {
@@ -242,47 +211,37 @@ test('Should parse empty bodies as a string', (t) => {
242
211
  }
243
212
  })
244
213
 
245
- const completion = waitForCb({ steps: 2 })
246
-
247
- fastify.listen({ port: 0 }, err => {
248
- t.assert.ifError(err)
249
- t.after(() => { fastify.close() })
250
-
251
- sget({
252
- method: 'POST',
253
- url: getServerUrl(fastify),
254
- body: '',
255
- headers: {
256
- 'Content-Type': 'text/plain'
257
- }
258
- }, (err, response, body) => {
259
- t.assert.ifError(err)
260
- t.assert.strictEqual(response.statusCode, 200)
261
- t.assert.strictEqual(body.toString(), '')
262
- completion.stepIn()
263
- })
214
+ const fastifyServer = await fastify.listen({ port: 0 })
215
+ t.after(() => fastify.close())
264
216
 
265
- sget({
266
- method: 'DELETE',
267
- url: getServerUrl(fastify),
268
- body: '',
269
- headers: {
270
- 'Content-Type': 'text/plain',
271
- 'Content-Length': '0'
272
- }
273
- }, (err, response, body) => {
274
- t.assert.ifError(err)
275
- t.assert.strictEqual(response.statusCode, 200)
276
- t.assert.strictEqual(body.toString(), '')
277
- completion.stepIn()
278
- })
217
+ const postResult = await fetch(fastifyServer, {
218
+ method: 'POST',
219
+ body: '',
220
+ headers: {
221
+ 'Content-Type': 'text/plain'
222
+ }
279
223
  })
280
224
 
281
- return completion.patience
225
+ t.assert.ok(postResult.ok)
226
+ t.assert.strictEqual(postResult.status, 200)
227
+ t.assert.strictEqual(await postResult.text(), '')
228
+
229
+ const deleteResult = await fetch(fastifyServer, {
230
+ method: 'DELETE',
231
+ body: '',
232
+ headers: {
233
+ 'Content-Type': 'text/plain',
234
+ 'Content-Length': '0'
235
+ }
236
+ })
237
+
238
+ t.assert.ok(deleteResult.ok)
239
+ t.assert.strictEqual(deleteResult.status, 200)
240
+ t.assert.strictEqual(await deleteResult.text(), '')
282
241
  })
283
242
 
284
- test('Should parse empty bodies as a buffer', (t, testDone) => {
285
- t.plan(6)
243
+ test('Should parse empty bodies as a buffer', async (t) => {
244
+ t.plan(4)
286
245
  const fastify = Fastify()
287
246
 
288
247
  fastify.post('/', (req, reply) => {
@@ -295,28 +254,23 @@ test('Should parse empty bodies as a buffer', (t, testDone) => {
295
254
  done(null, body)
296
255
  })
297
256
 
298
- fastify.listen({ port: 0 }, err => {
299
- t.assert.ifError(err)
300
-
301
- sget({
302
- method: 'POST',
303
- url: getServerUrl(fastify),
304
- body: '',
305
- headers: {
306
- 'Content-Type': 'text/plain'
307
- }
308
- }, (err, response, body) => {
309
- t.assert.ifError(err)
310
- t.assert.strictEqual(response.statusCode, 200)
311
- t.assert.strictEqual(body.length, 0)
312
- fastify.close()
313
- testDone()
314
- })
257
+ const fastifyServer = await fastify.listen({ port: 0 })
258
+ t.after(() => fastify.close())
259
+
260
+ const result = await fetch(fastifyServer, {
261
+ method: 'POST',
262
+ body: '',
263
+ headers: {
264
+ 'Content-Type': 'text/plain'
265
+ }
315
266
  })
267
+
268
+ t.assert.strictEqual(result.status, 200)
269
+ t.assert.strictEqual((await result.arrayBuffer()).byteLength, 0)
316
270
  })
317
271
 
318
- test('The charset should not interfere with the content type handling', (t, testDone) => {
319
- t.plan(5)
272
+ test('The charset should not interfere with the content type handling', async (t) => {
273
+ t.plan(4)
320
274
  const fastify = Fastify()
321
275
 
322
276
  fastify.post('/', (req, reply) => {
@@ -330,22 +284,18 @@ test('The charset should not interfere with the content type handling', (t, test
330
284
  })
331
285
  })
332
286
 
333
- fastify.listen({ port: 0 }, err => {
334
- t.assert.ifError(err)
335
-
336
- sget({
337
- method: 'POST',
338
- url: getServerUrl(fastify),
339
- body: '{"hello":"world"}',
340
- headers: {
341
- 'Content-Type': 'application/json; charset=utf-8'
342
- }
343
- }, (err, response, body) => {
344
- t.assert.ifError(err)
345
- t.assert.strictEqual(response.statusCode, 200)
346
- t.assert.strictEqual(body.toString(), '{"hello":"world"}')
347
- fastify.close()
348
- testDone()
349
- })
287
+ const fastifyServer = await fastify.listen({ port: 0 })
288
+ t.after(() => fastify.close())
289
+
290
+ const result = await fetch(fastifyServer, {
291
+ method: 'POST',
292
+ body: '{"hello":"world"}',
293
+ headers: {
294
+ 'Content-Type': 'application/json; charset=utf-8'
295
+ }
350
296
  })
297
+
298
+ t.assert.ok(result.ok)
299
+ t.assert.strictEqual(result.status, 200)
300
+ t.assert.strictEqual(await result.text(), '{"hello":"world"}')
351
301
  })
@@ -1,9 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { test } = require('node:test')
4
- const sget = require('simple-get').concat
5
4
  const Fastify = require('..')
6
- const { getServerUrl } = require('./helper')
7
5
 
8
6
  process.removeAllListeners('warning')
9
7
 
@@ -20,8 +18,8 @@ test('Wrong parseAs parameter', t => {
20
18
  }
21
19
  })
22
20
 
23
- test('Should allow defining the bodyLimit per parser', (t, done) => {
24
- t.plan(3)
21
+ test('Should allow defining the bodyLimit per parser', async (t) => {
22
+ t.plan(2)
25
23
  const fastify = Fastify()
26
24
  t.after(() => fastify.close())
27
25
 
@@ -38,31 +36,27 @@ test('Should allow defining the bodyLimit per parser', (t, done) => {
38
36
  }
39
37
  )
40
38
 
41
- fastify.listen({ port: 0 }, err => {
42
- t.assert.ifError(err)
43
-
44
- sget({
45
- method: 'POST',
46
- url: getServerUrl(fastify),
47
- body: '1234567890',
48
- headers: {
49
- 'Content-Type': 'x/foo'
50
- }
51
- }, (err, response, body) => {
52
- t.assert.ifError(err)
53
- t.assert.deepStrictEqual(JSON.parse(body.toString()), {
54
- statusCode: 413,
55
- code: 'FST_ERR_CTP_BODY_TOO_LARGE',
56
- error: 'Payload Too Large',
57
- message: 'Request body is too large'
58
- })
59
- done()
60
- })
39
+ const fastifyServer = await fastify.listen({ port: 0 })
40
+
41
+ const result = await fetch(fastifyServer, {
42
+ method: 'POST',
43
+ body: '1234567890',
44
+ headers: {
45
+ 'Content-Type': 'x/foo'
46
+ }
47
+ })
48
+
49
+ t.assert.ok(!result.ok)
50
+ t.assert.deepStrictEqual(await result.json(), {
51
+ statusCode: 413,
52
+ code: 'FST_ERR_CTP_BODY_TOO_LARGE',
53
+ error: 'Payload Too Large',
54
+ message: 'Request body is too large'
61
55
  })
62
56
  })
63
57
 
64
- test('route bodyLimit should take precedence over a custom parser bodyLimit', (t, done) => {
65
- t.plan(3)
58
+ test('route bodyLimit should take precedence over a custom parser bodyLimit', async (t) => {
59
+ t.plan(2)
66
60
  const fastify = Fastify()
67
61
  t.after(() => fastify.close())
68
62
 
@@ -79,23 +73,19 @@ test('route bodyLimit should take precedence over a custom parser bodyLimit', (t
79
73
  }
80
74
  )
81
75
 
82
- fastify.listen({ port: 0 }, err => {
83
- t.assert.ifError(err)
84
-
85
- sget({
86
- method: 'POST',
87
- url: getServerUrl(fastify),
88
- body: '1234567890',
89
- headers: { 'Content-Type': 'x/foo' }
90
- }, (err, response, body) => {
91
- t.assert.ifError(err)
92
- t.assert.deepStrictEqual(JSON.parse(body.toString()), {
93
- statusCode: 413,
94
- code: 'FST_ERR_CTP_BODY_TOO_LARGE',
95
- error: 'Payload Too Large',
96
- message: 'Request body is too large'
97
- })
98
- done()
99
- })
76
+ const fastifyServer = await fastify.listen({ port: 0 })
77
+
78
+ const result = await fetch(fastifyServer, {
79
+ method: 'POST',
80
+ body: '1234567890',
81
+ headers: { 'Content-Type': 'x/foo' }
82
+ })
83
+
84
+ t.assert.ok(!result.ok)
85
+ t.assert.deepStrictEqual(await result.json(), {
86
+ statusCode: 413,
87
+ code: 'FST_ERR_CTP_BODY_TOO_LARGE',
88
+ error: 'Payload Too Large',
89
+ message: 'Request body is too large'
100
90
  })
101
91
  })