fastify 4.14.1 → 4.15.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
@@ -304,6 +304,8 @@ listed in alphabetical order.
304
304
  <https://twitter.com/ayoubelkh>, <https://www.npmjs.com/~ayoubelk>
305
305
  * [__Denis Fäcke__](https://github.com/SerayaEryn),
306
306
  <https://twitter.com/serayaeryn>, <https://www.npmjs.com/~serayaeryn>
307
+ * [__Carlos Fuentes__](https://github.com/metcoder95),
308
+ <https://twitter.com/metcoder95>, <https://www.npmjs.com/~metcoder95>
307
309
  * [__Rafael Gonzaga__](https://github.com/rafaelgss),
308
310
  <https://twitter.com/_rafaelgss>, <https://www.npmjs.com/~rafaelgss>
309
311
  * [__Vincent Le Goff__](https://github.com/zekth)
@@ -327,6 +329,8 @@ listed in alphabetical order.
327
329
  <https://twitter.com/delvedor>, <https://www.npmjs.com/~delvedor>
328
330
  * [__Ayoub El Khattabi__](https://github.com/AyoubElk),
329
331
  <https://twitter.com/ayoubelkh>, <https://www.npmjs.com/~ayoubelk>
332
+ * [__Carlos Fuentes__](https://github.com/metcoder95),
333
+ <https://twitter.com/metcoder95>, <https://www.npmjs.com/~metcoder95>
330
334
  * [__Vincent Le Goff__](https://github.com/zekth)
331
335
  * [__Salman Mitha__](https://github.com/salmanm),
332
336
  <https://www.npmjs.com/~salmanm>
@@ -338,7 +342,7 @@ listed in alphabetical order.
338
342
  * [__Rafael Gonzaga__](https://github.com/rafaelgss),
339
343
  <https://twitter.com/_rafaelgss>, <https://www.npmjs.com/~rafaelgss>
340
344
  * [__Simone Busoli__](https://github.com/simoneb),
341
- <https://twitter.com/simonebu>, <https://www.npmjs.com/~simoneb>
345
+ <https://twitter.com/simonebu>, <https://www.npmjs.com/~simoneb>
342
346
 
343
347
  ### Great Contributors
344
348
  Great contributors on a specific area in the Fastify ecosystem will be invited
@@ -281,6 +281,12 @@ A callback for a hook timed out
281
281
 
282
282
  The logger accepts either a `'stream'` or a `'file'` as the destination.
283
283
 
284
+ #### FST_ERR_LOG_INVALID_LOGGER
285
+ <a id="FST_ERR_LOG_INVALID_LOGGER"></a>
286
+
287
+ The logger should have all these methods: `'info'`, `'error'`,
288
+ `'debug'`, `'fatal'`, `'warn'`, `'trace'`, `'child'`.
289
+
284
290
  #### FST_ERR_REP_INVALID_PAYLOAD_TYPE
285
291
  <a id="FST_ERR_REP_INVALID_PAYLOAD_TYPE"></a>
286
292
 
@@ -1545,35 +1545,61 @@ a custom constraint strategy with the same name.
1545
1545
  #### printRoutes
1546
1546
  <a id="print-routes"></a>
1547
1547
 
1548
- `fastify.printRoutes()`: Prints the representation of the internal radix tree
1549
- used by the router, useful for debugging. Alternatively, `fastify.printRoutes({
1550
- commonPrefix: false })` can be used to print the flattened routes tree.
1548
+ `fastify.printRoutes()`: Fastify router builds a tree of routes for each HTTP
1549
+ method. If you call the prettyPrint without specifying an HTTP method, it will
1550
+ merge all the trees into one and print it. The merged tree doesn't represent the
1551
+ internal router structure. **Don't use it for debugging.**
1551
1552
 
1552
1553
  *Remember to call it inside or after a `ready` call.*
1553
1554
 
1554
1555
  ```js
1555
1556
  fastify.get('/test', () => {})
1556
1557
  fastify.get('/test/hello', () => {})
1557
- fastify.get('/hello/world', () => {})
1558
- fastify.get('/helicopter', () => {})
1558
+ fastify.get('/testing', () => {})
1559
+ fastify.get('/testing/:param', () => {})
1560
+ fastify.put('/update', () => {})
1559
1561
 
1560
1562
  fastify.ready(() => {
1561
1563
  console.log(fastify.printRoutes())
1562
1564
  // └── /
1563
1565
  // ├── test (GET)
1564
- // │ └── /hello (GET)
1565
- // └── hel
1566
- // ├── lo/world (GET)
1567
- // └── licopter (GET)
1566
+ // │ ├── /hello (GET)
1567
+ // └── ing (GET)
1568
+ // │ └── /
1569
+ //└── :param (GET)
1570
+ // └── update (PUT)
1571
+ })
1572
+ ```
1568
1573
 
1569
- console.log(fastify.printRoutes({ commonPrefix: false }))
1570
- // └── / (-)
1571
- // ├── test (GET)
1572
- // │ └── /hello (GET)
1573
- // ├── hello/world (GET)
1574
- // └── helicopter (GET)
1574
+ If you want to print the internal router tree, you should specify the `method`
1575
+ param. Printed tree will represent the internal router structure.
1576
+ **You can use it for debugging.**
1575
1577
 
1576
- })
1578
+ ```js
1579
+ console.log(fastify.printRoutes({ method: 'GET' }))
1580
+ // └── /
1581
+ // └── test (GET)
1582
+ // ├── /hello (GET)
1583
+ // └── ing (GET)
1584
+ // └── /
1585
+ // └── :param (GET)
1586
+
1587
+ console.log(fastify.printRoutes({ method: 'PUT' }))
1588
+ // └── /
1589
+ // └── update (PUT)
1590
+ ```
1591
+
1592
+ `fastify.printRoutes({ commonPrefix: false })` will print compressed trees. This
1593
+ might useful when you have a large number of routes with common prefixes.
1594
+ It doesn't represent the internal router structure. **Don't use it for debugging.**
1595
+
1596
+ ```js
1597
+ console.log(fastify.printRoutes({ commonPrefix: false }))
1598
+ // ├── /test (GET)
1599
+ // │ ├── /hello (GET)
1600
+ // │ └── ing (GET)
1601
+ // │ └── /:param (GET)
1602
+ // └── /update (PUT)
1577
1603
  ```
1578
1604
 
1579
1605
  `fastify.printRoutes({ includeMeta: (true | []) })` will display properties from
@@ -1583,26 +1609,51 @@ A shorthand option, `fastify.printRoutes({ includeHooks: true })` will include
1583
1609
  all [hooks](./Hooks.md).
1584
1610
 
1585
1611
  ```js
1586
- console.log(fastify.printRoutes({ includeHooks: true, includeMeta: ['metaProperty'] }))
1612
+ fastify.get('/test', () => {})
1613
+ fastify.get('/test/hello', () => {})
1614
+
1615
+ const onTimeout = () => {}
1616
+
1617
+ fastify.addHook('onRequest', () => {})
1618
+ fastify.addHook('onTimeout', onTimeout)
1619
+
1620
+ console.log(fastify.printRoutes({ includeHooks: true, includeMeta: ['errorHandler'] }))
1587
1621
  // └── /
1588
- // ├── test (GET)
1589
- //• (onRequest) ["anonymous()","namedFunction()"]
1590
- //• (metaProperty) "value"
1591
- // │ └── /hello (GET)
1592
- // └── hel
1593
- // ├── lo/world (GET)
1594
- // • (onTimeout) ["anonymous()"]
1595
- // └── licopter (GET)
1622
+ // └── test (GET)
1623
+ // • (onTimeout) ["onTimeout()"]
1624
+ // • (onRequest) ["anonymous()"]
1625
+ // (errorHandler) "defaultErrorHandler()"
1626
+ // test (HEAD)
1627
+ // (onTimeout) ["onTimeout()"]
1628
+ // • (onRequest) ["anonymous()"]
1629
+ // (onSend) ["headRouteOnSendHandler()"]
1630
+ // • (errorHandler) "defaultErrorHandler()"
1631
+ // └── /hello (GET)
1632
+ // • (onTimeout) ["onTimeout()"]
1633
+ // • (onRequest) ["anonymous()"]
1634
+ // • (errorHandler) "defaultErrorHandler()"
1635
+ // /hello (HEAD)
1636
+ // • (onTimeout) ["onTimeout()"]
1637
+ // • (onRequest) ["anonymous()"]
1638
+ // • (onSend) ["headRouteOnSendHandler()"]
1639
+ // • (errorHandler) "defaultErrorHandler()"
1596
1640
 
1597
1641
  console.log(fastify.printRoutes({ includeHooks: true }))
1598
1642
  // └── /
1599
- // ├── test (GET)
1600
- //• (onRequest) ["anonymous()","namedFunction()"]
1601
- // │ └── /hello (GET)
1602
- // └── hel
1603
- // ├── lo/world (GET)
1604
- // • (onTimeout) ["anonymous()"]
1605
- // └── licopter (GET)
1643
+ // └── test (GET)
1644
+ // • (onTimeout) ["onTimeout()"]
1645
+ // (onRequest) ["anonymous()"]
1646
+ // test (HEAD)
1647
+ // (onTimeout) ["onTimeout()"]
1648
+ // • (onRequest) ["anonymous()"]
1649
+ // (onSend) ["headRouteOnSendHandler()"]
1650
+ // └── /hello (GET)
1651
+ // • (onTimeout) ["onTimeout()"]
1652
+ // • (onRequest) ["anonymous()"]
1653
+ // /hello (HEAD)
1654
+ // • (onTimeout) ["onTimeout()"]
1655
+ // • (onRequest) ["anonymous()"]
1656
+ // • (onSend) ["headRouteOnSendHandler()"]
1606
1657
  ```
1607
1658
 
1608
1659
  #### printPlugins
package/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '4.14.1'
3
+ const VERSION = '4.15.0'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('http')
@@ -396,10 +396,18 @@ function ParserListItem (contentType) {
396
396
  // we pre-calculate all the needed information
397
397
  // before content-type comparsion
398
398
  const parsed = safeParseContentType(contentType)
399
- this.type = parsed.type
399
+ this.isEssence = contentType.indexOf(';') === -1
400
+ // we should not allow empty string for parser list item
401
+ // because it would become a match-all handler
402
+ if (this.isEssence === false && parsed.type === '') {
403
+ // handle semicolon or empty string
404
+ const tmp = contentType.split(';')[0]
405
+ this.type = tmp === '' ? contentType : tmp
406
+ } else {
407
+ this.type = parsed.type
408
+ }
400
409
  this.parameters = parsed.parameters
401
410
  this.parameterKeys = Object.keys(parsed.parameters)
402
- this.isEssence = contentType.indexOf(';') === -1
403
411
  }
404
412
 
405
413
  // used in ContentTypeParser.remove
package/lib/errors.js CHANGED
@@ -177,6 +177,11 @@ const codes = {
177
177
  'Cannot specify both logger.stream and logger.file options'
178
178
  ),
179
179
 
180
+ FST_ERR_LOG_INVALID_LOGGER: createError(
181
+ 'FST_ERR_LOG_INVALID_LOGGER',
182
+ "Invalid logger object provided. The logger instance should have these functions(s): '%s'."
183
+ ),
184
+
180
185
  /**
181
186
  * reply
182
187
  */
package/lib/logger.js CHANGED
@@ -9,17 +9,17 @@
9
9
  const nullLogger = require('abstract-logging')
10
10
  const pino = require('pino')
11
11
  const { serializersSym } = pino.symbols
12
- const { FST_ERR_LOG_INVALID_DESTINATION } = require('./errors')
12
+ const {
13
+ FST_ERR_LOG_INVALID_DESTINATION,
14
+ FST_ERR_LOG_INVALID_LOGGER
15
+ } = require('./errors')
13
16
 
14
- function createPinoLogger (opts, stream) {
15
- stream = stream || opts.stream
16
- delete opts.stream
17
-
18
- if (stream && opts.file) {
17
+ function createPinoLogger (opts) {
18
+ if (opts.stream && opts.file) {
19
19
  throw new FST_ERR_LOG_INVALID_DESTINATION()
20
20
  } else if (opts.file) {
21
21
  // we do not have stream
22
- stream = pino.destination(opts.file)
22
+ opts.stream = pino.destination(opts.file)
23
23
  delete opts.file
24
24
  }
25
25
 
@@ -39,7 +39,7 @@ function createPinoLogger (opts, stream) {
39
39
  opts.logger = prevLogger
40
40
  opts.genReqId = prevGenReqId
41
41
  } else {
42
- logger = pino(opts, stream)
42
+ logger = pino(opts, opts.stream)
43
43
  }
44
44
 
45
45
  return logger
@@ -70,50 +70,60 @@ function now () {
70
70
  }
71
71
 
72
72
  function createLogger (options) {
73
- if (isValidLogger(options.logger)) {
73
+ if (!options.logger) {
74
+ const logger = nullLogger
75
+ logger.child = () => logger
76
+ return { logger, hasLogger: false }
77
+ }
78
+
79
+ if (validateLogger(options.logger)) {
74
80
  const logger = createPinoLogger({
75
81
  logger: options.logger,
76
82
  serializers: Object.assign({}, serializers, options.logger.serializers)
77
83
  })
78
84
  return { logger, hasLogger: true }
79
- } else if (!options.logger) {
80
- const logger = nullLogger
81
- logger.child = () => logger
82
- return { logger, hasLogger: false }
83
- } else {
84
- const localLoggerOptions = {}
85
- if (Object.prototype.toString.call(options.logger) === '[object Object]') {
86
- Reflect.ownKeys(options.logger).forEach(prop => {
87
- Object.defineProperty(localLoggerOptions, prop, {
88
- value: options.logger[prop],
89
- writable: true,
90
- enumerable: true,
91
- configurable: true
92
- })
93
- })
94
- }
95
- localLoggerOptions.level = localLoggerOptions.level || 'info'
96
- localLoggerOptions.serializers = Object.assign({}, serializers, localLoggerOptions.serializers)
97
- options.logger = localLoggerOptions
98
- const logger = createPinoLogger(options.logger)
99
- return { logger, hasLogger: true }
100
85
  }
101
- }
102
86
 
103
- function isValidLogger (logger) {
104
- if (!logger) {
105
- return false
87
+ const localLoggerOptions = {}
88
+ if (Object.prototype.toString.call(options.logger) === '[object Object]') {
89
+ Reflect.ownKeys(options.logger).forEach(prop => {
90
+ Object.defineProperty(localLoggerOptions, prop, {
91
+ value: options.logger[prop],
92
+ writable: true,
93
+ enumerable: true,
94
+ configurable: true
95
+ })
96
+ })
106
97
  }
98
+ localLoggerOptions.level = localLoggerOptions.level || 'info'
99
+ localLoggerOptions.serializers = Object.assign({}, serializers, localLoggerOptions.serializers)
100
+ options.logger = localLoggerOptions
101
+ const logger = createPinoLogger(options.logger)
102
+ return { logger, hasLogger: true }
103
+ }
107
104
 
108
- let result = true
105
+ /**
106
+ * Determines if a provided logger object meets the requirements
107
+ * of a Fastify compatible logger.
108
+ *
109
+ * @param {object} logger Object to validate.
110
+ *
111
+ * @returns {boolean} `true` when the logger meets the requirements.
112
+ *
113
+ * @throws {FST_ERR_LOG_INVALID_LOGGER} When the logger object is
114
+ * missing required methods.
115
+ */
116
+ function validateLogger (logger) {
109
117
  const methods = ['info', 'error', 'debug', 'fatal', 'warn', 'trace', 'child']
110
- for (let i = 0; i < methods.length; i += 1) {
111
- if (!logger[methods[i]] || typeof logger[methods[i]] !== 'function') {
112
- result = false
113
- break
114
- }
118
+ const missingMethods = methods.filter(method => !logger[method] || typeof logger[method] !== 'function')
119
+
120
+ if (!missingMethods.length) {
121
+ return true
122
+ } else if (missingMethods.length === methods.length) {
123
+ return false
124
+ } else {
125
+ throw FST_ERR_LOG_INVALID_LOGGER(missingMethods.join(','))
115
126
  }
116
- return result
117
127
  }
118
128
 
119
129
  module.exports = {
package/lib/route.js CHANGED
@@ -513,7 +513,6 @@ function handleOnRequestAbortHooksErrors (reply, err) {
513
513
  if (err) {
514
514
  reply.log.error({ err }, 'onRequestAborted hook failed')
515
515
  }
516
- reply[kReplyIsError] = true
517
516
  }
518
517
 
519
518
  function handleTimeout () {
package/lib/validation.js CHANGED
@@ -80,18 +80,26 @@ function compileSchemasForValidation (context, compile, isCustom) {
80
80
  })
81
81
  }
82
82
  context[headersSchema] = compile({ schema: headersSchemaLowerCase, method, url, httpPart: 'headers' })
83
+ } else if (Object.hasOwnProperty.call(schema, 'headers')) {
84
+ throw new Error('headers schema is undefined')
83
85
  }
84
86
 
85
87
  if (schema.body) {
86
88
  context[bodySchema] = compile({ schema: schema.body, method, url, httpPart: 'body' })
89
+ } else if (Object.hasOwnProperty.call(schema, 'body')) {
90
+ throw new Error('body schema is undefined')
87
91
  }
88
92
 
89
93
  if (schema.querystring) {
90
94
  context[querystringSchema] = compile({ schema: schema.querystring, method, url, httpPart: 'querystring' })
95
+ } else if (Object.hasOwnProperty.call(schema, 'querystring')) {
96
+ throw new Error('querystring schema is undefined')
91
97
  }
92
98
 
93
99
  if (schema.params) {
94
100
  context[paramsSchema] = compile({ schema: schema.params, method, url, httpPart: 'params' })
101
+ } else if (Object.hasOwnProperty.call(schema, 'params')) {
102
+ throw new Error('params schema is undefined')
95
103
  }
96
104
  }
97
105
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "4.14.1",
3
+ "version": "4.15.0",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -8,6 +8,7 @@
8
8
  "scripts": {
9
9
  "bench": "branchcmp -r 2 -g -s \"npm run benchmark\"",
10
10
  "benchmark": "npx concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"npx autocannon -c 100 -d 30 -p 10 localhost:3000/\"",
11
+ "build:validation": "node build/build-error-serializer.js && node build/build-validation.js",
11
12
  "coverage": "npm run unit -- --cov --coverage-report=html",
12
13
  "coverage:ci": "npm run unit -- --cov --coverage-report=html --no-browser --no-check-coverage -R terse",
13
14
  "coverage:ci-check-coverage": "nyc check-coverage --branches 100 --functions 100 --lines 100 --statements 100",
@@ -17,10 +18,11 @@
17
18
  "lint:markdown": "markdownlint-cli2",
18
19
  "lint:standard": "standard | snazzy",
19
20
  "lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts",
20
- "prepublishOnly": "PREPUBLISH=true tap --no-check-coverage test/build/**.test.js",
21
+ "prepublishOnly": "PREPUBLISH=true tap --no-check-coverage test/build/**.test.js && npm run test:validator:integrity",
21
22
  "test": "npm run lint && npm run unit && npm run test:typescript",
22
23
  "test:ci": "npm run unit -- -R terse --cov --coverage-report=lcovonly && npm run test:typescript",
23
24
  "test:report": "npm run lint && npm run unit:report && npm run test:typescript",
25
+ "test:validator:integrity": "npm run build:validation && git diff --quiet --ignore-all-space --ignore-blank-lines --ignore-cr-at-eol lib/error-serializer.js && git diff --quiet --ignore-all-space --ignore-blank-lines --ignore-cr-at-eol lib/configValidator.js",
24
26
  "test:typescript": "tsc test/types/import.ts && tsd",
25
27
  "test:watch": "npm run unit -- -w --no-coverage-report -R terse",
26
28
  "unit": "tap",
@@ -117,6 +119,11 @@
117
119
  "name": "Luis Orbaiceta",
118
120
  "email": "luisorbaiceta@gmail.com",
119
121
  "url": "https://luisorbaiceta.com"
122
+ },
123
+ {
124
+ "name": "Carlos Fuentes",
125
+ "email": "me@metcoder.dev",
126
+ "url": "https://metcoder.dev"
120
127
  }
121
128
  ],
122
129
  "license": "MIT",
@@ -164,7 +171,7 @@
164
171
  "split2": "^4.1.0",
165
172
  "standard": "^17.0.0",
166
173
  "tap": "^16.3.0",
167
- "tsd": "^0.25.0",
174
+ "tsd": "^0.26.0",
168
175
  "typescript": "^4.8.3",
169
176
  "undici": "^5.10.0",
170
177
  "vary": "^1.1.2",
@@ -173,11 +180,11 @@
173
180
  "dependencies": {
174
181
  "@fastify/ajv-compiler": "^3.5.0",
175
182
  "@fastify/error": "^3.0.0",
176
- "@fastify/fast-json-stringify-compiler": "^4.1.0",
183
+ "@fastify/fast-json-stringify-compiler": "^4.2.0",
177
184
  "abstract-logging": "^2.0.1",
178
185
  "avvio": "^8.2.0",
179
186
  "fast-content-type-parse": "^1.0.0",
180
- "find-my-way": "^7.3.0",
187
+ "find-my-way": "^7.6.0",
181
188
  "light-my-request": "^5.6.1",
182
189
  "pino": "^8.5.0",
183
190
  "process-warning": "^2.0.0",
@@ -649,3 +649,104 @@ test('content-type regexp list should be cloned when plugin override', async t =
649
649
  t.same(payload, 'png')
650
650
  }
651
651
  })
652
+
653
+ test('allow partial content-type - essence check', async t => {
654
+ t.plan(1)
655
+
656
+ const fastify = Fastify()
657
+ fastify.removeAllContentTypeParsers()
658
+ fastify.addContentTypeParser('json', function (request, body, done) {
659
+ t.pass('should be called')
660
+ done(null, body)
661
+ })
662
+
663
+ fastify.post('/', async () => {
664
+ return 'ok'
665
+ })
666
+
667
+ await fastify.inject({
668
+ method: 'POST',
669
+ path: '/',
670
+ headers: {
671
+ 'content-type': 'application/json; foo=bar; charset=utf8'
672
+ },
673
+ body: ''
674
+ })
675
+
676
+ await fastify.inject({
677
+ method: 'POST',
678
+ path: '/',
679
+ headers: {
680
+ 'content-type': 'image/jpeg'
681
+ },
682
+ body: ''
683
+ })
684
+ })
685
+
686
+ test('allow partial content-type - not essence check', async t => {
687
+ t.plan(1)
688
+
689
+ const fastify = Fastify()
690
+ fastify.removeAllContentTypeParsers()
691
+ fastify.addContentTypeParser('json;', function (request, body, done) {
692
+ t.pass('should be called')
693
+ done(null, body)
694
+ })
695
+
696
+ fastify.post('/', async () => {
697
+ return 'ok'
698
+ })
699
+
700
+ await fastify.inject({
701
+ method: 'POST',
702
+ path: '/',
703
+ headers: {
704
+ 'content-type': 'application/json; foo=bar; charset=utf8'
705
+ },
706
+ body: ''
707
+ })
708
+
709
+ await fastify.inject({
710
+ method: 'POST',
711
+ path: '/',
712
+ headers: {
713
+ 'content-type': 'image/jpeg'
714
+ },
715
+ body: ''
716
+ })
717
+ })
718
+
719
+ test('edge case content-type - ;', async t => {
720
+ t.plan(1)
721
+
722
+ const fastify = Fastify()
723
+ fastify.removeAllContentTypeParsers()
724
+ fastify.addContentTypeParser(';', function (request, body, done) {
725
+ t.fail('should not be called')
726
+ done(null, body)
727
+ })
728
+
729
+ fastify.post('/', async () => {
730
+ return 'ok'
731
+ })
732
+
733
+ await fastify.inject({
734
+ method: 'POST',
735
+ path: '/',
736
+ headers: {
737
+ 'content-type': 'application/json; foo=bar; charset=utf8'
738
+ },
739
+ body: ''
740
+ })
741
+
742
+ await fastify.inject({
743
+ method: 'POST',
744
+ path: '/',
745
+ headers: {
746
+ 'content-type': 'image/jpeg'
747
+ },
748
+ body: ''
749
+ })
750
+
751
+ t.pass('end')
752
+ })
@@ -3413,7 +3413,7 @@ test('onRequestAbort should be triggered', t => {
3413
3413
  const fastify = Fastify()
3414
3414
  let order = 0
3415
3415
 
3416
- t.plan(9)
3416
+ t.plan(7)
3417
3417
  t.teardown(() => fastify.close())
3418
3418
 
3419
3419
  fastify.addHook('onRequestAbort', function (req, done) {
@@ -3424,8 +3424,7 @@ test('onRequestAbort should be triggered', t => {
3424
3424
  })
3425
3425
 
3426
3426
  fastify.addHook('onError', function hook (request, reply, error, done) {
3427
- t.same(error, { hello: 'world' }, 'onError should be called')
3428
- t.ok(request.raw.aborted, 'request should be aborted')
3427
+ t.fail('onError should not be called')
3429
3428
  done()
3430
3429
  })
3431
3430
 
@@ -1,7 +1,6 @@
1
1
  'use strict'
2
2
 
3
3
  const { test, teardown, before } = require('tap')
4
- const helper = require('./helper')
5
4
  const http = require('http')
6
5
  const stream = require('stream')
7
6
  const split = require('split2')
@@ -13,6 +12,9 @@ const fs = require('fs')
13
12
  const sget = require('simple-get').concat
14
13
  const dns = require('dns')
15
14
 
15
+ const helper = require('./helper')
16
+ const { FST_ERR_LOG_INVALID_LOGGER } = require('../lib/errors')
17
+
16
18
  const files = []
17
19
  let count = 0
18
20
  let localhost
@@ -268,6 +270,34 @@ test('can use external logger instance with custom serializer', t => {
268
270
  })
269
271
  })
270
272
 
273
+ test('should throw in case the external logger provided does not have a child method', t => {
274
+ t.plan(1)
275
+ const loggerInstance = {
276
+ info: console.info,
277
+ error: console.error,
278
+ debug: console.debug,
279
+ fatal: console.error,
280
+ warn: console.warn,
281
+ trace: console.trace
282
+ }
283
+
284
+ t.throws(
285
+ () => Fastify({ logger: loggerInstance }),
286
+ FST_ERR_LOG_INVALID_LOGGER,
287
+ "Invalid logger object provided. The logger instance should have these functions(s): 'child'."
288
+ )
289
+ })
290
+
291
+ test('should throw in case a partially matching logger is provided', t => {
292
+ t.plan(1)
293
+
294
+ t.throws(
295
+ () => Fastify({ logger: console }),
296
+ FST_ERR_LOG_INVALID_LOGGER,
297
+ "Invalid logger object provided. The logger instance should have these functions(s): 'fatal,child'."
298
+ )
299
+ })
300
+
271
301
  test('expose the logger', t => {
272
302
  t.plan(2)
273
303
  let fastify = null
@@ -15,7 +15,8 @@ test('pretty print - static routes', t => {
15
15
  fastify.ready(() => {
16
16
  const tree = fastify.printRoutes()
17
17
 
18
- const expected = `└── /
18
+ const expected = `\
19
+ └── /
19
20
  ├── test (GET)
20
21
  │ └── /hello (GET)
21
22
  └── hello/world (GET)
@@ -26,6 +27,41 @@ test('pretty print - static routes', t => {
26
27
  })
27
28
  })
28
29
 
30
+ test('pretty print - internal tree - static routes', t => {
31
+ t.plan(4)
32
+
33
+ const fastify = Fastify({ exposeHeadRoutes: false })
34
+ fastify.get('/test', () => {})
35
+ fastify.get('/test/hello', () => {})
36
+ fastify.get('/hello/world', () => {})
37
+
38
+ fastify.put('/test', () => {})
39
+ fastify.put('/test/foo', () => {})
40
+
41
+ fastify.ready(() => {
42
+ const getTree = fastify.printRoutes({ method: 'GET' })
43
+ const expectedGetTree = `\
44
+ └── /
45
+ ├── test (GET)
46
+ │ └── /hello (GET)
47
+ └── hello/world (GET)
48
+ `
49
+
50
+ t.equal(typeof getTree, 'string')
51
+ t.equal(getTree, expectedGetTree)
52
+
53
+ const putTree = fastify.printRoutes({ method: 'PUT' })
54
+ const expectedPutTree = `\
55
+ └── /
56
+ └── test (PUT)
57
+ └── /foo (PUT)
58
+ `
59
+
60
+ t.equal(typeof putTree, 'string')
61
+ t.equal(putTree, expectedPutTree)
62
+ })
63
+ })
64
+
29
65
  test('pretty print - parametric routes', t => {
30
66
  t.plan(2)
31
67
 
@@ -37,10 +73,13 @@ test('pretty print - parametric routes', t => {
37
73
  fastify.ready(() => {
38
74
  const tree = fastify.printRoutes()
39
75
 
40
- const expected = `└── /
76
+ const expected = `\
77
+ └── /
41
78
  ├── test (GET)
42
- │ └── /:hello (GET)
43
- └── hello/:world (GET)
79
+ │ └── /
80
+ └── :hello (GET)
81
+ └── hello/
82
+ └── :world (GET)
44
83
  `
45
84
 
46
85
  t.equal(typeof tree, 'string')
@@ -48,6 +87,44 @@ test('pretty print - parametric routes', t => {
48
87
  })
49
88
  })
50
89
 
90
+ test('pretty print - internal tree - parametric routes', t => {
91
+ t.plan(4)
92
+
93
+ const fastify = Fastify({ exposeHeadRoutes: false })
94
+ fastify.get('/test', () => {})
95
+ fastify.get('/test/:hello', () => {})
96
+ fastify.get('/hello/:world', () => {})
97
+
98
+ fastify.put('/test', () => {})
99
+ fastify.put('/test/:hello', () => {})
100
+
101
+ fastify.ready(() => {
102
+ const getTree = fastify.printRoutes({ method: 'GET' })
103
+ const expectedGetTree = `\
104
+ └── /
105
+ ├── test (GET)
106
+ │ └── /
107
+ │ └── :hello (GET)
108
+ └── hello/
109
+ └── :world (GET)
110
+ `
111
+
112
+ t.equal(typeof getTree, 'string')
113
+ t.equal(getTree, expectedGetTree)
114
+
115
+ const putTree = fastify.printRoutes({ method: 'PUT' })
116
+ const expectedPutTree = `\
117
+ └── /
118
+ └── test (PUT)
119
+ └── /
120
+ └── :hello (PUT)
121
+ `
122
+
123
+ t.equal(typeof putTree, 'string')
124
+ t.equal(putTree, expectedPutTree)
125
+ })
126
+ })
127
+
51
128
  test('pretty print - mixed parametric routes', t => {
52
129
  t.plan(2)
53
130
 
@@ -60,11 +137,12 @@ test('pretty print - mixed parametric routes', t => {
60
137
  fastify.ready(() => {
61
138
  const tree = fastify.printRoutes()
62
139
 
63
- const expected = `└── /test (GET)
64
- └── /
65
- └── :hello (GET)
66
- :hello (POST)
67
- └── /world (GET)
140
+ const expected = `\
141
+ └── /
142
+ └── test (GET)
143
+ └── /
144
+ └── :hello (GET, POST)
145
+ └── /world (GET)
68
146
  `
69
147
 
70
148
  t.equal(typeof tree, 'string')
@@ -83,10 +161,13 @@ test('pretty print - wildcard routes', t => {
83
161
  fastify.ready(() => {
84
162
  const tree = fastify.printRoutes()
85
163
 
86
- const expected = `└── /
164
+ const expected = `\
165
+ └── /
87
166
  ├── test (GET)
88
- │ └── /* (GET)
89
- └── hello/* (GET)
167
+ │ └── /
168
+ └── * (GET)
169
+ └── hello/
170
+ └── * (GET)
90
171
  `
91
172
 
92
173
  t.equal(typeof tree, 'string')
@@ -94,6 +175,44 @@ test('pretty print - wildcard routes', t => {
94
175
  })
95
176
  })
96
177
 
178
+ test('pretty print - internal tree - wildcard routes', t => {
179
+ t.plan(4)
180
+
181
+ const fastify = Fastify({ exposeHeadRoutes: false })
182
+ fastify.get('/test', () => {})
183
+ fastify.get('/test/*', () => {})
184
+ fastify.get('/hello/*', () => {})
185
+
186
+ fastify.put('/*', () => {})
187
+ fastify.put('/test/*', () => {})
188
+
189
+ fastify.ready(() => {
190
+ const getTree = fastify.printRoutes({ method: 'GET' })
191
+ const expectedGetTree = `\
192
+ └── /
193
+ ├── test (GET)
194
+ │ └── /
195
+ │ └── * (GET)
196
+ └── hello/
197
+ └── * (GET)
198
+ `
199
+
200
+ t.equal(typeof getTree, 'string')
201
+ t.equal(getTree, expectedGetTree)
202
+
203
+ const putTree = fastify.printRoutes({ method: 'PUT' })
204
+ const expectedPutTree = `\
205
+ └── /
206
+ ├── test/
207
+ │ └── * (PUT)
208
+ └── * (PUT)
209
+ `
210
+
211
+ t.equal(typeof putTree, 'string')
212
+ t.equal(putTree, expectedPutTree)
213
+ })
214
+ })
215
+
97
216
  test('pretty print - empty plugins', t => {
98
217
  t.plan(2)
99
218
 
@@ -134,17 +253,15 @@ test('pretty print - commonPrefix', t => {
134
253
  const radixTree = fastify.printRoutes()
135
254
  const flatTree = fastify.printRoutes({ commonPrefix: false })
136
255
 
137
- const radixExpected = `└── /
138
- ├── hel
139
- │ ├── lo (GET)
140
- │ │ lo (HEAD)
141
- └── icopter (GET)
142
- │ icopter (HEAD)
143
- └── hello (PUT)
256
+ const radixExpected = `\
257
+ └── /
258
+ └── hel
259
+ ├── lo (GET, HEAD, PUT)
260
+ └── icopter (GET, HEAD)
144
261
  `
145
- const flatExpected = `└── / (-)
146
- ├── helicopter (GET, HEAD)
147
- └── hello (GET, HEAD, PUT)
262
+ const flatExpected = `\
263
+ ├── /hello (GET, HEAD, PUT)
264
+ └── /helicopter (GET, HEAD)
148
265
  `
149
266
  t.equal(typeof radixTree, 'string')
150
267
  t.equal(typeof flatTree, 'string')
@@ -170,49 +287,64 @@ test('pretty print - includeMeta, includeHooks', t => {
170
287
  const flatTree = fastify.printRoutes({ commonPrefix: false, includeHooks: true, includeMeta: ['errorHandler'] })
171
288
  const hooksOnly = fastify.printRoutes({ commonPrefix: false, includeHooks: true })
172
289
 
173
- const radixExpected = `└── /
174
- ├── hel
175
- │ ├── lo (GET)
176
- │ │ • (onTimeout) ["onTimeout()"]
177
- • (onRequest) ["anonymous()"]
178
- • (errorHandler) "defaultErrorHandler()"
179
- │ lo (HEAD)
180
- │ • (onTimeout) ["onTimeout()"]
181
- • (onRequest) ["anonymous()"]
182
- • (onSend) ["headRouteOnSendHandler()"]
183
- • (errorHandler) "defaultErrorHandler()"
184
- └── icopter (GET)
185
- │ • (onTimeout) ["onTimeout()"]
186
- • (onRequest) ["anonymous()"]
187
- • (errorHandler) "defaultErrorHandler()"
188
- │ icopter (HEAD)
189
- │ • (onTimeout) ["onTimeout()"]
190
- • (onRequest) ["anonymous()"]
191
- • (onSend) ["headRouteOnSendHandler()"]
192
- • (errorHandler) "defaultErrorHandler()"
193
- └── hello (PUT)
194
- • (onTimeout) ["onTimeout()"]
195
- • (onRequest) ["anonymous()"]
196
- • (errorHandler) "defaultErrorHandler()"
290
+ const radixExpected = `\
291
+ └── /
292
+ └── hel
293
+ ├── lo (GET, PUT)
294
+ │ • (onTimeout) ["onTimeout()"]
295
+ │ • (onRequest) ["anonymous()"]
296
+ (errorHandler) "defaultErrorHandler()"
297
+ lo (HEAD)
298
+ │ • (onTimeout) ["onTimeout()"]
299
+ │ • (onRequest) ["anonymous()"]
300
+ │ • (onSend) ["headRouteOnSendHandler()"]
301
+ (errorHandler) "defaultErrorHandler()"
302
+ └── icopter (GET)
303
+ • (onTimeout) ["onTimeout()"]
304
+ • (onRequest) ["anonymous()"]
305
+ (errorHandler) "defaultErrorHandler()"
306
+ icopter (HEAD)
307
+ • (onTimeout) ["onTimeout()"]
308
+ • (onRequest) ["anonymous()"]
309
+ • (onSend) ["headRouteOnSendHandler()"]
310
+ (errorHandler) "defaultErrorHandler()"
197
311
  `
198
- const flatExpected = `└── / (-)
199
- ├── helicopter (GET, HEAD)
200
- │ • (onTimeout) ["onTimeout()"]
201
- │ • (onRequest) ["anonymous()"]
202
- │ • (errorHandler) "defaultErrorHandler()"
203
- └── hello (GET, HEAD, PUT)
204
- • (onTimeout) ["onTimeout()"]
205
- • (onRequest) ["anonymous()"]
206
- • (errorHandler) "defaultErrorHandler()"
312
+ const flatExpected = `\
313
+ ├── /hello (GET, PUT)
314
+ │ • (onTimeout) ["onTimeout()"]
315
+ │ • (onRequest) ["anonymous()"]
316
+ │ • (errorHandler) "defaultErrorHandler()"
317
+ │ /hello (HEAD)
318
+ • (onTimeout) ["onTimeout()"]
319
+ • (onRequest) ["anonymous()"]
320
+ • (onSend) ["headRouteOnSendHandler()"]
321
+ │ • (errorHandler) "defaultErrorHandler()"
322
+ └── /helicopter (GET)
323
+ • (onTimeout) ["onTimeout()"]
324
+ • (onRequest) ["anonymous()"]
325
+ • (errorHandler) "defaultErrorHandler()"
326
+ /helicopter (HEAD)
327
+ • (onTimeout) ["onTimeout()"]
328
+ • (onRequest) ["anonymous()"]
329
+ • (onSend) ["headRouteOnSendHandler()"]
330
+ • (errorHandler) "defaultErrorHandler()"
207
331
  `
208
332
 
209
- const hooksOnlyExpected = `└── / (-)
210
- ├── helicopter (GET, HEAD)
211
- │ • (onTimeout) ["onTimeout()"]
212
- │ • (onRequest) ["anonymous()"]
213
- └── hello (GET, HEAD, PUT)
214
- • (onTimeout) ["onTimeout()"]
215
- • (onRequest) ["anonymous()"]
333
+ const hooksOnlyExpected = `\
334
+ ├── /hello (GET, PUT)
335
+ │ • (onTimeout) ["onTimeout()"]
336
+ │ • (onRequest) ["anonymous()"]
337
+ │ /hello (HEAD)
338
+ • (onTimeout) ["onTimeout()"]
339
+ • (onRequest) ["anonymous()"]
340
+ │ • (onSend) ["headRouteOnSendHandler()"]
341
+ └── /helicopter (GET)
342
+ • (onTimeout) ["onTimeout()"]
343
+ • (onRequest) ["anonymous()"]
344
+ /helicopter (HEAD)
345
+ • (onTimeout) ["onTimeout()"]
346
+ • (onRequest) ["anonymous()"]
347
+ • (onSend) ["headRouteOnSendHandler()"]
216
348
  `
217
349
  t.equal(typeof radixTree, 'string')
218
350
  t.equal(typeof flatTree, 'string')
@@ -253,6 +253,91 @@ test('Should not change the input schemas', t => {
253
253
  })
254
254
  })
255
255
 
256
+ test('Should throw if the schema body is undefined', t => {
257
+ t.plan(2)
258
+ const fastify = Fastify()
259
+
260
+ fastify.get('/:id', {
261
+ handler: echoParams,
262
+ schema: {
263
+ body: undefined
264
+ }
265
+ })
266
+
267
+ fastify.ready(err => {
268
+ t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD')
269
+ t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error body schema is undefined')
270
+ })
271
+ })
272
+
273
+ test('Should throw if the schema headers is undefined', t => {
274
+ t.plan(2)
275
+ const fastify = Fastify()
276
+
277
+ fastify.get('/:id', {
278
+ handler: echoParams,
279
+ schema: {
280
+ headers: undefined
281
+ }
282
+ })
283
+
284
+ fastify.ready(err => {
285
+ t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD')
286
+ t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error headers schema is undefined')
287
+ })
288
+ })
289
+
290
+ test('Should throw if the schema params is undefined', t => {
291
+ t.plan(2)
292
+ const fastify = Fastify()
293
+
294
+ fastify.get('/:id', {
295
+ handler: echoParams,
296
+ schema: {
297
+ params: undefined
298
+ }
299
+ })
300
+
301
+ fastify.ready(err => {
302
+ t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD')
303
+ t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error params schema is undefined')
304
+ })
305
+ })
306
+
307
+ test('Should throw if the schema query is undefined', t => {
308
+ t.plan(2)
309
+ const fastify = Fastify()
310
+
311
+ fastify.get('/:id', {
312
+ handler: echoParams,
313
+ schema: {
314
+ querystring: undefined
315
+ }
316
+ })
317
+
318
+ fastify.ready(err => {
319
+ t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD')
320
+ t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error querystring schema is undefined')
321
+ })
322
+ })
323
+
324
+ test('Should throw if the schema query is undefined', t => {
325
+ t.plan(2)
326
+ const fastify = Fastify()
327
+
328
+ fastify.get('/:id', {
329
+ handler: echoParams,
330
+ schema: {
331
+ querystring: undefined
332
+ }
333
+ })
334
+
335
+ fastify.ready(err => {
336
+ t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD')
337
+ t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error querystring schema is undefined')
338
+ })
339
+ })
340
+
256
341
  test('First level $ref', t => {
257
342
  t.plan(2)
258
343
  const fastify = Fastify()
@@ -284,6 +284,8 @@ expectType<string>(server.printRoutes({ includeHooks: true, commonPrefix: false,
284
284
 
285
285
  expectType<string>(server.printRoutes({ includeMeta: ['key1', Symbol('key2')] }))
286
286
 
287
+ expectType<string>(server.printRoutes({ method: 'GET' }))
288
+
287
289
  expectType<string>(server.printRoutes())
288
290
 
289
291
  server.decorate<(x: string) => void>('test', function (x: string): void {
package/types/errors.d.ts CHANGED
@@ -20,6 +20,7 @@ export type FastifyErrorCodes = Record<
20
20
  'FST_ERR_MISSING_MIDDLEWARE' |
21
21
  'FST_ERR_HOOK_TIMEOUT' |
22
22
  'FST_ERR_LOG_INVALID_DESTINATION' |
23
+ 'FST_ERR_LOG_INVALID_LOGGER' |
23
24
  'FST_ERR_REP_INVALID_PAYLOAD_TYPE' |
24
25
  'FST_ERR_REP_ALREADY_SENT' |
25
26
  'FST_ERR_REP_SENT_VALUE'|
@@ -20,10 +20,11 @@ import {
20
20
  FastifyTypeProvider,
21
21
  FastifyTypeProviderDefault
22
22
  } from './type-provider'
23
- import { ContextConfigDefault, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils'
23
+ import { HTTPMethods, ContextConfigDefault, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils'
24
24
  import { AddressInfo } from 'net'
25
25
 
26
26
  export interface PrintRoutesOptions {
27
+ method?: HTTPMethods;
27
28
  includeMeta?: boolean | (string | symbol)[]
28
29
  commonPrefix?: boolean
29
30
  includeHooks?: boolean