fastify 4.16.3 → 4.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/GOVERNANCE.md +2 -2
  2. package/README.md +13 -10
  3. package/docs/Guides/Ecosystem.md +6 -0
  4. package/docs/Guides/Serverless.md +12 -1
  5. package/docs/Reference/Errors.md +5 -0
  6. package/docs/Reference/LTS.md +5 -5
  7. package/docs/Reference/Lifecycle.md +3 -0
  8. package/docs/Reference/Logging.md +34 -1
  9. package/docs/Reference/Reply.md +19 -0
  10. package/docs/Reference/Request.md +1 -1
  11. package/docs/Reference/Server.md +20 -2
  12. package/docs/Reference/Validation-and-Serialization.md +3 -2
  13. package/examples/benchmark/body.json +3 -0
  14. package/fastify.d.ts +6 -1
  15. package/fastify.js +18 -13
  16. package/lib/contentTypeParser.js +3 -1
  17. package/lib/errors.js +5 -0
  18. package/lib/handleRequest.js +15 -4
  19. package/lib/reply.js +3 -2
  20. package/lib/route.js +14 -3
  21. package/lib/schemas.js +18 -21
  22. package/lib/server.js +9 -4
  23. package/lib/validation.js +100 -16
  24. package/package.json +33 -33
  25. package/test/404s.test.js +4 -2
  26. package/test/custom-parser.0.test.js +777 -0
  27. package/test/{custom-parser.test.js → custom-parser.1.test.js} +1 -742
  28. package/test/delete.test.js +3 -0
  29. package/test/fastify-instance.test.js +39 -0
  30. package/test/fluent-schema.test.js +4 -4
  31. package/test/get.test.js +3 -0
  32. package/test/hooks-async.test.js +154 -0
  33. package/test/hooks.test.js +29 -29
  34. package/test/input-validation.js +10 -5
  35. package/test/internals/reply.test.js +114 -45
  36. package/test/internals/validation.test.js +58 -0
  37. package/test/reply-error.test.js +7 -7
  38. package/test/request-error.test.js +3 -0
  39. package/test/route-hooks.test.js +38 -0
  40. package/test/route.test.js +192 -74
  41. package/test/schema-examples.test.js +2 -0
  42. package/test/schema-serialization.test.js +7 -5
  43. package/test/schema-special-usage.test.js +569 -0
  44. package/test/schema-validation.test.js +8 -4
  45. package/test/search.test.js +3 -0
  46. package/test/types/fastify.test-d.ts +4 -1
  47. package/test/types/hooks.test-d.ts +42 -0
  48. package/test/types/instance.test-d.ts +1 -0
  49. package/test/types/logger.test-d.ts +10 -4
  50. package/test/types/reply.test-d.ts +4 -1
  51. package/test/types/route.test-d.ts +12 -0
  52. package/test/url-rewriting.test.js +3 -0
  53. package/test/validation-error-handling.test.js +18 -3
  54. package/types/.eslintrc.json +1 -1
  55. package/types/instance.d.ts +1 -1
  56. package/types/logger.d.ts +10 -1
  57. package/types/reply.d.ts +7 -1
  58. package/types/route.d.ts +11 -31
  59. package/types/type-provider.d.ts +0 -1
  60. package/types/utils.d.ts +3 -1
  61. /package/types/{tsconfig.json → tsconfig.eslint.json} +0 -0
package/GOVERNANCE.md CHANGED
@@ -14,8 +14,8 @@
14
14
 
15
15
  ## Lead Maintainers
16
16
 
17
- Fastify Lead Maintainers are the founder of the project and the organization
18
- owners. They are the only members of the `@fastify/leads` team. The Lead
17
+ Fastify Lead Maintainers are the organization owners.
18
+ They are the only members of the `@fastify/leads` team. The Lead
19
19
  Maintainers are the curator of the Fastify project and their key responsibility
20
20
  is to issue releases of Fastify and its dependencies.
21
21
 
package/README.md CHANGED
@@ -38,6 +38,18 @@ resources of your server, knowing that you are serving the highest number of
38
38
  requests as possible, without sacrificing security validations and handy
39
39
  development?
40
40
 
41
+ Enter Fastify. Fastify is a web framework highly focused on providing the best
42
+ developer experience with the least overhead and a powerful plugin architecture.
43
+ It is inspired by Hapi and Express and as far as we know, it is one of the
44
+ fastest web frameworks in town.
45
+
46
+ The `main` branch refers to the Fastify `v4` release. Check out the
47
+ [`v3.x` branch](https://github.com/fastify/fastify/tree/3.x) for `v3`.
48
+
49
+
50
+
51
+ ### Table of Contents
52
+
41
53
  - [Quick start](#quick-start)
42
54
  - [Install](#install)
43
55
  - [Example](#example)
@@ -51,13 +63,6 @@ development?
51
63
  - [Hosted by](#hosted-by)
52
64
  - [License](#license)
53
65
 
54
- Enter Fastify. Fastify is a web framework highly focused on providing the best
55
- developer experience with the least overhead and a powerful plugin architecture.
56
- It is inspired by Hapi and Express and as far as we know, it is one of the
57
- fastest web frameworks in town.
58
-
59
- This branch refers to the Fastify v4 release. Check out the
60
- [v3.x](https://github.com/fastify/fastify/tree/v3.x) branch for v3.
61
66
 
62
67
  ### Quick start
63
68
 
@@ -189,7 +194,7 @@ changes should be based on **`branch 1.x`**. In a similar way, all Fastify
189
194
  - **Highly performant:** as far as we know, Fastify is one of the fastest web
190
195
  frameworks in town, depending on the code complexity we can serve up to 76+
191
196
  thousand requests per second.
192
- - **Extendible:** Fastify is fully extensible via its hooks, plugins and
197
+ - **Extensible:** Fastify is fully extensible via its hooks, plugins and
193
198
  decorators.
194
199
  - **Schema based:** even if it is not mandatory we recommend to use [JSON
195
200
  Schema](https://json-schema.org/) to validate your routes and serialize your
@@ -339,8 +344,6 @@ listed in alphabetical order.
339
344
  * [__Frazer Smith__](https://github.com/Fdawgs), <https://www.npmjs.com/~fdawgs>
340
345
  * [__Manuel Spigolon__](https://github.com/eomm),
341
346
  <https://twitter.com/manueomm>, <https://www.npmjs.com/~eomm>
342
- * [__Rafael Gonzaga__](https://github.com/rafaelgss),
343
- <https://twitter.com/_rafaelgss>, <https://www.npmjs.com/~rafaelgss>
344
347
  * [__Simone Busoli__](https://github.com/simoneb),
345
348
  <https://twitter.com/simonebu>, <https://www.npmjs.com/~simoneb>
346
349
 
@@ -310,6 +310,9 @@ section.
310
310
  - [`fastify-esso`](https://github.com/patrickpissurno/fastify-esso) The easiest
311
311
  authentication plugin for Fastify, with built-in support for Single sign-on
312
312
  (and great documentation).
313
+ - [`fastify-evervault`](https://github.com/Briscoooe/fastify-evervault/) Fastify
314
+ plugin for instantiating and encapsulating the
315
+ [Evervault](https://evervault.com/) client.
313
316
  - [`fastify-explorer`](https://github.com/Eomm/fastify-explorer) Get control of
314
317
  your decorators across all the encapsulated contexts.
315
318
  - [`fastify-favicon`](https://github.com/smartiniOnGitHub/fastify-favicon)
@@ -465,6 +468,9 @@ middlewares into Fastify plugins
465
468
  ORM.
466
469
  - [`fastify-objectionjs-classes`](https://github.com/kamikazechaser/fastify-objectionjs-classes)
467
470
  Plugin to cherry-pick classes from objectionjs ORM.
471
+ - [`fastify-opaque-apake`](https://github.com/squirrelchat/fastify-opaque-apake)
472
+ A Fastify plugin to implement the OPAQUE aPAKE protocol. Uses
473
+ [@squirrelchat/opaque-wasm-server](https://github.com/squirrelchat/opaque-wasm).
468
474
  - [`fastify-openapi-docs`](https://github.com/ShogunPanda/fastify-openapi-docs)
469
475
  A Fastify plugin that generates OpenAPI spec automatically.
470
476
  - [`fastify-openapi-glue`](https://github.com/seriousme/fastify-openapi-glue)
@@ -489,10 +489,21 @@ const app = Fastify({
489
489
  });
490
490
 
491
491
  // Register your application as a normal plugin.
492
- app.register(import("../src/app"));
492
+ app.register(import("../src/app.js"));
493
493
 
494
494
  export default async (req, res) => {
495
495
  await app.ready();
496
496
  app.server.emit('request', req, res);
497
497
  }
498
498
  ```
499
+
500
+ In `src/app.js` define the plugin.
501
+ ```js
502
+ async function routes (fastify, options) {
503
+ fastify.get('/', async (request, reply) => {
504
+ return { hello: 'world' }
505
+ })
506
+ }
507
+
508
+ export default routes;
509
+ ```
@@ -429,6 +429,11 @@ Handler for the route must be a function.
429
429
 
430
430
  Missing handler function for the route.
431
431
 
432
+ #### FST_ERR_ROUTE_METHOD_INVALID
433
+ <a id="FST_ERR_ROUTE_METHOD_INVALID"></a>
434
+
435
+ Method is not a valid value.
436
+
432
437
  #### FST_ERR_ROUTE_METHOD_NOT_SUPPORTED
433
438
  <a id="FST_ERR_ROUTE_METHOD_NOT_SUPPORTED"></a>
434
439
 
@@ -48,7 +48,7 @@ A "month" is defined as 30 consecutive days.
48
48
  | 1.0.0 | 2018-03-06 | 2019-09-01 | 6, 8, 9, 10, 11 |
49
49
  | 2.0.0 | 2019-02-25 | 2021-01-31 | 6, 8, 10, 12, 14 |
50
50
  | 3.0.0 | 2020-07-07 | 2023-06-30 | 10, 12, 14, 16, 18 |
51
- | 4.0.0 | 2022-06-08 | TBD | 14, 16, 18 |
51
+ | 4.0.0 | 2022-06-08 | TBD | 14, 16, 18, 20 |
52
52
 
53
53
  ### CI tested operating systems
54
54
  <a id="supported-os"></a>
@@ -61,10 +61,10 @@ YAML workflow labels below:
61
61
 
62
62
  | OS | YAML Workflow Label | Package Manager | Node.js |
63
63
  |---------|------------------------|---------------------------|--------------|
64
- | Linux | `ubuntu-latest` | npm | 14,16,18 |
65
- | Linux | `ubuntu-latest` | yarn,pnpm | 14,16,18 |
66
- | Windows | `windows-latest` | npm | 14,16,18 |
67
- | MacOS | `macos-latest` | npm | 14,16,18 |
64
+ | Linux | `ubuntu-latest` | npm | 14,16,18,20 |
65
+ | Linux | `ubuntu-latest` | yarn,pnpm | 14,16,18,20 |
66
+ | Windows | `windows-latest` | npm | 14,16,18,20 |
67
+ | MacOS | `macos-latest` | npm | 14,16,18,20 |
68
68
 
69
69
  Using [yarn](https://yarnpkg.com/) might require passing the `--ignore-engines`
70
70
  flag.
@@ -1,6 +1,8 @@
1
1
  <h1 align="center">Fastify</h1>
2
2
 
3
3
  ## Lifecycle
4
+ <a id="lifecycle"></a>
5
+
4
6
  Following the schema of the internal lifecycle of Fastify.
5
7
 
6
8
  On the right branch of every section there is the next phase of the lifecycle,
@@ -49,6 +51,7 @@ NB (*): If `reply.raw` is used to send a response back to the user, `onResponse`
49
51
  hooks will still be executed
50
52
 
51
53
  ## Reply Lifecycle
54
+ <a id="reply-lifecycle"></a>
52
55
 
53
56
  Whenever the user handles the request, the result may be:
54
57
 
@@ -22,7 +22,7 @@ const fastify = require('fastify')({
22
22
  ```
23
23
 
24
24
  Enabling the logger with appropriate configuration for both local development
25
- and production and test environment requires bit more configuration:
25
+ and production and test environment requires a bit more configuration:
26
26
 
27
27
  ```js
28
28
  const envToLogger = {
@@ -108,6 +108,7 @@ serialize objects with `req`, `res`, and `err` properties. The object received
108
108
  by `req` is the Fastify [`Request`](./Request.md) object, while the object
109
109
  received by `res` is the Fastify [`Reply`](./Reply.md) object. This behaviour
110
110
  can be customized by specifying custom serializers.
111
+
111
112
  ```js
112
113
  const fastify = require('fastify')({
113
114
  logger: {
@@ -152,6 +153,34 @@ const fastify = require('fastify')({
152
153
  }
153
154
  });
154
155
  ```
156
+
157
+ **Note**: In certain cases, the [`Reply`](./Reply.md) object passed to the `res`
158
+ serializer cannot be fully constructed. When writing a custom `res` serializer,
159
+ it is necessary to check for the existence of any properties on `reply` aside
160
+ from `statusCode`, which is always present. For example, the existence of
161
+ `getHeaders` must be verified before it can be called:
162
+
163
+ ```js
164
+ const fastify = require('fastify')({
165
+ logger: {
166
+ transport: {
167
+ target: 'pino-pretty'
168
+ },
169
+ serializers: {
170
+ res (reply) {
171
+ // The default
172
+ return {
173
+ statusCode: reply.statusCode
174
+ headers: typeof reply.getHeaders === 'function'
175
+ ? reply.getHeaders()
176
+ : {}
177
+ }
178
+ },
179
+ }
180
+ }
181
+ });
182
+ ```
183
+
155
184
  **Note**: The body cannot be serialized inside a `req` method because the
156
185
  request is serialized when we create the child logger. At that time, the body is
157
186
  not yet parsed.
@@ -167,6 +196,10 @@ app.addHook('preHandler', function (req, reply, done) {
167
196
  })
168
197
  ```
169
198
 
199
+ **Note**: Care should be take to ensure serializers never throw, as an error
200
+ thrown from a serializer has the potential to cause the Node process to exit.
201
+ See the [Pino documentation](https://getpino.io/#/docs/api?id=opt-serializers)
202
+ on serializers for more information.
170
203
 
171
204
  *Any logger other than Pino will ignore this option.*
172
205
 
@@ -708,6 +708,20 @@ fastify.get('/streams', async function (request, reply) {
708
708
  return reply
709
709
  })
710
710
  ```
711
+
712
+ #### TypedArrays
713
+ <a id="send-typedarrays"></a>
714
+
715
+ `send` manages TypedArray and sets the `'Content-Type'=application/octet-stream'`
716
+ header if not already set.
717
+ ```js
718
+ const fs = require('fs')
719
+ fastify.get('/streams', function (request, reply) {
720
+ const typedArray = new Uint16Array(10)
721
+ reply.send(typedArray)
722
+ })
723
+ ```
724
+
711
725
  #### Errors
712
726
  <a id="errors"></a>
713
727
 
@@ -789,6 +803,11 @@ fastify.setErrorHandler(function (error, request, reply) {
789
803
  })
790
804
  ```
791
805
 
806
+ Beware that calling `reply.send(error)` in your custom error handler will send
807
+ the error to the default error handler.
808
+ Check out the [Reply Lifecycle](./Lifecycle.md#reply-lifecycle)
809
+ for more information.
810
+
792
811
  The not found errors generated by the router will use the
793
812
  [`setNotFoundHandler`](./Server.md#setnotfoundhandler)
794
813
 
@@ -145,7 +145,7 @@ console.log(validate({ foo: 0.5 })) // false
145
145
  console.log(validate.errors) // validation errors
146
146
  ```
147
147
 
148
- See [.compilaValidationSchema(schema, [httpStatus])](#compilevalidationschema)
148
+ See [.compileValidationSchema(schema, [httpStatus])](#compilevalidationschema)
149
149
  for more information on how to compile validation function.
150
150
 
151
151
  ### .compileValidationSchema(schema, [httpPart])
@@ -63,6 +63,7 @@ describes the properties available in that options object.
63
63
  - [prefix](#prefix)
64
64
  - [pluginName](#pluginname)
65
65
  - [hasPlugin](#hasplugin)
66
+ - [listeningOrigin](#listeningOrigin)
66
67
  - [log](#log)
67
68
  - [version](#version)
68
69
  - [inject](#inject)
@@ -828,8 +829,16 @@ URLs.
828
829
  > Rewriting a URL will modify the `url` property of the `req` object
829
830
 
830
831
  ```js
831
- function rewriteUrl (req) { // req is the Node.js HTTP request
832
- return req.url === '/hi' ? '/hello' : req.url;
832
+ // @param {object} req The raw Node.js HTTP request, not the `FastifyRequest` object.
833
+ // @this Fastify The root Fastify instance (not an encapsulated instance).
834
+ // @returns {string} The path that the request should be mapped to.
835
+ function rewriteUrl (req) {
836
+ if (req.url === '/hi') {
837
+ this.log.debug({ originalUrl: req.url, url: '/hello' }, 'rewrite url');
838
+ return '/hello'
839
+ } else {
840
+ return req.url;
841
+ }
833
842
  }
834
843
  ```
835
844
 
@@ -1231,6 +1240,15 @@ fastify.ready(() => {
1231
1240
  })
1232
1241
  ```
1233
1242
 
1243
+ ### listeningOrigin
1244
+ <a id="listeningOrigin"></a>
1245
+
1246
+ The current origin the server is listening to.
1247
+ For example, a TCP socket based server returns
1248
+ a base address like `http://127.0.0.1:3000`,
1249
+ and a Unix socket server will return the socket
1250
+ path, e.g. `fastify.temp.sock`.
1251
+
1234
1252
  #### log
1235
1253
  <a id="log"></a>
1236
1254
 
@@ -19,8 +19,9 @@ All the examples in this section are using the [JSON Schema Draft
19
19
  > use with user-provided schemas. See [Ajv](https://npm.im/ajv) and
20
20
  > [fast-json-stringify](https://npm.im/fast-json-stringify) for more details.
21
21
  >
22
- > Moreover, the [`$async` Ajv
23
- > feature](https://ajv.js.org/guide/async-validation.html) should not be used as
22
+ > Regardless the [`$async` Ajv
23
+ > feature](https://ajv.js.org/guide/async-validation.html) is supported
24
+ > by Fastify, it should not be used as
24
25
  > part of the first validation strategy. This option is used to access Databases
25
26
  > and reading them during the validation process may lead to Denial of Service
26
27
  > Attacks to your application. If you need to run `async` tasks, use [Fastify's
@@ -0,0 +1,3 @@
1
+ {
2
+ "hello": "world"
3
+ }
package/fastify.d.ts CHANGED
@@ -148,7 +148,12 @@ declare namespace fastify {
148
148
  req: FastifyRequest<RequestGeneric, RawServer, RawRequestDefaultExpression<RawServer>, FastifySchema, TypeProvider>,
149
149
  res: FastifyReply<RawServer, RawRequestDefaultExpression<RawServer>, RawReplyDefaultExpression<RawServer>, RequestGeneric, FastifyContextConfig, SchemaCompiler, TypeProvider>
150
150
  ) => void,
151
- rewriteUrl?: (req: RawRequestDefaultExpression<RawServer>) => string,
151
+ rewriteUrl?: (
152
+ // The RawRequestDefaultExpression, RawReplyDefaultExpression, and FastifyTypeProviderDefault parameters
153
+ // should be narrowed further but those generic parameters are not passed to this FastifyServerOptions type
154
+ this: FastifyInstance<RawServer, RawRequestDefaultExpression<RawServer>, RawReplyDefaultExpression<RawServer>, Logger, FastifyTypeProviderDefault>,
155
+ req: RawRequestDefaultExpression<RawServer>
156
+ ) => string,
152
157
  schemaErrorFormatter?: SchemaErrorFormatter,
153
158
  /**
154
159
  * listener to error events emitted by client connections
package/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '4.16.3'
3
+ const VERSION = '4.18.0'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('http')
@@ -348,6 +348,16 @@ function fastify (options) {
348
348
  }
349
349
 
350
350
  Object.defineProperties(fastify, {
351
+ listeningOrigin: {
352
+ get () {
353
+ const address = this.addresses().slice(-1).pop()
354
+ /* istanbul ignore if windows: unix socket is not testable on Windows platform */
355
+ if (typeof address === 'string') {
356
+ return address
357
+ }
358
+ return `${this[kOptions].https ? 'https' : 'http'}://${address.address}:${address.port}`
359
+ }
360
+ },
351
361
  pluginName: {
352
362
  configurable: true,
353
363
  get () {
@@ -606,7 +616,6 @@ function fastify (options) {
606
616
  } else if (name === 'onReady') {
607
617
  this[kHooks].add(name, fn)
608
618
  } else if (name === 'onRoute') {
609
- this[kHooks].validate(name, fn)
610
619
  this[kHooks].add(name, fn)
611
620
  } else {
612
621
  this.after((err, done) => {
@@ -689,7 +698,7 @@ function fastify (options) {
689
698
  const reply = new Reply(res, request, childLogger)
690
699
  return frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)
691
700
  }
692
- const body = `{"error":"Bad Request","message":"'${path}' is not a valid url component","statusCode":400}`
701
+ const body = `{"error":"Bad Request","code":"FST_ERR_BAD_URL","message":"'${path}' is not a valid url component","statusCode":400}`
693
702
  res.writeHead(400, {
694
703
  'Content-Type': 'application/json',
695
704
  'Content-Length': body.length
@@ -784,16 +793,12 @@ function fastify (options) {
784
793
  // only call isAsyncConstraint once
785
794
  if (isAsync === undefined) isAsync = router.isAsyncConstraint()
786
795
  if (rewriteUrl) {
787
- const originalUrl = req.url
788
- const url = rewriteUrl(req)
789
- if (originalUrl !== url) {
790
- logger.debug({ originalUrl, url }, 'rewrite url')
791
- if (typeof url === 'string') {
792
- req.url = url
793
- } else {
794
- const err = new FST_ERR_ROUTE_REWRITE_NOT_STR(req.url, typeof url)
795
- req.destroy(err)
796
- }
796
+ const url = rewriteUrl.call(fastify, req)
797
+ if (typeof url === 'string') {
798
+ req.url = url
799
+ } else {
800
+ const err = new FST_ERR_ROUTE_REWRITE_NOT_STR(req.url, typeof url)
801
+ req.destroy(err)
797
802
  }
798
803
  }
799
804
  router.routing(req, res, buildAsyncConstraintCallback(isAsync, req, res))
@@ -248,7 +248,9 @@ function rawBody (request, reply, options, parser, done) {
248
248
  payload.removeListener('error', onEnd)
249
249
 
250
250
  if (err !== undefined) {
251
- err.statusCode = 400
251
+ if (!(typeof err.statusCode === 'number' && err.statusCode >= 400)) {
252
+ err.statusCode = 400
253
+ }
252
254
  reply[kReplyIsError] = true
253
255
  reply.code(err.statusCode).send(err)
254
256
  return
package/lib/errors.js CHANGED
@@ -336,6 +336,11 @@ const codes = {
336
336
  'Missing handler function for "%s:%s" route.',
337
337
  500
338
338
  ),
339
+ FST_ERR_ROUTE_METHOD_INVALID: createError(
340
+ 'FST_ERR_ROUTE_METHOD_INVALID',
341
+ 'Provided method is invalid!',
342
+ 500
343
+ ),
339
344
  FST_ERR_ROUTE_METHOD_NOT_SUPPORTED: createError(
340
345
  'FST_ERR_ROUTE_METHOD_NOT_SUPPORTED',
341
346
  '%s method is not supported.',
@@ -89,14 +89,25 @@ function preValidationCallback (err, request, reply) {
89
89
  return
90
90
  }
91
91
 
92
- const result = validateSchema(reply[kRouteContext], request)
93
- if (result) {
92
+ const validationErr = validateSchema(reply[kRouteContext], request)
93
+ const isAsync = (validationErr && typeof validationErr.then === 'function') || false
94
+
95
+ if (isAsync) {
96
+ const cb = validationCompleted.bind(null, request, reply)
97
+ validationErr.then(cb, cb)
98
+ } else {
99
+ validationCompleted(request, reply, validationErr)
100
+ }
101
+ }
102
+
103
+ function validationCompleted (request, reply, validationErr) {
104
+ if (validationErr) {
94
105
  if (reply[kRouteContext].attachValidation === false) {
95
- reply.send(result)
106
+ reply.send(validationErr)
96
107
  return
97
108
  }
98
109
 
99
- reply.request.validationError = result
110
+ reply.request.validationError = validationErr
100
111
  }
101
112
 
102
113
  // preHandler hook
package/lib/reply.js CHANGED
@@ -148,11 +148,12 @@ Reply.prototype.send = function (payload) {
148
148
  return this
149
149
  }
150
150
 
151
- if (Buffer.isBuffer(payload)) {
151
+ if (payload?.buffer instanceof ArrayBuffer) {
152
152
  if (hasContentType === false) {
153
153
  this[kReplyHeaders]['content-type'] = CONTENT_TYPE.OCTET
154
154
  }
155
- onSendHook(this, payload)
155
+ const payloadToSend = Buffer.isBuffer(payload) ? payload : Buffer.from(payload.buffer)
156
+ onSendHook(this, payloadToSend)
156
157
  return this
157
158
  }
158
159
 
package/lib/route.js CHANGED
@@ -27,6 +27,7 @@ const {
27
27
  FST_ERR_ROUTE_HANDLER_NOT_FN,
28
28
  FST_ERR_ROUTE_MISSING_HANDLER,
29
29
  FST_ERR_ROUTE_METHOD_NOT_SUPPORTED,
30
+ FST_ERR_ROUTE_METHOD_INVALID,
30
31
  FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED,
31
32
  FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT
32
33
  } = require('./errors')
@@ -185,10 +186,12 @@ function buildRouting (options) {
185
186
  if (Array.isArray(opts.method)) {
186
187
  // eslint-disable-next-line no-var
187
188
  for (var i = 0; i < opts.method.length; ++i) {
188
- validateMethodAndSchemaBodyOption(opts.method[i], path, opts.schema)
189
+ opts.method[i] = normalizeAndValidateMethod(opts.method[i])
190
+ validateSchemaBodyOption(opts.method[i], path, opts.schema)
189
191
  }
190
192
  } else {
191
- validateMethodAndSchemaBodyOption(opts.method, path, opts.schema)
193
+ opts.method = normalizeAndValidateMethod(opts.method)
194
+ validateSchemaBodyOption(opts.method, path, opts.schema)
192
195
  }
193
196
 
194
197
  if (!opts.handler) {
@@ -526,11 +529,19 @@ function handleTimeout () {
526
529
  )
527
530
  }
528
531
 
529
- function validateMethodAndSchemaBodyOption (method, path, schema) {
532
+ function normalizeAndValidateMethod (method) {
533
+ if (typeof method !== 'string') {
534
+ throw new FST_ERR_ROUTE_METHOD_INVALID()
535
+ }
536
+ method = method.toUpperCase()
530
537
  if (supportedMethods.indexOf(method) === -1) {
531
538
  throw new FST_ERR_ROUTE_METHOD_NOT_SUPPORTED(method)
532
539
  }
533
540
 
541
+ return method
542
+ }
543
+
544
+ function validateSchemaBodyOption (method, path, schema) {
534
545
  if ((method === 'GET' || method === 'HEAD') && schema && schema.body) {
535
546
  throw new FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED(method, path)
536
547
  }
package/lib/schemas.js CHANGED
@@ -44,6 +44,16 @@ Schemas.prototype.getSchema = function (schemaId) {
44
44
  return this.store[schemaId]
45
45
  }
46
46
 
47
+ /**
48
+ * Checks whether a schema is a non-plain object.
49
+ *
50
+ * @param {*} schema the schema to check
51
+ * @returns {boolean} true if schema has a custom prototype
52
+ */
53
+ function isCustomSchemaPrototype (schema) {
54
+ return typeof schema === 'object' && Object.getPrototypeOf(schema) !== Object.prototype
55
+ }
56
+
47
57
  function normalizeSchema (routeSchemas, serverOptions) {
48
58
  if (routeSchemas[kSchemaVisited]) {
49
59
  return routeSchemas
@@ -60,33 +70,20 @@ function normalizeSchema (routeSchemas, serverOptions) {
60
70
 
61
71
  generateFluentSchema(routeSchemas)
62
72
 
63
- // let's check if our schemas have a custom prototype
64
- for (const key of ['headers', 'querystring', 'params', 'body']) {
65
- if (typeof routeSchemas[key] === 'object' && Object.getPrototypeOf(routeSchemas[key]) !== Object.prototype) {
66
- routeSchemas[kSchemaVisited] = true
67
- return routeSchemas
73
+ for (const key of SCHEMAS_SOURCE) {
74
+ const schema = routeSchemas[key]
75
+ if (schema && !isCustomSchemaPrototype(schema)) {
76
+ routeSchemas[key] = getSchemaAnyway(schema, serverOptions.jsonShorthand)
68
77
  }
69
78
  }
70
79
 
71
- if (routeSchemas.body) {
72
- routeSchemas.body = getSchemaAnyway(routeSchemas.body, serverOptions.jsonShorthand)
73
- }
74
-
75
- if (routeSchemas.headers) {
76
- routeSchemas.headers = getSchemaAnyway(routeSchemas.headers, serverOptions.jsonShorthand)
77
- }
78
-
79
- if (routeSchemas.querystring) {
80
- routeSchemas.querystring = getSchemaAnyway(routeSchemas.querystring, serverOptions.jsonShorthand)
81
- }
82
-
83
- if (routeSchemas.params) {
84
- routeSchemas.params = getSchemaAnyway(routeSchemas.params, serverOptions.jsonShorthand)
85
- }
86
-
87
80
  if (routeSchemas.response) {
88
81
  const httpCodes = Object.keys(routeSchemas.response)
89
82
  for (const code of httpCodes) {
83
+ if (isCustomSchemaPrototype(routeSchemas.response[code])) {
84
+ continue
85
+ }
86
+
90
87
  const contentProperty = routeSchemas.response[code].content
91
88
 
92
89
  let hasContentMultipleContentTypes = false
package/lib/server.js CHANGED
@@ -127,6 +127,7 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o
127
127
  cb: (_ignoreErr) => {
128
128
  binded++
129
129
 
130
+ /* istanbul ignore next: the else won't be taken unless listening fails */
130
131
  if (!_ignoreErr) {
131
132
  this[kServerBindings].push(secondaryServer)
132
133
  }
@@ -144,6 +145,7 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o
144
145
  mainServer.on('unref', closeSecondary)
145
146
  mainServer.on('close', closeSecondary)
146
147
  mainServer.on('error', closeSecondary)
148
+ this[kState].listening = false
147
149
  listenCallback.call(this, secondaryServer, secondaryOpts)()
148
150
  }
149
151
  }
@@ -278,19 +280,22 @@ function compileValidateHTTPVersion (options) {
278
280
 
279
281
  function getServerInstance (options, httpHandler) {
280
282
  let server = null
283
+ // node@20 do not accepts options as boolean
284
+ // we need to provide proper https option
285
+ const httpsOptions = options.https === true ? {} : options.https
281
286
  if (options.serverFactory) {
282
287
  server = options.serverFactory(httpHandler, options)
283
288
  } else if (options.http2) {
284
- if (options.https) {
285
- server = http2().createSecureServer(options.https, httpHandler)
289
+ if (typeof httpsOptions === 'object') {
290
+ server = http2().createSecureServer(httpsOptions, httpHandler)
286
291
  } else {
287
292
  server = http2().createServer(httpHandler)
288
293
  }
289
294
  server.on('session', sessionTimeout(options.http2SessionTimeout))
290
295
  } else {
291
296
  // this is http1
292
- if (options.https) {
293
- server = https.createServer(options.https, httpHandler)
297
+ if (httpsOptions) {
298
+ server = https.createServer(httpsOptions, httpHandler)
294
299
  } else {
295
300
  server = http.createServer(options.http, httpHandler)
296
301
  }