fastify 3.26.0 → 4.0.0-alpha.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.
Files changed (129) hide show
  1. package/README.md +5 -4
  2. package/build/build-error-serializer.js +27 -0
  3. package/build/build-validation.js +49 -35
  4. package/docs/Guides/Ecosystem.md +2 -1
  5. package/docs/Guides/Prototype-Poisoning.md +3 -3
  6. package/docs/Migration-Guide-V4.md +12 -0
  7. package/docs/Reference/ContentTypeParser.md +8 -1
  8. package/docs/Reference/Errors.md +51 -6
  9. package/docs/Reference/Hooks.md +4 -7
  10. package/docs/Reference/LTS.md +5 -4
  11. package/docs/Reference/Reply.md +23 -22
  12. package/docs/Reference/Request.md +1 -3
  13. package/docs/Reference/Routes.md +17 -10
  14. package/docs/Reference/Server.md +98 -63
  15. package/docs/Reference/TypeScript.md +11 -13
  16. package/docs/Reference/Validation-and-Serialization.md +32 -54
  17. package/docs/Type-Providers.md +257 -0
  18. package/examples/hooks.js +1 -1
  19. package/examples/simple-stream.js +18 -0
  20. package/fastify.d.ts +36 -22
  21. package/fastify.js +72 -53
  22. package/lib/configValidator.js +902 -1023
  23. package/lib/contentTypeParser.js +6 -16
  24. package/lib/context.js +36 -10
  25. package/lib/decorate.js +5 -3
  26. package/lib/error-handler.js +158 -0
  27. package/lib/error-serializer.js +257 -0
  28. package/lib/errors.js +49 -10
  29. package/lib/fourOhFour.js +31 -20
  30. package/lib/handleRequest.js +10 -13
  31. package/lib/hooks.js +14 -9
  32. package/lib/noop-set.js +10 -0
  33. package/lib/pluginOverride.js +0 -3
  34. package/lib/pluginUtils.js +3 -2
  35. package/lib/reply.js +44 -163
  36. package/lib/request.js +13 -10
  37. package/lib/route.js +158 -139
  38. package/lib/schema-controller.js +3 -3
  39. package/lib/schemas.js +27 -1
  40. package/lib/server.js +219 -116
  41. package/lib/symbols.js +6 -4
  42. package/lib/validation.js +2 -1
  43. package/lib/warnings.js +2 -12
  44. package/lib/wrapThenable.js +4 -11
  45. package/package.json +40 -45
  46. package/test/404s.test.js +265 -108
  47. package/test/500s.test.js +2 -2
  48. package/test/async-await.test.js +15 -71
  49. package/test/close.test.js +39 -1
  50. package/test/content-parser.test.js +32 -0
  51. package/test/context-config.test.js +56 -4
  52. package/test/custom-http-server.test.js +14 -7
  53. package/test/custom-parser-async.test.js +0 -65
  54. package/test/custom-parser.test.js +54 -121
  55. package/test/decorator.test.js +1 -3
  56. package/test/delete.test.js +5 -5
  57. package/test/encapsulated-error-handler.test.js +50 -0
  58. package/test/esm/index.test.js +0 -14
  59. package/test/fastify-instance.test.js +4 -4
  60. package/test/fluent-schema.test.js +4 -4
  61. package/test/get.test.js +3 -3
  62. package/test/helper.js +18 -3
  63. package/test/hooks-async.test.js +14 -47
  64. package/test/hooks.on-ready.test.js +9 -4
  65. package/test/hooks.test.js +58 -99
  66. package/test/http2/closing.test.js +5 -11
  67. package/test/http2/unknown-http-method.test.js +3 -9
  68. package/test/https/custom-https-server.test.js +12 -6
  69. package/test/inject.test.js +1 -1
  70. package/test/input-validation.js +2 -2
  71. package/test/internals/all.test.js +2 -2
  72. package/test/internals/contentTypeParser.test.js +4 -4
  73. package/test/internals/handleRequest.test.js +9 -46
  74. package/test/internals/initialConfig.test.js +33 -12
  75. package/test/internals/logger.test.js +1 -1
  76. package/test/internals/reply.test.js +245 -3
  77. package/test/internals/request.test.js +13 -7
  78. package/test/internals/server.test.js +88 -0
  79. package/test/listen.test.js +84 -1
  80. package/test/logger.test.js +98 -58
  81. package/test/maxRequestsPerSocket.test.js +8 -6
  82. package/test/middleware.test.js +2 -25
  83. package/test/noop-set.test.js +19 -0
  84. package/test/nullable-validation.test.js +51 -14
  85. package/test/plugin.test.js +31 -5
  86. package/test/pretty-print.test.js +22 -10
  87. package/test/reply-error.test.js +123 -12
  88. package/test/request-error.test.js +2 -5
  89. package/test/route-hooks.test.js +17 -17
  90. package/test/route-prefix.test.js +2 -1
  91. package/test/route.test.js +216 -20
  92. package/test/router-options.test.js +1 -1
  93. package/test/schema-examples.test.js +11 -5
  94. package/test/schema-feature.test.js +24 -19
  95. package/test/schema-serialization.test.js +50 -9
  96. package/test/schema-special-usage.test.js +14 -81
  97. package/test/schema-validation.test.js +9 -9
  98. package/test/skip-reply-send.test.js +8 -8
  99. package/test/stream.test.js +23 -12
  100. package/test/throw.test.js +8 -5
  101. package/test/trust-proxy.test.js +1 -1
  102. package/test/type-provider.test.js +20 -0
  103. package/test/types/fastify.test-d.ts +12 -18
  104. package/test/types/hooks.test-d.ts +7 -3
  105. package/test/types/import.js +2 -0
  106. package/test/types/import.ts +1 -0
  107. package/test/types/instance.test-d.ts +61 -15
  108. package/test/types/logger.test-d.ts +44 -15
  109. package/test/types/route.test-d.ts +8 -2
  110. package/test/types/schema.test-d.ts +2 -39
  111. package/test/types/type-provider.test-d.ts +417 -0
  112. package/test/validation-error-handling.test.js +9 -9
  113. package/test/versioned-routes.test.js +29 -17
  114. package/test/wrapThenable.test.js +7 -6
  115. package/types/.eslintrc.json +1 -1
  116. package/types/content-type-parser.d.ts +17 -8
  117. package/types/hooks.d.ts +107 -60
  118. package/types/instance.d.ts +137 -105
  119. package/types/logger.d.ts +18 -104
  120. package/types/plugin.d.ts +10 -4
  121. package/types/register.d.ts +1 -1
  122. package/types/reply.d.ts +16 -11
  123. package/types/request.d.ts +10 -5
  124. package/types/route.d.ts +42 -31
  125. package/types/schema.d.ts +15 -1
  126. package/types/type-provider.d.ts +99 -0
  127. package/types/utils.d.ts +1 -1
  128. package/lib/schema-compilers.js +0 -12
  129. package/test/emit-warning.test.js +0 -166
@@ -79,7 +79,7 @@ test('ignore the result of the promise if reply.send is called beforehand (undef
79
79
  const payload = { hello: 'world' }
80
80
 
81
81
  server.get('/', async function awaitMyFunc (req, reply) {
82
- reply.send(payload)
82
+ await reply.send(payload)
83
83
  })
84
84
 
85
85
  t.teardown(server.close.bind(server))
@@ -104,7 +104,7 @@ test('ignore the result of the promise if reply.send is called beforehand (objec
104
104
  const payload = { hello: 'world2' }
105
105
 
106
106
  server.get('/', async function awaitMyFunc (req, reply) {
107
- reply.send(payload)
107
+ await reply.send(payload)
108
108
  return { hello: 'world' }
109
109
  })
110
110
 
@@ -139,7 +139,7 @@ test('server logs an error if reply.send is called and a value is returned via a
139
139
  })
140
140
 
141
141
  fastify.get('/', async (req, reply) => {
142
- reply.send({ hello: 'world' })
142
+ await reply.send({ hello: 'world' })
143
143
  return { hello: 'world2' }
144
144
  })
145
145
 
@@ -160,7 +160,7 @@ test('ignore the result of the promise if reply.send is called beforehand (undef
160
160
  const payload = { hello: 'world' }
161
161
 
162
162
  server.get('/', async function awaitMyFunc (req, reply) {
163
- reply.send(payload)
163
+ await reply.send(payload)
164
164
  })
165
165
 
166
166
  t.teardown(server.close.bind(server))
@@ -185,7 +185,7 @@ test('ignore the result of the promise if reply.send is called beforehand (objec
185
185
  const payload = { hello: 'world2' }
186
186
 
187
187
  server.get('/', async function awaitMyFunc (req, reply) {
188
- reply.send(payload)
188
+ await reply.send(payload)
189
189
  return { hello: 'world' }
190
190
  })
191
191
 
@@ -279,11 +279,13 @@ test('support reply decorators with await', t => {
279
279
  setImmediate(() => {
280
280
  this.send({ hello: 'world' })
281
281
  })
282
+
283
+ return this
282
284
  })
283
285
 
284
286
  fastify.get('/', async (req, reply) => {
285
287
  await sleep(1)
286
- reply.wow()
288
+ await reply.wow()
287
289
  })
288
290
 
289
291
  fastify.inject({
@@ -296,24 +298,6 @@ test('support reply decorators with await', t => {
296
298
  })
297
299
  })
298
300
 
299
- test('support 204', t => {
300
- t.plan(2)
301
-
302
- const fastify = Fastify()
303
-
304
- fastify.get('/', async (req, reply) => {
305
- reply.code(204)
306
- })
307
-
308
- fastify.inject({
309
- method: 'GET',
310
- url: '/'
311
- }, (err, res) => {
312
- t.error(err)
313
- t.equal(res.statusCode, 204)
314
- })
315
- })
316
-
317
301
  test('inject async await', async t => {
318
302
  t.plan(1)
319
303
 
@@ -399,55 +383,15 @@ test('does not call reply.send() twice if 204 response equal already sent', t =>
399
383
  })
400
384
  })
401
385
 
402
- test('error is logged because promise was fulfilled with undefined', t => {
403
- t.plan(3)
404
-
405
- let fastify = null
406
- const stream = split(JSON.parse)
407
- try {
408
- fastify = Fastify({
409
- logger: {
410
- stream: stream,
411
- level: 'error'
412
- }
413
- })
414
- } catch (e) {
415
- t.fail()
416
- }
417
-
418
- t.teardown(fastify.close.bind(fastify))
419
-
420
- fastify.get('/', async (req, reply) => {
421
- reply.code(200)
422
- })
423
-
424
- stream.once('data', line => {
425
- t.equal(line.msg, 'Promise may not be fulfilled with \'undefined\' when statusCode is not 204')
426
- })
427
-
428
- fastify.listen(0, (err) => {
429
- t.error(err)
430
- fastify.server.unref()
431
-
432
- sget({
433
- method: 'GET',
434
- url: 'http://localhost:' + fastify.server.address().port + '/',
435
- timeout: 500
436
- }, (err, res, body) => {
437
- t.equal(err.message, 'Request timed out')
438
- })
439
- })
440
- })
441
-
442
- test('error is not logged because promise was fulfilled with undefined but statusCode 204 was set', t => {
443
- t.plan(3)
386
+ test('promise was fulfilled with undefined', t => {
387
+ t.plan(4)
444
388
 
445
389
  let fastify = null
446
390
  const stream = split(JSON.parse)
447
391
  try {
448
392
  fastify = Fastify({
449
393
  logger: {
450
- stream: stream,
394
+ stream,
451
395
  level: 'error'
452
396
  }
453
397
  })
@@ -458,7 +402,6 @@ test('error is not logged because promise was fulfilled with undefined but statu
458
402
  t.teardown(fastify.close.bind(fastify))
459
403
 
460
404
  fastify.get('/', async (req, reply) => {
461
- reply.code(204)
462
405
  })
463
406
 
464
407
  stream.once('data', line => {
@@ -474,7 +417,8 @@ test('error is not logged because promise was fulfilled with undefined but statu
474
417
  url: 'http://localhost:' + fastify.server.address().port + '/'
475
418
  }, (err, res, body) => {
476
419
  t.error(err)
477
- t.equal(res.statusCode, 204)
420
+ t.equal(res.body, undefined)
421
+ t.equal(res.statusCode, 200)
478
422
  })
479
423
  })
480
424
  })
@@ -488,7 +432,7 @@ test('error is not logged because promise was fulfilled with undefined but respo
488
432
  try {
489
433
  fastify = Fastify({
490
434
  logger: {
491
- stream: stream,
435
+ stream,
492
436
  level: 'error'
493
437
  }
494
438
  })
@@ -601,7 +545,7 @@ test('customErrorHandler support without throwing', t => {
601
545
 
602
546
  fastify.setErrorHandler(async (err, req, reply) => {
603
547
  t.equal(err.message, 'ouch')
604
- reply.code(401).send('kaboom')
548
+ await reply.code(401).send('kaboom')
605
549
  reply.send = t.fail.bind(t, 'should not be called')
606
550
  })
607
551
 
@@ -215,7 +215,7 @@ t.test('Current opened connection should continue to work after closing and retu
215
215
  t.error(err)
216
216
 
217
217
  const port = fastify.server.address().port
218
- const client = net.createConnection({ port: port }, () => {
218
+ const client = net.createConnection({ port }, () => {
219
219
  client.write('GET / HTTP/1.1\r\n\r\n')
220
220
 
221
221
  client.once('data', data => {
@@ -291,3 +291,41 @@ test('Cannot be reopened the closed server has listen callback', async t => {
291
291
  t.ok(err)
292
292
  })
293
293
  })
294
+
295
+ test('shutsdown while keep-alive connections are active (non-async)', t => {
296
+ t.plan(5)
297
+
298
+ const timeoutTime = 2 * 60 * 1000
299
+ const fastify = Fastify({ forceCloseConnections: true })
300
+
301
+ fastify.server.setTimeout(timeoutTime)
302
+ fastify.server.keepAliveTimeout = timeoutTime
303
+
304
+ fastify.get('/', (req, reply) => {
305
+ reply.send({ hello: 'world' })
306
+ })
307
+
308
+ fastify.listen(0, (err, address) => {
309
+ t.error(err)
310
+
311
+ const client = new Client(
312
+ 'http://localhost:' + fastify.server.address().port,
313
+ { keepAliveTimeout: 1 * 60 * 1000 }
314
+ )
315
+ client.request({ path: '/', method: 'GET' }, (err, response) => {
316
+ t.error(err)
317
+ t.equal(client.closed, false)
318
+
319
+ fastify.close((err) => {
320
+ t.error(err)
321
+
322
+ // Due to the nature of the way we reap these keep-alive connections,
323
+ // there hasn't been enough time before the server fully closed in order
324
+ // for the client to have seen the socket get destroyed. The mere fact
325
+ // that we have reached this callback is enough indication that the
326
+ // feature being tested works as designed.
327
+ t.equal(client.closed, false)
328
+ })
329
+ })
330
+ })
331
+ })
@@ -187,6 +187,38 @@ test('add', t => {
187
187
  t.end()
188
188
  })
189
189
 
190
+ test('non-Error thrown from content parser is properly handled', t => {
191
+ t.plan(3)
192
+
193
+ const fastify = Fastify()
194
+
195
+ const throwable = 'test'
196
+ const payload = 'error'
197
+
198
+ fastify.addContentTypeParser('text/test', (request, payload, done) => {
199
+ done(throwable)
200
+ })
201
+
202
+ fastify.post('/', (req, reply) => {
203
+ })
204
+
205
+ fastify.setErrorHandler((err, req, res) => {
206
+ t.equal(err, throwable)
207
+
208
+ res.send(payload)
209
+ })
210
+
211
+ fastify.inject({
212
+ method: 'POST',
213
+ url: '/',
214
+ headers: { 'Content-Type': 'text/test' },
215
+ body: 'some text'
216
+ }, (err, res) => {
217
+ t.error(err)
218
+ t.equal(res.payload, payload)
219
+ })
220
+ })
221
+
190
222
  test('remove', t => {
191
223
  test('should remove default parser', t => {
192
224
  t.plan(2)
@@ -29,7 +29,7 @@ test('config', t => {
29
29
  method: 'GET',
30
30
  url: '/route',
31
31
  schema: schema.schema,
32
- handler: handler,
32
+ handler,
33
33
  config: Object.assign({}, schema.config)
34
34
  })
35
35
 
@@ -37,7 +37,7 @@ test('config', t => {
37
37
  method: 'GET',
38
38
  url: '/no-config',
39
39
  schema: schema.schema,
40
- handler: handler
40
+ handler
41
41
  })
42
42
 
43
43
  fastify.inject({
@@ -81,7 +81,7 @@ test('config with exposeHeadRoutes', t => {
81
81
  method: 'GET',
82
82
  url: '/route',
83
83
  schema: schema.schema,
84
- handler: handler,
84
+ handler,
85
85
  config: Object.assign({}, schema.config)
86
86
  })
87
87
 
@@ -89,7 +89,59 @@ test('config with exposeHeadRoutes', t => {
89
89
  method: 'GET',
90
90
  url: '/no-config',
91
91
  schema: schema.schema,
92
- handler: handler
92
+ handler
93
+ })
94
+
95
+ fastify.inject({
96
+ method: 'GET',
97
+ url: '/get'
98
+ }, (err, response) => {
99
+ t.error(err)
100
+ t.equal(response.statusCode, 200)
101
+ t.same(JSON.parse(response.payload), Object.assign({ url: '/get', method: 'GET' }, schema.config))
102
+ })
103
+
104
+ fastify.inject({
105
+ method: 'GET',
106
+ url: '/route'
107
+ }, (err, response) => {
108
+ t.error(err)
109
+ t.equal(response.statusCode, 200)
110
+ t.same(JSON.parse(response.payload), Object.assign({ url: '/route', method: 'GET' }, schema.config))
111
+ })
112
+
113
+ fastify.inject({
114
+ method: 'GET',
115
+ url: '/no-config'
116
+ }, (err, response) => {
117
+ t.error(err)
118
+ t.equal(response.statusCode, 200)
119
+ t.same(JSON.parse(response.payload), { url: '/no-config', method: 'GET' })
120
+ })
121
+ })
122
+
123
+ test('config without exposeHeadRoutes', t => {
124
+ t.plan(9)
125
+ const fastify = Fastify({ exposeHeadRoutes: false })
126
+
127
+ fastify.get('/get', {
128
+ schema: schema.schema,
129
+ config: Object.assign({}, schema.config)
130
+ }, handler)
131
+
132
+ fastify.route({
133
+ method: 'GET',
134
+ url: '/route',
135
+ schema: schema.schema,
136
+ handler,
137
+ config: Object.assign({}, schema.config)
138
+ })
139
+
140
+ fastify.route({
141
+ method: 'GET',
142
+ url: '/no-config',
143
+ schema: schema.schema,
144
+ handler
93
145
  })
94
146
 
95
147
  fastify.inject({
@@ -5,12 +5,15 @@ const test = t.test
5
5
  const Fastify = require('..')
6
6
  const http = require('http')
7
7
  const sget = require('simple-get').concat
8
+ const dns = require('dns').promises
8
9
 
9
- test('Should support a custom http server', t => {
10
- t.plan(6)
10
+ test('Should support a custom http server', async t => {
11
+ const localAddresses = await dns.lookup('localhost', { all: true })
12
+
13
+ t.plan(localAddresses.length + 3)
11
14
 
12
15
  const serverFactory = (handler, opts) => {
13
- t.ok(opts.serverFactory)
16
+ t.ok(opts.serverFactory, 'it is called twice for every HOST interface')
14
17
 
15
18
  const server = http.createServer((req, res) => {
16
19
  req.custom = true
@@ -29,16 +32,20 @@ test('Should support a custom http server', t => {
29
32
  reply.send({ hello: 'world' })
30
33
  })
31
34
 
32
- fastify.listen(0, err => {
33
- t.error(err)
35
+ await fastify.listen(0)
34
36
 
37
+ await new Promise((resolve, reject) => {
35
38
  sget({
36
39
  method: 'GET',
37
- url: 'http://localhost:' + fastify.server.address().port
40
+ url: 'http://localhost:' + fastify.server.address().port,
41
+ rejectUnauthorized: false
38
42
  }, (err, response, body) => {
39
- t.error(err)
43
+ if (err) {
44
+ return reject(err)
45
+ }
40
46
  t.equal(response.statusCode, 200)
41
47
  t.same(JSON.parse(body), { hello: 'world' })
48
+ resolve()
42
49
  })
43
50
  })
44
51
  })
@@ -64,68 +64,3 @@ test('contentTypeParser should add a custom async parser', t => {
64
64
  })
65
65
  })
66
66
  })
67
-
68
- test('contentTypeParser should add a custom async parser - deprecated syntax', t => {
69
- t.plan(5)
70
- const fastify = Fastify()
71
-
72
- process.on('warning', onWarning)
73
- function onWarning (warning) {
74
- t.equal(warning.name, 'FastifyDeprecation')
75
- t.equal(warning.code, 'FSTDEP003')
76
- }
77
-
78
- fastify.post('/', (req, reply) => {
79
- reply.send(req.body)
80
- })
81
-
82
- fastify.options('/', (req, reply) => {
83
- reply.send(req.body)
84
- })
85
-
86
- fastify.addContentTypeParser('application/jsoff', async function (req) {
87
- const res = await new Promise((resolve, reject) => resolve(req))
88
- return res
89
- })
90
-
91
- fastify.listen(0, err => {
92
- t.error(err)
93
-
94
- t.teardown(() => fastify.close())
95
-
96
- t.test('in POST', t => {
97
- t.plan(3)
98
-
99
- sget({
100
- method: 'POST',
101
- url: 'http://localhost:' + fastify.server.address().port,
102
- body: '{"hello":"world"}',
103
- headers: {
104
- 'Content-Type': 'application/jsoff'
105
- }
106
- }, (err, response, body) => {
107
- t.error(err)
108
- t.equal(response.statusCode, 200)
109
- t.same(body.toString(), JSON.stringify({ hello: 'world' }))
110
- })
111
- })
112
-
113
- t.test('in OPTIONS', t => {
114
- t.plan(3)
115
-
116
- sget({
117
- method: 'OPTIONS',
118
- url: 'http://localhost:' + fastify.server.address().port,
119
- body: '{"hello":"world"}',
120
- headers: {
121
- 'Content-Type': 'application/jsoff'
122
- }
123
- }, (err, response, body) => {
124
- t.error(err)
125
- t.equal(response.statusCode, 200)
126
- t.same(body.toString(), JSON.stringify({ hello: 'world' }))
127
- process.removeListener('warning', onWarning)
128
- })
129
- })
130
- })
131
- })