fastify 4.10.1 → 4.11.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.
package/README.md CHANGED
@@ -289,8 +289,6 @@ listed in alphabetical order.
289
289
  ### Fastify Core team
290
290
  * [__Tommaso Allevi__](https://github.com/allevo),
291
291
  <https://twitter.com/allevitommaso>, <https://www.npmjs.com/~allevo>
292
- * [__Ethan Arrowood__](https://github.com/Ethan-Arrowood/),
293
- <https://twitter.com/arrowoodtech>, <https://www.npmjs.com/~ethan_arrowood>
294
292
  * [__Harry Brundage__](https://github.com/airhorns/),
295
293
  <https://twitter.com/harrybrundage>, <https://www.npmjs.com/~airhorns>
296
294
  * [__David Mark Clements__](https://github.com/davidmarkclements),
@@ -362,6 +360,8 @@ to join this group by Lead Maintainers.
362
360
  <https://twitter.com/cemremengu>, <https://www.npmjs.com/~cemremengu>
363
361
  * [__Nathan Woltman__](https://github.com/nwoltman),
364
362
  <https://twitter.com/NathanWoltman>, <https://www.npmjs.com/~nwoltman>
363
+ * [__Ethan Arrowood__](https://github.com/Ethan-Arrowood/),
364
+ <https://twitter.com/arrowoodtech>, <https://www.npmjs.com/~ethan_arrowood>
365
365
 
366
366
  ## Hosted by
367
367
 
@@ -220,6 +220,10 @@ section.
220
220
  plugin to authenticate HTTP requests based on api key and signature
221
221
  - [`fastify-appwrite`](https://github.com/Dev-Manny/fastify-appwrite) Fastify
222
222
  Plugin for interacting with Appwrite server.
223
+ - [`fastify-at-mysql`](https://github.com/mateonunez/fastify-at-mysql) Fastify
224
+ MySQL plugin with auto SQL injection attack prevention.
225
+ - [`fastify-at-postgres`](https://github.com/mateonunez/fastify-at-postgres) Fastify
226
+ Postgres plugin with auto SQL injection attack prevention.
223
227
  - [`fastify-auth0-verify`](https://github.com/nearform/fastify-auth0-verify):
224
228
  Auth0 verification plugin for Fastify, internally uses
225
229
  [fastify-jwt](https://npm.im/fastify-jwt) and
@@ -387,6 +391,9 @@ section.
387
391
  - [`fastify-lured`](https://github.com/lependu/fastify-lured) Plugin to load lua
388
392
  scripts with [fastify-redis](https://github.com/fastify/fastify-redis) and
389
393
  [lured](https://github.com/enobufs/lured).
394
+ - [`fastify-lyra`](https://github.com/mateonunez/fastify-lyra)
395
+ A plugin to implement [Lyra](https://github.com/LyraSearch/lyra) search engine
396
+ on Fastify.
390
397
  - [`fastify-mailer`](https://github.com/coopflow/fastify-mailer) Plugin to
391
398
  initialize and encapsulate [Nodemailer](https://nodemailer.com)'s transporters
392
399
  instances in Fastify.
@@ -565,6 +572,8 @@ section.
565
572
  for [zod](https://github.com/colinhacks/zod).
566
573
  - [`fastify-typeorm-plugin`](https://github.com/inthepocket/fastify-typeorm-plugin)
567
574
  Fastify plugin to work with TypeORM.
575
+ - [`fastify-user-agent`](https://github.com/Eomm/fastify-user-agent) parses your
576
+ request's `user-agent` header.
568
577
  - [`fastify-vhost`](https://github.com/patrickpissurno/fastify-vhost) Proxy
569
578
  subdomain HTTP requests to another server (useful if you want to point
570
579
  multiple subdomains to the same IP address, while running different servers on
@@ -112,6 +112,9 @@ fastify.setErrorHandler(function (error, request, reply) {
112
112
  this.log.error(error)
113
113
  // Send error response
114
114
  reply.status(500).send({ ok: false })
115
+ } else {
116
+ // fastify will use parent error handler to handle this
117
+ reply.send(error)
115
118
  }
116
119
  })
117
120
 
@@ -382,4 +385,4 @@ Impossible to load plugin because the parent (mapped directly from `avvio`)
382
385
  <a name="FST_ERR_PLUGIN_TIMEOUT"></a>
383
386
  #### FST_ERR_PLUGIN_TIMEOUT
384
387
 
385
- Plugin did not start in time. Default timeout (in millis): `10000`
388
+ Plugin did not start in time. Default timeout (in millis): `10000`
@@ -674,6 +674,15 @@ fastify.get('/streams', function (request, reply) {
674
674
  reply.send(stream)
675
675
  })
676
676
  ```
677
+ When using async-await you will need to return or await the reply object:
678
+ ```js
679
+ fastify.get('/streams', async function (request, reply) {
680
+ const fs = require('fs')
681
+ const stream = fs.createReadStream('some-file', 'utf8')
682
+ reply.header('Content-Type', 'application/octet-stream')
683
+ return reply.send(stream)
684
+ })
685
+ ```
677
686
 
678
687
  #### Buffers
679
688
  <a id="send-buffers"></a>
@@ -689,6 +698,16 @@ fastify.get('/streams', function (request, reply) {
689
698
  })
690
699
  ```
691
700
 
701
+ When using async-await you will need to return or await the reply object:
702
+ ```js
703
+ const fs = require('fs')
704
+ fastify.get('/streams', async function (request, reply) {
705
+ fs.readFile('some-file', (err, fileBuffer) => {
706
+ reply.send(err || fileBuffer)
707
+ })
708
+ return reply
709
+ })
710
+ ```
692
711
  #### Errors
693
712
  <a id="errors"></a>
694
713
 
@@ -1011,6 +1011,9 @@ Note that the array contains the `fastify.server.address()` too.
1011
1011
  #### getDefaultRoute
1012
1012
  <a id="getDefaultRoute"></a>
1013
1013
 
1014
+ **Notice**: this method is deprecated and should be removed in the next Fastify
1015
+ major version.
1016
+
1014
1017
  The `defaultRoute` handler handles requests that do not match any URL specified
1015
1018
  by your Fastify application. This defaults to the 404 handler, but can be
1016
1019
  overridden with [setDefaultRoute](#setdefaultroute). Method to get the
@@ -1023,14 +1026,25 @@ const defaultRoute = fastify.getDefaultRoute()
1023
1026
  #### setDefaultRoute
1024
1027
  <a id="setDefaultRoute"></a>
1025
1028
 
1026
- **Note**: The default 404 handler, or one set using `setNotFoundHandler`, will
1027
- never trigger if the default route is overridden. This sets the handler for the
1029
+ **Notice**: this method is deprecated and should be removed in the next Fastify
1030
+ major version. Please, consider to use `setNotFoundHandler` or a wildcard
1031
+ matching route.
1032
+
1033
+ The default 404 handler, or one set using `setNotFoundHandler`, will
1034
+ never trigger if the default route is overridden. This sets the handler for the
1028
1035
  Fastify application, not just the current instance context. Use
1029
1036
  [setNotFoundHandler](#setnotfoundhandler) if you want to customize 404 handling
1030
- instead. Method to set the `defaultRoute` for the server:
1037
+ instead.
1038
+
1039
+ This method sets the `defaultRoute` for the server. Note that, its purpose is
1040
+ to interact with the underlying raw requests. Unlike other Fastify handlers, the
1041
+ arguments received are of type [RawRequest](./TypeScript.md#rawrequest) and
1042
+ [RawReply](./TypeScript.md#rawreply) respectively.
1031
1043
 
1032
1044
  ```js
1033
1045
  const defaultRoute = function (req, res) {
1046
+ // req = RawRequest
1047
+ // res = RawReply
1034
1048
  res.end('hello world')
1035
1049
  }
1036
1050
 
@@ -1670,7 +1684,7 @@ information.
1670
1684
  `fastify.defaultTextParser()` can be used to parse content as plain text.
1671
1685
 
1672
1686
  ```js
1673
- fastify.addContentTypeParser('text/json', { asString: true }, fastify.defaultTextParser())
1687
+ fastify.addContentTypeParser('text/json', { asString: true }, fastify.defaultTextParser)
1674
1688
  ```
1675
1689
 
1676
1690
  #### errorHandler
package/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '4.10.1'
3
+ const VERSION = '4.11.0'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('http')
@@ -2,6 +2,8 @@
2
2
 
3
3
  const { AsyncResource } = require('async_hooks')
4
4
  const lru = require('tiny-lru').lru
5
+ // TODO: find more perforamant solution
6
+ const { parse: parseContentType } = require('content-type')
5
7
 
6
8
  const secureJson = require('secure-json-parse')
7
9
  const {
@@ -33,7 +35,7 @@ function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning)
33
35
  this.customParsers = new Map()
34
36
  this.customParsers.set('application/json', new Parser(true, false, bodyLimit, this[kDefaultJsonParse]))
35
37
  this.customParsers.set('text/plain', new Parser(true, false, bodyLimit, defaultPlainTextParser))
36
- this.parserList = ['application/json', 'text/plain']
38
+ this.parserList = [new ParserListItem('application/json'), new ParserListItem('text/plain')]
37
39
  this.parserRegExpList = []
38
40
  this.cache = lru(100)
39
41
  }
@@ -66,8 +68,9 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) {
66
68
  this.customParsers.set('', parser)
67
69
  } else {
68
70
  if (contentTypeIsString) {
69
- this.parserList.unshift(contentType)
71
+ this.parserList.unshift(new ParserListItem(contentType))
70
72
  } else {
73
+ contentType.isEssence = contentType.source.indexOf(';') === -1
71
74
  this.parserRegExpList.unshift(contentType)
72
75
  }
73
76
  this.customParsers.set(contentType.toString(), parser)
@@ -97,11 +100,20 @@ ContentTypeParser.prototype.getParser = function (contentType) {
97
100
  const parser = this.cache.get(contentType)
98
101
  if (parser !== undefined) return parser
99
102
 
103
+ const parsed = safeParseContentType(contentType)
104
+
105
+ // dummyContentType always the same object
106
+ // we can use === for the comparsion and return early
107
+ if (parsed === dummyContentType) {
108
+ return this.customParsers.get('')
109
+ }
110
+
100
111
  // eslint-disable-next-line no-var
101
112
  for (var i = 0; i !== this.parserList.length; ++i) {
102
- const parserName = this.parserList[i]
103
- if (contentType.indexOf(parserName) !== -1) {
104
- const parser = this.customParsers.get(parserName)
113
+ const parserListItem = this.parserList[i]
114
+ if (compareContentType(parsed, parserListItem)) {
115
+ const parser = this.customParsers.get(parserListItem.name)
116
+ // we set request content-type in cache to reduce parsing of MIME type
105
117
  this.cache.set(contentType, parser)
106
118
  return parser
107
119
  }
@@ -110,8 +122,9 @@ ContentTypeParser.prototype.getParser = function (contentType) {
110
122
  // eslint-disable-next-line no-var
111
123
  for (var j = 0; j !== this.parserRegExpList.length; ++j) {
112
124
  const parserRegExp = this.parserRegExpList[j]
113
- if (parserRegExp.test(contentType)) {
125
+ if (compareRegExpContentType(contentType, parsed.type, parserRegExp)) {
114
126
  const parser = this.customParsers.get(parserRegExp.toString())
127
+ // we set request content-type in cache to reduce parsing of MIME type
115
128
  this.cache.set(contentType, parser)
116
129
  return parser
117
130
  }
@@ -346,6 +359,63 @@ function removeAllContentTypeParsers () {
346
359
  this[kContentTypeParser].removeAll()
347
360
  }
348
361
 
362
+ // dummy here to prevent repeated object creation
363
+ const dummyContentType = { type: '', parameters: Object.create(null) }
364
+
365
+ function safeParseContentType (contentType) {
366
+ try {
367
+ return parseContentType(contentType)
368
+ } catch (err) {
369
+ return dummyContentType
370
+ }
371
+ }
372
+
373
+ function compareContentType (contentType, parserListItem) {
374
+ if (parserListItem.isEssence) {
375
+ // we do essence check
376
+ return contentType.type.indexOf(parserListItem) !== -1
377
+ } else {
378
+ // when the content-type includes parameters
379
+ // we do a full-text search
380
+ // reject essence content-type before checking parameters
381
+ if (contentType.type.indexOf(parserListItem.type) === -1) return false
382
+ for (const key of parserListItem.parameterKeys) {
383
+ // reject when missing parameters
384
+ if (!(key in contentType.parameters)) return false
385
+ // reject when parameters do not match
386
+ if (contentType.parameters[key] !== parserListItem.parameters[key]) return false
387
+ }
388
+ return true
389
+ }
390
+ }
391
+
392
+ function compareRegExpContentType (contentType, essenceMIMEType, regexp) {
393
+ if (regexp.isEssence) {
394
+ // we do essence check
395
+ return regexp.test(essenceMIMEType)
396
+ } else {
397
+ // when the content-type includes parameters
398
+ // we do a full-text match
399
+ return regexp.test(contentType)
400
+ }
401
+ }
402
+
403
+ function ParserListItem (contentType) {
404
+ this.name = contentType
405
+ // we pre-calculate all the needed information
406
+ // before content-type comparsion
407
+ const parsed = safeParseContentType(contentType)
408
+ this.type = parsed.type
409
+ this.parameters = parsed.parameters
410
+ this.parameterKeys = Object.keys(parsed.parameters)
411
+ this.isEssence = contentType.indexOf(';') === -1
412
+ }
413
+
414
+ // used in ContentTypeParser.remove
415
+ ParserListItem.prototype.toString = function () {
416
+ return this.name
417
+ }
418
+
349
419
  module.exports = ContentTypeParser
350
420
  module.exports.helpers = {
351
421
  buildContentTypeParser,
package/lib/route.js CHANGED
@@ -89,9 +89,11 @@ function buildRouting (options) {
89
89
  hasRoute,
90
90
  prepareRoute,
91
91
  getDefaultRoute: function () {
92
+ warning.emit('FSTDEP014')
92
93
  return router.defaultRoute
93
94
  },
94
95
  setDefaultRoute: function (defaultRoute) {
96
+ warning.emit('FSTDEP014')
95
97
  if (typeof defaultRoute !== 'function') {
96
98
  throw new FST_ERR_DEFAULT_ROUTE_INVALID_TYPE()
97
99
  }
package/lib/warnings.js CHANGED
@@ -25,4 +25,6 @@ warning.create('FastifyDeprecation', 'FSTDEP012', 'Request#context property acce
25
25
 
26
26
  warning.create('FastifyDeprecation', 'FSTDEP013', 'Direct return of "trailers" function is deprecated. Please use "callback" or "async-await" for return value. The support of direct return will removed in `fastify@5`.')
27
27
 
28
+ warning.create('FastifyDeprecation', 'FSTDEP014', 'You are trying to set/access the default route. This property is deprecated. Please, use setNotFoundHandler if you want to custom a 404 handler or the wildcard (*) to match all routes.')
29
+
28
30
  module.exports = warning
@@ -35,7 +35,13 @@ function wrapThenable (thenable, reply) {
35
35
  }
36
36
 
37
37
  reply[kReplyIsError] = true
38
- reply.send(err)
38
+
39
+ // try-catch allow to re-throw error in error handler for async handler
40
+ try {
41
+ reply.send(err)
42
+ } catch (err) {
43
+ reply.send(err)
44
+ }
39
45
  })
40
46
  }
41
47
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "4.10.1",
3
+ "version": "4.11.0",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -164,7 +164,7 @@
164
164
  "split2": "^4.1.0",
165
165
  "standard": "^17.0.0",
166
166
  "tap": "^16.3.0",
167
- "tsd": "^0.24.1",
167
+ "tsd": "^0.25.0",
168
168
  "typescript": "^4.8.3",
169
169
  "undici": "^5.10.0",
170
170
  "vary": "^1.1.2",
@@ -176,6 +176,7 @@
176
176
  "@fastify/fast-json-stringify-compiler": "^4.1.0",
177
177
  "abstract-logging": "^2.0.1",
178
178
  "avvio": "^8.2.0",
179
+ "content-type": "^1.0.4",
179
180
  "find-my-way": "^7.3.0",
180
181
  "light-my-request": "^5.6.1",
181
182
  "pino": "^8.5.0",
@@ -36,7 +36,7 @@ test('Should return 503 while closing - pipelining', async t => {
36
36
  await instance.close()
37
37
  })
38
38
 
39
- const isV19plus = semver.satisfies(process.version, '>= v19.0.0')
39
+ const isV19plus = semver.gte(process.version, '19.0.0')
40
40
  test('Should not return 503 while closing - pipelining - return503OnClosing: false, skip Node >= v19.x', { skip: isV19plus }, async t => {
41
41
  const fastify = Fastify({
42
42
  return503OnClosing: false,
@@ -203,7 +203,7 @@ test('Should return error while closing (callback) - injection', t => {
203
203
  })
204
204
  })
205
205
 
206
- const isV19plus = semver.satisfies(process.version, '>= v19.0.0')
206
+ const isV19plus = semver.gte(process.version, '19.0.0')
207
207
  t.test('Current opened connection should continue to work after closing and return "connection: close" header - return503OnClosing: false, skip Node >= v19.x', { skip: isV19plus }, t => {
208
208
  const fastify = Fastify({
209
209
  return503OnClosing: false,
@@ -395,3 +395,217 @@ test('Safeguard against malicious content-type / 3', async t => {
395
395
 
396
396
  t.same(response.statusCode, 415)
397
397
  })
398
+
399
+ test('Safeguard against content-type spoofing - string', async t => {
400
+ t.plan(1)
401
+
402
+ const fastify = Fastify()
403
+ fastify.removeAllContentTypeParsers()
404
+ fastify.addContentTypeParser('text/plain', function (request, body, done) {
405
+ t.pass('should be called')
406
+ done(null, body)
407
+ })
408
+ fastify.addContentTypeParser('application/json', function (request, body, done) {
409
+ t.fail('shouldn\'t be called')
410
+ done(null, body)
411
+ })
412
+
413
+ fastify.post('/', async () => {
414
+ return 'ok'
415
+ })
416
+
417
+ await fastify.inject({
418
+ method: 'POST',
419
+ path: '/',
420
+ headers: {
421
+ 'content-type': 'text/plain; content-type="application/json"'
422
+ },
423
+ body: ''
424
+ })
425
+ })
426
+
427
+ test('Safeguard against content-type spoofing - regexp', async t => {
428
+ t.plan(1)
429
+
430
+ const fastify = Fastify()
431
+ fastify.removeAllContentTypeParsers()
432
+ fastify.addContentTypeParser(/text\/plain/, function (request, body, done) {
433
+ t.pass('should be called')
434
+ done(null, body)
435
+ })
436
+ fastify.addContentTypeParser(/application\/json/, function (request, body, done) {
437
+ t.fail('shouldn\'t be called')
438
+ done(null, body)
439
+ })
440
+
441
+ fastify.post('/', async () => {
442
+ return 'ok'
443
+ })
444
+
445
+ await fastify.inject({
446
+ method: 'POST',
447
+ path: '/',
448
+ headers: {
449
+ 'content-type': 'text/plain; content-type="application/json"'
450
+ },
451
+ body: ''
452
+ })
453
+ })
454
+
455
+ test('content-type match parameters - string 1', async t => {
456
+ t.plan(1)
457
+
458
+ const fastify = Fastify()
459
+ fastify.removeAllContentTypeParsers()
460
+ fastify.addContentTypeParser('text/plain; charset=utf8', function (request, body, done) {
461
+ t.fail('shouldn\'t be called')
462
+ done(null, body)
463
+ })
464
+ fastify.addContentTypeParser('application/json; charset=utf8', function (request, body, done) {
465
+ t.pass('should be called')
466
+ done(null, body)
467
+ })
468
+
469
+ fastify.post('/', async () => {
470
+ return 'ok'
471
+ })
472
+
473
+ await fastify.inject({
474
+ method: 'POST',
475
+ path: '/',
476
+ headers: {
477
+ 'content-type': 'application/json; charset=utf8'
478
+ },
479
+ body: ''
480
+ })
481
+ })
482
+
483
+ test('content-type match parameters - string 2', async t => {
484
+ t.plan(1)
485
+
486
+ const fastify = Fastify()
487
+ fastify.removeAllContentTypeParsers()
488
+ fastify.addContentTypeParser('application/json; charset=utf8; foo=bar', function (request, body, done) {
489
+ t.pass('should be called')
490
+ done(null, body)
491
+ })
492
+ fastify.addContentTypeParser('text/plain; charset=utf8; foo=bar', function (request, body, done) {
493
+ t.fail('shouldn\'t be called')
494
+ done(null, body)
495
+ })
496
+
497
+ fastify.post('/', async () => {
498
+ return 'ok'
499
+ })
500
+
501
+ await fastify.inject({
502
+ method: 'POST',
503
+ path: '/',
504
+ headers: {
505
+ 'content-type': 'application/json; foo=bar; charset=utf8'
506
+ },
507
+ body: ''
508
+ })
509
+ })
510
+
511
+ test('content-type match parameters - regexp', async t => {
512
+ t.plan(1)
513
+
514
+ const fastify = Fastify()
515
+ fastify.removeAllContentTypeParsers()
516
+ fastify.addContentTypeParser(/application\/json; charset=utf8/, function (request, body, done) {
517
+ t.pass('should be called')
518
+ done(null, body)
519
+ })
520
+
521
+ fastify.post('/', async () => {
522
+ return 'ok'
523
+ })
524
+
525
+ await fastify.inject({
526
+ method: 'POST',
527
+ path: '/',
528
+ headers: {
529
+ 'content-type': 'application/json; charset=utf8'
530
+ },
531
+ body: ''
532
+ })
533
+ })
534
+
535
+ test('content-type fail when parameters not match - string 1', async t => {
536
+ t.plan(1)
537
+
538
+ const fastify = Fastify()
539
+ fastify.removeAllContentTypeParsers()
540
+ fastify.addContentTypeParser('application/json; charset=utf8; foo=bar', function (request, body, done) {
541
+ t.fail('shouldn\'t be called')
542
+ done(null, body)
543
+ })
544
+
545
+ fastify.post('/', async () => {
546
+ return 'ok'
547
+ })
548
+
549
+ const response = await fastify.inject({
550
+ method: 'POST',
551
+ path: '/',
552
+ headers: {
553
+ 'content-type': 'application/json; charset=utf8'
554
+ },
555
+ body: ''
556
+ })
557
+
558
+ t.same(response.statusCode, 415)
559
+ })
560
+
561
+ test('content-type fail when parameters not match - string 2', async t => {
562
+ t.plan(1)
563
+
564
+ const fastify = Fastify()
565
+ fastify.removeAllContentTypeParsers()
566
+ fastify.addContentTypeParser('application/json; charset=utf8; foo=bar', function (request, body, done) {
567
+ t.fail('shouldn\'t be called')
568
+ done(null, body)
569
+ })
570
+
571
+ fastify.post('/', async () => {
572
+ return 'ok'
573
+ })
574
+
575
+ const response = await fastify.inject({
576
+ method: 'POST',
577
+ path: '/',
578
+ headers: {
579
+ 'content-type': 'application/json; charset=utf8; foo=baz'
580
+ },
581
+ body: ''
582
+ })
583
+
584
+ t.same(response.statusCode, 415)
585
+ })
586
+
587
+ test('content-type fail when parameters not match - regexp', async t => {
588
+ t.plan(1)
589
+
590
+ const fastify = Fastify()
591
+ fastify.removeAllContentTypeParsers()
592
+ fastify.addContentTypeParser(/application\/json; charset=utf8; foo=bar/, function (request, body, done) {
593
+ t.fail('shouldn\'t be called')
594
+ done(null, body)
595
+ })
596
+
597
+ fastify.post('/', async () => {
598
+ return 'ok'
599
+ })
600
+
601
+ const response = await fastify.inject({
602
+ method: 'POST',
603
+ path: '/',
604
+ headers: {
605
+ 'content-type': 'application/json; charset=utf8'
606
+ },
607
+ body: ''
608
+ })
609
+
610
+ t.same(response.statusCode, 415)
611
+ })
@@ -1053,7 +1053,7 @@ test('The charset should not interfere with the content type handling', t => {
1053
1053
  url: getUrl(fastify),
1054
1054
  body: '{"hello":"world"}',
1055
1055
  headers: {
1056
- 'Content-Type': 'application/json charset=utf-8'
1056
+ 'Content-Type': 'application/json; charset=utf-8'
1057
1057
  }
1058
1058
  }, (err, response, body) => {
1059
1059
  t.error(err)
@@ -1236,7 +1236,7 @@ test('contentTypeParser should add a custom parser with RegExp value', t => {
1236
1236
  url: getUrl(fastify),
1237
1237
  body: '{"hello":"world"}',
1238
1238
  headers: {
1239
- 'Content-Type': 'weird-content-type+json'
1239
+ 'Content-Type': 'weird/content-type+json'
1240
1240
  }
1241
1241
  }, (err, response, body) => {
1242
1242
  t.error(err)
@@ -1266,7 +1266,7 @@ test('contentTypeParser should add multiple custom parsers with RegExp values',
1266
1266
  done(null, 'xml')
1267
1267
  })
1268
1268
 
1269
- fastify.addContentTypeParser(/.*\+myExtension$/, function (req, payload, done) {
1269
+ fastify.addContentTypeParser(/.*\+myExtension$/i, function (req, payload, done) {
1270
1270
  let data = ''
1271
1271
  payload.on('data', chunk => { data += chunk })
1272
1272
  payload.on('end', () => {
@@ -3,6 +3,51 @@
3
3
  const t = require('tap')
4
4
  const test = t.test
5
5
  const Fastify = require('..')
6
+ const warning = require('../lib/warnings')
7
+
8
+ // Silence the standard warning logs. We will test the messages explicitly.
9
+ process.removeAllListeners('warning')
10
+
11
+ test('setDefaultRoute should emit a deprecation warning', t => {
12
+ t.plan(2)
13
+
14
+ const fastify = Fastify()
15
+ const defaultRoute = (req, res) => {
16
+ res.end('hello from defaultRoute')
17
+ }
18
+
19
+ process.on('warning', onWarning)
20
+ function onWarning (warning) {
21
+ t.equal(warning.name, 'FastifyDeprecation')
22
+ t.equal(warning.code, 'FSTDEP014')
23
+ }
24
+
25
+ t.teardown(() => {
26
+ process.removeListener('warning', onWarning)
27
+ warning.emitted.set('FSTDEP014', false)
28
+ })
29
+
30
+ fastify.setDefaultRoute(defaultRoute)
31
+ })
32
+
33
+ test('getDefaultRoute should emit a deprecation warning', t => {
34
+ t.plan(2)
35
+
36
+ const fastify = Fastify()
37
+
38
+ process.on('warning', onWarning)
39
+ function onWarning (warning) {
40
+ t.equal(warning.name, 'FastifyDeprecation')
41
+ t.equal(warning.code, 'FSTDEP014')
42
+ }
43
+
44
+ t.teardown(() => {
45
+ process.removeListener('warning', onWarning)
46
+ warning.emitted.set('FSTDEP014', false)
47
+ })
48
+
49
+ fastify.getDefaultRoute()
50
+ })
6
51
 
7
52
  test('should fail if defaultRoute is not a function', t => {
8
53
  t.plan(1)
@@ -514,6 +514,34 @@ test('error thrown by custom error handler routes to default error handler', t =
514
514
  })
515
515
  })
516
516
 
517
+ // Refs: https://github.com/fastify/fastify/pull/4484#issuecomment-1367301750
518
+ test('allow re-thrown error to default error handler when route handler is async and error handler is sync', t => {
519
+ t.plan(4)
520
+ const fastify = Fastify()
521
+
522
+ fastify.setErrorHandler(function (error) {
523
+ t.equal(error.message, 'kaboom')
524
+ throw Error('kabong')
525
+ })
526
+
527
+ fastify.get('/', async function () {
528
+ throw Error('kaboom')
529
+ })
530
+
531
+ fastify.inject({
532
+ url: '/',
533
+ method: 'GET'
534
+ }, (err, res) => {
535
+ t.error(err)
536
+ t.equal(res.statusCode, 500)
537
+ t.same(JSON.parse(res.payload), {
538
+ error: statusCodes['500'],
539
+ message: 'kabong',
540
+ statusCode: 500
541
+ })
542
+ })
543
+ })
544
+
517
545
  // Issue 2078 https://github.com/fastify/fastify/issues/2078
518
546
  // Supported error code list: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
519
547
  const invalidErrorCodes = [
@@ -123,6 +123,21 @@ const serverAutoInferredFileOption = fastify({
123
123
 
124
124
  expectType<FastifyBaseLogger>(serverAutoInferredFileOption.log)
125
125
 
126
+ const serverAutoInferredSerializerResponseObjectOption = fastify({
127
+ logger: {
128
+ serializers: {
129
+ res (ServerResponse) {
130
+ expectType<FastifyReply>(ServerResponse)
131
+ return {
132
+ status: '200'
133
+ }
134
+ }
135
+ }
136
+ }
137
+ })
138
+
139
+ expectType<FastifyBaseLogger>(serverAutoInferredSerializerResponseObjectOption.log)
140
+
126
141
  const serverAutoInferredSerializerObjectOption = fastify({
127
142
  logger: {
128
143
  serializers: {
@@ -101,3 +101,46 @@ expectAssignable<ServerWithTypeProvider>(serverWithTypeProvider.register(async (
101
101
  expectAssignable<ServerWithTypeProvider>(serverWithTypeProvider.register(async (instance: ServerWithTypeProvider) => {
102
102
  expectAssignable<ServerWithTypeProvider>(instance)
103
103
  }))
104
+
105
+ // With Type Provider and logger
106
+ const customLogger = {
107
+ level: 'info',
108
+ info: () => { },
109
+ warn: () => { },
110
+ error: () => { },
111
+ fatal: () => { },
112
+ trace: () => { },
113
+ debug: () => { },
114
+ child: () => customLogger,
115
+ silent: () => { }
116
+ }
117
+ const serverWithTypeProviderAndLogger = fastify({
118
+ logger: customLogger
119
+ }).withTypeProvider<TestTypeProvider>()
120
+ type ServerWithTypeProviderAndLogger = FastifyInstance<Server, IncomingMessage, ServerResponse, typeof customLogger, TestTypeProvider>
121
+ const testPluginWithTypeProviderAndLogger: FastifyPluginCallback<TestOptions, RawServerDefault, TestTypeProvider, typeof customLogger> = function (instance, opts, done) { }
122
+ const testPluginWithTypeProviderAndLoggerAsync: FastifyPluginAsync<TestOptions, RawServerDefault, TestTypeProvider, typeof customLogger> = async function (instance, opts) { }
123
+ const testPluginWithTypeProviderAndLoggerWithType = (instance: ServerWithTypeProviderAndLogger, opts: FastifyPluginOptions, done: (error?: FastifyError) => void) => { }
124
+ const testPluginWithTypeProviderAndLoggerWithTypeAsync = async (instance: ServerWithTypeProviderAndLogger, opts: FastifyPluginOptions) => { }
125
+ expectAssignable<ServerWithTypeProviderAndLogger>(serverWithTypeProviderAndLogger.register(testPluginCallback))
126
+ expectAssignable<ServerWithTypeProviderAndLogger>(serverWithTypeProviderAndLogger.register(testPluginAsync))
127
+ expectAssignable<ServerWithTypeProviderAndLogger>(serverWithTypeProviderAndLogger.register(testPluginOpts))
128
+ expectAssignable<ServerWithTypeProviderAndLogger>(serverWithTypeProviderAndLogger.register(testPluginOptsAsync))
129
+ expectAssignable<ServerWithTypeProviderAndLogger>(serverWithTypeProviderAndLogger.register(testPluginOptsWithType))
130
+ expectAssignable<ServerWithTypeProviderAndLogger>(serverWithTypeProviderAndLogger.register(testPluginOptsWithTypeAsync))
131
+ expectAssignable<ServerWithTypeProviderAndLogger>(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLogger))
132
+ expectAssignable<ServerWithTypeProviderAndLogger>(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerAsync))
133
+ expectAssignable<ServerWithTypeProviderAndLogger>(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerWithType))
134
+ expectAssignable<ServerWithTypeProviderAndLogger>(serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerWithTypeAsync))
135
+ expectAssignable<ServerWithTypeProviderAndLogger>(serverWithTypeProviderAndLogger.register((instance) => {
136
+ expectAssignable<FastifyInstance>(instance)
137
+ }))
138
+ expectAssignable<ServerWithTypeProviderAndLogger>(serverWithTypeProviderAndLogger.register((instance: ServerWithTypeProviderAndLogger) => {
139
+ expectAssignable<ServerWithTypeProviderAndLogger>(instance)
140
+ }))
141
+ expectAssignable<ServerWithTypeProviderAndLogger>(serverWithTypeProviderAndLogger.register(async (instance) => {
142
+ expectAssignable<FastifyInstance>(instance)
143
+ }))
144
+ expectAssignable<ServerWithTypeProviderAndLogger>(serverWithTypeProviderAndLogger.register(async (instance: ServerWithTypeProviderAndLogger) => {
145
+ expectAssignable<ServerWithTypeProviderAndLogger>(instance)
146
+ }))
package/types/logger.d.ts CHANGED
@@ -60,7 +60,7 @@ export interface FastifyLoggerOptions<
60
60
  [key: string]: unknown;
61
61
  };
62
62
  res?: (res: RawReply) => {
63
- statusCode: string | number;
63
+ statusCode?: string | number;
64
64
  [key: string]: unknown;
65
65
  };
66
66
  };
package/types/plugin.d.ts CHANGED
@@ -10,8 +10,13 @@ export type FastifyPluginOptions = Record<string, any>
10
10
  *
11
11
  * Fastify allows the user to extend its functionalities with plugins. A plugin can be a set of routes, a server decorator or whatever. To activate plugins, use the `fastify.register()` method.
12
12
  */
13
- export type FastifyPluginCallback<Options extends FastifyPluginOptions = Record<never, never>, Server extends RawServerBase = RawServerDefault, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault> = (
14
- instance: FastifyInstance<Server, RawRequestDefaultExpression<Server>, RawReplyDefaultExpression<Server>, FastifyBaseLogger, TypeProvider>,
13
+ export type FastifyPluginCallback<
14
+ Options extends FastifyPluginOptions = Record<never, never>,
15
+ Server extends RawServerBase = RawServerDefault,
16
+ TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault,
17
+ Logger extends FastifyBaseLogger = FastifyBaseLogger,
18
+ > = (
19
+ instance: FastifyInstance<Server, RawRequestDefaultExpression<Server>, RawReplyDefaultExpression<Server>, Logger, TypeProvider>,
15
20
  opts: Options,
16
21
  done: (err?: Error) => void
17
22
  ) => void
@@ -25,8 +30,9 @@ export type FastifyPluginAsync<
25
30
  Options extends FastifyPluginOptions = Record<never, never>,
26
31
  Server extends RawServerBase = RawServerDefault,
27
32
  TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault,
33
+ Logger extends FastifyBaseLogger = FastifyBaseLogger,
28
34
  > = (
29
- instance: FastifyInstance<Server, RawRequestDefaultExpression<Server>, RawReplyDefaultExpression<Server>, FastifyBaseLogger, TypeProvider>,
35
+ instance: FastifyInstance<Server, RawRequestDefaultExpression<Server>, RawReplyDefaultExpression<Server>, Logger, TypeProvider>,
30
36
  opts: Options
31
37
  ) => Promise<void>;
32
38
 
@@ -2,7 +2,7 @@ import { FastifyPluginOptions, FastifyPluginCallback, FastifyPluginAsync } from
2
2
  import { LogLevel } from './logger'
3
3
  import { FastifyInstance } from './instance'
4
4
  import { RawServerBase } from './utils'
5
- import { FastifyTypeProvider, RawServerDefault } from '../fastify'
5
+ import { FastifyBaseLogger, FastifyTypeProvider, RawServerDefault } from '../fastify'
6
6
 
7
7
  export interface RegisterOptions {
8
8
  prefix?: string;
@@ -17,17 +17,17 @@ export type FastifyRegisterOptions<Options> = (RegisterOptions & Options) | ((in
17
17
  *
18
18
  * Function for adding a plugin to fastify. The options are inferred from the passed in FastifyPlugin parameter.
19
19
  */
20
- export interface FastifyRegister<T = void, RawServer extends RawServerBase = RawServerDefault, TypeProviderDefault extends FastifyTypeProvider = FastifyTypeProvider> {
21
- <Options extends FastifyPluginOptions, Server extends RawServerBase = RawServer, TypeProvider extends FastifyTypeProvider = TypeProviderDefault>(
22
- plugin: FastifyPluginCallback<Options, Server, TypeProvider>,
20
+ export interface FastifyRegister<T = void, RawServer extends RawServerBase = RawServerDefault, TypeProviderDefault extends FastifyTypeProvider = FastifyTypeProvider, LoggerDefault extends FastifyBaseLogger = FastifyBaseLogger> {
21
+ <Options extends FastifyPluginOptions, Server extends RawServerBase = RawServer, TypeProvider extends FastifyTypeProvider = TypeProviderDefault, Logger extends FastifyBaseLogger = LoggerDefault>(
22
+ plugin: FastifyPluginCallback<Options, Server, TypeProvider, Logger>,
23
23
  opts?: FastifyRegisterOptions<Options>
24
24
  ): T;
25
- <Options extends FastifyPluginOptions, Server extends RawServerBase = RawServer, TypeProvider extends FastifyTypeProvider = TypeProviderDefault>(
26
- plugin: FastifyPluginAsync<Options, Server, TypeProvider>,
25
+ <Options extends FastifyPluginOptions, Server extends RawServerBase = RawServer, TypeProvider extends FastifyTypeProvider = TypeProviderDefault, Logger extends FastifyBaseLogger = LoggerDefault>(
26
+ plugin: FastifyPluginAsync<Options, Server, TypeProvider, Logger>,
27
27
  opts?: FastifyRegisterOptions<Options>
28
28
  ): T;
29
- <Options extends FastifyPluginOptions, Server extends RawServerBase = RawServer, TypeProvider extends FastifyTypeProvider = TypeProviderDefault>(
30
- plugin: FastifyPluginCallback<Options, Server, TypeProvider> | FastifyPluginAsync<Options, Server, TypeProvider> | Promise<{ default: FastifyPluginCallback<Options, Server, TypeProvider> }> | Promise<{ default: FastifyPluginAsync<Options, Server, TypeProvider> }>,
29
+ <Options extends FastifyPluginOptions, Server extends RawServerBase = RawServer, TypeProvider extends FastifyTypeProvider = TypeProviderDefault, Logger extends FastifyBaseLogger = LoggerDefault>(
30
+ plugin: FastifyPluginCallback<Options, Server, TypeProvider, Logger> | FastifyPluginAsync<Options, Server, TypeProvider, Logger> | Promise<{ default: FastifyPluginCallback<Options, Server, TypeProvider, Logger> }> | Promise<{ default: FastifyPluginAsync<Options, Server, TypeProvider, Logger> }>,
31
31
  opts?: FastifyRegisterOptions<Options>
32
32
  ): T;
33
33
  }